Skip to content

Commit

Permalink
Merge pull request #444 from xanunderscore/playeraggro
Browse files Browse the repository at this point in the history
add flag to track if actor is present in player's enemy list
  • Loading branch information
awgil authored Aug 26, 2024
2 parents 2e373ce + e24899e commit 9c8db26
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 2 deletions.
2 changes: 2 additions & 0 deletions BossMod/BossModule/AIHints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ public void FillPotentialTargets(WorldState ws, bool playerIsDefaultTank)
continue;

var allowedAttack = actor.InCombat && ws.Party.FindSlot(actor.TargetID) >= 0;
// enemies in our enmity list can also be attacked, regardless of who they are targeting (since they are keeping us in combat)
allowedAttack |= actor.AggroPlayer;

PotentialTargets.Add(new(actor, playerIsDefaultTank)
{
Expand Down
1 change: 1 addition & 0 deletions BossMod/Data/Actor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ public sealed class Actor(ulong instanceID, uint oid, int spawnIndex, string nam
public ActorCastInfo? CastInfo;
public ActorTetherInfo Tether;
public ActorStatus[] Statuses = new ActorStatus[60]; // empty slots have ID=0
public bool AggroPlayer; // determines whether a given actor shows in the player's UI enemy list

public Role Role => Class.GetRole();
public WPos Position => new(PosRot.X, PosRot.Z);
Expand Down
11 changes: 11 additions & 0 deletions BossMod/Data/ActorState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -383,4 +383,15 @@ public sealed record class OpEventNpcYell(ulong InstanceID, ushort Message) : Op
protected override void ExecActor(WorldState ws, Actor actor) => ws.Actors.EventNpcYell.Fire(actor, Message);
public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("NYEL"u8).EmitActor(InstanceID).Emit(Message);
}

public Event<Actor, bool> EventAggroPlayer = new();
public sealed record class OpAggroPlayer(ulong InstanceID, bool Has) : Operation(InstanceID)
{
protected override void ExecActor(WorldState ws, Actor actor)
{
actor.AggroPlayer = Has;
ws.Actors.EventAggroPlayer.Fire(actor, Has);
}
public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("NENP"u8).EmitActor(InstanceID).Emit(Has);
}
}
17 changes: 17 additions & 0 deletions BossMod/Framework/WorldStateGameSync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ sealed class WorldStateGameSync : IDisposable
private readonly DateTime _startTime;
private readonly long _startQPC;

// list of actors that are present in the user's enemy list
private readonly List<ulong> _playerEnmity = [];

private readonly List<WorldState.Operation> _globalOps = [];
private readonly Dictionary<ulong, List<WorldState.Operation>> _actorOps = [];
private readonly Dictionary<ulong, Vector3> _lastCastPositions = []; // unfortunately, game only saves cast location for area-targeted spells
Expand Down Expand Up @@ -159,6 +162,15 @@ public unsafe void Update(TimeSpan prevFramePerf)
}
_globalOps.Clear();

_playerEnmity.Clear();
var uiState = UIState.Instance();
var hater = (Hater*)((IntPtr)uiState + 0x110);
for (var i = 0; i < hater->HaterArrayLength; i++)
{
var h = ((HaterInfo*)hater->HaterArray) + i;
_playerEnmity.Add(h->ObjectId);
}

UpdateWaymarks();
UpdateActors();
UpdateParty();
Expand Down Expand Up @@ -287,6 +299,11 @@ private unsafe void UpdateActor(GameObject* obj, int index, Actor? act)
_ws.Execute(new ActorState.OpEventState(act.InstanceID, eventState));
if (act.TargetID != target)
_ws.Execute(new ActorState.OpTarget(act.InstanceID, target));

var hasAggro = _playerEnmity.IndexOf(act.InstanceID) >= 0;
if (hasAggro != act.AggroPlayer)
_ws.Execute(new ActorState.OpAggroPlayer(act.InstanceID, hasAggro));

DispatchActorEvents(act.InstanceID);

var castInfo = chr != null ? chr->GetCastInfo() : null;
Expand Down
2 changes: 2 additions & 0 deletions BossMod/Replay/ReplayParserLog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ private ReplayParserLog(Input input, ReplayBuilder builder)
[new("EANM"u8)] = ParseActorEventObjectAnimation,
[new("PATE"u8)] = ParseActorPlayActionTimelineEvent,
[new("NYEL"u8)] = ParseActorEventNpcYell,
[new("NENP"u8)] = ParseActorAggroPlayer,
[new("PAR "u8)] = ParsePartyModify,
[new("PAR+"u8)] = ParsePartyModify, // legacy (up to v3)
[new("PAR-"u8)] = ParsePartyLeave, // legacy (up to v3)
Expand Down Expand Up @@ -583,6 +584,7 @@ private ActorState.OpCastInfo ParseActorCastInfo(bool start)
private ActorState.OpEventObjectAnimation ParseActorEventObjectAnimation() => new(_input.ReadActorID(), _input.ReadUShort(true), _input.ReadUShort(true));
private ActorState.OpPlayActionTimelineEvent ParseActorPlayActionTimelineEvent() => new(_input.ReadActorID(), _input.ReadUShort(true));
private ActorState.OpEventNpcYell ParseActorEventNpcYell() => new(_input.ReadActorID(), _input.ReadUShort(false));
private ActorState.OpAggroPlayer ParseActorAggroPlayer() => new(_input.ReadActorID(), _input.ReadBool());
private PartyState.OpModify ParsePartyModify() => new(_input.ReadInt(), new(_input.ReadULong(true), _input.ReadULong(true), _version >= 15 && _input.ReadBool(), _version < 15 ? "" : _input.ReadString()));
private PartyState.OpModify ParsePartyLeave() => new(_input.ReadInt(), new(0, 0, false, ""));
private PartyState.OpLimitBreakChange ParsePartyLimitBreak() => new(_input.ReadInt(), _input.ReadInt());
Expand Down
4 changes: 2 additions & 2 deletions BossMod/Replay/Visualization/OpList.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Dalamud.Plugin.Services;
using ImGuiNET;
using ImGuiNET;
using System.IO;
using System.Text;

Expand Down Expand Up @@ -160,6 +159,7 @@ private string OpName(WorldState.Operation o)
ActorState.OpEventObjectAnimation op => $"EObjAnim: {ActorString(op.InstanceID, op.Timestamp)} = {((uint)op.Param1 << 16) | op.Param2:X8}",
ActorState.OpPlayActionTimelineEvent op => $"Play action timeline: {ActorString(op.InstanceID, op.Timestamp)} = {op.ActionTimelineID:X4}",
ActorState.OpEventNpcYell op => $"Yell: {ActorString(op.InstanceID, op.Timestamp)} = {op.Message} '{Service.LuminaRow<Lumina.Excel.GeneratedSheets.NpcYell>(op.Message)?.Text}'",
ActorState.OpAggroPlayer op => $"Aggro player: {ActorString(op.InstanceID, op.Timestamp)} = {op.Has}",
ClientState.OpDutyActionsChange op => $"Player duty actions change: {op.Slot0}, {op.Slot1}",
ClientState.OpBozjaHolsterChange op => $"Player bozja holster change: {string.Join(", ", op.Contents.Select(e => $"{e.count}x {e.entry}"))}",
_ => DumpOp(o)
Expand Down

0 comments on commit 9c8db26

Please sign in to comment.