Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 50 additions & 78 deletions PandorasBox/Features/UI/AutoVoteMvp.cs
Original file line number Diff line number Diff line change
@@ -1,51 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Bindings.ImGui;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Game.ClientState.Party;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using ECommons;
using ECommons.DalamudServices;
using ECommons.GameHelpers;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI;
using Dalamud.Bindings.ImGui;
using PandorasBox.FeaturesSetup;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;

namespace PandorasBox.Features.UI;

public class AutoVoteMvp : Feature
{
public override string Name => "Auto-Commendation after Duty";

public override string Description => "Automatically give a commendation to a random player in your party at the end of a duty.";

public override FeatureType FeatureType => FeatureType.UI;

public override bool UseAutoConfig => false;

private List<string> PremadePartyID { get; set; } = new();

private List<uint> DeadPlayers { get; set; } = new();

private Dictionary<uint, int> DeathTracker { get; set; } = new();
private List<string> PremadePartyID { get; set; } = [];
private List<uint> DeadPlayers { get; set; } = [];
private Dictionary<uint, int> DeathTracker { get; set; } = [];

public class Configs : FeatureConfig
{
public int Priority = 0;

public bool HideChat = false;

public bool ExcludeDeaths = false;

public int HowManyDeaths = 1;

public bool ResetOnWipe = false;
}

public Configs Config { get; private set; }
public Configs Config { get; private set; } = null!;

public override unsafe void Enable()
{
Expand All @@ -57,6 +50,7 @@ public override unsafe void Enable()
}
Config = LoadConfig<Configs>() ?? new Configs();
Svc.Framework.Update += FrameworkUpdate;
Svc.AddonLifecycle.RegisterListener(AddonEvent.PostSetup, "BannerMIP", OnBannerSetup);
Svc.Condition.ConditionChange += UpdatePartyCache;
base.Enable();
}
Expand Down Expand Up @@ -85,6 +79,7 @@ public override void Disable()
{
SaveConfig(Config);
Svc.Framework.Update -= FrameworkUpdate;
Svc.AddonLifecycle.UnregisterListener(OnBannerSetup);
Svc.Condition.ConditionChange -= UpdatePartyCache;
base.Disable();
}
Expand All @@ -94,13 +89,16 @@ private unsafe void FrameworkUpdate(IFramework framework)
if (Player.Object == null) return;
if (Svc.ClientState.IsPvP) return;
CheckForDeadPartyMembers();
}

var bannerWindow = (AtkUnitBase*)Svc.GameGui.GetAddonByName("BannerMIP", 1).Address;
if (bannerWindow == null) return;

private unsafe void OnBannerSetup(AddonEvent type, AddonArgs args)
{
if (Svc.ClientState.IsPvP) return;
var atk = (AtkUnitBase*)args.Addon.Address;
try
{
VoteBanner(bannerWindow, ChoosePlayer(bannerWindow));
if (ChoosePlayer(atk) is not -1 and var playerIndex)
VoteBanner((AtkUnitBase*)args.Addon.Address, playerIndex);
}
catch (Exception e)
{
Expand All @@ -110,58 +108,40 @@ private unsafe void FrameworkUpdate(IFramework framework)

private void CheckForDeadPartyMembers()
{
if (Svc.Party.Any())
if (!Svc.Party.Any())
{
if (Config.ResetOnWipe && Svc.Party.All(x => x.GameObject?.IsDead == true))
{
DeathTracker.Clear();
}
DeathTracker.Clear();
DeadPlayers.Clear();
return;
}

foreach (var pm in Svc.Party)
{
if (pm.GameObject == null) continue;
if (pm.EntityId == Svc.Objects.LocalPlayer?.GameObjectId) continue;
if (pm.GameObject.IsDead)
{
if (DeadPlayers.Contains(pm.EntityId)) continue;
DeadPlayers.Add(pm.EntityId);
if (DeathTracker.ContainsKey(pm.EntityId))
DeathTracker[pm.EntityId] += 1;
else
DeathTracker.TryAdd(pm.EntityId, 1);
if (Config.ResetOnWipe && Svc.Party.All(x => x.GameObject?.IsDead == true))
DeathTracker.Clear();

}
foreach (var pm in Svc.Party.Where(pm => pm.GameObject != null && pm.EntityId != Svc.Objects.LocalPlayer?.GameObjectId))
{
if (pm.GameObject?.IsDead ?? false)
{
if (DeadPlayers.Contains(pm.EntityId)) continue;
DeadPlayers.Add(pm.EntityId);
if (DeathTracker.ContainsKey(pm.EntityId))
DeathTracker[pm.EntityId] += 1;
else
{
DeadPlayers.Remove(pm.EntityId);
}
DeathTracker.TryAdd(pm.EntityId, 1);
}
}
else
{
DeathTracker.Clear();
DeadPlayers.Clear();
else
DeadPlayers.Remove(pm.EntityId);
}
}

private unsafe int ChoosePlayer(AtkUnitBase* bannerWindow)
{
var hud = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance()
->GetUIModule()->GetAgentModule()->GetAgentHUD();

var hud = UIModule.Instance()->GetAgentModule()->GetAgentHUD();
if (hud == null) throw new Exception("HUD is empty!");

foreach (var member in Svc.Party.Cast<IPartyMember>())
{
var m2 = (FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember*)member.Address;
var name = m2->NameString;
Svc.Log.Debug($"{name} {m2->Flags.ToString("g")}");
}

var list = Svc.Party.Where(i =>
i.EntityId != Player.Object.GameObjectId && i.GameObject != null && !PremadePartyID.Any(y => y == i.Name.GetText()))
.Select(PartyMember => (Math.Max(0, GetPartySlotIndex(PartyMember.EntityId, hud) - 1), PartyMember))
.ToList();
var list = Svc.Party.Where(i => i.EntityId != Svc.Objects.LocalPlayer?.GameObjectId && i.GameObject != null && !PremadePartyID.Any(y => y == i.Name.TextValue))
.Select(PartyMember => (Math.Max(0, GetPartySlotIndex(PartyMember.EntityId, hud) - 1), PartyMember))
.ToList();

if (!list.Any()) return -1;

Expand Down Expand Up @@ -218,17 +198,17 @@ private unsafe int ChoosePlayer(AtkUnitBase* bannerWindow)
if (!Config.HideChat)
{
var payload = PandoraPayload.Payloads.ToList();
payload.AddRange(new List<Payload>()
{
payload.AddRange(
[
new TextPayload("Commend given to "),
voteTarget.member.ClassJob.Value!.Role switch
{
1 => new IconPayload(BitmapFontIcon.Tank),
4 => new IconPayload(BitmapFontIcon.Healer),
_ => new IconPayload(BitmapFontIcon.DPS),
},
new PlayerPayload(voteTarget.member.Name.TextValue, voteTarget.member.World.Value!.RowId),
});
new PlayerPayload(voteTarget.member.Name.TextValue, voteTarget.member.World.Value.RowId),
]);
Svc.Chat.Print(new SeString(payload));
}

Expand Down Expand Up @@ -260,18 +240,10 @@ private static T RandomPick<T>(IEnumerable<T> list)
private static unsafe void VoteBanner(AtkUnitBase* bannerWindow, int index)
{
if (index == -1) return;
var atkValues = (AtkValue*)Marshal.AllocHGlobal(2 * sizeof(AtkValue));
atkValues[0].Type = atkValues[1].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
atkValues[0].Int = 12;
atkValues[1].Int = index;
try
{
bannerWindow->FireCallback(2, atkValues);
}
finally
{
Marshal.FreeHGlobal(new nint(atkValues));
}
var atkValues = stackalloc AtkValue[2];
atkValues[0].SetInt(12);
atkValues[1].SetInt(index);
bannerWindow->FireCallback(2, atkValues);
}

protected override DrawConfigDelegate DrawConfigTree => (ref bool _) =>
Expand Down