Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extend prio system #565

Merged
merged 1 commit into from
Jan 18, 2025
Merged
Show file tree
Hide file tree
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
3 changes: 3 additions & 0 deletions BossMod/ActionTweaks/AutoAutosTweak.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ public bool GetDesiredState(bool currentState)
if (_config.PyreticThreshold > 0 && hints.ImminentSpecialMode.mode == AIHints.SpecialMode.Pyretic && hints.ImminentSpecialMode.activation < ws.FutureTime(_config.PyreticThreshold))
return false; // pyretic => disable autos

if (hints.FindEnemy(target)?.Priority == AIHints.Enemy.PriorityForbidden)
return false;

return player.InCombat || ws.Client.CountdownRemaining <= PrePullThreshold; // no reason not to enable autos!
}
}
2 changes: 1 addition & 1 deletion BossMod/Autorotation/Legacy/LegacyModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ protected void PushResult(ActionID action, Actor? target)
return;
if (data.Range == 0)
target = Player; // override range-0 actions to always target player
if (target == null || Hints.ForbiddenTargets.FirstOrDefault(e => e.Actor == target)?.Priority == AIHints.Enemy.PriorityForbidFully)
if (target == null || Hints.ForbiddenTargets.FirstOrDefault(e => e.Actor == target)?.Priority == AIHints.Enemy.PriorityForbidden)
return; // forbidden
Hints.ActionsToExecute.Push(action, target, (data.IsGCD ? ActionQueue.Priority.High : ActionQueue.Priority.Low) + 500);
}
Expand Down
52 changes: 29 additions & 23 deletions BossMod/BossModule/AIHints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
// information relevant for AI decision making process for a specific player
public sealed class AIHints
{
public class Enemy(Actor actor, bool shouldBeTanked)
public class Enemy(Actor actor, int priority, bool shouldBeTanked)
{
// TODO: split 'pointless to attack' (eg invulnerable, but fine to hit by aoes) vs 'actually bad to hit' (eg can lead to wipe)
public const int PriorityForbidAI = -1; // ai is forbidden from attacking this enemy, but player explicitly targeting it is not (e.g. out of combat enemies that we might not want to pull)
public const int PriorityForbidFully = -2; // attacking this enemy is forbidden both by ai or player (e.g. invulnerable, or attacking/killing might lead to a wipe)
public const int PriorityPointless = -1; // attacking enemy won't improve your parse, but will give gauge and advance combo (e.g. boss locked to 1 HP, useless add in raid, etc)
public const int PriorityInvincible = -2; // attacking enemy will have no effect at all besides breaking your combo, but hitting it with AOEs is fine
public const int PriorityUndesirable = -3; // enemy can be attacked if targeted manually by a player, but should be considered forbidden for AOE actions (i.e. mobs that are not in combat, or are in combat with someone else's party)
public const int PriorityForbidden = -4; // attacking this enemy will probably lead to a wipe; autoattacks and actions that target it will be forcibly prevented (if custom queueing is enabled)

public Actor Actor = actor;
public int Priority = actor.InCombat ? 0 : PriorityForbidAI; // <0 means damaging is actually forbidden, 0 is default (TODO: revise default...)
public int Priority = priority;
//public float TimeToKill;
public float AttackStrength = 0.05f; // target's predicted HP percent is decreased by this amount (0.05 by default)
public WPos DesiredPosition = actor.Position; // tank AI will try to move enemy to this position
Expand Down Expand Up @@ -40,6 +41,10 @@ public enum SpecialMode
public Bitmap.Region PathfindMapObstacles;

// list of potential targets
private readonly Enemy?[] _enemies = new Enemy?[99];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this is an oversight and should be [100]

public Enemy? FindEnemy(Actor actor) => actor.SpawnIndex % 2 == 0 ? _enemies[actor.SpawnIndex / 2] : null;

// enemies in priority order
public List<Enemy> PotentialTargets = [];
public int HighestPotentialTargetPriority;

Expand Down Expand Up @@ -125,27 +130,28 @@ public void FillPotentialTargets(WorldState ws, bool playerIsDefaultTank)
var allowedFateID = playerInFate ? ws.Client.ActiveFate.ID : 0;
foreach (var actor in ws.Actors.Where(a => a.IsTargetable && !a.IsAlly && !a.IsDead))
{
// fate mob in fate we are NOT a part of, skip entirely. it's okay to "attack" these (i.e., they won't be added as forbidden targets) because we can't even hit them
// (though aggro'd mobs will continue attacking us after we unsync, but who really cares)
int prio = Enemy.PriorityUndesirable;
// fate mob in fate we are NOT a part of; we can't damage them at all
if (actor.FateID > 0 && actor.FateID != allowedFateID)
continue;
prio = Enemy.PriorityInvincible;
else if (Utils.ActorIsDying(actor, ws))
prio = Enemy.PriorityPointless;
else
{
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;
// all fate mobs can be attacked if we are level synced (non synced mobs are skipped above)
allowedAttack |= actor.FateID > 0;

// target is dying; skip it so that AI retargets, but ensure that it's not marked as a forbidden target
// skip this check on striking dummies (name ID 541) as they die constantly
var predictedHP = ws.PendingEffects.PendingHPDifference(actor.InstanceID);
if (actor.HPMP.CurHP + predictedHP <= 0 && actor.NameID != 541)
continue;
if (allowedAttack)
prio = 0;
}

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;
// all fate mobs can be attacked if we are level synced (non synced mobs are skipped above)
allowedAttack |= actor.FateID > 0;
var enemy = new Enemy(actor, prio, playerIsDefaultTank);

PotentialTargets.Add(new(actor, playerIsDefaultTank)
{
Priority = allowedAttack ? 0 : Enemy.PriorityForbidAI
});
PotentialTargets.Add(enemy);
_enemies[actor.SpawnIndex / 2] = enemy;
}
}

Expand Down Expand Up @@ -211,7 +217,7 @@ public void InitPathfindMap(Pathfinding.Map map)
// query utilities
public IEnumerable<Enemy> PotentialTargetsEnumerable => PotentialTargets;
public IEnumerable<Enemy> PriorityTargets => PotentialTargets.TakeWhile(e => e.Priority == HighestPotentialTargetPriority);
public IEnumerable<Enemy> ForbiddenTargets => PotentialTargetsEnumerable.Reverse().TakeWhile(e => e.Priority < 0);
public IEnumerable<Enemy> ForbiddenTargets => PotentialTargetsEnumerable.Reverse().TakeWhile(e => e.Priority <= Enemy.PriorityUndesirable);

// TODO: verify how source/target hitboxes are accounted for by various aoe shapes
public int NumPriorityTargetsInAOE(Func<Enemy, bool> pred) => ForbiddenTargets.Any(pred) ? 0 : PriorityTargets.Count(pred);
Expand Down
11 changes: 9 additions & 2 deletions BossMod/Framework/ActionManagerEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,15 @@ public void FinishActionGather()
AutoQueue = _hints.ActionsToExecute.FindBest(_ws, player, _ws.Client.Cooldowns, EffectiveAnimationLock, _hints, _animLockTweak.DelayEstimate);
if (AutoQueue.Delay > 0)
AutoQueue = default;
if (Config.PyreticThreshold > 0 && _hints.ImminentSpecialMode.mode == AIHints.SpecialMode.Pyretic && _hints.ImminentSpecialMode.activation < _ws.FutureTime(Config.PyreticThreshold) && AutoQueue.Priority < ActionQueue.Priority.ManualEmergency)
AutoQueue = default; // do not execute non-emergency actions when pyretic is imminent

if (AutoQueue.Priority < ActionQueue.Priority.ManualEmergency)
{
if (Config.PyreticThreshold > 0 && _hints.ImminentSpecialMode.mode == AIHints.SpecialMode.Pyretic && _hints.ImminentSpecialMode.activation < _ws.FutureTime(Config.PyreticThreshold))
AutoQueue = default; // do not execute non-emergency actions when pyretic is imminent

if (AutoQueue.Target is Actor t && _hints.FindEnemy(t)?.Priority == AIHints.Enemy.PriorityForbidden)
AutoQueue = default; // or if selected target is forbidden
}
}

public Vector3? GetWorldPosUnderCursor()
Expand Down
10 changes: 10 additions & 0 deletions BossMod/Framework/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ public static string ObjectKindString(IGameObject obj)
public static string CastTimeString(ActorCastInfo cast, DateTime now) => CastTimeString(cast.ElapsedTime, cast.TotalTime);
public static string LogMessageString(uint id) => $"{id} '{Service.LuminaRow<Lumina.Excel.Sheets.LogMessage>(id)?.Text}'";

public static bool ActorIsDying(Actor actor, WorldState ws)
{
// striking dummy - HP resets to full when "killed"
if (actor.NameID == 541)
return false;

var predicted = ws.PendingEffects.PendingHPDifference(actor.InstanceID);
return actor.HPMP.CurHP + predicted <= 0;
}

public static unsafe T ReadField<T>(void* address, int offset) where T : unmanaged => *(T*)((IntPtr)address + offset);
public static unsafe void WriteField<T>(void* address, int offset, T value) where T : unmanaged => *(T*)((IntPtr)address + offset) = value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
if (slot < _assignedBoss.Length && _assignedBoss[slot] != null)
foreach (var enemy in hints.PotentialTargets)
if (enemy.Actor != _assignedBoss[slot])
enemy.Priority = AIHints.Enemy.PriorityForbidFully;
enemy.Priority = AIHints.Enemy.PriorityInvincible;
}

public override void OnTethered(Actor source, ActorTetherInfo tether)
Expand Down
4 changes: 1 addition & 3 deletions BossMod/Modules/Endwalker/Ultimate/TOP/P2LimitlessSynergy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
{
var e = hints.PotentialTargets.FirstOrDefault(e => e.Actor == _invincible);
if (e != null)
{
e.Priority = AIHints.Enemy.PriorityForbidFully;
}
e.Priority = AIHints.Enemy.PriorityInvincible;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ protected override void CalculateModuleAIHints(int slot, Actor actor, PartyRoles
e.Priority = (OID)e.Actor.OID switch
{
OID.MythrilVerge => 3,
OID.SunJuror => WorldState.Actors.Where(other => (OID)other.OID is OID.Platform1 or OID.Platform2 or OID.Platform3).InRadius(e.Actor.Position, 1).Any() ? 2 : AIHints.Enemy.PriorityForbidAI,
OID.SunJuror => WorldState.Actors.Where(other => (OID)other.OID is OID.Platform1 or OID.Platform2 or OID.Platform3).InRadius(e.Actor.Position, 1).Any() ? 2 : AIHints.Enemy.PriorityPointless,
OID.Boss => 1,
_ => 0
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
e.ShouldBeTanked = false;
break;
case OID.SpinyPlume:
e.Priority = Module.PrimaryActor.IsTargetable ? AIHints.Enemy.PriorityForbidAI : 6;
e.Priority = Module.PrimaryActor.IsTargetable ? AIHints.Enemy.PriorityPointless : 6;
e.AttackStrength = 0;
e.ShouldBeTanked = false;
if (actor.Role == Role.Tank && e.Actor.TargetID != actor.InstanceID && (WorldState.Actors.Find(e.Actor.TargetID)?.FindStatus(SID.ThermalLow)?.Extra ?? 0) >= 2)
Expand Down
12 changes: 0 additions & 12 deletions BossMod/Modules/RealmReborn/Extreme/Ex3Titan/Ex3Titan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,6 @@ public class Ex3Titan : BossModule
Bombs = Enemies(OID.BombBoulder);
}

protected override void CalculateModuleAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
{
var heart = Heart();
if (heart != null && heart.IsTargetable)
{
// heart is not added by default, since it has weird actor type
// boss is not really a valid target, but it still hits tank pretty hard, so we want to set attacker strength (?)
hints.PotentialTargets.Add(new(heart, false));
//hints.PotentialTargets.Add(new(PrimaryActor, false));
}
}

protected override void DrawEnemies(int pcSlot, Actor pc)
{
Arena.Actor(PrimaryActor, ArenaColor.Enemy, true);
Expand Down
4 changes: 2 additions & 2 deletions BossMod/Modules/RealmReborn/Raid/T01Caduceus/T01AI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
{
e.Priority = 1; // this is a baseline; depending on whether we want to prioritize clone vs boss, clone's priority changes
if (cloneSpawningSoon && e.Actor.FindStatus(SID.SteelScales) != null)
e.Priority = AIHints.Enemy.PriorityForbidAI; // stop dps until stack can be dropped
e.Priority = AIHints.Enemy.PriorityPointless; // stop dps until stack can be dropped
}
else
{
Expand All @@ -114,7 +114,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
// for now, let kiter damage it until 20%
var predictedHP = (int)e.Actor.HPMP.CurHP + WorldState.PendingEffects.PendingHPDifference(e.Actor.InstanceID);
//e.Priority = predictedHP > 0.7f * e.Actor.HPMP.MaxHP ? (actor.Role is Role.Ranged or Role.Melee ? 3 : AIHints.Enemy.PriorityForbidAI) : AIHints.Enemy.PriorityForbidAI;
e.Priority = predictedHP > 0.2f * e.Actor.HPMP.MaxHP ? (e.Actor.TargetID == actor.InstanceID ? 3 : AIHints.Enemy.PriorityForbidAI) : AIHints.Enemy.PriorityForbidAI;
e.Priority = predictedHP > 0.2f * e.Actor.HPMP.MaxHP ? (e.Actor.TargetID == actor.InstanceID ? 3 : AIHints.Enemy.PriorityPointless) : AIHints.Enemy.PriorityPointless;
e.ShouldBeTanked = false;
e.ForbidDOTs = true;
}
Expand Down
2 changes: 0 additions & 2 deletions BossMod/Modules/RealmReborn/Trial/T02TitanN/T02TitanN.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ public class T02TitanN : BossModule

protected override void CalculateModuleAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
{
foreach (var heart in ActiveHeart)
hints.PotentialTargets.Add(new(heart, actor.Role == Role.Tank));
foreach (var e in hints.PotentialTargets)
{
e.Priority = (OID)e.Actor.OID switch
Expand Down
2 changes: 0 additions & 2 deletions BossMod/Modules/RealmReborn/Trial/T07TitanH/T07TitanH.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,6 @@ public class T07TitanH : BossModule

protected override void CalculateModuleAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
{
foreach (var heart in ActiveHeart)
hints.PotentialTargets.Add(new(heart, assignment == PartyRolesConfig.Assignment.MT));
foreach (var enemy in hints.PotentialTargets)
{
enemy.Priority = (OID)enemy.Actor.OID switch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,6 @@ public override void AddQuestAIHints(Actor player, AIHints hints)
{
foreach (var h in hints.PotentialTargets)
if (h.Actor.OID == 0x2955)
h.Priority = AIHints.Enemy.PriorityForbidFully;
h.Priority = AIHints.Enemy.PriorityForbidden;
}
}
2 changes: 1 addition & 1 deletion BossMod/QuestBattle/Stormblood/MSQ/ItsProbablyATrap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public override void AddQuestAIHints(Actor player, AIHints hints)
foreach (var h in hints.PotentialTargets)
// attacking sekiseigumi fails the mission
if (h.Actor.OID is 0x1A6B or 0x1A66)
h.Priority = AIHints.Enemy.PriorityForbidFully;
h.Priority = AIHints.Enemy.PriorityForbidden;

if (SmokeBomb && player.FindStatus(SID.Bind) != null)
hints.ActionsToExecute.Push(ActionID.MakeSpell(ClassShared.AID.SmokeScreen), player, ActionQueue.Priority.Medium);
Expand Down
Loading