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

[DRK] ClassDRKUtility.cs Implementation #428

Closed
wants to merge 11 commits into from
Closed
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
69 changes: 69 additions & 0 deletions BossMod/Autorotation/Utility/ClassDRKUtility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
namespace BossMod.Autorotation;

public sealed class ClassDRKUtility(RotationModuleManager manager, Actor player) : RoleTankUtility(manager, player)
{
public enum Track { DarkMind = SharedTrack.Count, ShadowWall, LivingDead, TheBlackestNight, Oblation, DarkMissionary, Stance }
public enum WallOption { None, ShadowWall, ShadowedVigil }
public enum ForceStanceOption { None, StanceOn, StanceOff }

public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(DRK.AID.DarkForce);
public static readonly ActionID IDStanceApply = ActionID.MakeSpell(DRK.AID.Grit);
public static readonly ActionID IDStanceRemove = ActionID.MakeSpell(DRK.AID.ReleaseGrit);

public static RotationModuleDefinition Definition()
{
var res = new RotationModuleDefinition("Utility: DRK", "Planner support for utility actions", "Akechi", RotationModuleQuality.Ok, BitMask.Build((int)Class.DRK), 100);
DefineShared(res, IDLimitBreak3, IDStanceApply, IDStanceRemove);

DefineSimpleConfig(res, Track.DarkMind, "DarkMind", "DMind", 450, DRK.AID.DarkMind, 10); //120s CD, 15s duration

res.Define(Track.ShadowWall).As<WallOption>("ShadowWall", "Wall", 550) //120s CD, 15s duration
.AddOption(WallOption.None, "None", "Do not use automatically")
.AddOption(WallOption.ShadowWall, "Use", "Use ShadowWall", 120, 15, ActionTargets.Self, 38, 91)
.AddOption(WallOption.ShadowedVigil, "UseEx", "Use ShadowedVigil", 120, 15, ActionTargets.Self, 92)
.AddAssociatedActions(DRK.AID.ShadowWall, DRK.AID.ShadowedVigil);

DefineSimpleConfig(res, Track.LivingDead, "LivingDead", "LD", 400, DRK.AID.LivingDead, 10); //300s CD, 10s duration
DefineSimpleConfig(res, Track.TheBlackestNight, "The Blackest Night", "TBN", 400, DRK.AID.TheBlackestNight, 7); //15s CD, 7s duration, 3000MP cost
DefineSimpleConfig(res, Track.Oblation, "Oblation", "Obl", 320, DRK.AID.Oblation, 10); //60s CD, 10s duration (TODO: Has Two (2) charges; re-consider better use of both in CDPlanner)
DefineSimpleConfig(res, Track.DarkMissionary, "DarkMissionary", "Mission", 220, DRK.AID.DarkMissionary, 15); //90s CD, 15s duration

res.Define(Track.Stance).As<ForceStanceOption>("Stance", "", 200) //Forcing Stance for CD planning use
.AddOption(ForceStanceOption.None, "None", "Do not use automatically")
.AddOption(ForceStanceOption.StanceOn, "", "Force Stance On", 0, 0, ActionTargets.Self)
.AddOption(ForceStanceOption.StanceOff, "", "Force Stance Off", 0, 0, ActionTargets.Self)
.AddAssociatedActions(DRK.AID.Grit, DRK.AID.ReleaseGrit);

return res;
}

public override void Execute(StrategyValues strategy, Actor? primaryTarget, float estimatedAnimLockDelay, float forceMovementIn, bool isMoving)
{
ExecuteShared(strategy, IDLimitBreak3, IDStanceApply, IDStanceRemove, (uint)DRK.SID.Grit, primaryTarget); //Execution of our shared abilities
ExecuteSimple(strategy.Option(Track.DarkMind), DRK.AID.DarkMind, Player); //Execution of DarkMind
ExecuteSimple(strategy.Option(Track.LivingDead), DRK.AID.LivingDead, Player); //Execution of LivingDead
ExecuteSimple(strategy.Option(Track.TheBlackestNight), DRK.AID.TheBlackestNight, Player); //Execution of TheBlackestNight
ExecuteSimple(strategy.Option(Track.Oblation), DRK.AID.Oblation, Player); //Execution of Oblation
ExecuteSimple(strategy.Option(Track.DarkMissionary), DRK.AID.DarkMissionary, Player); //Execution of DarkMissionary

var wall = strategy.Option(Track.ShadowWall);
var wallAction = wall.As<WallOption>() switch
{
WallOption.ShadowWall => DRK.AID.ShadowWall,
WallOption.ShadowedVigil => DRK.AID.ShadowedVigil,
_ => default
};
if (wallAction != default)
Hints.ActionsToExecute.Push(ActionID.MakeSpell(wallAction), Player, wall.Priority(), wall.Value.ExpireIn); //Checking proper use of said option

var stance = strategy.Option(Track.Stance);
var stanceOption = stance.As<ForceStanceOption>() switch
{
ForceStanceOption.StanceOn => DRK.AID.Grit,
ForceStanceOption.StanceOff => DRK.AID.ReleaseGrit,
_ => default
};
if (stanceOption != default)
Hints.ActionsToExecute.Push(ActionID.MakeSpell(stanceOption), Player, stance.Priority()); //Checking proper use of said option
}
}
8 changes: 1 addition & 7 deletions BossMod/Framework/ActionManagerEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,13 +235,7 @@ private bool ExecuteAction(ActionID action, ulong targetId, Vector3 targetPos)
{
// real action type, just execute our UAL hook
// note that for items extraParam should be 0xFFFF (since we want to use any item, not from first inventory slot)
// note that for 'summon carbuncle/eos/titan/ifrit/garuda' actions, extraParam can be used to select glamour
var extraParam = action.Type switch
{
ActionType.Spell => ActionManager.GetExtraParamForSummonAction(action.ID), // will return 0 for non-summon actions
ActionType.Item => 0xFFFFu,
_ => 0u
};
var extraParam = action.Type == ActionType.Item ? 0xFFFFu : 0;
return _inst->UseActionLocation((CSActionType)action.Type, action.ID, targetId, &targetPos, extraParam);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,81 +3,79 @@
public enum OID : uint
{
Boss = 0x4159, // R5.280, x1
Helper = 0x233C, // R0.500, x4, Helper type
QorrlohTehHelper = 0x43A2, // R0.500, x4
QorrlohTeh = 0x415A, // R3.000, x0 (spawn during fight) - circle aoe add
RorrlohTeh = 0x415B, // R1.500, x0 (spawn during fight) - rect aoe add
Snowball = 0x415C, // R2.500, x0 (spawn during fight)
Helper = 0x233C,
}

public enum AID : uint
{
AutoAttack = 872, // Boss/Snowball->player, no cast, single-target
FrostingFracas = 36279, // Boss->self, 5.0s cast, single-target, visual (raidwide)
FrostingFracasAOE = 36280, // Helper->self, 5.0s cast, range 60 circle, raidwide
FluffleUp = 36265, // Boss->self, 4.0s cast, single-target, visual (spawn adds)
ColdFeat = 36266, // Boss->self, 4.0s cast, single-target, visual (freeze adds)
IceScream = 36270, // RorrlohTeh->self, 12.0s cast, range 20 width 20 rect
FrozenSwirl = 36271, // QorrlohTeh->self, 12.0s cast, single-target, visual (circle aoe)
FrozenSwirlAOE = 36272, // QorrlohTehHelper->self, 12.0s cast, range 15 circle
Snowscoop = 36275, // Boss->self, 4.0s cast, single-target, visual (spawn snowballs)
SnowBoulder = 36278, // Snowball->self, 4.0s cast, range 50 width 6 rect
SparklingSprinkling = 36713, // Boss->self, 5.0s cast, single-target, visual (spread)
SparklingSprinklingAOE = 36281, // Helper->player, 5.0s cast, range 5 circle spread
FrostingFracas = 36280, // 233C->self, 5.0s cast, range 60 circle
IceScream = 36270, // 415B->self, 12.0s cast, range 20 width 20 rect
FrozenSwirl = 36272, // 43A2->self, 12.0s cast, range 15 circle
SnowBoulder = 36278, // 415C->self, 4.0s cast, range 50 width 6 rect
SparklingSprinkling = 36281, // 233C->player, 5.0s cast, range 5 circle
}

public enum IconID : uint
public enum SID : uint
{
SparklingSprinkling = 376, // player
Frozen = 3944, // none->_Gen_RorrlohTeh/_Gen_QorrlohTeh1, extra=0x0
Frozen2 = 3445
}

public enum TetherID : uint
class FrostingFracas(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.FrostingFracas));

abstract class FreezableAOEs(BossModule module, ActionID action, AOEShape shape) : Components.GenericAOEs(module)
{
Freeze = 272, // RorrlohTeh/QorrlohTeh->Boss
}
protected Dictionary<Actor, bool> _casters = [];
protected byte _numFrozen;
protected bool _anyCastFinished;

class FrostingFracas(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.FrostingFracasAOE));
public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor)
{
if (_numFrozen < 2)
yield break;

abstract class FreezableAOEs(BossModule module, ActionID action, AOEShape shape) : Components.GenericAOEs(module, action)
{
private readonly List<AOEInstance> _aoes = [];
private int _numFrozen;
foreach ((var caster, var isFrozen) in _casters)
{
var isCastReal = !isFrozen || _anyCastFinished;

public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor) => _numFrozen >= 2 ? _aoes.Take(2) : [];
if (isCastReal)
yield return new AOEInstance(shape, caster.Position, caster.Rotation, Module.CastFinishAt(caster.CastInfo, isFrozen ? 8 : 0));
}
}

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if (spell.Action == WatchedAction)
_aoes.Add(new(shape, caster.Position, spell.Rotation, Module.CastFinishAt(spell)));
if (spell.Action == action)
_casters[caster] = false;
}

public override void OnCastFinished(Actor caster, ActorCastInfo spell)
{
if (spell.Action == WatchedAction && _aoes.Count > 0)
if (spell.Action == action)
if (_casters.Remove(caster))
_anyCastFinished = true;

if (_casters.Count == 0)
{
_aoes.RemoveAt(0);
if (_aoes.Count == 0)
_numFrozen = 0;
_anyCastFinished = false;
_numFrozen = 0;
}
}

public override void OnTethered(Actor source, ActorTetherInfo tether)
public override void OnStatusGain(Actor actor, ActorStatus status)
{
if (tether.ID == (uint)TetherID.Freeze && _aoes.FindIndex(aoe => aoe.Origin.AlmostEqual(source.Position, 1)) is var index && index >= 0)
if ((SID)status.ID is SID.Frozen or SID.Frozen2 && _casters.ContainsKey(actor))
{
++_numFrozen;
var aoe = _aoes[index];
aoe.Activation += TimeSpan.FromSeconds(8);
_aoes.Add(aoe);
_aoes.RemoveAt(index);
_casters[actor] = true;
_numFrozen += 1;
}
}
}
class IceScream(BossModule module) : FreezableAOEs(module, ActionID.MakeSpell(AID.IceScream), new AOEShapeRect(20, 10));
class FrozenSwirl(BossModule module) : FreezableAOEs(module, ActionID.MakeSpell(AID.FrozenSwirl), new AOEShapeCircle(15)); // note that helpers that cast visual cast are tethered

class SnowBoulder(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.SnowBoulder), new AOEShapeRect(50, 3), 6);
class SparklingSprinkling(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.SparklingSprinklingAOE), 5);
class IceScream(BossModule module) : FreezableAOEs(module, ActionID.MakeSpell(AID.IceScream), new AOEShapeRect(20, 10));
class FrozenSwirl(BossModule module) : FreezableAOEs(module, ActionID.MakeSpell(AID.FrozenSwirl), new AOEShapeCircle(15));
class SparklingSprinkling(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.SparklingSprinkling), 5);
class SnowBoulder(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.SnowBoulder), new AOEShapeRect(50, 3), maxCasts: 6);

class D021RyoqorTertehStates : StateMachineBuilder
{
Expand All @@ -87,10 +85,10 @@ public D021RyoqorTertehStates(BossModule module) : base(module)
.ActivateOnEnter<FrostingFracas>()
.ActivateOnEnter<IceScream>()
.ActivateOnEnter<FrozenSwirl>()
.ActivateOnEnter<SnowBoulder>()
.ActivateOnEnter<SparklingSprinkling>();
.ActivateOnEnter<SparklingSprinkling>()
.ActivateOnEnter<SnowBoulder>();
}
}

[ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "xan", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 824, NameID = 12699)]
[ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "xan", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 824, NameID = 12699)]
public class D021RyoqorTerteh(WorldState ws, Actor primary) : BossModule(ws, primary, new(-108, 119), new ArenaBoundsCircle(20));
Loading
Loading