Skip to content

Commit

Permalink
Merge pull request #468 from FFXIV-CombatReborn/mergeWIP
Browse files Browse the repository at this point in the history
Jeuno trash modules
  • Loading branch information
CarnifexOptimus authored Nov 26, 2024
2 parents 112c1e7 + 1fd81f4 commit 5aa012e
Show file tree
Hide file tree
Showing 28 changed files with 142 additions and 134 deletions.
23 changes: 16 additions & 7 deletions BossMod/AI/AIBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public void Execute(Actor player, Actor master)
_afkMode = _config.AutoAFK && !master.InCombat && (WorldState.CurrentTime - _masterLastMoved).TotalSeconds > _config.AFKModeTimer;
var gazeImminent = autorot.Hints.ForbiddenDirections.Count > 0 && autorot.Hints.ForbiddenDirections[0].activation <= WorldState.FutureTime(0.5f);
var pyreticImminent = autorot.Hints.ImminentSpecialMode.mode == AIHints.SpecialMode.Pyretic && autorot.Hints.ImminentSpecialMode.activation <= WorldState.FutureTime(1);
var misdirectionMode = autorot.Hints.ImminentSpecialMode.mode == AIHints.SpecialMode.Misdirection && autorot.Hints.ImminentSpecialMode.activation <= WorldState.CurrentTime;
var forbidActions = _config.ForbidActions || _afkMode || gazeImminent || pyreticImminent;

Targeting target = new();
Expand All @@ -63,12 +64,7 @@ public void Execute(Actor player, Actor master)
var moveWithMaster = masterIsMoving && (master == player || _followMaster);
ForceMovementIn = moveWithMaster || gazeImminent || pyreticImminent ? 0 : _naviDecision.LeewaySeconds;

if (!forbidActions)
{
autorot.Preset = target.Target != null ? AIPreset : null;
}

UpdateMovement(player, master, target, gazeImminent || pyreticImminent, !forbidActions ? autorot.Hints.ActionsToExecute : null);
UpdateMovement(player, master, target, gazeImminent || pyreticImminent, misdirectionMode ? autorot.Hints.MisdirectionThreshold : default, !forbidActions ? autorot.Hints.ActionsToExecute : null);
}

// returns null if we're to be idle, otherwise target to attack
Expand Down Expand Up @@ -185,7 +181,7 @@ private bool TrackMasterMovement(Actor master)
return masterIsMoving;
}

private void UpdateMovement(Actor player, Actor master, Targeting target, bool gazeOrPyreticImminent, ActionQueue? queueForSprint)
private void UpdateMovement(Actor player, Actor master, Targeting target, bool gazeOrPyreticImminent, Angle misdirectionAngle, ActionQueue? queueForSprint)
{
if (gazeOrPyreticImminent)
{
Expand All @@ -194,6 +190,19 @@ private void UpdateMovement(Actor player, Actor master, Targeting target, bool g
ctrl.NaviTargetVertical = null;
ctrl.ForceCancelCast = true;
}
else if (misdirectionAngle != default && _naviDecision.Destination != null)
{
ctrl.NaviTargetPos = _naviDecision.NextTurn == 0 ? _naviDecision.Destination
: player.Position + (_naviDecision.Destination.Value - player.Position).Rotate(_naviDecision.NextTurn > 0 ? -misdirectionAngle : misdirectionAngle);
ctrl.AllowInterruptingCastByMovement = true;

// debug
//void drawLine(WPos from, WPos to, uint color) => Camera.Instance!.DrawWorldLine(new(from.X, player.PosRot.Y, from.Z), new(to.X, player.PosRot.Y, to.Z), color);
//var toDest = _naviDecision.Destination.Value - player.Position;
//drawLine(player.Position, _naviDecision.Destination.Value, 0xff00ff00);
//drawLine(_naviDecision.Destination.Value, _naviDecision.Destination.Value + toDest.Normalized().OrthoL(), 0xff00ff00);
//drawLine(player.Position, ctrl.NaviTargetPos.Value, 0xff00ffff);
}
else
{
var toDest = _naviDecision.Destination != null ? _naviDecision.Destination.Value - player.Position : new();
Expand Down
6 changes: 5 additions & 1 deletion BossMod/BossModule/AIHints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public enum SpecialMode
Normal,
Pyretic, // pyretic/acceleration bomb type of effects - no movement, no actions, no casting allowed at activation time
Freezing, // should be moving at activation time
// TODO: misdirection, etc
Misdirection, // temporary misdirection - if current time is greater than activation, use special pathfinding codepath
}

public static readonly ArenaBounds DefaultBounds = new ArenaBoundsSquare(30);
Expand Down Expand Up @@ -79,6 +79,9 @@ public void SetPositional(Positional positional)
// closest special movement/targeting/action mode, if any
public (SpecialMode mode, DateTime activation) ImminentSpecialMode;

// for misdirection: if forced movement is set, make real direction be within this angle
public Angle MisdirectionThreshold;

// predicted incoming damage (raidwides, tankbusters, etc.)
// AI will attempt to shield & mitigate
public List<(BitMask players, DateTime activation)> PredictedDamage = [];
Expand Down Expand Up @@ -113,6 +116,7 @@ public void Clear()
RecommendedPositional = default;
ForbiddenDirections.Clear();
ImminentSpecialMode = default;
MisdirectionThreshold = 20.Degrees();
PredictedDamage.Clear();
MaxCastTimeEstimate = float.MaxValue;
ActionsToExecute.Clear();
Expand Down
2 changes: 1 addition & 1 deletion BossMod/BossModule/AIHintsVisualizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public void Draw(UITree tree)
tree.LeafNodes(hints.PotentialTargets, e => $"[{e.Priority}] {e.Actor} (str={e.AttackStrength:f2}), dist={(e.Actor.Position - player.Position).Length():f2}, tank={e.ShouldBeTanked}/{e.PreferProvoking}/{e.DesiredPosition}/{e.DesiredRotation}");
}
tree.LeafNode($"Forced target: {hints.ForcedTarget}");
tree.LeafNode($"Forced movement: {hints.ForcedMovement}");
tree.LeafNode($"Forced movement: {hints.ForcedMovement} (misdirection threshold={hints.MisdirectionThreshold})");
tree.LeafNode($"Special movement: {hints.ImminentSpecialMode.mode} in {Math.Max(0, (hints.ImminentSpecialMode.activation - ws.CurrentTime).TotalSeconds):f3}s");
foreach (var _1 in tree.Node("Forbidden zones", hints.ForbiddenZones.Count == 0))
{
Expand Down
16 changes: 16 additions & 0 deletions BossMod/Components/TemporaryMisdirection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace BossMod.Components;

// generic temporary misdirection component
public class TemporaryMisdirection(BossModule module, ActionID aid, string hint = "Applies temporary misdirection") : CastHint(module, aid, hint)
{
private static readonly List<uint> tempMisdirectionSIDs = [1422, 2936, 3694, 3909];
public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
{
for (var i = 0; i < 4; ++i)
if (actor.FindStatus(tempMisdirectionSIDs[i]) != null)
{
hints.AddSpecialMode(AIHints.SpecialMode.Misdirection, default);
break;
}
}
}
1 change: 1 addition & 0 deletions BossMod/Debug/DebugInput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ public void Draw()
_prevPos = curPos;
_prevSpeed = speedAbs;
ImGui.TextUnformatted($"Speed={speedAbs:f3}, SpeedH={speed.XZ().Length():f3}, SpeedV={speed.Y:f3}, Accel={accel:f3}, Azimuth={Angle.FromDirection(new(speed.XZ()))}, Altitude={Angle.FromDirection(new(speed.Y, speed.XZ().Length()))}");
ImGui.TextUnformatted($"MO: desired={_move.DesiredDirection}, user={_move.UserMove}, actual={_move.ActualMove}");
//Service.Log($"Speed: {speedAbs:f3}, accel: {accel:f3}");

ImGui.SliderFloat("Move direction", ref _moveDir, -180, 180);
Expand Down
93 changes: 44 additions & 49 deletions BossMod/Framework/MovementOverride.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,20 @@ public unsafe struct PlayerMoveControllerFlyInput
public sealed unsafe class MovementOverride : IDisposable
{
public Vector3? DesiredDirection;
public Angle MisdirectionThreshold;

public WDir UserMove { get; private set; } // unfiltered movement direction, as read from input
public WDir ActualMove { get; private set; } // actual movement direction, as of last input read

private float UserMoveLeft;
private float UserMoveUp;
private float ActualMoveLeft;
private float ActualMoveUp;
#pragma warning disable IDE0032
private readonly ActionTweaksConfig _tweaksConfig = Service.Config.Get<ActionTweaksConfig>();
#pragma warning disable IDE0032
private bool _movementBlocked;
#pragma warning restore IDE0032
private bool? _forcedControlState;
private bool _legacyMode;

public bool IsMoving() => ActualMoveLeft != 0 || ActualMoveUp != 0;
public bool IsMoveRequested() => UserMoveLeft != 0 || UserMoveUp != 0;
public bool IsMoving() => ActualMove != default;
public bool IsMoveRequested() => UserMove != default;

public bool IsForceUnblocked() => _tweaksConfig.MoveEscapeHatch switch
{
Expand Down Expand Up @@ -90,11 +90,7 @@ public MovementOverride()
public void Dispose()
{
Service.GameConfig.UiControlChanged -= OnConfigChanged;
MovementBlocked = false;
UserMoveLeft = 0;
UserMoveUp = 0;
ActualMoveLeft = 0;
ActualMoveUp = 0;
_movementBlocked = false;
_mcIsInputActiveHook.Dispose();
_rmiWalkHook.Dispose();
_rmiFlyHook.Dispose();
Expand All @@ -104,55 +100,54 @@ private void RMIWalkDetour(void* self, float* sumLeft, float* sumForward, float*
{
_forcedControlState = null;
_rmiWalkHook.Original(self, sumLeft, sumForward, sumTurnLeft, haveBackwardOrStrafe, a6, bAdditiveUnk);
UserMoveLeft = *sumLeft;
UserMoveUp = *sumForward;

// TODO: this allows AI mode to move even if movement is "blocked", is this the right behavior? AI mode should try to avoid moving while casting anyway...
if (_movementBlocked)
// TODO: we really need to introduce some extra checks that PlayerMoveController::readInput does - sometimes it skips reading input, and returning something non-zero breaks stuff...
var movementAllowed = bAdditiveUnk == 0 && _rmiWalkIsInputEnabled1(self) && _rmiWalkIsInputEnabled2(self);
var misdirectionMode = PlayerHasMisdirection();
if (!movementAllowed && misdirectionMode)
{
*sumLeft = 0;
*sumForward = 0;
// in misdirection mode, when we are already moving, the 'original' call will not actually sample input and just return immediately
// we actually want to know the direction, in case user changes input mid movement - so force sample raw input
float realTurn = 0;
byte realStrafe = 0, realUnk = 0;
_rmiWalkHook.Original(self, sumLeft, sumForward, &realTurn, &realStrafe, &realUnk, 1);
}

// TODO: we really need to introduce some extra checks that PlayerMoveController::readInput does - sometimes it skips reading input, and returning something non-zero breaks stuff...
var movementAllowed = bAdditiveUnk == 0 && _rmiWalkIsInputEnabled1(self) && _rmiWalkIsInputEnabled2(self); //&& !_movementBlocked
if (movementAllowed && *sumLeft == 0 && *sumForward == 0 && DirectionToDestination(false) is var relDir && relDir != null)
// at this point, UserMove contains true user input
UserMove = new(*sumLeft, *sumForward);

// apply movement block logic
// note: currently movement block is ignored in misdirection mode
// the assumption is that, with misdirection active, it's not safe to block movement just because player is casting or doing something else (as arrow will rotate away)
ActualMove = !MovementBlocked || misdirectionMode ? UserMove : default;

// movement override logic
// note: currently we follow desired direction, only if user does not have any input _or_ if manual movement is blocked
// this allows AI mode to move even if movement is blocked (TODO: is this the right behavior? AI mode should try to avoid moving while casting anyway...)
if ((movementAllowed || misdirectionMode) && ActualMove == default && DirectionToDestination(false) is var relDir && relDir != null)
{
var dir = relDir.Value.h.ToDirection();
*sumLeft = dir.X;
*sumForward = dir.Z;
ActualMove = relDir.Value.h.ToDirection();
}

if ((AIManager.Instance?.Beh != null || _tweaksConfig.MisdirectionThreshold < 180) && PlayerHasMisdirection())
// misdirection override logic
if (misdirectionMode)
{
var threshold = AIManager.Instance?.Beh != null ? 20 : _tweaksConfig.MisdirectionThreshold;
if (!movementAllowed)
{
// we are already moving, see whether we need to force stop it
// unfortunately, the base implementation would not sample the input if movement is disabled - force it
float realLeft = 0, realForward = 0, realTurn = 0;
byte realStrafe = 0, realUnk = 0;
if (!MovementBlocked)
_rmiWalkHook.Original(self, &realLeft, &realForward, &realTurn, &realStrafe, &realUnk, 1);
var desiredRelDir = realLeft != 0 || realForward != 0 ? Angle.FromDirection(new(realLeft, realForward)) : DirectionToDestination(false)?.h;
_forcedControlState = desiredRelDir != null && (desiredRelDir.Value + ForwardMovementDirection() - ForcedMovementDirection->Radians()).Normalized().Abs().Deg <= threshold;
}
else if (*sumLeft != 0 || *sumForward != 0)
var thresholdDeg = UserMove != default ? _tweaksConfig.MisdirectionThreshold : MisdirectionThreshold.Deg;
if (thresholdDeg < 180)
{
var currentDir = Angle.FromDirection(new(*sumLeft, *sumForward)) + ForwardMovementDirection();
var dirDelta = currentDir - ForcedMovementDirection->Radians();
_forcedControlState = dirDelta.Normalized().Abs().Deg <= threshold;
if (!_forcedControlState.Value)
{
// forbid movement for now
*sumLeft = *sumForward = 0;
}
// note: if we are already moving, it doesn't matter what we do here, only whether 'is input active' function returns true or false
_forcedControlState = ActualMove != default && (Angle.FromDirection(ActualMove) + ForwardMovementDirection() - ForcedMovementDirection->Radians()).Normalized().Abs().Deg <= thresholdDeg;
}
// else: movement is allowed (so we're not already moving), but we don't want to move anywhere, do nothing
}

ActualMoveLeft = *sumLeft;
ActualMoveUp = *sumForward;
// finally, update output
var output = !misdirectionMode ? ActualMove // standard mode - just return desired movement
: !movementAllowed ? default // misdirection and already moving - always return 0, as game does
: _forcedControlState == null ? ActualMove // misdirection mode, but we're not trying to help user
: _forcedControlState.Value ? ActualMove // misdirection mode, not moving yet, but want to start - can return anything really
: default; // misdirection mode, not moving yet and don't want to
*sumLeft = output.X;
*sumForward = output.Z;
}

private void RMIFlyDetour(void* self, PlayerMoveControllerFlyInput* result)
Expand Down
1 change: 1 addition & 0 deletions BossMod/Framework/Plugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ private unsafe bool QuestUnlocked(uint link)
private unsafe void ExecuteHints()
{
_movementOverride.DesiredDirection = _hints.ForcedMovement;
_movementOverride.MisdirectionThreshold = _hints.MisdirectionThreshold;
// update forced target, if needed (TODO: move outside maybe?)
if (_hints.ForcedTarget != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ class Bloodburst(BossModule module) : Components.RaidwideCast(module, ActionID.M
class DarkSouls(BossModule module) : Components.SingleTargetCast(module, ActionID.MakeSpell(AID.DarkSouls));
class TelltaleTears(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.TelltaleTears), 5);
class SoulDouse(BossModule module) : Components.StackWithCastTargets(module, ActionID.MakeSpell(AID.SoulDouse), 6, 4, 4);
class LostHope(BossModule module) : Components.CastHint(module, ActionID.MakeSpell(AID.LostHope), "Apply temporary misdirection");
class Necrohazard(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Necrohazard), new AOEShapeCircle(18)) { }
class LostHope(BossModule module) : Components.TemporaryMisdirection(module, ActionID.MakeSpell(AID.LostHope));
class Necrohazard(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Necrohazard), new AOEShapeCircle(18));

class DarkII(BossModule module) : Components.GenericAOEs(module)
{
Expand Down
2 changes: 1 addition & 1 deletion BossMod/Modules/Dawntrail/Hunt/RankA/Starcrier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public enum AID : uint
class WingsbreadthWinds(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.WingsbreadthWinds), new AOEShapeCircle(8));
class StormwallWinds(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.StormwallWinds), new AOEShapeDonut(8, 25));
class DirgeOfTheLost(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.DirgeOfTheLost), new AOEShapeCircle(40));
class DirgeOfTheLostHint(BossModule module) : Components.CastHint(module, ActionID.MakeSpell(AID.DirgeOfTheLost), "Applies Temporary Misdirection!");
class DirgeOfTheLostHint(BossModule module) : Components.TemporaryMisdirection(module, ActionID.MakeSpell(AID.DirgeOfTheLost));
class AeroIV(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.AeroIV));
class SwiftwindSerenade(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.SwiftwindSerenade), new AOEShapeRect(40, 4));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell)
}

class BewilderingBlight(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.BewilderingBlight), 6);
class BewilderingBlightTM(BossModule module) : Components.TemporaryMisdirection(module, ActionID.MakeSpell(AID.BewilderingBlight));

abstract class SkineaterSurge(BossModule module, AID aid) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(aid), new AOEShapeCone(40, 90.Degrees()));
class SkineaterSurge1(BossModule module) : SkineaterSurge(module, AID.SkineaterSurge1);
Expand Down Expand Up @@ -157,6 +158,7 @@ public AnAntidoteForAnarchyStates(BossModule module) : base(module)
.ActivateOnEnter<StingingMalady>()
.ActivateOnEnter<StingingMaladyBait>()
.ActivateOnEnter<BewilderingBlight>()
.ActivateOnEnter<BewilderingBlightTM>()
.ActivateOnEnter<SkineaterSurge1>()
.ActivateOnEnter<SkineaterSurge2>()
.ActivateOnEnter<Burst>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Hieroglyphika(BossModule module) : Components.GenericAOEs(module, ActionID
public readonly List<AOEInstance> AOEs = [];

private static readonly AOEShapeRect _shape = new(6, 6, 6);
private static readonly WDir[] _canonicalSafespots = [new(-6, 18), new(18, -18)];
private static readonly WDir[] _canonicalSafespots = [new(6, -18), new(-18, 18)];

public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor) => AOEs;

Expand All @@ -31,7 +31,7 @@ public override void OnEventEnvControl(byte index, uint state)

WDir dir = index switch
{
0x17 => new(1, 0),
0x17 => new(-1, 0),
0x4A => new(0, 1),
_ => default
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public override void OnEventIcon(Actor actor, uint iconID, ulong targetID)
{
WDir dir = (IconID)iconID switch
{
IconID.HieroglyphikaCW => new(1, 0),
IconID.HieroglyphikaCCW => new(-1, 0),
IconID.HieroglyphikaCW => new(-1, 0),
IconID.HieroglyphikaCCW => new(1, 0),
_ => default
};
if (dir == default)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public enum AID : uint
class WhatIsLeft(BossModule module) : Cleave(module, AID.WhatIsLeft);
class WhatIsRight(BossModule module) : Cleave(module, AID.WhatIsRight);

class LostHope(BossModule module) : Components.CastHint(module, ActionID.MakeSpell(AID.LostHope), "Applies temporary misdirection");
class LostHope(BossModule module) : Components.TemporaryMisdirection(module, ActionID.MakeSpell(AID.LostHope));
class Vitriol(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Vitriol), new AOEShapeCircle(13));
class NoteOfDespair(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.NoteOfDespair));
class Wallow(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.Wallow), 6);
Expand Down
Loading

0 comments on commit 5aa012e

Please sign in to comment.