diff --git a/RotationSolver/Actions/Actions.cd b/RotationSolver.Basic/Actions/Actions.cd similarity index 100% rename from RotationSolver/Actions/Actions.cd rename to RotationSolver.Basic/Actions/Actions.cd diff --git a/RotationSolver.Basic/Actions/BaseAction/BaseAction_ActionInfo.cs b/RotationSolver.Basic/Actions/BaseAction/BaseAction_ActionInfo.cs new file mode 100644 index 000000000..ec67f83c9 --- /dev/null +++ b/RotationSolver.Basic/Actions/BaseAction/BaseAction_ActionInfo.cs @@ -0,0 +1,138 @@ +using Dalamud.Game.ClientState.Objects.Types; +using FFXIVClientStructs.FFXIV.Client.Game; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.CustomRotation; +using RotationSolver.SigReplacers; + +namespace RotationSolver.Actions.BaseAction; + +public partial class BaseAction +{ + public float Range => ActionManager.GetActionRange(ID); + + public ActionID[] ComboIdsNot { private get; set; } = null; + + public ActionID[] ComboIds { private get; set; } = null; + + public StatusID[] StatusProvide { get; set; } = null; + + public virtual StatusID[] StatusNeed { get; set; } = null; + + public Func ActionCheck { get; set; } = null; + + public Func RotationCheck { get; set; } = null; + + private bool WillCooldown + { + get + { + if (!IsGeneralGCD && IsCoolingDown) + { + if (IsRealGCD) + { + if (!WillHaveOneChargeGCD()) return false; + } + else + { + if ((ClassJobID)Service.Player.ClassJob.Id != ClassJobID.BlueMage + && ChoiceTarget != TargetFilter.FindTargetForMoving + && DataCenter.LastAction == (ActionID)AdjustedID) return false; + + if (!WillHaveOneCharge(DataCenter.AbilityRemain, false)) return false; + } + } + + return true; + } + } + + public unsafe virtual bool CanUse(out IAction act, bool mustUse = false, bool emptyOrSkipCombo = false, bool skipDisable = false, uint gcdCountForAbility = 0, bool recordTarget = true) + { + act = this; + + var player = Service.Player; + if (player == null) return false; + + if (!skipDisable && !IsEnabled) return false; + + if (ConfigurationHelper.BadStatus.Contains(ActionManager.Instance()->GetActionStatus(ActionType.Spell, AdjustedID))) + return false; + + if (!EnoughLevel) return false; + + if (player.CurrentMp < MPNeed) return false; + + if (StatusNeed != null) + { + if (!player.HasStatus(true, StatusNeed)) return false; + } + + if (StatusProvide != null && !mustUse) + { + if (player.HasStatus(true, StatusProvide)) return false; + } + + if (!WillCooldown) return false; + + if (!emptyOrSkipCombo) + { + if (IsGeneralGCD) + { + if (!CheckForCombo()) return false; + } + else + { + if (RecastTimeRemain > DataCenter.WeaponRemain + DataCenter.WeaponTotal * gcdCountForAbility) + return false; + } + } + + if (CastTime > 0 && DataCenter.IsMoving && + !player.HasStatus(true, CustomRotation.Swiftcast.StatusProvide)) return false; + + if (!FindTarget(mustUse, out var target)) return false; + + if (ActionCheck != null && !ActionCheck(target)) return false; + if (!skipDisable && RotationCheck != null && !RotationCheck(target)) return false; + + Target = target; + if(recordTarget) _targetId = target.ObjectId; + return true; + } + + private bool CheckForCombo() + { + if (ComboIdsNot != null) + { + if (ComboIdsNot.Contains(DataCenter.LastComboAction)) return false; + } + + var comboActions = _action.ActionCombo?.Row != 0 + ? new ActionID[] { (ActionID)_action.ActionCombo.Row } + : new ActionID[0]; + if (ComboIds != null) comboActions = comboActions.Union(ComboIds).ToArray(); + + if (comboActions.Length > 0) + { + if (comboActions.Contains(DataCenter.LastComboAction)) + { + if (DataCenter.ComboTime < DataCenter.WeaponRemain) return false; + } + else + { + return false; + } + } + return true; + } + + public unsafe bool Use() + { + var loc = new FFXIVClientStructs.FFXIV.Common.Math.Vector3() { X = _position.X, Y = _position.Y, Z = _position.Z }; + + return _action.TargetArea ? ActionManager.Instance()->UseActionLocation(ActionType.Spell, ID, Service.Player.ObjectId, &loc) : + ActionManager.Instance()->UseAction(ActionType.Spell, AdjustedID, _targetId); + } +} diff --git a/RotationSolver.Basic/Actions/BaseAction/BaseAction_BasicInfo.cs b/RotationSolver.Basic/Actions/BaseAction/BaseAction_BasicInfo.cs new file mode 100644 index 000000000..8df9b0e95 --- /dev/null +++ b/RotationSolver.Basic/Actions/BaseAction/BaseAction_BasicInfo.cs @@ -0,0 +1,128 @@ +using FFXIVClientStructs.FFXIV.Client.Game; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using Action = Lumina.Excel.GeneratedSheets.Action; + +namespace RotationSolver.Actions.BaseAction; + +public partial class BaseAction : IBaseAction +{ + + public bool IsFriendly { get; } + public bool IsEot { get; } + Action _action; + + public bool ShouldEndSpecial { get; private set; } + public bool IsTimeline { get; } = false; + + public Func GetDotGcdCount { private get; set; } + + /// + /// EnoughLevel for using. + /// + public bool EnoughLevel => Service.Player.Level >= _action.ClassJobLevel; + public string Name => _action.Name; + + public string Description => string.Empty; + + //public string CateName + //{ + // get + // { + // string result; + + // if (_isFriendly) + // { + // result = LocalizationManager.RightLang.Action_Friendly; + // if (_isEot) + // { + // result += "Hot"; + // } + // } + // else + // { + // result = LocalizationManager.RightLang.Action_Attack; + + // if (_isEot) + // { + // result += "Dot"; + // } + // } + // result += "-" + (IsRealGCD ? "GCD" : LocalizationManager.RightLang.Timeline_Ability); + // return result; + // } + //} + public bool IsEnabled + { + get => !Service.Config.DisabledActions.Contains(ID); + set + { + if (value) + { + Service.Config.DisabledActions.Remove(ID); + } + else + { + Service.Config.DisabledActions.Add(ID); + } + } + } + public uint ID => _action.RowId; + public uint AdjustedID => (uint)Service.GetAdjustedActionId((ActionID)ID); + + public uint IconID => _action.Icon; + + private bool IsGeneralGCD { get; } + + public bool IsRealGCD { get; } + + private byte CoolDownGroup { get; } + + public unsafe float CastTime => ActionManager.GetAdjustedCastTime(ActionType.Spell, AdjustedID) / 1000f; + + public virtual EnemyPositional EnemyPositional + { + get + { + if (ConfigurationHelper.ActionPositional.TryGetValue((ActionID)ID, out var location)) + { + return location.Pos; + } + return EnemyPositional.None; + } + } + + public virtual unsafe uint MPNeed + { + get + { + var mp = (uint)ActionManager.GetActionCost(ActionType.Spell, AdjustedID, 0, 0, 0, 0); + if (mp < 100) return 0; + return mp; + } + } + + /// + /// + /// + /// + /// is a friendly or supporting action + /// end special after using it + /// is hot or dot action + /// should I put it to the timeline (heal and defense only) + public BaseAction(ActionID actionID, bool isFriendly = false, bool shouldEndSpecial = false, bool isEot = false, bool isTimeline = false) + { + _action = Service.GetSheet().GetRow((uint)actionID); + ShouldEndSpecial = shouldEndSpecial; + IsFriendly = isFriendly; + IsEot = isEot; + IsTimeline = isTimeline; + + IsGeneralGCD = _action.IsGeneralGCD(); + IsRealGCD = _action.IsRealGCD(); + CoolDownGroup = _action.GetCoolDownGroup(); + } + + public override string ToString() => Name; +} diff --git a/RotationSolver.Basic/Actions/BaseAction/BaseAction_Cooldown.cs b/RotationSolver.Basic/Actions/BaseAction/BaseAction_Cooldown.cs new file mode 100644 index 000000000..82c067416 --- /dev/null +++ b/RotationSolver.Basic/Actions/BaseAction/BaseAction_Cooldown.cs @@ -0,0 +1,90 @@ +using FFXIVClientStructs.FFXIV.Client.Game; +using RotationSolver.Basic; +using RotationSolver.Helpers; +using System; + + +namespace RotationSolver.Actions.BaseAction; + +public partial class BaseAction +{ + public bool ElapsedAfterGCD(uint gcdCount = 0, uint abilityCount = 0) + { + if (!IsCoolingDown) return false; + var elapsed = RecastTimeElapsedOneCharge; + return CooldownHelper.ElapsedAfterGCD(elapsed, gcdCount, abilityCount); + } + + public bool ElapsedAfter(float time) + { + if (!IsCoolingDown) return false; + var elapsed = RecastTimeElapsedOneCharge; + return CooldownHelper.ElapsedAfter(elapsed, time); + } + + public bool WillHaveOneChargeGCD(uint gcdCount = 0, uint abilityCount = 0) + { + if (HaveOneCharge) return true; + var recast = RecastTimeRemainOneCharge; + return CooldownHelper.RecastAfterGCD(recast, gcdCount, abilityCount); + } + + public bool WillHaveOneCharge(float remain) => WillHaveOneCharge(remain, true); + + private bool WillHaveOneCharge(float remain, bool addWeaponRemain) + { + if (HaveOneCharge) return true; + var recast = RecastTimeRemainOneCharge; + return CooldownHelper.RecastAfter(recast, remain, addWeaponRemain); + } + + + private unsafe RecastDetail* CoolDownDetail => ActionManager.Instance()->GetRecastGroupDetail(CoolDownGroup - 1); + /// + /// 复唱时间 + /// + private unsafe float RecastTime => CoolDownDetail->Total; + + /// + /// 复唱经过时间 + /// + private unsafe float RecastTimeElapsed => CoolDownDetail->Elapsed; + + /// + /// 是否正在冷却中 + /// + public unsafe bool IsCoolingDown => CoolDownDetail->IsActive != 0; + + /// + /// 复唱剩余时间 + /// + private float RecastTimeRemain => RecastTime - RecastTimeElapsed; + + /// + /// 技能的最大层数 + /// + public unsafe ushort MaxCharges => Math.Max(ActionManager.GetMaxCharges(AdjustedID, Service.Player.Level), (ushort)1); + /// + /// 是否起码有一层技能 + /// + private bool HaveOneCharge => IsCoolingDown ? RecastTimeElapsed >= RecastTimeOneCharge : true; + /// + /// 当前技能层数 + /// + public ushort CurrentCharges => IsCoolingDown ? (ushort)(RecastTimeElapsed / RecastTimeOneCharge) : MaxCharges; + + private float RecastTimeOneCharge => ActionManager.GetAdjustedRecastTime(ActionType.Spell, AdjustedID) / 1000f; + + /// + /// 下一层转好的时间 + /// + private float RecastTimeRemainOneCharge => RecastTimeRemain % RecastTimeOneCharge; + private float RecastTimeElapsedOneCharge => RecastTimeElapsed % RecastTimeOneCharge; + +#if DEBUG + public bool HaveOneChargeDEBUG => HaveOneCharge; + public float RecastTimeOneChargeDEBUG => RecastTimeOneCharge; + public float RecastTimeElapsedDEBUG => RecastTimeElapsed; + public float RecastTimeRemainDEBUG => RecastTimeRemain; +#endif +} diff --git a/RotationSolver.Basic/Actions/BaseAction/BaseAction_Target.cs b/RotationSolver.Basic/Actions/BaseAction/BaseAction_Target.cs new file mode 100644 index 000000000..8a29bc77a --- /dev/null +++ b/RotationSolver.Basic/Actions/BaseAction/BaseAction_Target.cs @@ -0,0 +1,504 @@ +using Dalamud.Game.ClientState.Objects.SubKinds; +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Logging; +using FFXIVClientStructs.FFXIV.Client.Game; +using RotationSolver.Basic; +using RotationSolver.Commands; +using RotationSolver.Data; +using RotationSolver.Helpers; +using System.Numerics; + +namespace RotationSolver.Actions.BaseAction; + +public partial class BaseAction +{ + public byte AOECount { private get; set; } = 3; + + /// + /// Shortcut for Target.IsDying(); + /// + public bool IsTargetDying => Target?.IsDying() ?? false; + + /// + /// Shortcut for Target.IsBoss(); + /// + public bool IsTargetBoss => Target?.IsBoss() ?? false; + + /// + /// The action's target. + /// + public BattleChara Target { get; private set; } = Service.Player; + + private Vector3 _position = default; + private uint _targetId = Service.Player.ObjectId; + + private Func, bool, BattleChara> _choiceTarget = null; + public Func, bool, BattleChara> ChoiceTarget + { + private get + { + if (_choiceTarget != null) return _choiceTarget; + return IsFriendly ? TargetFilter.DefaultChooseFriend : TargetFilter.DefaultFindHostile; + } + set => _choiceTarget = value; + } + + internal Func, IEnumerable> FilterForHostiles { private get; set; } = null; + + public StatusID[] TargetStatus { get; set; } = null; + + internal static bool TankDefenseSelf(BattleChara chara) + { + return DataCenter.TarOnMeTargets.Any(); + } + internal static bool TankBreakOtherCheck(ClassJobID id) + { + var tankHealth = id.GetHealthForDyingTank(); + + return DataCenter.HasHostilesInRange + && Service.Player.GetHealthRatio() < tankHealth + && DataCenter.PartyMembersAverHP > tankHealth + 0.01f; + } + + private bool FindTarget(bool mustUse, out BattleChara target) + { + int aoeCount = mustUse ? 1 : AOECount; + + _position = Service.Player.Position; + var player = Service.Player; + + float range = Range; + + //如果都没有距离,这个还需要选对象嘛?选自己啊! + if (range == 0 && _action.EffectRange == 0) + { + target = player; + return true; + } + else if (_action.TargetArea) + { + target = player; + return TargetArea(range, mustUse, aoeCount, player); + } + //如果能对友方和敌方都能选中 + else if (_action.CanTargetParty && _action.CanTargetHostile) + { + return TargetPartyAndHostile(range, mustUse, out target); + } + //首先看看是不是能对小队成员进行操作的。 + else if (_action.CanTargetParty) + { + return TargetParty(range, aoeCount, mustUse, out target); + } + //再看看是否可以选中敌对的。 + else if (_action.CanTargetHostile) + { + return TargetHostile(range, mustUse, aoeCount, out target); + } + //如果只能选自己,那就选自己吧。 + else if (_action.CanTargetSelf) + { + target = player; + return TargetSelf(mustUse, aoeCount); + } + else + { + target = Service.TargetManager.Target is BattleChara battle ? battle : player; + return true; + } + } + + #region TargetArea + private bool TargetArea(float range, bool mustUse, int aoeCount, PlayerCharacter player) + { + //移动 + if (_action.EffectRange == 1 && range >= 15) + { + return TargetAreaMove(range, mustUse); + } + //其他友方 + else if (IsFriendly) + { + return TargetAreaFriend(range, mustUse, player); + } + //敌方 + else + { + return TargetAreaHostile(aoeCount); + } + } + + private bool TargetAreaHostile(int aoeCount) + { + var target = GetMostObjects(DataCenter.HostileTargets, aoeCount) + .OrderByDescending(ObjectHelper.GetHealthRatio).FirstOrDefault(); + if (target == null) return false; + _position = target.Position; + return true; + } + + private bool TargetAreaMove(float range, bool mustUse) + { + if (Service.Config.MoveAreaActionFarthest) + { + Vector3 pPosition = Service.Player.Position; + float rotation = Service.Player.Rotation; + _position = new Vector3(pPosition.X + (float)Math.Sin(rotation) * range, pPosition.Y, + pPosition.Z + (float)Math.Cos(rotation) * range); + return true; + } + else + { + var availableCharas = DataCenter.AllTargets.Where(b => b.ObjectId != Service.Player.ObjectId); + var target = TargetFilter.GetObjectInRadius(availableCharas, range).FindTargetForMoving(mustUse); + if (target == null) return false; + _position = target.Position; + return true; + } + } + + private bool TargetAreaFriend(float range, bool mustUse, PlayerCharacter player) + { + //如果用户不想使用自动友方地面放置功能 + if (!Service.Config.UseGroundBeneficialAbility) return false; + + //如果当前目标是Boss且有身位,放他身上。 + if (Service.TargetManager.Target is BattleChara b && b.DistanceToPlayer() < range && b.IsBoss() && b.HasPositional()) + { + _position = b.Position; + return true; + } + //计算玩家和被打的T之间的关系。 + else + { + var attackT = TargetFilter.FindAttackedTarget(DataCenter.PartyTanks.GetObjectInRadius(range + _action.EffectRange), mustUse); + + if (attackT == null) + { + _position = player.Position; + } + else + { + var disToTankRound = Math.Max(range, Vector3.Distance(player.Position, attackT.Position) + attackT.HitboxRadius); + + if (disToTankRound < _action.EffectRange + || disToTankRound > 2 * _action.EffectRange - player.HitboxRadius + || disToTankRound > range) + { + _position = player.Position; + } + else + { + Vector3 directionToTank = attackT.Position - player.Position; + var MoveDirection = directionToTank / directionToTank.Length() * (disToTankRound - _action.EffectRange); + _position = player.Position + MoveDirection; + } + } + return true; + } + } + #endregion + + private bool TargetPartyAndHostile(float range, bool mustUse, out BattleChara target) + { + var availableCharas = DataCenter.PartyMembers.Union(DataCenter.HostileTargets) + .Where(b => b.ObjectId != Service.Player.ObjectId); + availableCharas = TargetFilter.GetObjectInRadius(availableCharas, range).Where(CanUseTo); + + target = ChoiceTarget(availableCharas, mustUse); + if (target == null) return false; + return true; + } + + #region Target party + private bool TargetParty(float range, int aoeCount, bool mustUse, out BattleChara target) + { + //还消耗2400的蓝,那肯定是复活的。 + if (_action.PrimaryCostType == 3 && _action.PrimaryCostValue == 24 || (ActionID)ID == ActionID.AngelWhisper) + { + return TargetDeath(out target); + } + + //找到没死的队友们。 + var availableCharas = DataCenter.PartyMembers.Where(player => player.CurrentHp != 0); + + if (Service.Config.TargetFriendly ? _action.CanTargetFriendly : (ActionID)ID == ActionID.AethericMimicry) + { + availableCharas = availableCharas.Union(DataCenter.AllianceMembers); + } + if (!_action.CanTargetSelf) + { + availableCharas = availableCharas.Where(p => p.ObjectId != Service.Player.ObjectId); + } + if (!availableCharas.Any()) + { + target = null; + return false; + } + + //判断是否是范围。 + if (_action.CastType > 1 && (ActionID)ID != ActionID.DeploymentTactics) + { + //找到能覆盖最多的位置,并且选血最少的来。 + target = ChoiceTarget(GetMostObjects(availableCharas, aoeCount), mustUse); + } + else + { + availableCharas = TargetFilter.GetObjectInRadius(availableCharas, range) + .Where(CanUseTo); + //特殊选队友的方法。 + target = ChoiceTarget(availableCharas, mustUse); + } + + return mustUse || CheckStatus(target); + } + + private bool TargetDeath(out BattleChara target) + { + target = TargetFilter.GetDeathPeople(DataCenter.DeathPeopleAll, DataCenter.DeathPeopleParty); + if (target == null) return false; + return true; + } + #endregion + + #region Target Hostile + private bool TargetHostile(float range, bool mustUse, int aoeCount, out BattleChara target) + { + //如果不用自动找目标,那就直接返回。 + if (DataCenter.StateType == StateCommandType.Manual) + { + if (Service.TargetManager.Target is BattleChara b && b.IsNPCEnemy() && b.DistanceToPlayer() <= range) + { + return TargetHostileManual(b, mustUse, aoeCount, out target); + } + + target = null; + return false; + } + + //判断一下AOE攻击的时候如果有攻击目标标记目标 + if (_action.CastType > 1 && NoAOE) + { + target = null; + return false; + } + + target = ChoiceTarget(GetMostObjects(TargetFilterFuncEot(DataCenter.HostileTargets, mustUse), aoeCount), mustUse); + if (target == null) return false; + return true; + } + + private bool TargetHostileManual(BattleChara b, bool mustUse, int aoeCount, out BattleChara target) + { + target = b; + if (!CanUseTo(b)) return false; + + if (_action.CastType == 1) + { + if (!mustUse) + { + //No need to dot. + if (TargetStatus != null && !ObjectHelper.CanDot(b)) return false; + + //Already has status. + if (!CheckStatus(b)) return false; + } + + return true; + } + + if (Service.Config.UseAOEAction && Service.Config.UseAOEWhenManual || mustUse) + { + if (GetMostObjects(TargetFilterFuncEot(DataCenter.HostileTargets, mustUse), aoeCount).Contains(b)) + { + return true; + } + } + target = null; + return false; + } + #endregion + + private bool TargetSelf(bool mustUse, int aoeCount) + { + if (_action.EffectRange > 0 && !IsFriendly) + { + if (NoAOE) + { + return false; + } + + //如果不用自动找目标,那就不打AOE + if (DataCenter.StateType == StateCommandType.Manual) + { + if (!Service.Config.UseAOEWhenManual && !mustUse) return false; + } + + var tars = TargetFilter.GetObjectInRadius(TargetFilterFuncEot(DataCenter.HostileTargets, mustUse), _action.EffectRange); + if (tars.Count() < aoeCount) return false; + + if (Service.Config.NoNewHostiles && TargetFilter.GetObjectInRadius(DataCenter.AllHostileTargets, _action.EffectRange) + .Any(t => t.TargetObject == null)) return false; + } + return true; + } + + #region Get Most Target + private IEnumerable GetMostObjects(IEnumerable targets, int maxCount) + { + var range = Range; + var canAttack = targets.Where(t => t.DistanceToPlayer() <= range + _action.EffectRange); + var canGetObj = canAttack.Where(t => t.DistanceToPlayer() <= range && CanUseTo(t)); + + if (_action.CastType == 1) return canGetObj; + + //能打到MaxCount以上数量的怪的怪。 + List objectMax = new List(canGetObj.Count()); + + //循环能打中的目标。 + foreach (var t in canGetObj) + { + //计算能达到的所有怪的数量。 + int count = CanGetTargetCount(t, canAttack); + + if (count == maxCount) + { + objectMax.Add(t); + } + else if (count > maxCount) + { + maxCount = count; + objectMax.Clear(); + objectMax.Add(t); + } + } + + return objectMax; + } + + private int CanGetTargetCount(BattleChara target, IEnumerable canAttack) + { + int count = 0; + foreach (var t in canAttack) + { + if(target == t) + { + count++; + } + else if(CanGetTarget(target, t)) + { + count++; + } + } + if (Service.Config.NoNewHostiles) + { + if (DataCenter.AllHostileTargets.Where(t => t.TargetObject == null) + .Any(t => CanGetTarget(target, t))) return 0; + } + return count; + } + + const double _alpha = Math.PI / 3; + + public bool CanGetTarget(BattleChara target, BattleChara subTarget) + { + if (target == null) return false; + if (_action.CastType == 1) return false; + if (target.DistanceToPlayer() > Range) return false; + + var pPos = Service.Player.Position; + Vector3 dir = target.Position - pPos; + Vector3 tdir = subTarget.Position - pPos; + + switch (_action.CastType) + { + case 10: //环形范围攻击也就这么判断吧,我烦了。 + var dis = Vector3.Distance(target.Position, subTarget.Position) - subTarget.HitboxRadius; + return dis <= _action.EffectRange && dis >= 8; + + case 2: // 圆形范围攻击 + return Vector3.Distance(target.Position, subTarget.Position) - subTarget.HitboxRadius <= _action.EffectRange; + + case 3: // Sector + if(subTarget.DistanceToPlayer() > _action.EffectRange) return false; + tdir += dir / dir.Length() * target.HitboxRadius / (float)Math.Sin(_alpha); + return Vector3.Dot(dir, tdir) / (dir.Length() * tdir.Length()) >= Math.Cos(_alpha); + + case 4: //直线范围攻击 + if (subTarget.DistanceToPlayer() > _action.EffectRange) return false; + return Vector3.Cross(dir, tdir).Length() / dir.Length() <= 2 + target.HitboxRadius; + } + + PluginLog.LogDebug(Name + "'s CastType is not valid! The value is " + _action.CastType.ToString()); + return false; + } + #endregion + + private IEnumerable TargetFilterFuncEot(IEnumerable tars, bool mustUse) + { + if (FilterForHostiles != null) return FilterForHostiles(tars); + if (TargetStatus == null || !IsEot) return tars; + + var dontHave = tars.Where(CheckStatus); + var canDot = dontHave.Where(ObjectHelper.CanDot); + + if (mustUse) + { + if (canDot.Any()) return canDot; + if (dontHave.Any()) return dontHave; + return tars; + } + else + { + return canDot; + } + } + + /// + /// + /// + /// + /// True for add Eot. + bool CheckStatus(BattleChara tar) + { + if (tar == null) return false; + + if (TargetStatus == null) return true; + + return tar.WillStatusEndGCD(GetDotGcdCount?.Invoke() ?? (uint)Service.Config.AddDotGCDCount, + 0, true, TargetStatus); + } + + +#if DEBUG + public unsafe bool CanUseTo(BattleChara tar) +#else + unsafe bool CanUseTo(BattleChara tar) + +#endif + { + if (tar == null) return false; + + var tarAddress = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)tar.Address; + + if (!ActionManager.CanUseActionOnTarget(AdjustedID, tarAddress)) return false; + + var id = ActionManager.GetActionInRangeOrLoS(AdjustedID, + (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Service.RawPlayer, + tarAddress); + + return id is 0 or 565; + } + + private static bool NoAOE + { + get + { + if (!Service.Config.UseAOEAction) return true; + + return Service.Config.ChooseAttackMark + && !Service.Config.CanAttackMarkAOE + && MarkingHelper.HaveAttackChara(DataCenter.HostileTargets); + } + } +} diff --git a/RotationSolver.Basic/Actions/BaseItem.cs b/RotationSolver.Basic/Actions/BaseItem.cs new file mode 100644 index 000000000..125f19f60 --- /dev/null +++ b/RotationSolver.Basic/Actions/BaseItem.cs @@ -0,0 +1,81 @@ +using FFXIVClientStructs.FFXIV.Client.Game; +using ImGuiNET; +using Lumina.Excel.GeneratedSheets; +using RotationSolver.Basic; +using RotationSolver.Helpers; + +namespace RotationSolver.Actions; + +internal class BaseItem : IBaseItem +{ + private Item _item = null; + private uint A4 { get; } = 0; + + public uint ID => _item.RowId; + public uint AdjustedID => ID; + + public Func OtherCheck { private get; set; } + public unsafe bool HaveIt => InventoryManager.Instance()->GetInventoryItemCount(_item.RowId, false) > 0 || + InventoryManager.Instance()->GetInventoryItemCount(_item.RowId, true) > 0; + + public uint IconID { get; } + + public string Name => _item.Name; + + public bool IsEnabled + { + get => !Service.Config.DisabledItems.Contains(ID); + set + { + if (value) + { + Service.Config.DisabledItems.Remove(ID); + } + else + { + Service.Config.DisabledItems.Add(ID); + } + } + } + + public string Description => string.Empty; + + public BaseItem(uint row, uint a4 = 65535) + { + _item = Service.GetSheet().GetRow(row); + IconID = _item.Icon; + A4 = a4; + } + + public unsafe bool CanUse(out IAction item) + { + item = this; + + if (_item == null) return false; + + if (!Service.Config.UseItem) return false; + + if (ConfigurationHelper.BadStatus.Contains(ActionManager.Instance()->GetActionStatus(ActionType.Item, ID))) return false; + + var remain = ActionManager.Instance()->GetRecastTime(ActionType.Item, ID) - ActionManager.Instance()->GetRecastTimeElapsed(ActionType.Item, ID); + + if (!CooldownHelper.RecastAfter(DataCenter.AbilityRemain, remain, false)) return false; + + if (OtherCheck != null && !OtherCheck()) return false; + + return HaveIt; + } + + public unsafe bool Use() + { + if (_item == null) return false; + + if (InventoryManager.Instance()->GetInventoryItemCount(_item.RowId, true) > 0) + { + return ActionManager.Instance()->UseAction(ActionType.Item, _item.RowId + 1000000, Service.Player.ObjectId, A4); + } + + return ActionManager.Instance()->UseAction(ActionType.Item, _item.RowId, Service.Player.ObjectId, A4); + } + +} diff --git a/RotationSolver.Basic/Actions/IAction.cs b/RotationSolver.Basic/Actions/IAction.cs new file mode 100644 index 000000000..68c77f9b9 --- /dev/null +++ b/RotationSolver.Basic/Actions/IAction.cs @@ -0,0 +1,8 @@ +namespace RotationSolver.Actions; + +public interface IAction : ITexture +{ + bool Use(); + uint ID { get; } + uint AdjustedID { get; } +} diff --git a/RotationSolver.Basic/Actions/IBaseAction.cs b/RotationSolver.Basic/Actions/IBaseAction.cs new file mode 100644 index 000000000..eb87a1d85 --- /dev/null +++ b/RotationSolver.Basic/Actions/IBaseAction.cs @@ -0,0 +1,153 @@ +using Dalamud.Game.ClientState.Objects.Types; +using RotationSolver.Data; +using System; + +namespace RotationSolver.Actions +{ + public interface IBaseAction : IAction + { + /// + /// MP for casting. + /// + uint MPNeed { get; } + + /// + /// Casting time + /// + float CastTime { get; } + + float Range { get; } + + bool IsFriendly { get; } + bool IsTimeline { get; } + bool IsEot { get; } + EnemyPositional EnemyPositional { get; } + + /// + /// If combo id is on this list, this action will not used. + /// + ActionID[] ComboIdsNot { set; } + + /// + /// The combos that are not written on the action list. + /// + ActionID[] ComboIds { set; } + + /// + /// If player has these statuses from player self, this action will not used. + /// + StatusID[] StatusProvide { get; set; } + + /// + /// If player doesn't have these statuses from player self, this action will not used. + /// + StatusID[] StatusNeed { get; set; } + + /// + /// Check for this action, but not for the rotation. It is some additional conditions for this action. + /// Input data is the target for this action. + /// + Func ActionCheck { get; set; } + + /// + /// Check for rotation, you can add it for simplify the rotation file. + /// Input data is the target for this action. + Func RotationCheck { get; set; } + + /// + /// Player's level is enough for this action's usage. + /// + bool EnoughLevel { get; } + + /// + /// Is a GCD action. + /// + bool IsRealGCD { get; } + + /// + /// Can I use this action at this time. It will check a lot of things. + /// Level, Enabled, Action Status, MP, Player Status, Coll down, Combo, Moving (for casting), Charges, Target, etc. + /// + /// + /// AOE only need one target to use. + /// Moving action don't need to have enough distance to use. + /// Skip for and checking. + /// Use all charges, no keeping one. + /// Do not need to check the combo. + /// Skip the disable for emergency use. Please always set this to false. + /// The count of gcd for ability to delay. Only used in BLM right now + /// Should I use. + bool CanUse(out IAction act, bool mustUse = false, bool emptyOrSkipCombo = false, bool skipDisable = false, uint gcdCountForAbility = 0, bool recordTarget = true); + + #region CoolDown + /// + /// Is action cooling down. + /// + bool IsCoolingDown { get; } + + /// + /// Current charges count. + /// + ushort CurrentCharges { get; } + + /// + /// Max charges count. + /// + ushort MaxCharges { get; } + + /// + /// Has it been in cooldown for gcds and abilities? + /// + /// + /// + /// + bool ElapsedAfterGCD(uint gcdCount = 0, uint abilityCount = 0); + + /// + /// Has it been in cooldown for seconds? + /// + /// + /// + bool ElapsedAfter(float time); + + /// + /// Will have at least one charge after gcds and abilities? + /// + /// + /// + /// + bool WillHaveOneChargeGCD(uint gcdCount = 0, uint abilityCount = 0); + + /// + /// Will have at least one charge after seconds? + /// + /// + /// + bool WillHaveOneCharge(float remain); + #endregion + + #region Target + /// + /// If target has these statuses from player self, this aciton will not used. + /// + StatusID[] TargetStatus { get; set; } + + BattleChara Target { get; } + + /// + /// Is target a boss. + /// + bool IsTargetBoss { get; } + + /// + /// Is target will die immediately. + /// + bool IsTargetDying { get; } + + /// + /// If this is an aoe action, how many hostile target would want to attack on, when you use this action. + /// + byte AOECount { set; } + #endregion + } +} diff --git a/RotationSolver/Actions/IBaseItem.cs b/RotationSolver.Basic/Actions/IBaseItem.cs similarity index 100% rename from RotationSolver/Actions/IBaseItem.cs rename to RotationSolver.Basic/Actions/IBaseItem.cs diff --git a/RotationSolver.Basic/Actions/MedicineItem.cs b/RotationSolver.Basic/Actions/MedicineItem.cs new file mode 100644 index 000000000..6ff9cbcb6 --- /dev/null +++ b/RotationSolver.Basic/Actions/MedicineItem.cs @@ -0,0 +1,16 @@ +using RotationSolver.Basic.Rotations; +using RotationSolver.Rotations.CustomRotation; + +namespace RotationSolver.Actions +{ + internal class MedicineItem : BaseItem + { + private MedicineType _type; + public MedicineItem(uint row, MedicineType type, uint a4 = 65535) : base(row, a4) + { + _type = type; + } + + internal bool InType(ICustomRotation rotation) => rotation.MedicineType == _type; + } +} diff --git a/RotationSolver/Attributes/LinkDescAttribute.cs b/RotationSolver.Basic/Attributes/LinkDescAttribute.cs similarity index 100% rename from RotationSolver/Attributes/LinkDescAttribute.cs rename to RotationSolver.Basic/Attributes/LinkDescAttribute.cs diff --git a/RotationSolver.Basic/Attributes/RotationDescAttribute.cs b/RotationSolver.Basic/Attributes/RotationDescAttribute.cs new file mode 100644 index 000000000..5cf4041e8 --- /dev/null +++ b/RotationSolver.Basic/Attributes/RotationDescAttribute.cs @@ -0,0 +1,124 @@ +using Dalamud.Interface.Colors; +using ImGuiNET; +using RotationSolver.Basic; +using RotationSolver.Basic.Rotations; +using RotationSolver.Commands; +using RotationSolver.Data; + +namespace RotationSolver.Attributes; + +[AttributeUsage(AttributeTargets.All, AllowMultiple = true)] +public class RotationDescAttribute : Attribute +{ + public string Description { get; private set; } = string.Empty; + public DescType Type { get; private set; } = DescType.None; + public IEnumerable Actions { get; private set; } = Enumerable.Empty(); + public uint IconID => Type switch + { + DescType.BurstActions => 62583, + + DescType.HealAreaGCD => 62582, + DescType.HealAreaAbility => 62582, + DescType.HealSingleGCD => 62582, + DescType.HealSingleAbility => 62582, + + DescType.DefenseAreaGCD => 62581, + DescType.DefenseAreaAbility => 62581, + DescType.DefenseSingleGCD => 62581, + DescType.DefenseSingleAbility => 62581, + + DescType.MoveForwardGCD => 104, + DescType.MoveForwardAbility => 104, + DescType.MoveBackAbility => 104, + + _ => 62144, + }; + + public bool IsOnCommand + { + get + { + var command = DataCenter.SpecialType; + switch(Type) + { + case DescType.BurstActions: + return command == SpecialCommandType.Burst; + + case DescType.HealAreaAbility: + case DescType.HealAreaGCD: + return command == SpecialCommandType.HealArea; + + case DescType.HealSingleAbility: + case DescType.HealSingleGCD: + return command == SpecialCommandType.HealSingle; + + case DescType.DefenseAreaGCD: + case DescType.DefenseAreaAbility: + return command == SpecialCommandType.DefenseArea; + + case DescType.DefenseSingleGCD: + case DescType.DefenseSingleAbility: + return command == SpecialCommandType.DefenseSingle; + + case DescType.MoveForwardGCD: + case DescType.MoveForwardAbility: + return command == SpecialCommandType.MoveForward; + + case DescType.MoveBackAbility: + return command == SpecialCommandType.MoveBack; + + default: + return false; + } + } + } + + public RotationDescAttribute(DescType descType) + { + Type = descType; + } + public RotationDescAttribute(params ActionID[] actions) + :this(string.Empty, actions) + { + } + + public RotationDescAttribute(string desc, params ActionID[] actions) + { + Description = desc; + Actions = actions; + } + + private RotationDescAttribute() + { + + } + + + public static IEnumerable Merge(IEnumerable rotationDescAttributes) + => from r in rotationDescAttributes + where r is RotationDescAttribute + group r by r.Type into gr + orderby gr.Key + select gr.ToArray(); + + public static RotationDescAttribute MergeToOne(IEnumerable rotationDescAttributes) + { + var result = new RotationDescAttribute(); + foreach (var attr in rotationDescAttributes) + { + if(attr == null) continue; + if(!string.IsNullOrEmpty(attr.Description)) + { + result.Description = attr.Description; + } + if(attr.Type != DescType.None) + { + result.Type = attr.Type; + } + result.Actions = result.Actions.Union(attr.Actions); + } + + if (result.Type == DescType.None) return null; + return result; + } +} diff --git a/RotationSolver.Basic/Configuration/ActionEventInfo.cs b/RotationSolver.Basic/Configuration/ActionEventInfo.cs new file mode 100644 index 000000000..f8b90fd5a --- /dev/null +++ b/RotationSolver.Basic/Configuration/ActionEventInfo.cs @@ -0,0 +1,14 @@ +using ImGuiNET; +using RotationSolver.Basic; + +namespace RotationSolver.Configuration; + +public class ActionEventInfo : MacroInfo +{ + public string Name; + + public ActionEventInfo() + { + Name = ""; + } +} diff --git a/RotationSolver.Basic/Configuration/MacroInfo.cs b/RotationSolver.Basic/Configuration/MacroInfo.cs new file mode 100644 index 000000000..c3b4c1d3e --- /dev/null +++ b/RotationSolver.Basic/Configuration/MacroInfo.cs @@ -0,0 +1,31 @@ +using Dalamud.Game.ClientState.Objects.Types; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; +using ImGuiNET; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; + +namespace RotationSolver.Configuration; + +public class MacroInfo +{ + public int MacroIndex; + public bool IsShared; + + public MacroInfo() + { + MacroIndex = -1; + IsShared = false; + } + + public unsafe bool AddMacro(GameObject tar = null) + { + if (MacroIndex < 0 || MacroIndex > 99) return false; + + DataCenter.Macros.Enqueue(new MacroItem(tar, IsShared ? + RaptureMacroModule.Instance->Shared[MacroIndex] : + RaptureMacroModule.Instance->Individual[MacroIndex])); + + return true; + } +} diff --git a/RotationSolver.Basic/Configuration/PluginConfiguration.cs b/RotationSolver.Basic/Configuration/PluginConfiguration.cs new file mode 100644 index 000000000..2e9d02d7b --- /dev/null +++ b/RotationSolver.Basic/Configuration/PluginConfiguration.cs @@ -0,0 +1,157 @@ +using Dalamud.Configuration; +using RotationSolver.Basic; +using RotationSolver.Data; +using System; +using System.Collections.Generic; +using System.Numerics; + +namespace RotationSolver.Configuration; + +[Serializable] +public class PluginConfiguration : IPluginConfiguration +{ + public int Version { get; set; } = 6; + + public int VoiceVolume = 100; + public SortedSet DisabledCombos { get; private set; } = new SortedSet(); + public SortedSet DisabledActions { get; private set; } = new SortedSet(); + public SortedSet DisabledItems { get; private set; } = new SortedSet(); + public List Events { get; private set; } = new List(); + public Dictionary>> RotationsConfigurations { get; private set; } + = new Dictionary>>(); + public Dictionary RotationChoices { get; private set; } = new Dictionary(); + public Dictionary TargetToHostileTypes { get; set; } = + new Dictionary(); + public int AddDotGCDCount = 2; + + public int TimelineIndex = 0; + public bool AutoBurst = true; + public bool AutoOffBetweenArea = true; + public bool UseAbility = true; + public bool UseDefenseAbility = true; + public bool NeverReplaceIcon = false; + public bool AutoProvokeForTank = true; + public bool AutoUseTrueNorth = true; + public bool ChangeTargetForFate = true; + public bool MoveTowardsScreenCenter = true; + + public bool SayOutStateChanged = true; + + public bool ShowInfoOnDtr = true; + + public bool SayPositional = true; + + public bool FlytextPositional = true; + public bool HealOutOfCombat = false; + public bool ShowInfoOnToast = true; + public bool RaiseAll = false; + public bool CastingDisplay = true; + public bool PoslockCasting = false; + public int PoslockModifier = 0; + public bool RaisePlayerByCasting = true; + public bool RaisePlayerBySwift = true; + public bool RaiseBrinkOfDeath = true; + public int LessMPNoRaise = 0; + public bool AutoShield = true; + public bool AddEnemyListToHostile = true; + public bool UseAOEWhenManual = false; + public bool UseAOEAction = true; + public bool UseItem = false; + public bool PositionalFeedback = true; + public bool DrawPositional = true; + public bool DrawMeleeRange = true; + public bool ShowMoveTarget = true; + public bool ShowHealthRatio = false; + public bool ShowTarget = true; + public bool ChooseAttackMark = true; + public bool CanAttackMarkAOE = true; + public bool FilterStopMark = true; + public bool UseOverlayWindow = true; + public bool TeachingMode = true; + public Vector3 TeachingModeColor = new(0f, 1f, 0.8f); + public Vector3 MovingTargetColor = new(0f, 1f, 0.8f); + public Vector3 TargetColor = new(1f, 0.2f, 0f); + public Vector3 SubTargetColor = new(1f, 0.9f, 0f); + public bool KeyBoardNoise = true; + public bool UseGroundBeneficialAbility = true; + public bool MoveAreaActionFarthest = true; + public bool StartOnCountdown = true; + public bool NoNewHostiles = false; + public bool UseHealWhenNotAHealer = true; + public float ObjectMinRadius = 0f; + public float HealthDifference = 0.25f; + public float MeleeRangeOffset = 1; + public bool TargetFriendly = false; + + public Dictionary HealingOfTimeSubtractSingles { get; set; } = new Dictionary(); + + public Dictionary HealingOfTimeSubtractAreas { get; set; } = new Dictionary(); + public Dictionary HealthAreaAbilities { get; set; } = new Dictionary(); + public float HealthAreaAbility = 0.75f; + + public Dictionary HealthAreaSpells { get; set; } = new Dictionary(); + public float HealthAreaSpell = 0.65f; + + public Dictionary HealthSingleAbilities { get; set; } = new Dictionary(); + public float HealthSingleAbility = 0.7f; + + public Dictionary HealthSingleSpells { get; set; } = new Dictionary(); + public float HealthSingleSpell = 0.55f; + + public Dictionary HealthForDyingTanks { get; set; } = new Dictionary(); + + public bool InterruptibleMoreCheck = true; + public float SpecialDuration = 3; + public float AbilitiesInterval = 0.67f; + public float ActionAhead = 0.08f; + + public float WeaponDelayMin = 0; + public float WeaponDelayMax = 0; + + public float DeathDelayMin = 0.5f; + public float DeathDelayMax = 1; + + public float WeakenDelayMin = 0.5f; + public float WeakenDelayMax = 1; + + public float HostileDelayMin = 0; + public float HostileDelayMax = 0; + + public float HealDelayMin = 0; + public float HealDelayMax = 0; + public float StopCastingDelayMin = 0.5f; + public float StopCastingDelayMax = 1; + + public float InterruptDelayMin = 0.5f; + public float InterruptDelayMax = 1; + + public float NotInCombatDelayMin = 1f; + public float NotInCombatDelayMax = 2; + + public bool UseWorkTask = true; + + public bool UseStopCasting = false; + public bool EsunaAll = false; + public bool OnlyAttackInView = false; + + public string PositionalErrorText = string.Empty; + public float CountDownAhead = 0.6f; + + public int NamePlateIconId = 61437; // 61435 + public bool ShowActionFlag = false; + + + public int MoveTargetAngle = 24; + public float HealthRatioBoss = 1.85f; + public float HealthRatioDying = 0.8f; + public float HealthRatioDot = 1.5f; + + public List TargetingTypes { get; set; } = new List(); + public int TargetingIndex { get; set; } = 0; + public MacroInfo DutyStart { get; set; } = new MacroInfo(); + public MacroInfo DutyEnd { get; set; } = new MacroInfo(); + public void Save() + { + Service.Interface.SavePluginConfig(this); + } +} diff --git a/RotationSolver.Basic/Configuration/RotationConfig/IRotationConfig.cs b/RotationSolver.Basic/Configuration/RotationConfig/IRotationConfig.cs new file mode 100644 index 000000000..c2d598466 --- /dev/null +++ b/RotationSolver.Basic/Configuration/RotationConfig/IRotationConfig.cs @@ -0,0 +1,13 @@ +using RotationSolver.Data; + +namespace RotationSolver.Configuration.RotationConfig; + +public interface IRotationConfig +{ + string Name { get; } + string DisplayName { get; } + string GetValue(ClassJobID job, string rotationName); + string GetDisplayValue(ClassJobID job, string rotationName); + void SetValue(ClassJobID job, string rotationName, string value); + bool DoCommand(IRotationConfigSet set, string str); +} diff --git a/RotationSolver.Basic/Configuration/RotationConfig/IRotationConfigSet.cs b/RotationSolver.Basic/Configuration/RotationConfig/IRotationConfigSet.cs new file mode 100644 index 000000000..a3b951021 --- /dev/null +++ b/RotationSolver.Basic/Configuration/RotationConfig/IRotationConfigSet.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; + +namespace RotationSolver.Configuration.RotationConfig; + +public interface IRotationConfigSet : IEnumerable +{ + HashSet Configs { get; } + + IRotationConfigSet SetFloat(string name, float value, string displayName, float min = 0, float max = 1, float speed = 0.002f); + + IRotationConfigSet SetString(string name, string value, string displayName); + + IRotationConfigSet SetBool(string name, bool value, string displayName); + + IRotationConfigSet SetCombo(string name, int value, string displayName, params string[] items); + + void SetValue(string name, string value); + + int GetCombo(string name); + + bool GetBool(string name); + + float GetFloat(string name); + + /// + /// Get the raw string value in the saved dictionary, is not readable. + /// + /// + /// + string GetString(string name); + + /// + /// Get the readable string for display. + /// + /// + /// + string GetDisplayString(string name); +} diff --git a/RotationSolver/Configuration/RotationConfig/RotationConfig.cd b/RotationSolver.Basic/Configuration/RotationConfig/RotationConfig.cd similarity index 100% rename from RotationSolver/Configuration/RotationConfig/RotationConfig.cd rename to RotationSolver.Basic/Configuration/RotationConfig/RotationConfig.cd diff --git a/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigBase.cs b/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigBase.cs new file mode 100644 index 000000000..f226a6f51 --- /dev/null +++ b/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigBase.cs @@ -0,0 +1,46 @@ +using RotationSolver.Basic; +using RotationSolver.Data; +using System.Collections.Generic; + +namespace RotationSolver.Configuration.RotationConfig; + +public abstract class RotationConfigBase : IRotationConfig +{ + public string Name { get; } + public string DefaultValue { get; } + public string DisplayName { get; } + + public RotationConfigBase(string name, string value, string displayName) + { + Name = name; + DefaultValue = value; + DisplayName = displayName; + } + + public string GetValue(ClassJobID job, string rotationName) + { + if (!Service.Config.RotationsConfigurations.TryGetValue((uint)job, out var jobDict)) return DefaultValue; + if (!jobDict.TryGetValue(rotationName, out var configDict)) return DefaultValue; + if (!configDict.TryGetValue(Name, out var config)) return DefaultValue; + return config; + } + + public virtual string GetDisplayValue(ClassJobID job, string rotationName) => GetValue(job, rotationName); + + public void SetValue(ClassJobID job, string rotationName, string value) + { + if (!Service.Config.RotationsConfigurations.TryGetValue((uint)job, out var jobDict)) + { + jobDict = Service.Config.RotationsConfigurations[(uint)job] = new Dictionary>(); + } + + if (!jobDict.TryGetValue(rotationName, out var configDict)) + { + configDict = jobDict[rotationName] = new Dictionary(); + } + + configDict[Name] = value; + } + + public virtual bool DoCommand(IRotationConfigSet set, string str) => str.StartsWith(Name); +} diff --git a/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigBoolean.cs b/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigBoolean.cs new file mode 100644 index 000000000..2f98d92b2 --- /dev/null +++ b/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigBoolean.cs @@ -0,0 +1,15 @@ +namespace RotationSolver.Configuration.RotationConfig; + +public class RotationConfigBoolean : RotationConfigBase +{ + public RotationConfigBoolean(string name, bool value, string displayName) : base(name, value.ToString(), displayName) + { + } + + public override bool DoCommand(IRotationConfigSet set, string str) + { + if (!base.DoCommand(set, str)) return false; + set.SetValue(Name, (!set.GetBool(Name)).ToString()); + return true; + } +} diff --git a/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigCombo.cs b/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigCombo.cs new file mode 100644 index 000000000..d5d6119ae --- /dev/null +++ b/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigCombo.cs @@ -0,0 +1,48 @@ +using ImGuiNET; +using RotationSolver.Basic; +using RotationSolver.Commands; +using RotationSolver.Data; +namespace RotationSolver.Configuration.RotationConfig; + +public class RotationConfigCombo : RotationConfigBase +{ + public string[] Items { get; set; } + public RotationConfigCombo(string name, int value, string displayName, string[] items) : base(name, value.ToString(), displayName) + { + Items = items; + } + + public override string GetDisplayValue(ClassJobID job, string rotationName) + { + var indexStr = base.GetDisplayValue(job, rotationName); + if (!int.TryParse(indexStr, out var index)) return Items[0]; + return Items[index]; + } + + public override bool DoCommand(IRotationConfigSet set, string str) + { + if (!base.DoCommand(set, str)) return false; + + string numStr = str.Substring(Name.Length).Trim(); + var length = Items.Length; + + int nextId = (set.GetCombo(Name) + 1) % length; + if (int.TryParse(numStr, out int num)) + { + nextId = num % length; + } + else + { + for (int i = 0; i < length; i++) + { + if (Items[i] == str) + { + nextId = i; + } + } + } + + set.SetValue(Name, nextId.ToString()); + return true; + } +} diff --git a/RotationSolver/Configuration/RotationConfig/RotationConfigComparer.cs b/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigComparer.cs similarity index 100% rename from RotationSolver/Configuration/RotationConfig/RotationConfigComparer.cs rename to RotationSolver.Basic/Configuration/RotationConfig/RotationConfigComparer.cs diff --git a/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigFloat.cs b/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigFloat.cs new file mode 100644 index 000000000..3dd6a875b --- /dev/null +++ b/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigFloat.cs @@ -0,0 +1,13 @@ +namespace RotationSolver.Configuration.RotationConfig; + +public class RotationConfigFloat : RotationConfigBase +{ + public float Min, Max, Speed; + + public RotationConfigFloat(string name, float value, string displayName, float min, float max, float speed) : base(name, value.ToString(), displayName) + { + Min = min; + Max = max; + Speed = speed; + } +} diff --git a/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigSet.cs b/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigSet.cs new file mode 100644 index 000000000..6c12d18e8 --- /dev/null +++ b/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigSet.cs @@ -0,0 +1,98 @@ +using RotationSolver.Data; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace RotationSolver.Configuration.RotationConfig; + +public class RotationConfigSet : IRotationConfigSet +{ + ClassJobID _job; + string _rotationName; + public HashSet Configs { get; } = new HashSet(new RotationConfigComparer()); + + public RotationConfigSet(ClassJobID job, string rotationName) + { + _job = job; + _rotationName = rotationName; + } + + #region Set + public IRotationConfigSet SetFloat(string name, float value, string displayName, float min = 0, float max = 1, float speed = 0.002f) + { + Configs.Add(new RotationConfigFloat(name, value, displayName, min, max, speed)); + return this; + } + + public IRotationConfigSet SetString(string name, string value, string displayName) + { + Configs.Add(new RotationConfigString(name, value, displayName)); + return this; + } + + public IRotationConfigSet SetBool(string name, bool value, string displayName) + { + Configs.Add(new RotationConfigBoolean(name, value, displayName)); + return this; + } + + public IRotationConfigSet SetCombo(string name, int value, string displayName, params string[] items) + { + Configs.Add(new RotationConfigCombo(name, value, displayName, items)); + return this; + } + + public void SetValue(string name, string value) + { + var config = Configs.FirstOrDefault(config => config.Name == name); + if (config == null) return; + config.SetValue(_job, _rotationName, value); + } + #endregion + + #region Get + public int GetCombo(string name) + { + var result = GetString(name); + if (int.TryParse(result, out var f)) return f; + return 0; + } + + public bool GetBool(string name) + { + var result = GetString(name); + if (bool.TryParse(result, out var f)) return f; + return false; + } + + public float GetFloat(string name) + { + var result = GetString(name); + if (float.TryParse(result, out var f)) return f; + return float.NaN; + } + + public string GetString(string name) + { + var config = GetConfig(name); + return config?.GetValue(_job, _rotationName); + } + + public string GetDisplayString(string name) + { + var config = GetConfig(name); + return config?.GetDisplayValue(_job, _rotationName); + } + + private IRotationConfig GetConfig(string name) => Configs.FirstOrDefault(config => config.Name == name); + #endregion + + + + public IEnumerator GetEnumerator() => Configs.GetEnumerator(); + + + IEnumerator IEnumerable.GetEnumerator() => Configs.GetEnumerator(); + + +} diff --git a/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigString.cs b/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigString.cs new file mode 100644 index 000000000..46385a10b --- /dev/null +++ b/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigString.cs @@ -0,0 +1,11 @@ +using ImGuiNET; +using RotationSolver.Basic; + +namespace RotationSolver.Configuration.RotationConfig; + +public class RotationConfigString : RotationConfigBase +{ + public RotationConfigString(string name, string value, string displayName) : base(name, value, displayName) + { + } +} diff --git a/RotationSolver.Basic/CountDown.cs b/RotationSolver.Basic/CountDown.cs new file mode 100644 index 000000000..556484346 --- /dev/null +++ b/RotationSolver.Basic/CountDown.cs @@ -0,0 +1,46 @@ +using Dalamud.Hooking; +using Dalamud.Utility.Signatures; +using RotationSolver.Basic; +using System; +using System.Runtime.InteropServices; + +namespace RotationSolver.SigReplacers; + +public class CountDown: IDisposable +{ + private delegate IntPtr CountdownTimerDelegate(IntPtr p1); + + /// + ///https://github.com/xorus/EngageTimer/blob/main/Game/CountdownHook.cs + /// + [Signature("48 89 5C 24 ?? 57 48 83 EC 40 8B 41", DetourName = nameof(CountdownTimerFunc))] + private readonly Hook _countdownTimerHook = null; + + private static IntPtr _countDown = IntPtr.Zero; + + public static float CountDownTime + { + get + { + if (_countDown == IntPtr.Zero) return 0; + return Math.Max(0, Marshal.PtrToStructure(_countDown + 0x2c)); + } + } + + public CountDown() + { + SignatureHelper.Initialise(this); + _countdownTimerHook?.Enable(); + } + + private IntPtr CountdownTimerFunc(IntPtr value) + { + _countDown = value; + return _countdownTimerHook!.Original(value); + } + + public void Dispose() + { + _countdownTimerHook?.Dispose(); + } +} diff --git a/RotationSolver.Basic/Data/ActionCate.cs b/RotationSolver.Basic/Data/ActionCate.cs new file mode 100644 index 000000000..a0a64f236 --- /dev/null +++ b/RotationSolver.Basic/Data/ActionCate.cs @@ -0,0 +1,22 @@ +namespace RotationSolver.Data; + +public enum ActionCate : uint +{ + AutoAttack = 1, + Spell = 2, + WeaponSkill = 3, + Ability = 4, + Item = 5, + DoLAbility = 6, + DoHAbility = 7, + Event = 8, + LimitBreak9 = 9, + System10 = 10, + System11 = 11, + Mount = 12, + Special = 13, + ItemManipulation = 14, + LimitBreak15 = 15, + + Artillery = 17, +} diff --git a/RotationSolver/Data/ActionID.cs b/RotationSolver.Basic/Data/ActionID.cs similarity index 100% rename from RotationSolver/Data/ActionID.cs rename to RotationSolver.Basic/Data/ActionID.cs diff --git a/RotationSolver.Basic/Data/ActionRec.cs b/RotationSolver.Basic/Data/ActionRec.cs new file mode 100644 index 000000000..7eeb5341f --- /dev/null +++ b/RotationSolver.Basic/Data/ActionRec.cs @@ -0,0 +1,4 @@ +namespace RotationSolver.Basic.Data; +using Action = Lumina.Excel.GeneratedSheets.Action; + +public record ActionRec(DateTime UsedTime, Action Action); diff --git a/RotationSolver/Data/ChatPayload.cs b/RotationSolver.Basic/Data/ChatPayload.cs similarity index 100% rename from RotationSolver/Data/ChatPayload.cs rename to RotationSolver.Basic/Data/ChatPayload.cs diff --git a/RotationSolver/Data/ClassJobIDs.cs b/RotationSolver.Basic/Data/ClassJobIDs.cs similarity index 100% rename from RotationSolver/Data/ClassJobIDs.cs rename to RotationSolver.Basic/Data/ClassJobIDs.cs diff --git a/RotationSolver.Basic/Data/DescType.cs b/RotationSolver.Basic/Data/DescType.cs new file mode 100644 index 000000000..959f6fd89 --- /dev/null +++ b/RotationSolver.Basic/Data/DescType.cs @@ -0,0 +1,23 @@ +namespace RotationSolver.Data; + +public enum DescType : byte +{ + None, + BurstActions, + + HealAreaGCD, + HealAreaAbility, + + HealSingleGCD, + HealSingleAbility, + + DefenseAreaGCD, + DefenseAreaAbility, + + DefenseSingleGCD, + DefenseSingleAbility, + + MoveForwardGCD, + MoveForwardAbility, + MoveBackAbility, +} diff --git a/RotationSolver/Data/EnemyPositional.cs b/RotationSolver.Basic/Data/EnemyPositional.cs similarity index 100% rename from RotationSolver/Data/EnemyPositional.cs rename to RotationSolver.Basic/Data/EnemyPositional.cs diff --git a/RotationSolver.Basic/Data/IconSet.cs b/RotationSolver.Basic/Data/IconSet.cs new file mode 100644 index 000000000..56a843264 --- /dev/null +++ b/RotationSolver.Basic/Data/IconSet.cs @@ -0,0 +1,186 @@ +using ImGuiScene; +using RotationSolver.Basic; +using RotationSolver.Basic.Rotations; +using System.Collections.Generic; + +namespace RotationSolver.Data; + +public enum IconType : byte +{ + Gold, + Framed, + Glowing, + Grey, + Black, + Yellow, + Orange, + Red, + Purple, + Blue, + Green, + Role, +} + +public static class IconSet +{ + private static readonly Dictionary _textures = new Dictionary(); + + public static TextureWrap GetTexture(this ITexture text) => GetTexture(text.IconID); + + public static TextureWrap GetTexture(uint id) + { + if (!_textures.TryGetValue(id, out var texture)) + { + texture = Service.GetTextureIcon(id); + _textures.Add(id, texture); + } + + return texture; + } + + public static void Dispose() + { + foreach (var item in _textures.Values) + { + item.Dispose(); + } + } + + private static readonly Dictionary _icons = new Dictionary() + { + { IconType.Gold, new uint[40] + { + 62001, 62002, 62003, 62004, 62005, 62006, 62007, 62008, 62009, 62010, + 62011, 62012, 62013, 62014, 62015, 62016, 62017, 62018, 62019, 62020, + 62021, 62022, 62023, 62024, 62025, 62026, 62027, 62028, 62029, 62030, + 62031, 62032, 62033, 62034, 62035, 62036, 62037, 62038, 62039, 62040 + } + }, + + { IconType.Framed, new uint[40] + { + 62101, 62102, 62103, 62104, 62105, 62106, 62107, 62108, 62109, 62110, + 62111, 62112, 62113, 62114, 62115, 62116, 62117, 62118, 62119, 62120, + 62121, 62122, 62123, 62124, 62125, 62126, 62127, 62128, 62129, 62130, + 62131, 62132, 62133, 62134, 62135, 62136, 62137, 62138, 62139, 62140 + } + }, + + { IconType.Glowing, new uint[40] + { + 62301, 62302, 62303, 62304, 62305, 62306, 62307, 62310, 62311, 62312, + 62313, 62314, 62315, 62316, 62317, 62318, 62319, 62320, 62401, 62402, + 62403, 62404, 62405, 62406, 62407, 62308, 62408, 62409, 62309, 62410, + 62411, 62412, 62413, 62414, 62415, 62416, 62417, 62418, 62419, 62420 + } + }, + + { IconType.Grey, new uint[40] + { + 91022, 91023, 91024, 91025, 91026, 91028, 91029, 91031, 91032, 91033, + 91034, 91035, 91036, 91037, 91038, 91039, 91040, 91041, 91079, 91080, + 91081, 91082, 91083, 91084, 91085, 91030, 91086, 91087, 91121, 91122, + 91125, 91123, 91124, 91127, 91128, 91129, 91130, 91131, 91132, 91133 + } + }, + + { IconType.Black, new uint[40] + { + 91522, 91523, 91524, 91525, 91526, 91528, 91529, 91531, 91532, 91533, + 91534, 91535, 91536, 91537, 91538, 91539, 91540, 91541, 91579, 91580, + 91581, 91582, 91583, 91584, 91585, 91530, 91586, 91587, 91621, 91622, + 91625, 91623, 91624, 91627, 91628, 91629, 91630, 91631, 91632, 91633 + } + }, + + { IconType.Yellow, new uint[40] + { + 92022, 92023, 92024, 92025, 92026, 92028, 92029, 92031, 92032, 92033, + 92034, 92035, 92036, 92037, 92038, 92039, 92040, 92041, 92079, 92080, + 92081, 92082, 92083, 92084, 92085, 92030, 92086, 92087, 92121, 92122, + 92125, 92123, 92124, 92127, 92128, 92129, 92130, 92131, 92132, 92133 + } + }, + + { IconType.Orange, new uint[40] + { + 92522, 92523, 92524, 92525, 92526, 92528, 92529, 92531, 92532, 92533, + 92534, 92535, 92536, 92537, 92538, 92539, 92540, 92541, 92579, 92580, + 92581, 92582, 92583, 92584, 92585, 92530, 92586, 92587, 92621, 92622, + 92625, 92623, 92624, 92627, 92628, 92629, 92630, 92631, 92632, 92633 + } + }, + + + { IconType.Red, new uint[40] + { + 93022, 93023, 93024, 93025, 93026, 93028, 93029, 93031, 93032, 93033, + 93034, 93035, 93036, 93037, 93038, 93039, 93040, 93041, 93079, 93080, + 93081, 93082, 93083, 93084, 93085, 93030, 93086, 93087, 93121, 93122, + 93125, 93123, 93124, 93127, 93128, 93129, 93130, 93131, 93132, 93133 + } + }, + + + { IconType.Purple, new uint[40] + { + 93522, 93523, 93524, 93525, 93526, 93528, 93529, 93531, 93532, 93533, + 93534, 93535, 93536, 93537, 93538, 93539, 93540, 93541, 93579, 93580, + 93581, 93582, 93583, 93584, 93585, 93530, 93586, 93587, 93621, 93622, + 93625, 93623, 93624, 93627, 93628, 93629, 93630, 93631, 93632, 93633 + } + }, + + { IconType.Blue, new uint[40] + { + 94022, 94023, 94024, 94025, 94026, 94028, 94029, 94031, 94032, 94033, + 94034, 94035, 94036, 94037, 94038, 94039, 94040, 94041, 94079, 94080, + 94081, 94082, 94083, 94084, 94085, 94030, 94086, 94087, 94121, 94122, + 94125, 94123, 94124, 94127, 94128, 94129, 94130, 94131, 94132, 94133 + } + }, + + { IconType.Green, new uint[40] + { + 94522, 94523, 94524, 94525, 94526, 94528, 94529, 94531, 94532, 94533, + 94534, 94535, 94536, 94537, 94538, 94539, 94540, 94541, 94579, 94580, + 94581, 94582, 94583, 94584, 94585, 94530, 94586, 94587, 94621, 94622, + 94625, 94623, 94624, 94627, 94628, 94629, 94630, 94631, 94632, 94633 + } + }, + { IconType.Role, new uint[40] + { + 62581, 62584, 62581, 62584, 62586, 62582, 62502, 62502, 62503, 62504, + 62505, 62506, 62507, 62508, 62509, 62510, 62511, 62512, 62581, 62584, + 62581, 62584, 62586, 62582, 62587, 62587, 62587, 62582, 62584, 62584, + 62586, 62581, 62582, 62584, 62587, 62587, 62581, 62586, 62584, 62582 + } + }, + + }; + + public static uint GetJobIcon(ICustomRotation combo) + { + IconType type = IconType.Gold; + switch (combo.Job.GetJobRole()) + { + case JobRole.Tank: + type = IconType.Blue; + break; + case JobRole.RangedPhysical: + case JobRole.RangedMagical: + case JobRole.Melee: + type = IconType.Red; + break; + case JobRole.Healer: + type = IconType.Green; + break; + } + return GetJobIcon(combo, type); + } + + public static uint GetJobIcon(ICustomRotation combo, IconType type) + { + return _icons[type][(uint)combo.JobIDs[0] - 1]; + } +} diff --git a/RotationSolver.Basic/Data/JobRole.cs b/RotationSolver.Basic/Data/JobRole.cs new file mode 100644 index 000000000..c39b105d4 --- /dev/null +++ b/RotationSolver.Basic/Data/JobRole.cs @@ -0,0 +1,37 @@ +using Lumina.Excel.GeneratedSheets; + +namespace RotationSolver.Data; + +public enum JobRole : byte +{ + None = 0, + Tank = 1, + Melee = 2, + Ranged = 3, + Healer = 4, + RangedPhysical = 5, + RangedMagical = 6, + DiscipleOfTheLand = 7, + DiscipleOfTheHand = 8, +} + +public static class JobRoleExtension +{ + public static JobRole GetJobRole(this ClassJob job) + { + var role = (JobRole)job.Role; + + if (role is JobRole.Ranged or JobRole.None) + { + role = job.ClassJobCategory.Row switch + { + 30 => JobRole.RangedPhysical, + 31 => JobRole.RangedMagical, + 32 => JobRole.DiscipleOfTheLand, + 33 => JobRole.DiscipleOfTheHand, + _ => JobRole.None, + }; + } + return role; + } +} diff --git a/RotationSolver.Basic/Data/MacroItem.cs b/RotationSolver.Basic/Data/MacroItem.cs new file mode 100644 index 000000000..302812ab2 --- /dev/null +++ b/RotationSolver.Basic/Data/MacroItem.cs @@ -0,0 +1,42 @@ +using Dalamud.Game.ClientState.Objects.Types; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; +using FFXIVClientStructs.FFXIV.Client.UI.Shell; +using RotationSolver.Basic; + +namespace RotationSolver.Data; + +public unsafe class MacroItem +{ + private GameObject _lastTarget; + public GameObject Target { get; } + public bool IsRunning { get; private set; } + public RaptureMacroModule.Macro* Macro { get; } + + public MacroItem(GameObject target, RaptureMacroModule.Macro* macro) + { + Macro = macro; + Target = target; + } + + public bool StartUseMacro() + { + if (RaptureShellModule.Instance->MacroCurrentLine > -1) return false; + + _lastTarget = Service.TargetManager.Target; + Service.TargetManager.SetTarget(Target); + RaptureShellModule.Instance->ExecuteMacro(Macro); + + IsRunning = true; + return true; + } + + public bool EndUseMacro() + { + if (RaptureShellModule.Instance->MacroCurrentLine > -1) return false; + + Service.TargetManager.SetTarget(_lastTarget); + + IsRunning = false; + return true; + } +} diff --git a/RotationSolver.Basic/Data/NextAct.cs b/RotationSolver.Basic/Data/NextAct.cs new file mode 100644 index 000000000..010ac8f17 --- /dev/null +++ b/RotationSolver.Basic/Data/NextAct.cs @@ -0,0 +1,5 @@ +using RotationSolver.Actions; + +namespace RotationSolver.Basic.Data; + +public record NextAct(IBaseAction act, DateTime deadTime); diff --git a/RotationSolver.Basic/Data/ObjectListDelay.cs b/RotationSolver.Basic/Data/ObjectListDelay.cs new file mode 100644 index 000000000..8b4f2d704 --- /dev/null +++ b/RotationSolver.Basic/Data/ObjectListDelay.cs @@ -0,0 +1,46 @@ +using Dalamud.Game.ClientState.Objects.Types; +using System.Collections; + +namespace RotationSolver.Data; + +public class ObjectListDelay : IEnumerable where T : GameObject +{ + IEnumerable _list = new T[0]; + Func<(float min, float max)> _getRange; + SortedList _revealTime = new SortedList(); + Random _ran = new Random(DateTime.Now.Millisecond); + + public ObjectListDelay(Func<(float min, float max)> getRange) + { + _getRange = getRange; + } + + public void Delay(IEnumerable originData) + { + var outList= new List(originData.Count()); + var revealTime = new SortedList(); + var now = DateTime.Now; + + foreach (var item in originData) + { + if(!_revealTime.TryGetValue(item.ObjectId, out var time)) + { + var range = _getRange(); + var delaySecond = range.min + (float)_ran.NextDouble() * (range.max - range.min); + time = now + new TimeSpan(0, 0, 0, 0, (int)(delaySecond * 1000)); + } + revealTime.Add(item.ObjectId, time); + + if (now > time) + { + outList.Add(item); + } + } + + _list = outList; + _revealTime = revealTime; + } + + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); +} diff --git a/RotationSolver.Basic/Data/RSCommandType.cs b/RotationSolver.Basic/Data/RSCommandType.cs new file mode 100644 index 000000000..bb1ecabcc --- /dev/null +++ b/RotationSolver.Basic/Data/RSCommandType.cs @@ -0,0 +1,31 @@ +namespace RotationSolver.Commands; + +public enum SpecialCommandType : byte +{ + EndSpecial, + HealArea, + HealSingle, + DefenseArea, + DefenseSingle, + EsunaStanceNorth, + RaiseShirk, + MoveForward, + MoveBack, + AntiKnockback, + Burst, +} + +public enum StateCommandType : byte +{ + Cancel, + Smart, + Manual, +} + +public enum OtherCommandType : byte +{ + Settings, + Rotations, + DoActions, + ToggleActions, +} diff --git a/RotationSolver.Basic/Data/RandomDelay.cs b/RotationSolver.Basic/Data/RandomDelay.cs new file mode 100644 index 000000000..c8a6c3564 --- /dev/null +++ b/RotationSolver.Basic/Data/RandomDelay.cs @@ -0,0 +1,44 @@ +namespace RotationSolver.Data; + +public struct RandomDelay +{ + DateTime _startDelayTime = DateTime.Now; + float _delayTime = -1; + Func<(float min, float max)> _getRange; + Random _ran = new Random(DateTime.Now.Millisecond); + bool _lastValue = false; + public RandomDelay(Func<(float min, float max)> getRange) + { + _getRange = getRange; + } + + public bool Delay(bool originData) + { + if(_getRange == null) return false; + + if (!originData) + { + _lastValue = false; + _delayTime = -1; + return false; + } + + //Not start And changed. + if (_delayTime < 0 && !_lastValue) + { + _lastValue = true; + _startDelayTime = DateTime.Now; + var range = _getRange(); + _delayTime = range.min + (float)_ran.NextDouble() * (range.max - range.min); + } + //Times up + else if ((DateTime.Now - _startDelayTime).TotalSeconds >= _delayTime) + { + //I set it. + _delayTime = -1; + return true; + } + + return false; + } +} diff --git a/RotationSolver/Data/StatusID.cs b/RotationSolver.Basic/Data/StatusID.cs similarity index 100% rename from RotationSolver/Data/StatusID.cs rename to RotationSolver.Basic/Data/StatusID.cs diff --git a/RotationSolver.Basic/Data/TargetHostileType.cs b/RotationSolver.Basic/Data/TargetHostileType.cs new file mode 100644 index 000000000..cd9888de5 --- /dev/null +++ b/RotationSolver.Basic/Data/TargetHostileType.cs @@ -0,0 +1,8 @@ +namespace RotationSolver.Data; + +public enum TargetHostileType : byte +{ + AllTargetsCanAttack, + TargetsHaveTargetOrAllTargetsCanAttack, + TargetsHaveTarget, +} diff --git a/RotationSolver/Data/TargetType.cs b/RotationSolver.Basic/Data/TargetType.cs similarity index 100% rename from RotationSolver/Data/TargetType.cs rename to RotationSolver.Basic/Data/TargetType.cs diff --git a/RotationSolver.Basic/DataCenter.cs b/RotationSolver.Basic/DataCenter.cs new file mode 100644 index 000000000..12adada73 --- /dev/null +++ b/RotationSolver.Basic/DataCenter.cs @@ -0,0 +1,220 @@ +using Dalamud.Game.ClientState.Objects.SubKinds; +using Dalamud.Game.ClientState.Objects.Types; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using RotationSolver.Actions; +using RotationSolver.Basic.Data; +using RotationSolver.Commands; +using RotationSolver.Data; +using CharacterManager = FFXIVClientStructs.FFXIV.Client.Game.Character.CharacterManager; +using Action = Lumina.Excel.GeneratedSheets.Action; +using System.ComponentModel; +using RotationSolver.Helpers; +using Dalamud.Logging; +using FFXIVClientStructs.FFXIV.Client.Game.Fate; + +namespace RotationSolver.Basic; + +public static class DataCenter +{ + + private static List NextActs = new List(); + public static IBaseAction TimeLineAction { internal get; set; } + internal static IBaseAction CommandNextAction + { + get + { + if (TimeLineAction != null) return TimeLineAction; + + var next = NextActs.FirstOrDefault(); + + while (next != null && NextActs.Count > 0 && (next.deadTime < DateTime.Now || IActionHelper.IsLastAction(true, next.act))) + { + NextActs.RemoveAt(0); + next = NextActs.FirstOrDefault(); + } + return next?.act; + } + } + + public static void AddOneTimelineAction(IBaseAction act, double time) + { + var index = NextActs.FindIndex(i => i.act.ID == act.ID); + var newItem = new NextAct(act, DateTime.Now.AddSeconds(time)); + if (index < 0) + { + NextActs.Add(newItem); + } + else + { + NextActs[index] = newItem; + } + NextActs = NextActs.OrderBy(i => i.deadTime).ToList(); + } + + public static unsafe ActionID LastComboAction => (ActionID)ActionManager.Instance()->Combo.Action; + public static unsafe float ComboTime => ActionManager.Instance()->Combo.Timer; + internal static TargetingType TargetingType + { + get + { + if (Service.Config.TargetingTypes.Count == 0) + { + Service.Config.TargetingTypes.Add(TargetingType.Big); + Service.Config.TargetingTypes.Add(TargetingType.Small); + Service.Config.Save(); + } + + return Service.Config.TargetingTypes[Service.Config.TargetingIndex %= Service.Config.TargetingTypes.Count]; + } + } + + public unsafe static bool IsMoving => AgentMap.Instance()->IsPlayerMoving > 0; + + public static unsafe ushort FateId + { + get + { + try + { + if (Service.Config.ChangeTargetForFate && (IntPtr)FateManager.Instance() != IntPtr.Zero + && (IntPtr)FateManager.Instance()->CurrentFate != IntPtr.Zero + && Service.Player.Level <= FateManager.Instance()->CurrentFate->MaxLevel) + { + return FateManager.Instance()->CurrentFate->FateId; + } + } + catch (Exception ex) + { + PluginLog.Error(ex.StackTrace); + } + return 0; + } + } + + #region GCD + public static float WeaponRemain { get; set; } + + public static float WeaponTotal { get; set; } + + public static float WeaponElapsed { get; set; } + + public static byte AbilityRemainCount { get; set; } + + public static float AbilityRemain { get; set; } + + public static float CastingTotal { get; set; } + #endregion + public static uint[] BluSlots { get; set; } = new uint[24]; + + public static SpecialCommandType SpecialType { get; set; } + public static StateCommandType StateType { get; set; } + public static bool InCombat { get; set; } + + public static float CombatTime { get; set; } + + public static IEnumerable PartyMembers { get; set; } = new PlayerCharacter[0]; + + public static IEnumerable PartyTanks { get; set; } = new PlayerCharacter[0]; + + public static IEnumerable PartyHealers { get; set; } = new PlayerCharacter[0]; + + public static IEnumerable AllianceMembers { get; set; } = new PlayerCharacter[0]; + + public static IEnumerable AllianceTanks { get; set; } = new PlayerCharacter[0]; + + public static ObjectListDelay DeathPeopleAll { get; } = new( + () => (Service.Config.DeathDelayMin, Service.Config.DeathDelayMax)); + + public static ObjectListDelay DeathPeopleParty { get; } = new( + () => (Service.Config.DeathDelayMin, Service.Config.DeathDelayMax)); + + public static ObjectListDelay WeakenPeople { get; } = new( + () => (Service.Config.WeakenDelayMin, Service.Config.WeakenDelayMax)); + + public static ObjectListDelay DyingPeople { get; } = new( + () => (Service.Config.WeakenDelayMin, Service.Config.WeakenDelayMax)); + + public static ObjectListDelay HostileTargets { get; } = new ObjectListDelay( + () => (Service.Config.HostileDelayMin, Service.Config.HostileDelayMax)); + + public static IEnumerable AllHostileTargets { get; set; } = new BattleChara[0]; + + public static IEnumerable TarOnMeTargets { get; set; } = new BattleChara[0]; + + public static ObjectListDelay CanInterruptTargets { get; } = new ObjectListDelay( + () => (Service.Config.InterruptDelayMin, Service.Config.InterruptDelayMax)); + + public static IEnumerable AllTargets { get; set; } + + public static uint[] TreasureCharas { get; set; } = new uint[0]; + public static bool HasHostilesInRange { get; set; } + + public static bool IsHostileCastingAOE { get; set; } + + public static bool IsHostileCastingToTank { get; set; } + + public static bool HasPet { get; set; } + + public static unsafe bool HasCompanion => (IntPtr)CharacterManager.Instance()->LookupBuddyByOwnerObject(Service.RawPlayer) != IntPtr.Zero; + + #region HP + public static IEnumerable PartyMembersHP { get; set; } + public static float PartyMembersMinHP { get; set; } + public static float PartyMembersAverHP { get; set; } + public static float PartyMembersDifferHP { get; set; } + + public static bool HPNotFull { get; set; } + + public static bool CanHealAreaAbility { get; set; } + + public static bool CanHealAreaSpell { get; set; } + + public static bool CanHealSingleAbility { get; set; } + + public static bool CanHealSingleSpell { get; set; } + #endregion + public static Queue Macros { get; } = new Queue(); + + #region Action Record + const int QUEUECAPACITY = 32; + private static Queue _actions = new Queue(QUEUECAPACITY); + + public static ActionRec[] RecordActions => _actions.Reverse().ToArray(); + private static DateTime _timeLastActionUsed = DateTime.Now; + public static TimeSpan TimeSinceLastAction => DateTime.Now - _timeLastActionUsed; + + internal static ActionID LastAction { get; private set; } = 0; + + internal static ActionID LastGCD { get; private set; } = 0; + + internal static ActionID LastAbility { get; private set; } = 0; + public static void AddActionRec(Action act) + { + var id = (ActionID)act.RowId; + + //Record + switch (act.GetActionType()) + { + case ActionCate.Spell: //魔法 + case ActionCate.WeaponSkill: //战技 + LastGCD = id; + break; + case ActionCate.Ability: //能力 + LastAbility = id; + break; + default: + return; + } + LastAction = id; + + if (_actions.Count >= QUEUECAPACITY) + { + _actions.Dequeue(); + } + _timeLastActionUsed = DateTime.Now; + _actions.Enqueue(new ActionRec(_timeLastActionUsed, act)); + } + #endregion + +} diff --git a/RotationSolver.Basic/Helpers/ActionHelper.cs b/RotationSolver.Basic/Helpers/ActionHelper.cs new file mode 100644 index 000000000..0cf2ed561 --- /dev/null +++ b/RotationSolver.Basic/Helpers/ActionHelper.cs @@ -0,0 +1,22 @@ +using RotationSolver.Data; +using Action = Lumina.Excel.GeneratedSheets.Action; + + +namespace RotationSolver.Helpers; + +public static class ActionHelper +{ + internal const byte GCDCooldownGroup = 58; + + public static ActionCate GetActionType(this Action action) => (ActionCate)action.ActionCategory.Value.RowId; + internal static bool IsGeneralGCD(this Action action) => action.CooldownGroup == GCDCooldownGroup; + + internal static bool IsRealGCD(this Action action) => action.IsGeneralGCD() || action.AdditionalCooldownGroup == GCDCooldownGroup; + + internal static byte GetCoolDownGroup(this Action action) + { + var group = action.IsGeneralGCD() ? action.AdditionalCooldownGroup : action.CooldownGroup; + if (group == 0) group = GCDCooldownGroup; + return group; + } +} diff --git a/RotationSolver.Basic/Helpers/ConditionHelper.cs b/RotationSolver.Basic/Helpers/ConditionHelper.cs new file mode 100644 index 000000000..74115b06c --- /dev/null +++ b/RotationSolver.Basic/Helpers/ConditionHelper.cs @@ -0,0 +1,82 @@ +using ImGuiNET; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Basic; +using RotationSolver.Basic.Rotations; +using RotationSolver.Data; +using System.Reflection; + +namespace RotationSolver.Helpers; + +public class ConditionHelper +{ + public static bool CheckBaseAction(ICustomRotation rotation, ActionID id, ref BaseAction action) + { + if (id != ActionID.None && (action == null || (ActionID)action.ID != id)) + { + action = rotation.AllBaseActions.OfType().FirstOrDefault(a => (ActionID)a.ID == id); + } + if (action == null || Service.Player == null) return false; + return true; + } + + public static void CheckMemberInfo(ICustomRotation rotation, string name, ref T value) where T : MemberInfo + { + if (!string.IsNullOrEmpty(name) && (value == null || value.Name != name)) + { + if (typeof(T).IsAssignableFrom(typeof(PropertyInfo))) + { + value = (T)(MemberInfo)rotation.GetType().GetPropertyInfo(name); + } + else if (typeof(T).IsAssignableFrom(typeof(MethodInfo))) + { + value = (T)(MemberInfo)rotation.GetType().GetMethodInfo(name); + } + } + } + + public static void DrawIntEnum(string name, ref T value, Func function) where T : struct, Enum + { + var type = (int)(object)value; + var names = Enum.GetValues().Select(function).ToArray(); + ImGui.SetNextItemWidth(100); + + if (ImGui.Combo(name, ref type, names, names.Length)) + { + value = (T)(object)type; + } + } + + public static bool DrawDragFloat(string name, ref float value) + { + ImGui.SameLine(); + ImGui.SetNextItemWidth(50); + return ImGui.DragFloat(name, ref value); + } + + public static bool DrawDragInt(string name, ref int value) + { + ImGui.SameLine(); + ImGui.SetNextItemWidth(50); + return ImGui.DragInt(name, ref value); + } + + public static bool DrawCheckBox(string name, ref int value, string desc = "") + { + ImGui.SameLine(); + + var @bool = value != 0; + + var result = false; + if (ImGui.Checkbox(name, ref @bool)) + { + value = @bool ? 1 : 0; + result = true; + } + if (!string.IsNullOrEmpty(desc) && ImGui.IsItemHovered()) + { + ImGui.SetTooltip(desc); + } + + return result; + } +} diff --git a/RotationSolver.Basic/Helpers/ConfigurationHelper.cs b/RotationSolver.Basic/Helpers/ConfigurationHelper.cs new file mode 100644 index 000000000..86d88560e --- /dev/null +++ b/RotationSolver.Basic/Helpers/ConfigurationHelper.cs @@ -0,0 +1,67 @@ +using Dalamud.Game.ClientState.Keys; +using RotationSolver.Basic; +using RotationSolver.Data; +using System.Collections.Generic; + +namespace RotationSolver.Helpers; + +public static class ConfigurationHelper +{ + public record PositionalInfo(EnemyPositional Pos, byte[] Tags); + public static readonly SortedList ActionPositional = new SortedList() + { + {ActionID.FangandClaw, new(EnemyPositional.Flank, new byte[] { 13, 10 })}, + {ActionID.WheelingThrust, new(EnemyPositional.Rear, new byte[] { 10, 13 }) }, + {ActionID.ChaosThrust, new(EnemyPositional.Rear, new byte[] { 61, 28 }) }, + {ActionID.ChaoticSpring, new(EnemyPositional.Rear, new byte[] { 66, 28 }) }, + {ActionID.Demolish, new(EnemyPositional.Rear, new byte[] { 46, 60 }) }, + {ActionID.SnapPunch, new(EnemyPositional.Flank, new byte[] { 19, 21 }) }, + {ActionID.TrickAttack, new(EnemyPositional.Rear, new byte[] { 25 }) }, + {ActionID.AeolianEdge,new(EnemyPositional.Rear, new byte[] { 30, 68 }) }, + {ActionID.ArmorCrush, new(EnemyPositional.Flank, new byte[] { 30, 66 }) }, + {ActionID.Suiton, new(EnemyPositional.Rear, new byte[] { }) }, + {ActionID.Gibbet, new(EnemyPositional.Flank , new byte[] { 11 })}, + {ActionID.Gallows, new(EnemyPositional.Rear, new byte[] { 11 }) }, + {ActionID.Gekko, new(EnemyPositional.Rear , new byte[] { 68, 29, 72 })}, + {ActionID.Kasha, new(EnemyPositional.Flank, new byte[] { 29, 68, 72 }) }, + }; + + + public static readonly string[] AuthorKeys = new string[] + { + "Ig4lHXUohMZNIeheUtAtRg==", //ArchiTed + }; + + public static readonly uint[] BadStatus = new uint[] + { + 583, //No items. + 581, //Unable to use. + 579, //Between Area + 574, //Job + 573, //没学会 ? + 572, //一些额外条件未满足 ? + }; + + public static readonly VirtualKey[] Keys = new VirtualKey[] { VirtualKey.CONTROL, VirtualKey.SHIFT, VirtualKey.MENU }; + + public static float GetHealAreaAbility(this ClassJobID job) + => Service.Config.HealthAreaAbilities.TryGetValue(job, out var value) ? value : Service.Config.HealthAreaAbility; + + public static float GetHealAreaSpell(this ClassJobID job) + => Service.Config.HealthAreaSpells.TryGetValue(job, out var value) ? value : Service.Config.HealthAreaSpell; + + public static float GetHealingOfTimeSubtractArea(this ClassJobID job) + => Service.Config.HealingOfTimeSubtractAreas.TryGetValue(job, out var value) ? value : 0.2f; + + public static float GetHealSingleAbility(this ClassJobID job) + => Service.Config.HealthSingleAbilities.TryGetValue(job, out var value) ? value : Service.Config.HealthSingleAbility; + + public static float GetHealSingleSpell(this ClassJobID job) + => Service.Config.HealthSingleSpells.TryGetValue(job, out var value) ? value : Service.Config.HealthSingleSpell; + + public static float GetHealingOfTimeSubtractSingle(this ClassJobID job) + => Service.Config.HealingOfTimeSubtractSingles.TryGetValue(job, out var value) ? value : 0.2f; + + public static float GetHealthForDyingTank(this ClassJobID job) + => Service.Config.HealthForDyingTanks.TryGetValue(job, out var value) ? value : 0.15f; +} diff --git a/RotationSolver.Basic/Helpers/CooldownHelper.cs b/RotationSolver.Basic/Helpers/CooldownHelper.cs new file mode 100644 index 000000000..9e5af5742 --- /dev/null +++ b/RotationSolver.Basic/Helpers/CooldownHelper.cs @@ -0,0 +1,53 @@ +using RotationSolver.Basic; + +namespace RotationSolver.Helpers; + +public static class CooldownHelper +{ + private static float WeaponRemain(uint gcdCount = 0, uint abilityCount = 0) + => WeaponTime(gcdCount, abilityCount) + DataCenter.WeaponRemain; + + private static float WeaponElapsed(uint gcdCount = 0, uint abilityCount = 0) + => WeaponTime(gcdCount, abilityCount) + DataCenter.WeaponElapsed; + + + private static float WeaponTime(uint gcdCount = 0, uint abilityCount = 0) + => DataCenter.WeaponTotal * gcdCount + + Service.Config.AbilitiesInterval * abilityCount; + + internal static bool ElapsedAfterGCD(float elapsed, uint gcdCount = 0, uint abilityCount = 0) + { + var gcdElapsed = WeaponElapsed(gcdCount, abilityCount); + + return gcdElapsed.IsLessThan(elapsed); + } + + internal static bool ElapsedAfter(float elapsed, float gcdElapsed) + { + gcdElapsed += DataCenter.WeaponElapsed; + return gcdElapsed.IsLessThan(elapsed); + } + + public static bool RecastAfterGCD(float recast, uint gcdCount = 0, uint abilityCount = 0) + { + var remain = WeaponRemain(gcdCount, abilityCount); + + return recast.IsLessThan(remain); + } + + internal static bool RecastAfter(float recast, float remain, bool addWeaponRemain = true) + { + if (addWeaponRemain) remain += DataCenter.WeaponRemain; + + return recast.IsLessThan(remain); + } + + internal static bool IsLessThan(this float a, float b) + { + if (a <= b) return true; + + if (Math.Abs(a - b) < 0.05) return true; + + return false; + } +} diff --git a/RotationSolver.Basic/Helpers/IActionHelper.cs b/RotationSolver.Basic/Helpers/IActionHelper.cs new file mode 100644 index 000000000..98e08d4df --- /dev/null +++ b/RotationSolver.Basic/Helpers/IActionHelper.cs @@ -0,0 +1,60 @@ +using RotationSolver.Actions; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.SigReplacers; + +namespace RotationSolver.Helpers; + +public static class IActionHelper +{ + public static bool IsLastGCD(bool isAdjust, params IAction[] actions) + { + return IsLastGCD(GetIDFromActions(isAdjust, actions)); + } + public static bool IsLastGCD(params ActionID[] ids) + { + return IsActionID(DataCenter.LastGCD, ids); + } + + public static bool IsLastAbility(bool isAdjust, params IAction[] actions) + { + return IsLastAbility(GetIDFromActions(isAdjust, actions)); + } + public static bool IsLastAbility(params ActionID[] ids) + { + return IsActionID(DataCenter.LastAbility, ids); + } + + public static bool IsLastAction(bool isAdjust, params IAction[] actions) + { + return IsLastAction(GetIDFromActions(isAdjust, actions)); + } + public static bool IsLastAction(params ActionID[] ids) + { + return IsActionID(DataCenter.LastAction, ids); + } + + public static bool IsTheSameTo(this IAction action, bool isAdjust, params IAction[] actions) + { + return IsActionID(isAdjust ? (ActionID)action.AdjustedID : (ActionID)action.ID, GetIDFromActions(isAdjust, actions)); + } + + private static bool IsActionID(ActionID id, params ActionID[] ids) + { + foreach (var i in ids) + { + if (i == id) return true; + } + return false; + } + + private static ActionID[] GetIDFromActions(bool isAdjust, params IAction[] actions) + { + return actions.Select(a => isAdjust ? (ActionID)a.AdjustedID : (ActionID)a.ID).ToArray(); + } + + internal static bool IsMeleeAction(this IBaseAction act) + { + return act.Range == 3; + } +} diff --git a/RotationSolver.Basic/Helpers/MarkingHelper.cs b/RotationSolver.Basic/Helpers/MarkingHelper.cs new file mode 100644 index 000000000..02dbe2510 --- /dev/null +++ b/RotationSolver.Basic/Helpers/MarkingHelper.cs @@ -0,0 +1,34 @@ +using Dalamud.Game.ClientState.Objects.Types; +using FFXIVClientStructs.FFXIV.Client.Game.UI; + +namespace RotationSolver.Helpers; + +public class MarkingHelper +{ + private unsafe static long GetMarker(uint index) => MarkingController.Instance()->MarkerArray[index]; + + + internal static bool HaveAttackChara(IEnumerable charas) => GetAttackMarkChara(charas) != null; + + internal static BattleChara GetAttackMarkChara(IEnumerable charas) + { + for (uint i = 0; i < 5; i++) + { + var b = GetChara(charas, GetMarker(i)); + if (b?.IsNPCEnemy() ?? false) return b; + } + return null; + } + + private static BattleChara GetChara(IEnumerable charas, long id) + { + if (id == 0xE0000000) return null; + return charas.FirstOrDefault(item => item.ObjectId == id); + } + + internal unsafe static IEnumerable FilterStopCharaes(IEnumerable charas) + { + var ids = new List() { GetMarker(8), GetMarker(9) }.Where(id => id != 0xE0000000); + return charas.Where(b => !ids.Contains(b.ObjectId)); + } +} diff --git a/RotationSolver.Basic/Helpers/ObjectHelper.cs b/RotationSolver.Basic/Helpers/ObjectHelper.cs new file mode 100644 index 000000000..8dc3e0f0e --- /dev/null +++ b/RotationSolver.Basic/Helpers/ObjectHelper.cs @@ -0,0 +1,206 @@ +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.ClientState.Objects.Types; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.Event; +using Lumina.Excel.GeneratedSheets; +using RotationSolver.Basic; +using RotationSolver.Data; +using System.Numerics; + +namespace RotationSolver.Helpers; + +public static class ObjectHelper +{ + static readonly EventHandlerType[] _eventType = new EventHandlerType[] + { + EventHandlerType.TreasureHuntDirector, + EventHandlerType.Quest, + }; + + private unsafe static BNpcBase GetObjectNPC(this GameObject obj) + { + if (obj == null) return null; + return Service.GetSheet().GetRow(obj.DataId); + } + + public static bool HasPositional(this GameObject obj) + { + if (obj == null) return false; + return !(obj.GetObjectNPC()?.Unknown10 ?? false); + } + + public static unsafe FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* GetAddress(this GameObject obj) + => (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)(void*)obj.Address; + + public static unsafe bool IsOthersPlayers(this GameObject obj) + { + //SpecialType but no NamePlateIcon + if (_eventType.Contains(obj.GetEventType())) + { + return obj.GetNamePlateIcon() == 0; + } + return false; + } + + public static unsafe bool IsNPCEnemy(this GameObject obj) + => obj.GetObjectKind() == ObjectKind.BattleNpc + && (byte)obj.GetBattleNPCSubkind() is (byte)BattleNpcSubKind.Enemy or 1; + + + public static unsafe ObjectKind GetObjectKind(this GameObject obj) => (ObjectKind)obj.GetAddress()->ObjectKind; + + public static bool IsTopPriorityHostile(this GameObject obj) + { + var icon = obj.GetNamePlateIcon(); + //Hunting log and weapon. + if (icon + is 60092 //Hunting + or 60096 //Weapon + or 71204 //Main Quest + or 71144 //Major Quest + or 71224 //Other Quest + or 71244 //Leve + or 71344 //Major Quest + ) return true; + if(icon == 0) return false; + var type = obj.GetEventType(); + + if(type is EventHandlerType.Quest) return true; + + return false; + } + + public static unsafe uint GetNamePlateIcon(this GameObject obj) => obj.GetAddress()->NamePlateIconId; + public static unsafe EventHandlerType GetEventType(this GameObject obj) => obj.GetAddress()->EventId.Type; + + public static unsafe BattleNpcSubKind GetBattleNPCSubkind(this GameObject obj) => (BattleNpcSubKind)obj.GetAddress()->SubKind; + + public static unsafe uint FateId(this GameObject obj) => obj.GetAddress()->FateId; + + public static unsafe bool IsTargetable(this GameObject obj) => obj.GetAddress()->GetIsTargetable(); + + static readonly Dictionary _effectRangeCheck = new Dictionary(); + public static bool CanInterrupt(this BattleChara b) + { + var baseCheck = b.IsCasting && b.IsCastInterruptible && b.TotalCastTime >= 2; + + if (!baseCheck) return false; + if (!Service.Config.InterruptibleMoreCheck) return true; + + var id = b.CastActionId; + if (_effectRangeCheck.TryGetValue(id, out var check)) return check; + + var act = Service.GetSheet().GetRow(b.CastActionId); + if (act == null) return _effectRangeCheck[id] = false; + if (act.CastType is 3 or 4) return _effectRangeCheck[id] = false; + if (act.EffectRange is > 0 and < 8) return _effectRangeCheck[id] = false; + return _effectRangeCheck[id] = true; + } + + private static bool IsDummy(this BattleChara obj) => obj?.NameId == 541; + /// + /// Is character a boss? Max HP exceeds a certain amount. + /// + /// + /// + public static bool IsBoss(this BattleChara obj) + { + if (obj == null) return false; + if (obj.IsDummy() && !Service.Config.ShowHealthRatio) return true; + return obj.MaxHp >= GetHealthFromMulty(Service.Config.HealthRatioBoss) + || !(obj.GetObjectNPC()?.IsTargetLine ?? true); + } + + /// + /// Is character a dying? Current HP is below a certain amount. It is for running out of resources. + /// + /// + /// + public static bool IsDying(this BattleChara b) + { + if (b == null) return false; + if (b.IsDummy() && !Service.Config.ShowHealthRatio) return false; + return b.CurrentHp <= GetHealthFromMulty(Service.Config.HealthRatioDying) || b.GetHealthRatio() < 0.02f; + } + + /// + /// Get the 's current HP percentage. + /// + /// + /// + public static float GetHealthRatio(this BattleChara b) + { + if (b == null) return 0; + return (float)b.CurrentHp / b.MaxHp; + } + + public static float GetHealingRatio(this BattleChara b) + { + return b.GetHealthRatio(); + } + + public static bool CanDot(this BattleChara b) + { + if (b == null) return false; + if (b.IsDummy() && !Service.Config.ShowHealthRatio) return true; + return b.CurrentHp >= GetHealthFromMulty(Service.Config.HealthRatioDot); + } + + public static EnemyPositional FindEnemyPositional(this GameObject enemy) + { + Vector3 pPosition = enemy.Position; + float rotation = enemy.Rotation; + Vector2 faceVec = new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)); + + Vector3 dir = Service.Player.Position - pPosition; + Vector2 dirVec = new Vector2(dir.Z, dir.X); + + double angle = Math.Acos(Vector2.Dot(dirVec, faceVec) / dirVec.Length() / faceVec.Length()); + + if (angle < Math.PI / 4) return EnemyPositional.Front; + else if (angle > Math.PI * 3 / 4) return EnemyPositional.Rear; + return EnemyPositional.Flank; + } + + public static uint GetHealthFromMulty(float mult) + { + if (Service.Player == null) return 0; + + var role = Service.GetSheet().GetRow( + Service.Player.ClassJob.Id).GetJobRole(); + float multi = mult * role switch + { + JobRole.Tank => 1, + JobRole.Healer => 1.6f, + _ => 1.5f, + }; + + var partyCount = DataCenter.PartyMembers.Count(); + if (partyCount > 4) + { + multi *= 6.4f; + } + else if (partyCount > 1) + { + multi *= 3.5f; + } + + return (uint)(multi * Service.Player.MaxHp); + } + + /// + /// 对象距玩家的距离 + /// + /// + /// + public static float DistanceToPlayer(this GameObject obj) + { + if (obj == null) return float.MaxValue; + var player = Service.Player; + if (player == null) return float.MaxValue; + + var distance = Vector3.Distance(player.Position, obj.Position) - player.HitboxRadius; + distance -= Math.Max(obj.HitboxRadius, Service.Config.ObjectMinRadius); + return distance; + } +} diff --git a/RotationSolver.Basic/Helpers/ReflectionHelper.cs b/RotationSolver.Basic/Helpers/ReflectionHelper.cs new file mode 100644 index 000000000..b04f3d1d8 --- /dev/null +++ b/RotationSolver.Basic/Helpers/ReflectionHelper.cs @@ -0,0 +1,62 @@ +using System.ComponentModel; +using System.Reflection; + +namespace RotationSolver.Helpers; + +public static class ReflectionHelper +{ + internal static PropertyInfo[] GetStaticProperties(this Type type) + { + if (type == null) return new PropertyInfo[0]; + + var props = from prop in type.GetRuntimeProperties() + where typeof(T).IsAssignableFrom(prop.PropertyType) + && prop.GetMethod is MethodInfo info + && info.IsStatic + select prop; + + return props.Union(type.BaseType.GetStaticProperties()).ToArray(); + } + + internal static MethodInfo[] GetStaticBoolMethodInfo(this Type type, Func checks) + { + return type.GetAllMethodInfo().Where(m => checks(m) && m.ReturnType == typeof(bool) && m.IsStatic).ToArray(); + } + + public static IEnumerable GetAllMethodInfo(this Type type) + { + if (type == null) return new MethodInfo[0]; + + var methods = from method in type.GetRuntimeMethods() + where !method.IsConstructor + select method; + + return methods.Union(type.BaseType.GetAllMethodInfo()); + } + + public static PropertyInfo GetPropertyInfo(this Type type, string name) + { + if (type == null) return null; + + foreach (var item in type.GetRuntimeProperties()) + { + if (item.Name == name && item.GetMethod is MethodInfo info + && info.IsStatic) return item; + } + + return type.BaseType.GetPropertyInfo(name); + } + + internal static MethodInfo GetMethodInfo(this Type type, string name) + { + if (type == null) return null; + + foreach (var item in type.GetRuntimeMethods()) + { + if (item.Name == name && item.IsStatic + && !item.IsConstructor && item.ReturnType == typeof(bool)) return item; + } + + return type.BaseType.GetMethodInfo(name); + } +} diff --git a/RotationSolver.Basic/Helpers/StatusHelper.cs b/RotationSolver.Basic/Helpers/StatusHelper.cs new file mode 100644 index 000000000..22dc22efc --- /dev/null +++ b/RotationSolver.Basic/Helpers/StatusHelper.cs @@ -0,0 +1,180 @@ +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Game.ClientState.Statuses; +using RotationSolver.Basic; +using RotationSolver.Commands; +using RotationSolver.Data; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + + +namespace RotationSolver.Helpers; + +public static class StatusHelper +{ + public static StatusID[] AreaHots { get; } = new StatusID[] + { + StatusID.AspectedHelios, StatusID.Medica2, StatusID.TrueMedica2 + }; + + public static StatusID[] SingleHots { get; } = new StatusID[] + { + StatusID.AspectedBenefic, StatusID.Regen1, StatusID.Regen2, StatusID.Regen3 + }; + + public static StatusID[] TankStanceStatus { get; } = new StatusID[] + { + StatusID.Grit, StatusID.RoyalGuard, StatusID.IronWill, StatusID.Defiance + }; + + public static StatusID[] NoNeedHealingStatus { get; } = new StatusID[] + { + StatusID.Holmgang, StatusID.WillDead, StatusID.WalkingDead, + }; + + public static bool NeedHealing(this BattleChara p) => p.WillStatusEndGCD(2, 0, false, NoNeedHealingStatus); + + /// + /// Will any of be end after gcds and abilities? + /// + /// + /// + /// + /// + /// + /// + public static bool WillStatusEndGCD(this BattleChara obj, uint gcdCount = 0, uint abilityCount = 0, bool isFromSelf = true, params StatusID[] statusIDs) + { + var remain = obj.StatusTime(isFromSelf, statusIDs); + return CooldownHelper.RecastAfterGCD(remain, gcdCount, abilityCount); + } + + + /// + /// Will any of be end after seconds? + /// + /// + /// + /// + /// + /// + public static bool WillStatusEnd(this BattleChara obj, float time, bool isFromSelf = true, params StatusID[] statusIDs) + { + var remain = obj.StatusTime(isFromSelf, statusIDs); + return CooldownHelper.RecastAfter(remain, time); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public static float StatusTime(this BattleChara obj, bool isFromSelf, params StatusID[] statusIDs) + { + var times = obj.StatusTimes(isFromSelf, statusIDs); + if (times == null || !times.Any()) return 0; + return times.Min(); + } + + private static IEnumerable StatusTimes(this BattleChara obj, bool isFromSelf, params StatusID[] statusIDs) + { + return obj.GetStatus(isFromSelf, statusIDs).Select(status => status.RemainingTime == 0 ? float.MaxValue : status.RemainingTime); + } + + public static byte StatusStack(this BattleChara obj, bool isFromSelf, params StatusID[] statusIDs) + { + var stacks = obj.StatusStacks(isFromSelf, statusIDs); + if (stacks == null || !stacks.Any()) return 0; + return stacks.Min(); + } + + private static IEnumerable StatusStacks(this BattleChara obj, bool isFromSelf, params StatusID[] statusIDs) + { + return obj.GetStatus(isFromSelf, statusIDs).Select(status => status.StackCount == 0 ? byte.MaxValue : status.StackCount); + } + + /// + /// Has one status right now. + /// + /// + /// + /// + /// + public static bool HasStatus(this BattleChara obj, bool isFromSelf, params StatusID[] statusIDs) + { + return obj.GetStatus(isFromSelf, statusIDs).Any(); + } + + public static void StatusOff(StatusID status) + { + if (!Service.Player?.HasStatus(false, status) ?? true) return; + Service.SubmitToChat($"/statusoff {GetStatusName(status)}"); + } + + internal static string GetStatusName(StatusID id) + { + return Service.GetSheet().GetRow((uint)id).Name.ToString(); + } + + private static IEnumerable GetStatus(this BattleChara obj, bool isFromSelf, params StatusID[] statusIDs) + { + var newEffects = statusIDs.Select(a => (uint)a); + return obj.GetAllStatus(isFromSelf).Where(status => newEffects.Contains(status.StatusId)); + } + + private static IEnumerable GetAllStatus(this BattleChara obj, bool isFromSelf) + { + if (obj == null) return new Status[0]; + + return obj.StatusList.Where(status => isFromSelf ? status.SourceId == Service.Player.ObjectId + || status.SourceObject?.OwnerId == Service.Player.ObjectId : true); + } + + static readonly StatusID[] invincibleStatus = new StatusID[] + { + StatusID.StoneSkin, + StatusID.IceSpikesInvincible, + }; + + public static bool IsInvincible(this Status status) + { + if (status.GameData.Icon == 15024) return true; + + return invincibleStatus.Any(id => (uint)id == status.StatusId); + } + + static readonly StatusID[] dangerousStatus = new StatusID[] + { + StatusID.Doom, + StatusID.Amnesia, + StatusID.Stun, + StatusID.Stun2, + StatusID.Sleep, + StatusID.Sleep2, + StatusID.Sleep3, + StatusID.Pacification, + StatusID.Pacification2, + StatusID.Silence, + StatusID.Slow, + StatusID.Slow2, + StatusID.Slow3, + StatusID.Slow4, + StatusID.Slow5, + StatusID.Blind, + StatusID.Blind2, + StatusID.Blind3, + StatusID.Paralysis, + StatusID.Paralysis2, + StatusID.Nightmare, + StatusID.Necrosis, + }; + + public static bool IsDangerous(this Status status) + { + if (!status.CanDispel()) return false; + if(status.StackCount > 2) return true; + return dangerousStatus.Any(id => (uint)id == status.StatusId); + } + + public static bool CanDispel(this Status status) + { + return status.GameData.CanDispel && status.RemainingTime > 2; + } +} diff --git a/RotationSolver.Basic/Helpers/TargetFilter.cs b/RotationSolver.Basic/Helpers/TargetFilter.cs new file mode 100644 index 000000000..8aa3b27a5 --- /dev/null +++ b/RotationSolver.Basic/Helpers/TargetFilter.cs @@ -0,0 +1,374 @@ +using Dalamud.Game.ClientState.Objects.Types; +using Lumina.Excel.GeneratedSheets; +using RotationSolver.Basic; +using RotationSolver.Commands; +using RotationSolver.Data; +using System.Data; +using System.Linq; +using System.Numerics; + +namespace RotationSolver.Helpers; + +public static class TargetFilter +{ + #region Find one target + internal static IEnumerable MeleeRangeTargetFilter(IEnumerable availableCharas) + => availableCharas.Where(t => t.DistanceToPlayer() >= 3 + Service.Config.MeleeRangeOffset); + internal static BattleChara DefaultChooseFriend(IEnumerable availableCharas, bool mustUse) + { + if (availableCharas == null || !availableCharas.Any()) return null; + + //根据默认设置排序怪且没有大招 + availableCharas = DefaultTargetingType(availableCharas).Where(StatusHelper.NeedHealing); + + var tar = availableCharas.OrderBy(ObjectHelper.GetHealingRatio).FirstOrDefault(); + + if (tar.GetHealingRatio() < 1) return tar; + + availableCharas = availableCharas.GetJobCategory(JobRole.Tank); + + return availableCharas.FirstOrDefault(t => t.HasStatus(false, StatusHelper.TankStanceStatus)) + ?? availableCharas.FirstOrDefault(); + } + + internal static BattleChara DefaultFindHostile(IEnumerable availableCharas, bool mustUse) + { + if (availableCharas == null || !availableCharas.Any()) return null; + + //找到被标记攻击的怪 + var b = MarkingHelper.GetAttackMarkChara(availableCharas); + if (Service.Config.ChooseAttackMark && b != null) return b; + + //去掉停止标记的怪 + if (Service.Config.FilterStopMark) + { + var charas = MarkingHelper.FilterStopCharaes(availableCharas); + if (charas?.Any() ?? false) availableCharas = charas; + } + + b = availableCharas.FirstOrDefault(ObjectHelper.IsTopPriorityHostile); + if (b != null) return b; + + if (DataCenter.TreasureCharas.Length > 0) + { + b = availableCharas.FirstOrDefault(b => b.ObjectId == DataCenter.TreasureCharas[0]); + if (b != null) return b; + availableCharas = availableCharas.Where(b => !DataCenter.TreasureCharas.Contains(b.ObjectId)); + } + + //根据默认设置排序怪 + availableCharas = DefaultTargetingType(availableCharas); + + //找到体积一样小的 + float radius = availableCharas.FirstOrDefault().HitboxRadius; + + return availableCharas.Where(c => c.HitboxRadius == radius) + .OrderBy(ObjectHelper.DistanceToPlayer).FirstOrDefault(); + } + + internal static T FindTargetForMoving(this IEnumerable charas, bool mustUse) where T : GameObject + { + if (mustUse) + { + var tar = charas.OrderBy(ObjectHelper.DistanceToPlayer).FirstOrDefault(); + if(tar == null) return null; + if (tar.DistanceToPlayer() < 1) return tar; + return null; + } + + if (Service.Config.MoveTowardsScreenCenter) + { + return FindMoveTargetScreenCenter(charas); + } + else + { + return FindMoveTargetFaceDirection(charas); + } + } + + const float DISTANCE_TO_MOVE = 3; + private static T FindMoveTargetFaceDirection(IEnumerable charas) where T : GameObject + { + Vector3 pPosition = Service.Player.Position; + float rotation = Service.Player.Rotation; + Vector2 faceVec = new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)); + + var tars = charas.Where(t => + { + if (t.DistanceToPlayer() < DISTANCE_TO_MOVE) return false; + + Vector3 dir = t.Position - pPosition; + Vector2 dirVec = new Vector2(dir.Z, dir.X); + double angle = Math.Acos(Vector2.Dot(dirVec, faceVec) / dirVec.Length() / faceVec.Length()); + return angle <= Math.PI * Service.Config.MoveTargetAngle / 360; + }).OrderByDescending(ObjectHelper.DistanceToPlayer); + + return tars.FirstOrDefault(); + } + + private static T FindMoveTargetScreenCenter(IEnumerable charas) where T : GameObject + { + var pPosition = Service.Player.Position; + if (!Service.GameGui.WorldToScreen(pPosition, out var playerScrPos)) return null; + + var tars = charas.Where(t => + { + if (t.DistanceToPlayer() < DISTANCE_TO_MOVE) return false; + + if (!Service.GameGui.WorldToScreen(t.Position, out var scrPos)) return false; + + var dir = scrPos - playerScrPos; + + if (dir.Y > 0) return false; + + return Math.Abs(dir.X / dir.Y) < Math.Tan(Math.PI * Service.Config.MoveTargetAngle / 360); + }).OrderByDescending(ObjectHelper.DistanceToPlayer); + + return tars.FirstOrDefault(); + } + + /// + /// 发现被攻击的目标 + /// + /// + /// + public static BattleChara FindAttackedTarget(IEnumerable charas, bool mustUse) + { + if (!charas.Any()) return null; + var attachedT = charas.Where(tank => tank.TargetObject?.TargetObject == tank); + + if (!attachedT.Any()) + { + attachedT = charas.Where(tank => tank.HasStatus(false, StatusHelper.TankStanceStatus)); + } + + if (!attachedT.Any()) + { + attachedT = charas.GetJobCategory(JobRole.Tank); + } + + if (!attachedT.Any()) + { + attachedT = charas; + } + + return attachedT.OrderBy(ObjectHelper.GetHealthRatio).FirstOrDefault(); + } + + internal static IEnumerable TankRangeTarget(IEnumerable inputCharas) + => ProvokeTarget(MeleeRangeTargetFilter(inputCharas)); + + /// + /// 挑衅目标 + /// + /// + /// + /// + internal static IEnumerable ProvokeTarget(IEnumerable inputCharas, bool needDistance = false) + { + var tankIDS = DataCenter.AllianceMembers.GetJobCategory(JobRole.Tank).Select(member => (ulong)member.ObjectId); + var loc = Service.Player.Position; + var id = Service.Player.ObjectId; + + var targets = inputCharas.Where(target => + { + //有目标 + if (target.TargetObject?.IsValid() ?? false) + { + //居然在打非T! + if (!tankIDS.Contains(target.TargetObjectId) && (!needDistance || Vector3.Distance(target.Position, loc) > 5)) + { + return true; + } + } + return false; + }); + + //没有敌对势力,那随便用 + if (!targets.Any()) return inputCharas; + //返回在打队友的讨厌鬼! + return targets; + } + + /// + /// 获得死亡的角色 + /// + /// + /// + /// + internal static BattleChara GetDeathPeople(IEnumerable deathAll, IEnumerable deathParty) + { + if (deathParty.Any()) + { + //确认一下死了的T有哪些。 + + var deathT = deathParty.GetJobCategory(JobRole.Tank); + int TCount = DataCenter.PartyTanks.Count(); + + //如果全死了,赶紧复活啊。 + if (TCount > 0 && deathT.Count() == TCount) + { + return deathT.FirstOrDefault(); + } + + //确认一下死了的H有哪些。 + var deathH = deathParty.GetJobCategory(JobRole.Healer); + + //如果H死了,就先救他。 + if (deathH.Any()) return deathH.FirstOrDefault(); + + //如果T死了,就再救他。 + if (deathT.Any()) return deathT.FirstOrDefault(); + + //T和H都还活着,那就随便救一个。 + return deathParty.FirstOrDefault(); + } + + if (deathAll.Any()) + { + //确认一下死了的H有哪些。 + var deathAllH = deathAll.GetJobCategory(JobRole.Healer); + if (deathAllH.Any()) return deathAllH.FirstOrDefault(); + + //确认一下死了的T有哪些。 + var deathAllT = deathAll.GetJobCategory(JobRole.Tank); + if (deathAllT.Any()) return deathAllT.FirstOrDefault(); + + return deathAll.FirstOrDefault(); + } + + return null; + } + + public unsafe static IEnumerable GetDeath(this IEnumerable charas) => charas.Where(item => + { + if (!item.IsDead) return false; + if (item.CurrentHp != 0) return false; + + if (!item.IsTargetable()) return false; + + //如果已经有复活的Buff了,那就算了。 + if (item.HasStatus(false, StatusID.Raise)) return false; + + //如果濒死了,那给我TMD冷静冷静!等着另一个奶大发慈悲吧。 + if (!Service.Config.RaiseBrinkOfDeath && item.HasStatus(false, StatusID.BrinkofDeath)) return false; + + //如果有人在对着他咏唱,那就算了。 + if (DataCenter.AllianceMembers.Any(c => c.CastTargetObjectId == item.ObjectId)) return false; + + return true; + }); + + public static IEnumerable GetJobCategory(this IEnumerable objects, params JobRole[] roles) + { + return roles.SelectMany(role => + { + return objects.Where(obj => + { + return obj.IsJobCategory(role); + }); + }); + } + + public static bool IsJobCategory(this BattleChara obj, JobRole role) + { + SortedSet validJobs = new(Service.GetSheet() + .Where(job => role == job.GetJobRole()) + .Select(job => (byte)job.RowId)); + + return obj.IsJobCategory(validJobs); + } + + + private static bool IsJobCategory(this BattleChara obj, SortedSet validJobs) + { + return validJobs.Contains((byte)obj.ClassJob.GameData?.RowId); + } + + internal static BattleChara ASTRangeTarget(IEnumerable ASTTargets, bool mustUse) + { + ASTTargets = ASTTargets.Where(b => !b.HasStatus(false, StatusID.Weakness, StatusID.BrinkofDeath)); + + return ASTTargets.ASTGetTargetByJobs(JobRole.RangedMagical, JobRole.RangedPhysical, JobRole.Melee); + } + + internal static BattleChara ASTMeleeTarget(IEnumerable ASTTargets, bool mustUse) + { + ASTTargets = ASTTargets.Where(b => !b.HasStatus(false, StatusID.Weakness, StatusID.BrinkofDeath)); + + + return ASTTargets.ASTGetTargetByJobs(JobRole.Melee, JobRole.RangedMagical, JobRole.RangedPhysical); + } + + private static BattleChara ASTGetTargetByJobs(this IEnumerable tars, params JobRole[] roles) + { + foreach (var role in roles) + { + var targets = GetASTCardTargets(tars.GetJobCategory(role)); + if (targets.Count() > 0) return RandomObject(targets); + } + var ts = GetASTCardTargets(tars); + if (ts.Count() > 0) return RandomObject(ts); + + return null; + } + + private static IEnumerable GetASTCardTargets(IEnumerable sources) + { + var allStatus = new StatusID[] + { + StatusID.TheArrow, + StatusID.TheBalance, + StatusID.TheBole, + StatusID.TheEwer, + StatusID.TheSpear, + StatusID.TheSpire, + }; + return sources.Where((t) => !t.HasStatus(true, allStatus)); + } + + private static BattleChara RandomObject(IEnumerable objs) + { + Random ran = new Random(DateTime.Now.Millisecond); + return objs.ElementAt(ran.Next(objs.Count())); + } + + #endregion + + /// + /// 获得范围内对象 + /// + /// + /// + /// + /// + public static IEnumerable GetObjectInRadius(this IEnumerable objects, float radius) where T : GameObject + { + return objects.Where(o => o.DistanceToPlayer() <= radius); + } + + private static IEnumerable DefaultTargetingType(IEnumerable charas) + { + switch (DataCenter.TargetingType) + { + default: + case TargetingType.Big: + return charas.OrderByDescending(p => p.HitboxRadius); + + case TargetingType.Small: + return charas.OrderBy(p => p.HitboxRadius); + + case TargetingType.HighHP: + return charas.OrderByDescending(p => p.CurrentHp); + + case TargetingType.LowHP: + return charas.OrderBy(p => p.CurrentHp); + + case TargetingType.HighMaxHP: + return charas.OrderByDescending(p => p.MaxHp); + + case TargetingType.LowMaxHP: + return charas.OrderBy(p => p.MaxHp); + } + } +} diff --git a/RotationSolver/ITexture.cs b/RotationSolver.Basic/ITexture.cs similarity index 100% rename from RotationSolver/ITexture.cs rename to RotationSolver.Basic/ITexture.cs diff --git a/RotationSolver.Basic/RotationSolver.Basic.csproj b/RotationSolver.Basic/RotationSolver.Basic.csproj new file mode 100644 index 000000000..d7723d1aa --- /dev/null +++ b/RotationSolver.Basic/RotationSolver.Basic.csproj @@ -0,0 +1,45 @@ + + + + net7.0-windows + true + enable + + $(AppData)\XIVLauncher\addon\Hooks\dev\ + + AnyCPU;ARM64 + + + + + + + $(DalamudLibPath)Dalamud.dll + False + + + $(DalamudLibPath)ImGui.NET.dll + False + + + $(DalamudLibPath)ImGuiScene.dll + False + + + $(DalamudLibPath)Lumina.dll + False + + + $(DalamudLibPath)Lumina.Excel.dll + False + + + $(DalamudLibPath)FFXIVClientStructs.dll + False + + + $(DalamudLibPath)Newtonsoft.Json.dll + False + + + diff --git a/RotationSolver.Basic/Rotations/Basic/AST_Base.cs b/RotationSolver.Basic/Rotations/Basic/AST_Base.cs new file mode 100644 index 000000000..d0b6d2777 --- /dev/null +++ b/RotationSolver.Basic/Rotations/Basic/AST_Base.cs @@ -0,0 +1,301 @@ +using Dalamud.Game.ClientState.JobGauge.Enums; +using Dalamud.Game.ClientState.JobGauge.Types; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.CustomRotation; + +namespace RotationSolver.Rotations.Basic; + +public abstract class AST_Base : CustomRotation.CustomRotation +{ + private static ASTGauge JobGauge => Service.JobGauges.Get(); + + public override MedicineType MedicineType => MedicineType.Mind; + + /// + /// 抽出来的卡是啥。 + /// + private static CardType DrawnCard => JobGauge.DrawnCard; + + /// + /// 抽出来的王卡是啥 + /// + protected static CardType DrawnCrownCard => JobGauge.DrawnCrownCard; + + /// + /// 已经有的星象 + /// + protected static SealType[] Seals => JobGauge.Seals; + + public sealed override ClassJobID[] JobIDs => new ClassJobID[] { ClassJobID.Astrologian }; + + private sealed protected override IBaseAction Raise => Ascend; + + /// + /// 生辰 + /// + public static IBaseAction Ascend { get; } = new BaseAction(ActionID.Ascend, true); + + /// + /// 凶星 + /// + public static IBaseAction Malefic { get; } = new BaseAction(ActionID.Malefic); + + /// + /// 烧灼 + /// + public static IBaseAction Combust { get; } = new BaseAction(ActionID.Combust, isEot: true) + { + TargetStatus = new StatusID[] + { + StatusID.Combust, + StatusID.Combust2, + StatusID.Combust3, + StatusID.Combust4, + } + }; + + /// + /// 重力 + /// + public static IBaseAction Gravity { get; } = new BaseAction(ActionID.Gravity); + + /// + /// 吉星 + /// + public static IBaseAction Benefic { get; } = new BaseAction(ActionID.Benefic, true, isTimeline: true); + + /// + /// 福星 + /// + public static IBaseAction Benefic2 { get; } = new BaseAction(ActionID.Benefic2, true, isTimeline: true); + + /// + /// 吉星相位 + /// + public static IBaseAction AspectedBenefic { get; } = new BaseAction(ActionID.AspectedBenefic, true, isEot: true, isTimeline: true) + { + TargetStatus = new StatusID[] { StatusID.AspectedBenefic }, + }; + + /// + /// 先天禀赋 + /// + public static IBaseAction EssentialDignity { get; } = new BaseAction(ActionID.EssentialDignity, true, isTimeline: true); + + /// + /// 星位合图 + /// + public static IBaseAction Synastry { get; } = new BaseAction(ActionID.Synastry, true, isTimeline: true); + + /// + /// 天星交错 + /// + public static IBaseAction CelestialIntersection { get; } = new BaseAction(ActionID.CelestialIntersection, true, isTimeline: true) + { + ChoiceTarget = TargetFilter.FindAttackedTarget, + + TargetStatus = new StatusID[] { StatusID.Intersection }, + }; + + /// + /// 擢升 + /// + public static IBaseAction Exaltation { get; } = new BaseAction(ActionID.Exaltation, true, isTimeline: true) + { + ChoiceTarget = TargetFilter.FindAttackedTarget, + }; + + /// + /// 阳星 + /// + public static IBaseAction Helios { get; } = new BaseAction(ActionID.Helios, true, isTimeline: true); + + /// + /// 阳星相位 + /// + public static IBaseAction AspectedHelios { get; } = new BaseAction(ActionID.AspectedHelios, true, isEot: true, isTimeline: true) + { + ActionCheck = b => !IsLastGCD(ActionID.AspectedHelios), + StatusProvide = new StatusID[] { StatusID.AspectedHelios }, + }; + + /// + /// 天星冲日 + /// + public static IBaseAction CelestialOpposition { get; } = new BaseAction(ActionID.CelestialOpposition, true, isTimeline: true); + + /// + /// 地星 + /// + public static IBaseAction EarthlyStar { get; } = new BaseAction(ActionID.EarthlyStar, true, isTimeline: true); + + /// + /// 命运之轮 减伤,手动放。 + /// + public static IBaseAction CollectiveUnconscious { get; } = new BaseAction(ActionID.CollectiveUnconscious, true, isTimeline: true); + + /// + /// 天宫图 + /// + public static IBaseAction Horoscope { get; } = new BaseAction(ActionID.Horoscope, true, isTimeline: true); + + /// + /// 光速 + /// + public static IBaseAction Lightspeed { get; } = new BaseAction(ActionID.Lightspeed); + + /// + /// 中间学派 + /// + public static IBaseAction NeutralSect { get; } = new BaseAction(ActionID.NeutralSect, isTimeline: true); + + /// + /// 大宇宙 + /// + public static IBaseAction Macrocosmos { get; } = new BaseAction(ActionID.Macrocosmos, isTimeline: true); + + /// + /// 星力 + /// + public static IBaseAction Astrodyne { get; } = new BaseAction(ActionID.Astrodyne) + { + ActionCheck = b => + { + if (JobGauge.Seals.Length != 3) return false; + if (JobGauge.Seals.Contains(SealType.NONE)) return false; + return true; + }, + }; + + /// + /// 占卜 + /// + public static IBaseAction Divination { get; } = new BaseAction(ActionID.Divination, true); + + /// + /// 抽卡 + /// + public static IBaseAction Draw { get; } = new BaseAction(ActionID.Draw) + { + ActionCheck = b => DrawnCard == CardType.NONE, + }; + + /// + /// 重抽 + /// + public static IBaseAction Redraw { get; } = new BaseAction(ActionID.Redraw) + { + StatusNeed = new[] { StatusID.ClarifyingDraw }, + ActionCheck = b => DrawnCard != CardType.NONE && Seals.Contains(GetCardSeal(DrawnCard)), + }; + + /// + /// 小奥秘卡 + /// + public static IBaseAction MinorArcana { get; } = new BaseAction(ActionID.MinorArcana) + { + ActionCheck = b => InCombat && DrawnCrownCard == CardType.NONE, + }; + + ///// + ///// 出王冠卡 + ///// + //public static IBaseAction CrownPlay { get; } = new BaseAction(ActionID.CrownPlay) + //{ + // ActionCheck = b => DrawnCrownCard is CardType.LADY or CardType.LORD, + //}; + + /// + /// 太阳神之衡 + /// + private static IBaseAction Balance { get; } = new BaseAction(ActionID.Balance) + { + ChoiceTarget = TargetFilter.ASTMeleeTarget, + ActionCheck = b => DrawnCard == CardType.BALANCE, + }; + + /// + /// 放浪神之箭 + /// + private static IBaseAction Arrow { get; } = new BaseAction(ActionID.Arrow) + { + ChoiceTarget = TargetFilter.ASTMeleeTarget, + ActionCheck = b => DrawnCard == CardType.ARROW, + }; + + /// + /// 战争神之枪 + /// + private static IBaseAction Spear { get; } = new BaseAction(ActionID.Spear) + { + ChoiceTarget = TargetFilter.ASTMeleeTarget, + ActionCheck = b => DrawnCard == CardType.SPEAR, + }; + + /// + /// 世界树之干 + /// + private static IBaseAction Bole { get; } = new BaseAction(ActionID.Bole) + { + ChoiceTarget = TargetFilter.ASTRangeTarget, + ActionCheck = b => DrawnCard == CardType.BOLE, + }; + + /// + /// 河流神之瓶 + /// + private static IBaseAction Ewer { get; } = new BaseAction(ActionID.Ewer) + { + ChoiceTarget = TargetFilter.ASTRangeTarget, + ActionCheck = b => DrawnCard == CardType.EWER, + + }; + + /// + /// 建筑神之塔 + /// + private static IBaseAction Spire { get; } = new BaseAction(ActionID.Spire) + { + ChoiceTarget = TargetFilter.ASTRangeTarget, + ActionCheck = b => DrawnCard == CardType.SPIRE, + }; + + protected static bool PlayCard(out IAction act) + { + act = null; + if (!Seals.Contains(SealType.NONE)) return false; + + if (Balance.CanUse(out act)) return true; + if (Arrow.CanUse(out act)) return true; + if (Spear.CanUse(out act)) return true; + if (Bole.CanUse(out act)) return true; + if (Ewer.CanUse(out act)) return true; + if (Spire.CanUse(out act)) return true; + + return false; + } + + private static SealType GetCardSeal(CardType card) + { + switch (card) + { + default: return SealType.NONE; + + case CardType.BALANCE: + case CardType.BOLE: + return SealType.SUN; + + case CardType.ARROW: + case CardType.EWER: + return SealType.MOON; + + case CardType.SPEAR: + case CardType.SPIRE: + return SealType.CELESTIAL; + } + } +} diff --git a/RotationSolver.Basic/Rotations/Basic/BLM_Base.cs b/RotationSolver.Basic/Rotations/Basic/BLM_Base.cs new file mode 100644 index 000000000..4b230a331 --- /dev/null +++ b/RotationSolver.Basic/Rotations/Basic/BLM_Base.cs @@ -0,0 +1,337 @@ +using Dalamud.Game.ClientState.JobGauge.Types; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.CustomRotation; + +namespace RotationSolver.Rotations.Basic; + +public abstract partial class BLM_Base : CustomRotation.CustomRotation +{ + private static BLMGauge JobGauge => Service.JobGauges.Get(); + + public override MedicineType MedicineType => MedicineType.Intelligence; + + + /// + /// 冰状态层数 + /// + protected static byte UmbralIceStacks => JobGauge.UmbralIceStacks; + + /// + /// 火状态层数 + /// + protected static byte AstralFireStacks => JobGauge.AstralFireStacks; + + /// + /// 通晓层数 + /// + protected static byte PolyglotStacks => JobGauge.PolyglotStacks; + + /// + /// 灵极心层数 + /// + protected static byte UmbralHearts => JobGauge.UmbralHearts; + + /// + /// 是否有悖论 + /// + protected static bool IsParadoxActive => JobGauge.IsParadoxActive; + + /// + /// 在冰状态 + /// + protected static bool InUmbralIce => JobGauge.InUmbralIce; + + /// + /// 在火状态 + /// + protected static bool InAstralFire => JobGauge.InAstralFire; + + /// + /// 是否有天语状态 + /// + protected static bool IsEnochianActive => JobGauge.IsEnochianActive; + + /// + /// 下一个通晓还剩多少时间好 + /// + /// + /// + protected static bool EnchinaEndAfter(float time) + { + return EndAfter(JobGauge.EnochianTimer / 1000f, time); + } + + /// + /// 下一个通晓还剩多少时间好 + /// + /// + /// + protected static bool EnchinaEndAfterGCD(uint gctCount = 0, uint abilityCount = 0) + { + return EndAfterGCD(JobGauge.EnochianTimer / 1000f, gctCount, abilityCount); + } + + protected static bool ElementTimeEndAfter(float time) + { + return EndAfter(JobGauge.ElementTimeRemaining / 1000f, time); + } + + protected static bool ElementTimeEndAfterGCD(uint gctCount = 0, uint abilityCount = 0) + { + return EndAfterGCD(JobGauge.ElementTimeRemaining / 1000f, gctCount, abilityCount); + } + + public sealed override ClassJobID[] JobIDs => new ClassJobID[] { ClassJobID.BlackMage, ClassJobID.Thaumaturge }; + + /// + /// 有火苗 + /// + protected static bool HasFire => Player.HasStatus(true, StatusID.Firestarter); + /// + /// 有雷云 + /// + protected static bool HasThunder => Player.HasStatus(true, StatusID.Thundercloud); + /// + /// 通晓是否已经达到当前等级下的最大层数 + /// + protected static bool IsPolyglotStacksMaxed => Xenoglossy.EnoughLevel ? JobGauge.PolyglotStacks == 2 : JobGauge.PolyglotStacks == 1; + + public class ThunderAction : BaseAction + { + public override uint MPNeed => HasThunder ? 0 : base.MPNeed; + + internal ThunderAction(ActionID actionID) + : base(actionID, false, false, true) + { + } + } + + public class Fire3Action : BaseAction + { + public override uint MPNeed => HasFire ? 0 : base.MPNeed; + + internal Fire3Action(ActionID actionID) + : base(actionID, false, false, false) + { + } + } + + public class ElementAction : BaseAction + { + internal ElementAction(ActionID actionID, bool isFriendly = false, bool shouldEndSpecial = false, bool isEot = false) : base(actionID, isFriendly, shouldEndSpecial, isEot) + { + } + + public override bool CanUse(out IAction act, bool mustUse = false, bool emptyOrSkipCombo = false, bool skipDisable = false, uint gcdCountForAbility = 0, bool recordTarget = true) + { + if (ElementTimeEndAfter(CastTime - 0.1f)) + { + act = null; + return false; + } + return base.CanUse(out act, mustUse, emptyOrSkipCombo, skipDisable); + } + } + + /// + /// 闪雷 + /// + public static IBaseAction Thunder { get; } = new ThunderAction(ActionID.Thunder); + + /// + /// 震雷 + /// + public static IBaseAction Thunder2 { get; } = new ThunderAction(ActionID.Thunder2); + + /// + /// 星灵移位 + /// + public static IBaseAction Transpose { get; } = new BaseAction(ActionID.Transpose) { ActionCheck = b => DataCenter.AbilityRemain.IsLessThan(JobGauge.ElementTimeRemaining / 1000f) }; + + /// + /// 灵极魂 + /// + public static IBaseAction UmbralSoul { get; } = new BaseAction(ActionID.UmbralSoul) { ActionCheck = b => JobGauge.InUmbralIce && Transpose.ActionCheck(b) }; + + /// + /// 魔罩 + /// + public static IBaseAction Manaward { get; } = new BaseAction(ActionID.Manaward, true, isTimeline: true); + + /// + /// 魔泉 + /// + public static IBaseAction Manafont { get; } = new BaseAction(ActionID.Manafont) + { + ActionCheck = b => Player.CurrentMp <= 7000, + }; + + /// + /// 激情咏唱 + /// + public static IBaseAction Sharpcast { get; } = new BaseAction(ActionID.Sharpcast) + { + StatusProvide = new[] { StatusID.Sharpcast }, + ActionCheck = b => HasHostilesInRange, + }; + + /// + /// 三连咏唱 + /// + public static IBaseAction Triplecast { get; } = new BaseAction(ActionID.Triplecast) + { + StatusProvide = Swiftcast.StatusProvide, + }; + + /// + /// 黑魔纹 + /// + public static IBaseAction Leylines { get; } = new BaseAction(ActionID.Leylines, true, shouldEndSpecial: true) + { + StatusProvide = new[] { StatusID.LeyLines, }, + }; + + /// + /// 魔纹步 + /// + public static IBaseAction BetweenTheLines { get; } = new BaseAction(ActionID.BetweenTheLines, true, shouldEndSpecial: true) + { + StatusNeed = Leylines.StatusProvide, + }; + + /// + /// 以太步 + /// + public static IBaseAction AetherialManipulation { get; } = new BaseAction(ActionID.AetherialManipulation, true) + { + ChoiceTarget = TargetFilter.FindTargetForMoving, + }; + + /// + /// 详述 + /// + public static IBaseAction Amplifier { get; } = new BaseAction(ActionID.Amplifier) { ActionCheck = b => JobGauge.EnochianTimer > 10000 && JobGauge.PolyglotStacks < 2 }; + + /// + /// 核爆 + /// + public static IBaseAction Flare { get; } = new ElementAction(ActionID.Flare) { ActionCheck = b => JobGauge.InAstralFire }; + + /// + /// 绝望 + /// + public static IBaseAction Despair { get; } = new ElementAction(ActionID.Despair) { ActionCheck = b => JobGauge.InAstralFire }; + + /// + /// 秽浊 + /// + public static IBaseAction Foul { get; } = new BaseAction(ActionID.Foul) { ActionCheck = b => JobGauge.PolyglotStacks != 0 }; + + /// + /// 异言 + /// + public static IBaseAction Xenoglossy { get; } = new BaseAction(ActionID.Xenoglossy) { ActionCheck = b => JobGauge.PolyglotStacks != 0 }; + + /// + /// 崩溃 + /// + public static IBaseAction Scathe { get; } = new BaseAction(ActionID.Scathe); + + /// + /// 悖论 + /// + public static IBaseAction Paradox { get; } = new BaseAction(ActionID.Paradox) + { + ActionCheck = b => JobGauge.IsParadoxActive, + }; + + /// + /// 火1 + /// + public static IBaseAction Fire { get; } = new BaseAction(ActionID.Fire); + + /// + /// 火2 + /// + public static IBaseAction Fire2 { get; } = new BaseAction(ActionID.Fire2); + + /// + /// 火3 + /// + public static IBaseAction Fire3 { get; } = new Fire3Action(ActionID.Fire3) + { + ActionCheck = b => !IsLastGCD(ActionID.Fire3), + }; + + /// + /// 火4 + /// + public static IBaseAction Fire4 { get; } = new ElementAction(ActionID.Fire4) + { + ActionCheck = b => JobGauge.InAstralFire, + }; + + /// + /// 冰1 + /// + public static IBaseAction Blizzard { get; } = new BaseAction(ActionID.Blizzard); + + /// + /// 冰2 + /// + public static IBaseAction Blizzard2 { get; } = new BaseAction(ActionID.Blizzard2); + + /// + /// 冰3 + /// + public static IBaseAction Blizzard3 { get; } = new BaseAction(ActionID.Blizzard3) + { + ActionCheck = b => !IsLastGCD(ActionID.Blizzard3), + }; + + /// + /// 冰4 + /// + public static IBaseAction Blizzard4 { get; } = new ElementAction(ActionID.Blizzard4); + + /// + /// 冻结 + /// + public static IBaseAction Freeze { get; } = new ElementAction(ActionID.Freeze); + + public static float Fire4Time { get; private set; } + protected override void UpdateInfo() + { + if (Player.CastActionId == (uint)ActionID.Fire4 && Player.CurrentCastTime < 0.2) + { + Fire4Time = Player.TotalCastTime; + } + base.UpdateInfo(); + } + + [RotationDesc(ActionID.Manaward)] + protected sealed override bool DefenseSingleGCD(out IAction act) + { + if (Manaward.CanUse(out act)) return true; + return base.DefenseSingleGCD(out act); + } + + [RotationDesc(ActionID.Addle)] + protected override bool DefenseAreaAbility(byte abilitiesRemaining, out IAction act) + { + if (Addle.CanUse(out act)) return true; + return false; + } + + [RotationDesc(ActionID.AetherialManipulation)] + protected sealed override bool MoveForwardGCD(out IAction act) + { + if (AetherialManipulation.CanUse(out act, mustUse: true)) return true; + return base.MoveForwardGCD(out act); + } +} diff --git a/RotationSolver.Basic/Rotations/Basic/BLU_Base.cs b/RotationSolver.Basic/Rotations/Basic/BLU_Base.cs new file mode 100644 index 000000000..bb22f0b17 --- /dev/null +++ b/RotationSolver.Basic/Rotations/Basic/BLU_Base.cs @@ -0,0 +1,835 @@ +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Basic; +using RotationSolver.Commands; +using RotationSolver.Configuration.RotationConfig; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.CustomRotation; +using System.Linq; + +namespace RotationSolver.Rotations.Basic; + + +public interface IBLUAction : IBaseAction +{ + bool OnSlot { get; } + bool RightType { get; } +} +public abstract class BLU_Base : CustomRotation.CustomRotation +{ + public override MedicineType MedicineType => MedicineType.Intelligence; + + public enum BLUID : byte + { + Tank, + Healer, + DPS, + } + + public enum BLUAttackType : byte + { + Magical, + Physical, + Both, + } + + public enum BLUActionType : byte + { + None, + Magical, + Physical, + } + + public sealed override ClassJobID[] JobIDs => new ClassJobID[] { ClassJobID.BlueMage }; + + protected static BLUAttackType AttackType { get; set; } = BLUAttackType.Both; + + protected static BLUID BlueId { get; set; } = BLUID.DPS; + + private protected sealed override IBaseAction Raise => AngelWhisper; + + public class BLUAction : BaseAction, IBLUAction + { + static readonly StatusID[] NoPhysic = new StatusID[] + { + StatusID.IceSpikes, + }; + + static readonly StatusID[] NoMagic = new StatusID[] + { + StatusID.RespellingSpray, + StatusID.Magitek, + }; + + private BLUActionType _type; + public bool RightType + { + get + { + if (_type == BLUActionType.None) return true; + if (AttackType == BLUAttackType.Physical && _type == BLUActionType.Magical) return false; + if (AttackType == BLUAttackType.Magical && _type == BLUActionType.Physical) return false; + + if (Target.HasStatus(false, NoPhysic) && _type == BLUActionType.Physical) return false; + if (Target.HasStatus(false, NoMagic) && _type == BLUActionType.Magical) return false; + return true; + } + } + + public unsafe bool OnSlot => DataCenter.BluSlots.Any(i => AdjustedID == Service.GetAdjustedActionId(i)); + + internal BLUAction(ActionID actionID, BLUActionType type, bool isFriendly = false, bool shouldEndSpecial = false, bool isEot = false, bool isTimeline = false) + : base(actionID, isFriendly, shouldEndSpecial, isEot, isTimeline) + { + _type = type; + ActionCheck = t => OnSlot && RightType; + } + + public override bool CanUse(out IAction act, bool mustUse = false, bool emptyOrSkipCombo = false, bool skipDisable = false, uint gcdCountForAbility = 0, bool recordTarget = true) + { + act = null; + + if (!OnSlot) return false; + return base.CanUse(out act, mustUse, emptyOrSkipCombo, skipDisable); + } + } + + #region 魔法单体 + + /// + /// 水炮 + /// + public static IBLUAction WaterCannon { get; } = new BLUAction(ActionID.WaterCannon, BLUActionType.Magical); + + /// + /// 苦闷之歌 + /// + public static IBLUAction SongofTorment { get; } = new BLUAction(ActionID.SongofTorment, BLUActionType.Magical, isEot: true) + { + TargetStatus = new[] { StatusID.Bleeding } + }; + + /// + /// 吸血 + /// + public static IBLUAction BloodDrain { get; } = new BLUAction(ActionID.BloodDrain, BLUActionType.Magical) + { + ActionCheck = b => Player.CurrentMp <= 9500, + }; + + /// + /// 音爆 + /// + public static IBLUAction SonicBoom { get; } = new BLUAction(ActionID.SonicBoom, BLUActionType.Magical); + + /// + /// 永恒射线 + /// + public static IBLUAction PerpetualRay { get; } = new BLUAction(ActionID.PerpetualRay, BLUActionType.Magical); + + + /// + /// 逆流 + /// + public static IBLUAction Reflux { get; } = new BLUAction(ActionID.Reflux, BLUActionType.Magical); + + /// + /// 捕食 + /// + public static IBLUAction Devour { get; } = new BLUAction(ActionID.Devour, BLUActionType.Magical, isTimeline: true); + + /// + /// 斗灵弹 + /// + public static IBLUAction TheRoseofDestruction { get; } = new BLUAction(ActionID.TheRoseofDestruction, BLUActionType.Magical); + + /// + /// 马特拉魔术 + /// + public static IBLUAction MatraMagic { get; } = new BLUAction(ActionID.MatraMagic, BLUActionType.Magical); + + /// + /// 冰雾 + /// + public static IBLUAction WhiteDeath { get; } = new BLUAction(ActionID.WhiteDeath, BLUActionType.Magical) + { + ActionCheck = b => Player.HasStatus(true, StatusID.TouchofFrost) + }; + #endregion + + #region 魔法群体 + + /// + /// 火炎放射 + /// + public static IBLUAction FlameThrower { get; } = new BLUAction(ActionID.FlameThrower, BLUActionType.Magical); + + /// + /// 水流吐息 + /// + public static IBLUAction AquaBreath { get; } = new BLUAction(ActionID.AquaBreath, BLUActionType.Magical, isEot: true); + + /// + /// 高压电流 + /// + public static IBLUAction HighVoltage { get; } = new BLUAction(ActionID.HighVoltage, BLUActionType.Magical); + + /// + /// 怒视 + /// + public static IBLUAction Glower { get; } = new BLUAction(ActionID.Glower, BLUActionType.Magical); + + /// + /// 平原震裂 + /// + public static IBLUAction Plaincracker { get; } = new BLUAction(ActionID.Plaincracker, BLUActionType.Magical); + + /// + /// 诡异视线 + /// + public static IBLUAction TheLook { get; } = new BLUAction(ActionID.TheLook, BLUActionType.Magical); + + /// + /// 寒冰咆哮 + /// + public static IBLUAction TheRamVoice { get; } = new BLUAction(ActionID.TheRamVoice, BLUActionType.Magical); + + /// + /// 雷电咆哮 + /// + public static IBLUAction TheDragonVoice { get; } = new BLUAction(ActionID.TheDragonVoice, BLUActionType.Magical); + + /// + /// 喷墨 + /// + public static IBLUAction InkJet { get; } = new BLUAction(ActionID.InkJet, BLUActionType.Magical); + + /// + /// 火投枪 + /// + public static IBLUAction FireAngon { get; } = new BLUAction(ActionID.FireAngon, BLUActionType.Magical); + + /// + /// 精神冲击 + /// + public static IBLUAction MindBlast { get; } = new BLUAction(ActionID.MindBlast, BLUActionType.Magical); + + /// + /// 飞翎雨 + /// + public static IBLUAction FeatherRain { get; } = new BLUAction(ActionID.FeatherRain, BLUActionType.Magical); + + /// + /// 地火喷发 + /// + public static IBLUAction Eruption { get; } = new BLUAction(ActionID.Eruption, BLUActionType.Magical); + + /// + /// 山崩 + /// + public static IBLUAction MountainBuster { get; } = new BLUAction(ActionID.MountainBusterBLU, BLUActionType.Magical); + + /// + /// 轰雷 + /// + public static IBLUAction ShockStrike { get; } = new BLUAction(ActionID.ShockStrike, BLUActionType.Magical); + + /// + /// 冰雪乱舞 + /// + public static IBLUAction GlassDance { get; } = new BLUAction(ActionID.GlassDance, BLUActionType.Magical); + + /// + /// 高山气流 + /// + public static IBLUAction AlpineDraft { get; } = new BLUAction(ActionID.AlpineDraft, BLUActionType.Magical); + + /// + /// 万变水波 + /// + public static IBLUAction ProteanWave { get; } = new BLUAction(ActionID.ProteanWave, BLUActionType.Magical); + + /// + /// 狂风暴雪 + /// + public static IBLUAction Northerlies { get; } = new BLUAction(ActionID.Northerlies, BLUActionType.Magical); + + /// + /// 生物电 + /// + public static IBLUAction Electrogenesis { get; } = new BLUAction(ActionID.Electrogenesis, BLUActionType.Magical); + + /// + /// 魔法锤 + /// + public static IBLUAction MagicHammer { get; } = new BLUAction(ActionID.MagicHammer, BLUActionType.Magical, isTimeline: true); + + /// + /// 白骑士之旅 + /// + public static IBLUAction WhiteKnightsTour { get; } = new BLUAction(ActionID.WhiteKnightsTour, BLUActionType.Magical); + + /// + /// 黑骑士之旅 + /// + public static IBLUAction BlackKnightsTour { get; } = new BLUAction(ActionID.BlackKnightsTour, BLUActionType.Magical); + + /// + /// 穿甲散弹 + /// + public static IBLUAction Surpanakha { get; } = new BLUAction(ActionID.Surpanakha, BLUActionType.Magical); + + /// + /// 类星体 + /// + public static IBLUAction Quasar { get; } = new BLUAction(ActionID.Quasar, BLUActionType.Magical); + + /// + /// 哔哩哔哩 + /// + public static IBLUAction Tingle { get; } = new BLUAction(ActionID.Tingle, BLUActionType.Magical) + { + StatusProvide = new StatusID[] {StatusID.Tingling}, + }; + + /// + /// 掀地板之术 + /// + public static IBLUAction Tatamigaeshi { get; } = new BLUAction(ActionID.Tatamigaeshi, BLUActionType.Magical); + + /// + /// 圣光射线 + /// + public static IBLUAction SaintlyBeam { get; } = new BLUAction(ActionID.SaintlyBeam, BLUActionType.Magical); + + /// + /// 污泥泼洒 + /// + public static IBLUAction FeculentFlood { get; } = new BLUAction(ActionID.FeculentFlood, BLUActionType.Magical); + + /// + /// 冰焰 + /// + public static IBLUAction Blaze { get; } = new BLUAction(ActionID.Blaze, BLUActionType.Magical); + + /// + /// 芥末爆弹 + /// + public static IBLUAction MustardBomb { get; } = new BLUAction(ActionID.MustardBomb, BLUActionType.Magical); + + /// + /// 以太火花 + /// + public static IBLUAction AetherialSpark { get; } = new BLUAction(ActionID.AetherialSpark, BLUActionType.Magical, isEot: true); + + /// + /// 水力吸引 + /// + public static IBLUAction HydroPull { get; } = new BLUAction(ActionID.HydroPull, BLUActionType.Magical); + + /// + /// 水脉诅咒 + /// + public static IBLUAction MaledictionofWater { get; } = new BLUAction(ActionID.MaledictionofWater, BLUActionType.Magical); + + /// + /// 陆行鸟陨石 + /// + public static IBLUAction ChocoMeteor { get; } = new BLUAction(ActionID.ChocoMeteor, BLUActionType.Magical); + + /// + /// 月下彼岸花 + /// + public static IBLUAction Nightbloom { get; } = new BLUAction(ActionID.Nightbloom, BLUActionType.Magical); + + /// + /// 玄天武水壁 + /// + public static IBLUAction DivineCataract { get; } = new BLUAction(ActionID.DivineCataract, BLUActionType.Magical) + { + ActionCheck = b => Player.HasStatus(true, StatusID.AuspiciousTrance) + }; + + /// + /// 鬼宿脚(需要buff版本) + /// + public static IBLUAction PhantomFlurry2 { get; } = new BLUAction(ActionID.PhantomFlurry2, BLUActionType.Magical) + { + ActionCheck = b => Player.HasStatus(true, StatusID.PhantomFlurry) + }; + #endregion + + #region 物理单体 + + /// + /// 终极针 + /// + public static IBLUAction FinalSting { get; } = new BLUAction(ActionID.FinalSting, BLUActionType.Physical) + { + ActionCheck = b => !Player.HasStatus(true, StatusID.BrushwithDeath), + }; + + /// + /// 锋利菜刀 + /// + public static IBLUAction SharpenedKnife { get; } = new BLUAction(ActionID.SharpenedKnife, BLUActionType.Physical); + + /// + /// 投掷沙丁鱼 + /// + public static IBLUAction FlyingSardine { get; } = new BLUAction(ActionID.FlyingSardine, BLUActionType.Physical); + + /// + /// 深渊贯穿 + /// + public static IBLUAction AbyssalTransfixion { get; } = new BLUAction(ActionID.AbyssalTransfixion, BLUActionType.Physical); + + /// + /// 渔叉三段 + /// + public static IBLUAction TripleTrident { get; } = new BLUAction(ActionID.TripleTrident, BLUActionType.Physical); + + /// + /// 复仇冲击 + /// + public static IBLUAction RevengeBlast { get; } = new BLUAction(ActionID.RevengeBlast, BLUActionType.Physical) + { + ActionCheck = b => b.GetHealthRatio() < 0.2f, + }; + + #endregion + + #region 物理群体 + + /// + /// 狂乱 + /// + public static IBLUAction FlyingFrenzy { get; } = new BLUAction(ActionID.FlyingFrenzy, BLUActionType.Physical); + + /// + /// 钻头炮 + /// + public static IBLUAction DrillCannons { get; } = new BLUAction(ActionID.DrillCannons, BLUActionType.Physical); + + /// + /// 4星吨 + /// + public static IBLUAction Weight4tonze { get; } = new BLUAction(ActionID.Weight4tonze, BLUActionType.Physical); + + /// + /// 千针刺 + /// + public static IBLUAction Needles1000 { get; } = new BLUAction(ActionID.Needles1000, BLUActionType.Physical); + + /// + /// 寒光 + /// + public static IBLUAction Kaltstrahl { get; } = new BLUAction(ActionID.Kaltstrahl, BLUActionType.Physical); + + /// + /// 正义飞踢 + /// + public static IBLUAction JKick { get; } = new BLUAction(ActionID.JKick, BLUActionType.Physical); + + /// + /// 生成外设 + /// + public static IBLUAction PeripheralSynthesis { get; } = new BLUAction(ActionID.PeripheralSynthesis, BLUActionType.Physical); + + /// + /// 如意大旋风 + /// + public static IBLUAction BothEnds { get; } = new BLUAction(ActionID.BothEnds, BLUActionType.Physical); + #endregion + + #region 其他单体 + /// + /// 滑舌 + /// + public static IBLUAction StickyTongue { get; } = new BLUAction(ActionID.StickyTongue, BLUActionType.None); + + /// + /// 导弹 + /// + public static IBLUAction Missile { get; } = new BLUAction(ActionID.Missile, BLUActionType.None); + + /// + /// 螺旋尾 + /// + public static IBLUAction TailScrew { get; } = new BLUAction(ActionID.TailScrew, BLUActionType.None); + + /// + /// 死亡宣告 + /// + public static IBLUAction Doom { get; } = new BLUAction(ActionID.Doom, BLUActionType.None); + + /// + /// 怪音波 + /// + public static IBLUAction EerieSoundwave { get; } = new BLUAction(ActionID.EerieSoundwave, BLUActionType.None); + + /// + /// 小侦测 + /// + public static IBLUAction CondensedLibra { get; } = new BLUAction(ActionID.CondensedLibra, BLUActionType.None); + #endregion + + #region 其他群体 + + /// + /// 5级石化 + /// + public static IBLUAction Level5Petrify { get; } = new BLUAction(ActionID.Level5Petrify, BLUActionType.None); + + /// + /// 橡果炸弹 + /// + public static IBLUAction AcornBomb { get; } = new BLUAction(ActionID.AcornBomb, BLUActionType.None); + + /// + /// 投弹 + /// + public static IBLUAction BombToss { get; } = new BLUAction(ActionID.BombToss, BLUActionType.None); + + /// + /// 自爆 + /// + public static IBLUAction Selfdestruct { get; } = new BLUAction(ActionID.Selfdestruct, BLUActionType.None) + { + ActionCheck = b => !Player.HasStatus(true, StatusID.BrushwithDeath), + }; + + /// + /// 拍掌 + /// + public static IBLUAction Faze { get; } = new BLUAction(ActionID.Faze, BLUActionType.None); + + /// + /// 鼻息 + /// + public static IBLUAction Snort { get; } = new BLUAction(ActionID.Snort, BLUActionType.None); + + /// + /// 臭气 + /// + public static IBLUAction BadBreath { get; } = new BLUAction(ActionID.BadBreath, BLUActionType.None, isTimeline: true); + + /// + /// 唧唧咋咋 + /// + public static IBLUAction Chirp { get; } = new BLUAction(ActionID.Chirp, BLUActionType.None); + + /// + /// 蛙腿 + /// + public static IBLUAction FrogLegs { get; } = new BLUAction(ActionID.FrogLegs, BLUActionType.None); + + /// + /// 5级即死 + /// + public static IBLUAction Level5Death { get; } = new BLUAction(ActionID.Level5Death, BLUActionType.None); + + /// + /// 火箭炮 + /// + public static IBLUAction Launcher { get; } = new BLUAction(ActionID.Launcher, BLUActionType.None); + + /// + /// 超振动 + /// + public static IBLUAction Ultravibration { get; } = new BLUAction(ActionID.Ultravibration, BLUActionType.None); + + /// + /// 鬼宿脚 + /// + public static IBLUAction PhantomFlurry { get; } = new BLUAction(ActionID.PhantomFlurry, BLUActionType.None); + #endregion + + #region 防御 + + /// + /// 冰棘屏障 + /// + public static IBLUAction IceSpikes { get; } = new BLUAction(ActionID.IceSpikes, BLUActionType.None, true); + + /// + /// 水神的面纱 + /// + public static IBLUAction VeiloftheWhorl { get; } = new BLUAction(ActionID.VeiloftheWhorl, BLUActionType.None, true); + + /// + /// 超硬化 + /// + public static IBLUAction Diamondback { get; } = new BLUAction(ActionID.Diamondback, BLUActionType.None, true, isTimeline: true) + { + StatusProvide = Rampart.StatusProvide, + ActionCheck = BaseAction.TankDefenseSelf, + }; + + /// + /// 哥布防御 + /// + public static IBLUAction Gobskin { get; } = new BLUAction(ActionID.Gobskin, BLUActionType.None, true, isTimeline: true) + { + StatusProvide = Rampart.StatusProvide, + ActionCheck = BaseAction.TankDefenseSelf, + }; + + /// + /// 仙人盾 + /// + public static IBLUAction Cactguard { get; } = new BLUAction(ActionID.Cactguard, BLUActionType.None, true, isTimeline: true) + { + StatusProvide = Rampart.StatusProvide, + ActionCheck = BaseAction.TankDefenseSelf, + }; + + /// + /// 玄结界 + /// + public static IBLUAction ChelonianGate { get; } = new BLUAction(ActionID.ChelonianGate, BLUActionType.None, true, isTimeline: true) + { + StatusProvide = Rampart.StatusProvide, + ActionCheck = BaseAction.TankDefenseSelf, + }; + + /// + /// 龙之力 + /// + public static IBLUAction DragonForce { get; } = new BLUAction(ActionID.DragonForce, BLUActionType.None, true, isTimeline: true) + { + StatusProvide = Rampart.StatusProvide, + ActionCheck = BaseAction.TankDefenseSelf, + }; + #endregion + + + #region 辅助 + /// + /// 油性分泌物 + /// + public static IBLUAction ToadOil { get; } = new BLUAction(ActionID.ToadOil, BLUActionType.None, true); + + /// + /// 怒发冲冠 + /// + public static IBLUAction Bristle { get; } = new BLUAction(ActionID.Bristle, BLUActionType.Magical, true) + { + StatusProvide = new StatusID[] { StatusID.Boost, StatusID.Harmonized }, + }; + + /// + /// 破防 + /// + public static IBLUAction Offguard { get; } = new BLUAction(ActionID.Offguard, BLUActionType.None, true); + + /// + /// 强力守护 + /// + public static IBLUAction MightyGuard { get; } = new BLUAction(ActionID.MightyGuard, BLUActionType.None, true) + { + StatusProvide = new StatusID[] + { + StatusID.MightyGuard, + }, + }; + + /// + /// 月之笛 + /// + public static IBLUAction MoonFlute { get; } = new BLUAction(ActionID.MoonFlute, BLUActionType.None, true) + { + StatusProvide = new StatusID[] { StatusID.WaxingNocturne }, + }; + + /// + /// 惊奇光 + /// + public static IBLUAction PeculiarLight { get; } = new BLUAction(ActionID.PeculiarLight, BLUActionType.Magical); + + /// + /// 防御指示 + /// + public static IBLUAction Avail { get; } = new BLUAction(ActionID.Avail, BLUActionType.Magical); + + /// + /// 口笛 + /// + public static IBLUAction Whistle { get; } = new BLUAction(ActionID.Whistle, BLUActionType.Physical, true) + { + StatusProvide = new StatusID[] { StatusID.Boost, StatusID.Harmonized }, + }; + + + /// + /// 彻骨雾寒 + /// + public static IBLUAction ColdFog { get; } = new BLUAction(ActionID.ColdFog, BLUActionType.Magical, true); + #endregion + + #region 治疗 + /// + /// 白风 + /// + public static IBLUAction WhiteWind { get; } = new BLUAction(ActionID.WhiteWind, BLUActionType.None, true) + { + ActionCheck = b => Player.GetHealthRatio() is > 0.3f and < 0.5f, + }; + + /// + /// 赞歌 + /// + public static IBLUAction Stotram { get; } = new BLUAction(ActionID.Stotram, BLUActionType.Magical, true); + + + /// + /// 绒绒治疗 + /// + public static IBLUAction PomCure { get; } = new BLUAction(ActionID.PomCure, BLUActionType.None, true); + + /// + /// 天使低语 + /// + public static IBLUAction AngelWhisper { get; } = new BLUAction(ActionID.AngelWhisper, BLUActionType.None, true); + + /// + /// 蜕皮 + /// + public static IBLUAction Exuviation { get; } = new BLUAction(ActionID.Exuviation, BLUActionType.None, true); + + /// + /// 天使的点心 + /// + public static IBLUAction AngelsSnack { get; } = new BLUAction(ActionID.AngelsSnack, BLUActionType.None, true); + #endregion + + #region 其他 + /// + /// 若隐若现 + /// + private static IBLUAction Loom { get; } = new BLUAction(ActionID.Loom, BLUActionType.None, shouldEndSpecial: true); + + /// + /// 斗争本能 + /// + public static IBLUAction BasicInstinct { get; } = new BLUAction(ActionID.BasicInstinct, BLUActionType.None) + { + StatusProvide = new StatusID[] { StatusID.BasicInstinct }, + ActionCheck = b => + { + //TODO: 还需要判断是否为多人本 + return Service.Conditions[Dalamud.Game.ClientState.Conditions.ConditionFlag.BoundByDuty56] + && DataCenter.PartyMembers.Count(p => p.GetHealthRatio() > 0) == 1; + }, + }; + + /// + /// 以太复制 + /// + private static IBLUAction AethericMimicry { get; } = new BLUAction(ActionID.AethericMimicry, BLUActionType.None, true) + { + ChoiceTarget = (charas, mustUse) => + { + switch (BlueId) + { + case BLUID.DPS: + if (!Player.HasStatus(true, StatusID.AethericMimicryDPS)) + { + return charas.GetJobCategory(JobRole.Melee, JobRole.RangedMagical, JobRole.RangedPhysical).FirstOrDefault(); + } + break; + + case BLUID.Tank: + if (!Player.HasStatus(true, StatusID.AethericMimicryTank)) + { + return charas.GetJobCategory(JobRole.Tank).FirstOrDefault(); + } + break; + + case BLUID.Healer: + if (!Player.HasStatus(true, StatusID.AethericMimicryHealer)) + { + return charas.GetJobCategory(JobRole.Healer).FirstOrDefault(); + } + break; + } + return null; + }, + }; + + #endregion + + protected override bool MoveForwardGCD(out IAction act) + { + if (Loom.CanUse(out act)) return true; + return base.MoveForwardGCD(out act); + } + + + protected override bool EmergencyGCD(out IAction act) + { + if (AethericMimicry.CanUse(out act)) return true; + if (BlueId == BLUID.Healer) + { + //有某些非常危险的状态。 + if (DataCenter.SpecialType == SpecialCommandType.EsunaStanceNorth && DataCenter.WeakenPeople.Any() || DataCenter.DyingPeople.Any()) + { + if (Exuviation.CanUse(out act, mustUse: true)) return true; + } + } + if (BasicInstinct.CanUse(out _)) + { + if (MightyGuard.CanUse(out act)) return true; + act = BasicInstinct; + return true; + } + + return base.EmergencyGCD(out act); + } + + protected static bool AllOnSlot(params IBLUAction[] actions) => actions.All(a => a.OnSlot); + protected static uint OnSlotCount(params IBLUAction[] actions) => (uint)actions.Count(a => a.OnSlot); + + public override IBaseAction[] AllBaseActions => base.AllBaseActions.Where(a => + { + if (a is not BLUAction b) return false; + return b.OnSlot; + }).ToArray(); + + protected override IRotationConfigSet CreateConfiguration() + { + return base.CreateConfiguration() + .SetCombo("BlueId", 2, "Role", "Tank", "Healer", "DPS") + .SetCombo("AttackType", 2, "Type", "Magic", "Physic", "Both"); + } + + protected override void UpdateInfo() + { + BlueId = (BLUID)Configs.GetCombo("BlueId"); + AttackType = (BLUAttackType)Configs.GetCombo("AttackType"); + base.UpdateInfo(); + } + + protected override bool HealSingleGCD(out IAction act) + { + if (BlueId == BLUID.Healer) + { + if (PomCure.CanUse(out act)) return true; + } + if (WhiteWind.CanUse(out act, mustUse: true)) return true; + return base.HealSingleGCD(out act); + } + + protected override bool HealAreaGCD(out IAction act) + { + if (BlueId == BLUID.Healer) + { + if (AngelsSnack.CanUse(out act)) return true; + if (Stotram.CanUse(out act)) return true; + } + + if (WhiteWind.CanUse(out act, mustUse: true)) return true; + return base.HealAreaGCD(out act); + } +} diff --git a/RotationSolver.Basic/Rotations/Basic/BRD_Base.cs b/RotationSolver.Basic/Rotations/Basic/BRD_Base.cs new file mode 100644 index 000000000..1d32710a6 --- /dev/null +++ b/RotationSolver.Basic/Rotations/Basic/BRD_Base.cs @@ -0,0 +1,236 @@ +using Dalamud.Game.ClientState.JobGauge.Enums; +using Dalamud.Game.ClientState.JobGauge.Types; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Commands; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.CustomRotation; + +namespace RotationSolver.Rotations.Basic; + +public abstract class BRD_Base : CustomRotation.CustomRotation +{ + private static BRDGauge JobGauge => Service.JobGauges.Get(); + + public override MedicineType MedicineType => MedicineType.Dexterity; + + /// + /// ʫ������ + /// + protected static byte Repertoire => JobGauge.Repertoire; + + /// + /// ��ǰ���ڳ��ĸ� + /// + protected static Song Song => JobGauge.Song; + + /// + /// ��һ�׳��ĸ� + /// + protected static Song LastSong => JobGauge.LastSong; + + /// + /// ���֮�� + /// + protected static byte SoulVoice => JobGauge.SoulVoice; + + /// + /// ���׸谡�ڶ�ú��ڳ���(�Ƿ��Ѿ�����) + /// + /// + /// + protected static bool SongEndAfter(float time) => EndAfter(SongTime, time); + + /// + /// ���׸谡�ڶ�ú��ڳ��� + /// + /// + /// + /// + protected static bool SongEndAfterGCD(uint gctCount = 0, uint abilityCount = 0) => EndAfterGCD(SongTime, gctCount, abilityCount); + + private static float SongTime => JobGauge.SongTimer / 1000f; + + public sealed override ClassJobID[] JobIDs => new[] { ClassJobID.Bard, ClassJobID.Archer }; + + /// + /// ǿ����� + /// + public static IBaseAction HeavyShoot { get; } = new BaseAction(ActionID.HeavyShoot) { StatusProvide = new[] { StatusID.StraightShotReady } }; + + /// + /// ֱ����� + /// + public static IBaseAction StraitShoot { get; } = new BaseAction(ActionID.StraitShoot) { StatusNeed = new[] { StatusID.StraightShotReady } }; + + /// + /// ��ҧ�� + /// + public static IBaseAction VenomousBite { get; } = new BaseAction(ActionID.VenomousBite, isEot: true) + { + TargetStatus = new[] { StatusID.VenomousBite, StatusID.CausticBite } + }; + + /// + /// ��ʴ�� + /// + public static IBaseAction Windbite { get; } = new BaseAction(ActionID.Windbite, isEot: true) + { + TargetStatus = new[] { StatusID.Windbite, StatusID.Stormbite } + }; + + /// + /// �������� + /// + public static IBaseAction IronJaws { get; } = new BaseAction(ActionID.IronJaws, isEot: true) + { + TargetStatus = VenomousBite.TargetStatus.Union(Windbite.TargetStatus).ToArray(), + ActionCheck = b => b.HasStatus(true, VenomousBite.TargetStatus) & b.HasStatus(true, Windbite.TargetStatus), + }; + + /// + /// �������С������ + /// + public static IBaseAction WanderersMinuet { get; } = new BaseAction(ActionID.WanderersMinuet); + + /// + /// ���ߵ�����ҥ + /// + public static IBaseAction MagesBallad { get; } = new BaseAction(ActionID.MagesBallad); + + /// + /// ����������� + /// + public static IBaseAction ArmysPaeon { get; } = new BaseAction(ActionID.ArmysPaeon); + + /// + /// ս��֮�� + /// + public static IBaseAction BattleVoice { get; } = new BaseAction(ActionID.BattleVoice, true); + + /// + /// ����ǿ�� + /// + public static IBaseAction RagingStrikes { get; } = new BaseAction(ActionID.RagingStrikes, true); + + /// + /// ��������������� + /// + public static IBaseAction RadiantFinale { get; } = new BaseAction(ActionID.RadiantFinale, true) + { + ActionCheck = b => JobGauge.Coda.Any(s => s != Song.NONE), + }; + + /// + /// ���Ҽ� + /// + public static IBaseAction Barrage { get; } = new BaseAction(ActionID.Barrage); + + /// + /// �������� + /// + public static IBaseAction EmpyrealArrow { get; } = new BaseAction(ActionID.EmpyrealArrow); + + /// + /// �������� + /// + public static IBaseAction PitchPerfect { get; } = new BaseAction(ActionID.PitchPerfect) + { + ActionCheck = b => JobGauge.Song == Song.WANDERER, + }; + + /// + /// ʧѪ�� + /// + public static IBaseAction Bloodletter { get; } = new BaseAction(ActionID.Bloodletter); + + /// + /// �������� + /// + public static IBaseAction RainofDeath { get; } = new BaseAction(ActionID.RainofDeath); + + /// + /// ����� + /// + public static IBaseAction QuickNock { get; } = new BaseAction(ActionID.QuickNock) + { + StatusProvide = new[] { StatusID.ShadowbiteReady } + }; + + /// + /// Ӱ�ɼ� + /// + public static IBaseAction Shadowbite { get; } = new BaseAction(ActionID.Shadowbite) + { + StatusNeed = new[] { StatusID.ShadowbiteReady } + }; + + /// + /// ����������޿��� + /// + public static IBaseAction WardensPaean { get; } = new BaseAction(ActionID.WardensPaean, true, isTimeline: true); + + /// + /// �������������� + /// + public static IBaseAction NaturesMinne { get; } = new BaseAction(ActionID.NaturesMinne, true, isTimeline: true); + + /// + /// ����յ��� + /// + public static IBaseAction Sidewinder { get; } = new BaseAction(ActionID.Sidewinder); + + /// + /// ����� + /// + public static IBaseAction ApexArrow { get; } = new BaseAction(ActionID.ApexArrow) + { + ActionCheck = b => JobGauge.SoulVoice >= 20 && !Player.HasStatus(true, StatusID.BlastArrowReady), + }; + + /// + /// ���Ƽ� + /// + public static IBaseAction BlastArrow { get; } = new BaseAction(ActionID.BlastArrow) + { + ActionCheck = b => Player.HasStatus(true, StatusID.BlastArrowReady), + }; + + /// + /// ���� + /// + public static IBaseAction Troubadour { get; } = new BaseAction(ActionID.Troubadour, true, isTimeline: true) + { + ActionCheck = b => !Player.HasStatus(false, StatusID.Troubadour, + StatusID.Tactician1, + StatusID.Tactician2, + StatusID.ShieldSamba), + }; + + protected override bool EmergencyAbility(byte abilityRemain, IAction nextGCD, out IAction act) + { + //��ijЩ�dz�Σ�յ�״̬�� + if (DataCenter.SpecialType == SpecialCommandType.EsunaStanceNorth && DataCenter.WeakenPeople.Any() || DataCenter.DyingPeople.Any()) + { + if (WardensPaean.CanUse(out act, mustUse: true)) return true; + } + return base.EmergencyAbility(abilityRemain, nextGCD, out act); + } + + [RotationDesc(ActionID.Troubadour)] + protected sealed override bool DefenseAreaAbility(byte abilitiesRemaining, out IAction act) + { + if (Troubadour.CanUse(out act)) return true; + return false; + } + + [RotationDesc(ActionID.NaturesMinne)] + protected sealed override bool HealSingleAbility(byte abilitiesRemaining, out IAction act) + { + if (NaturesMinne.CanUse(out act)) return true; + return false; + } +} diff --git a/RotationSolver.Basic/Rotations/Basic/DNC_Base.cs b/RotationSolver.Basic/Rotations/Basic/DNC_Base.cs new file mode 100644 index 000000000..2520a9be8 --- /dev/null +++ b/RotationSolver.Basic/Rotations/Basic/DNC_Base.cs @@ -0,0 +1,338 @@ +using Dalamud.Game.ClientState.JobGauge.Types; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.CustomRotation; +using System.Linq; + +namespace RotationSolver.Rotations.Basic; +public abstract class DNC_Base : CustomRotation.CustomRotation +{ + private static DNCGauge JobGauge => Service.JobGauges.Get(); + + public override MedicineType MedicineType => MedicineType.Dexterity; + + /// + /// �������� + /// + protected static bool IsDancing => JobGauge.IsDancing; + + /// + /// ���� + /// + protected static byte Esprit => JobGauge.Esprit; + + /// + /// ������ + /// + protected static byte Feathers => JobGauge.Feathers; + + protected static byte CompletedSteps => JobGauge.CompletedSteps; + + public sealed override ClassJobID[] JobIDs => new ClassJobID[] { ClassJobID.Dancer }; + + /// + /// ��к + /// + public static IBaseAction Cascade { get; } = new BaseAction(ActionID.Cascade) + { + StatusProvide = new[] { StatusID.SilkenSymmetry } + }; + + /// + /// ��Ȫ + /// + public static IBaseAction Fountain { get; } = new BaseAction(ActionID.Fountain) + { + StatusProvide = new[] { StatusID.SilkenFlow } + }; + + /// + /// ����к + /// + public static IBaseAction ReverseCascade { get; } = new BaseAction(ActionID.ReverseCascade) + { + StatusNeed = new[] { StatusID.SilkenSymmetry, StatusID.SilkenSymmetry2 }, + }; + + /// + /// ׹��Ȫ + /// + public static IBaseAction Fountainfall { get; } = new BaseAction(ActionID.Fountainfall) + { + StatusNeed = new[] { StatusID.SilkenFlow, StatusID.SilkenFlow2 } + }; + + /// + /// ���衤�� + /// + public static IBaseAction FanDance { get; } = new BaseAction(ActionID.FanDance) + { + ActionCheck = b => JobGauge.Feathers > 0, + StatusProvide = new[] { StatusID.ThreefoldFanDance }, + }; + + /// + /// �糵 + /// + public static IBaseAction Windmill { get; } = new BaseAction(ActionID.Windmill) + { + StatusProvide = Cascade.StatusProvide, + }; + + /// + /// ������ + /// + public static IBaseAction Bladeshower { get; } = new BaseAction(ActionID.Bladeshower) + { + StatusProvide = Fountain.StatusProvide, + }; + + /// + /// ���糵 + /// + public static IBaseAction RisingWindmill { get; } = new BaseAction(ActionID.RisingWindmill) + { + StatusNeed = ReverseCascade.StatusNeed, + }; + + /// + /// ��Ѫ�� + /// + public static IBaseAction Bloodshower { get; } = new BaseAction(ActionID.Bloodshower) + { + AOECount = 2, + StatusNeed = Fountainfall.StatusNeed, + }; + + /// + /// ���衤�� + /// + public static IBaseAction FanDance2 { get; } = new BaseAction(ActionID.FanDance2) + { + ActionCheck = b => Feathers > 0, + AOECount = 2, + StatusProvide = new[] { StatusID.ThreefoldFanDance }, + }; + + /// + /// ���衤�� + /// + public static IBaseAction FanDance3 { get; } = new BaseAction(ActionID.FanDance3) + { + StatusNeed = FanDance2.StatusProvide, + }; + + /// + /// ���衤�� + /// + public static IBaseAction FanDance4 { get; } = new BaseAction(ActionID.FanDance4) + { + StatusNeed = new[] { StatusID.FourfoldFanDance }, + }; + + /// + /// ���� + /// + public static IBaseAction SaberDance { get; } = new BaseAction(ActionID.SaberDance) + { + ActionCheck = b => Esprit >= 50, + }; + + /// + /// ������ + /// + public static IBaseAction StarfallDance { get; } = new BaseAction(ActionID.StarfallDance) + { + StatusNeed = new[] { StatusID.FlourishingStarfall }, + }; + + /// + /// ǰ�岽 + /// + public static IBaseAction EnAvant { get; } = new BaseAction(ActionID.EnAvant, true, shouldEndSpecial: true); + + /// + /// Ǿޱ���Ų� + /// + private static IBaseAction Emboite { get; } = new BaseAction(ActionID.Emboite, true) + { + ActionCheck = b => (ActionID)JobGauge.NextStep == ActionID.Emboite, + }; + + /// + /// С�񽻵��� + /// + private static IBaseAction Entrechat { get; } = new BaseAction(ActionID.Entrechat, true) + { + ActionCheck = b => (ActionID)JobGauge.NextStep == ActionID.Entrechat, + }; + + /// + /// ��ҶС���� + /// + private static IBaseAction Jete { get; } = new BaseAction(ActionID.Jete, true) + { + ActionCheck = b => (ActionID)JobGauge.NextStep == ActionID.Jete, + }; + + /// + /// ���ֺ��ת + /// + private static IBaseAction Pirouette { get; } = new BaseAction(ActionID.Pirouette, true) + { + ActionCheck = b => (ActionID)JobGauge.NextStep == ActionID.Pirouette, + }; + + /// + /// ��׼�貽 + /// + public static IBaseAction StandardStep { get; } = new BaseAction(ActionID.StandardStep) + { + StatusProvide = new[] + { + StatusID.StandardStep, + StatusID.TechnicalStep, + }, + }; + + /// + /// �����貽 + /// + public static IBaseAction TechnicalStep { get; } = new BaseAction(ActionID.TechnicalStep) + { + StatusNeed = new[] + { + StatusID.StandardFinish, + }, + StatusProvide = StandardStep.StatusProvide, + }; + + /// + /// ��׼�貽���� + /// + protected static IBaseAction StandardFinish { get; } = new BaseAction(ActionID.StandardFinish) + { + StatusNeed = new[] { StatusID.StandardStep }, + ActionCheck = b => IsDancing && JobGauge.CompletedSteps == 2, + }; + + /// + /// �����貽���� + /// + protected static IBaseAction TechnicalFinish { get; } = new BaseAction(ActionID.TechnicalFinish) + { + StatusNeed = new[] { StatusID.TechnicalStep }, + ActionCheck = b => IsDancing && JobGauge.CompletedSteps == 4, + }; + + /// + /// ����֮ɣ�� + /// + public static IBaseAction ShieldSamba { get; } = new BaseAction(ActionID.ShieldSamba, true, isTimeline: true) + { + ActionCheck = b => !Player.HasStatus(false, StatusID.Troubadour, + StatusID.Tactician1, + StatusID.Tactician2, + StatusID.ShieldSamba), + }; + + /// + /// ����֮������ + /// + public static IBaseAction CuringWaltz { get; } = new BaseAction(ActionID.CuringWaltz, true, isTimeline: true); + + /// + /// ��ʽ���� + /// + public static IBaseAction ClosedPosition { get; } = new BaseAction(ActionID.ClosedPosition, true) + { + ChoiceTarget = (Targets, mustUse) => + { + Targets = Targets.Where(b => b.ObjectId != Player.ObjectId && b.CurrentHp != 0 && + //Remove Weak + !b.HasStatus(false, StatusID.Weakness, StatusID.BrinkofDeath) + //Remove other partner. + && !b.HasStatus(false, StatusID.ClosedPosition2) | b.HasStatus(true, StatusID.ClosedPosition2) + ); + + return Targets.GetJobCategory(JobRole.Melee, JobRole.RangedMagical, JobRole.RangedPhysical).FirstOrDefault(); + }, + }; + + /// + /// ����֮̽�� + /// + public static IBaseAction Devilment { get; } = new BaseAction(ActionID.Devilment, true); + + /// + /// �ٻ����� + /// + public static IBaseAction Flourish { get; } = new BaseAction(ActionID.Flourish, true) + { + StatusNeed = new[] { StatusID.StandardFinish }, + StatusProvide = new[] + { + StatusID.ThreefoldFanDance, + StatusID.FourfoldFanDance, + }, + ActionCheck = b => InCombat, + }; + + /// + /// ���˱��� + /// + public static IBaseAction Improvisation { get; } = new BaseAction(ActionID.Improvisation, true); + + /// + /// ������ + /// + public static IBaseAction Tillana { get; } = new BaseAction(ActionID.Tillana) + { + StatusNeed = new[] { StatusID.FlourishingFinish }, + }; + + /// + /// ִ���貽 + /// + /// + /// + protected static bool ExcutionStepGCD(out IAction act) + { + act = null; + if (!Player.HasStatus(true, StatusID.StandardStep, StatusID.TechnicalStep)) return false; + if (Player.HasStatus(true, StatusID.StandardStep) && CompletedSteps == 2) return false; + if (Player.HasStatus(true, StatusID.TechnicalStep) && CompletedSteps == 4) return false; + + if (Emboite.CanUse(out act)) return true; + if (Entrechat.CanUse(out act)) return true; + if (Jete.CanUse(out act)) return true; + if (Pirouette.CanUse(out act)) return true; + return false; + } + + [RotationDesc(ActionID.EnAvant)] + protected sealed override bool MoveForwardAbility(byte abilitiesRemaining, out IAction act, bool recordTarget = true) + { + if (EnAvant.CanUse(out act, emptyOrSkipCombo: true, recordTarget: recordTarget)) return true; + return false; + } + + [RotationDesc(ActionID.CuringWaltz, ActionID.Improvisation)] + protected sealed override bool HealAreaAbility(byte abilitiesRemaining, out IAction act) + { + if (CuringWaltz.CanUse(out act, emptyOrSkipCombo: true)) return true; + if (Improvisation.CanUse(out act, emptyOrSkipCombo: true)) return true; + return false; + } + + [RotationDesc(ActionID.ShieldSamba)] + protected sealed override bool DefenseAreaAbility(byte abilitiesRemaining, out IAction act) + { + if (ShieldSamba.CanUse(out act, emptyOrSkipCombo: true)) return true; + return false; + } +} diff --git a/RotationSolver.Basic/Rotations/Basic/DRG_Base.cs b/RotationSolver.Basic/Rotations/Basic/DRG_Base.cs new file mode 100644 index 000000000..e0e1f532a --- /dev/null +++ b/RotationSolver.Basic/Rotations/Basic/DRG_Base.cs @@ -0,0 +1,203 @@ +using Dalamud.Game.ClientState.JobGauge.Types; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.CustomRotation; +using System.Linq; + +namespace RotationSolver.Rotations.Basic; + +public abstract class DRG_Base : CustomRotation.CustomRotation +{ + private static DRGGauge JobGauge => Service.JobGauges.Get(); + + public override MedicineType MedicineType => MedicineType.Strength; + + public sealed override ClassJobID[] JobIDs => new ClassJobID[] { ClassJobID.Dragoon, ClassJobID.Lancer }; + + /// + /// ��׼�� + /// + public static IBaseAction TrueThrust { get; } = new BaseAction(ActionID.TrueThrust); + + /// + /// ��ͨ�� + /// + public static IBaseAction VorpalThrust { get; } = new BaseAction(ActionID.VorpalThrust) + { + ComboIds = new[] { ActionID.RaidenThrust } + }; + + /// + /// ֱ�� + /// + public static IBaseAction FullThrust { get; } = new BaseAction(ActionID.FullThrust); + + /// + /// ����ǹ + /// + public static IBaseAction Disembowel { get; } = new BaseAction(ActionID.Disembowel) + { + ComboIds = new[] { ActionID.RaidenThrust } + }; + + /// + /// ӣ��ŭ�� + /// + public static IBaseAction ChaosThrust { get; } = new BaseAction(ActionID.ChaosThrust); + + /// + /// ������צ + /// + public static IBaseAction FangandClaw { get; } = new BaseAction(ActionID.FangandClaw) + { + StatusNeed = new StatusID[] { StatusID.SharperFangandClaw }, + }; + + /// + /// ��β����� + /// + public static IBaseAction WheelingThrust { get; } = new BaseAction(ActionID.WheelingThrust) + { + StatusNeed = new StatusID[] { StatusID.EnhancedWheelingThrust }, + }; + + /// + /// �ᴩ�� + /// + public static IBaseAction PiercingTalon { get; } = new BaseAction(ActionID.PiercingTalon) + { + FilterForHostiles = TargetFilter.MeleeRangeTargetFilter, + }; + + /// + /// ����ǹ + /// + public static IBaseAction DoomSpike { get; } = new BaseAction(ActionID.DoomSpike); + + /// + /// ���ٴ� + /// + public static IBaseAction SonicThrust { get; } = new BaseAction(ActionID.SonicThrust) + { + ComboIds = new[] { ActionID.DraconianFury } + }; + + /// + /// ɽ������ + /// + public static IBaseAction CoerthanTorment { get; } = new BaseAction(ActionID.CoerthanTorment); + + /// + /// ����� + /// + public static IBaseAction SpineshatterDive { get; } = new BaseAction(ActionID.SpineshatterDive); + + /// + /// ���׳� + /// + public static IBaseAction DragonfireDive { get; } = new BaseAction(ActionID.DragonfireDive); + + /// + /// ��Ծ + /// + public static IBaseAction Jump { get; } = new BaseAction(ActionID.Jump) + { + StatusProvide = new StatusID[] { StatusID.DiveReady }, + }; + + /// + /// ���� + /// + public static IBaseAction HighJump { get; } = new BaseAction(ActionID.HighJump) + { + StatusProvide = Jump.StatusProvide, + }; + + /// + /// ����� + /// + public static IBaseAction MirageDive { get; } = new BaseAction(ActionID.MirageDive) + { + StatusNeed = Jump.StatusProvide, + }; + + /// + /// ����ǹ + /// + public static IBaseAction Geirskogul { get; } = new BaseAction(ActionID.Geirskogul); + + /// + /// ����֮�� + /// + public static IBaseAction Nastrond { get; } = new BaseAction(ActionID.Nastrond) + { + ActionCheck = b => JobGauge.IsLOTDActive, + }; + + /// + /// ׹�dz� + /// + public static IBaseAction Stardiver { get; } = new BaseAction(ActionID.Stardiver) + { + ActionCheck = b => JobGauge.IsLOTDActive, + }; + + /// + /// �����㾦 + /// + public static IBaseAction WyrmwindThrust { get; } = new BaseAction(ActionID.WyrmwindThrust) + { + ActionCheck = b => JobGauge.FirstmindsFocusCount == 2, + }; + + /// + /// ���� + /// + public static IBaseAction LifeSurge { get; } = new BaseAction(ActionID.LifeSurge, true) + { + StatusProvide = new[] { StatusID.LifeSurge }, + + ActionCheck = b => !IsLastAbility(true, LifeSurge), + }; + + /// + /// ��ǹ + /// + public static IBaseAction LanceCharge { get; } = new BaseAction(ActionID.LanceCharge, true); + + /// + /// �������� + /// + public static IBaseAction DragonSight { get; } = new BaseAction(ActionID.DragonSight, true) + { + ChoiceTarget = (Targets, mustUse) => + { + Targets = Targets.Where(b => b.ObjectId != Player.ObjectId && + !b.HasStatus(false, StatusID.Weakness, StatusID.BrinkofDeath)).ToArray(); + + if (Targets.Count() == 0) return Player; + + return Targets.GetJobCategory(JobRole.Melee, JobRole.RangedMagical, JobRole.RangedPhysical, JobRole.Tank).FirstOrDefault(); + }, + }; + + /// + /// ս������ + /// + public static IBaseAction BattleLitany { get; } = new BaseAction(ActionID.BattleLitany, true) + { + StatusNeed = new[] { StatusID.PowerSurge }, + }; + + + [RotationDesc(ActionID.Feint)] + protected sealed override bool DefenseAreaAbility(byte abilitiesRemaining, out IAction act) + { + if (Feint.CanUse(out act)) return true; + return false; + } +} diff --git a/RotationSolver.Basic/Rotations/Basic/DRK_Base.cs b/RotationSolver.Basic/Rotations/Basic/DRK_Base.cs new file mode 100644 index 000000000..c18c31c7d --- /dev/null +++ b/RotationSolver.Basic/Rotations/Basic/DRK_Base.cs @@ -0,0 +1,232 @@ +using Dalamud.Game.ClientState.JobGauge.Types; +using Dalamud.Game.ClientState.Objects.Types; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.CustomRotation; + +namespace RotationSolver.Rotations.Basic; +public abstract class DRK_Base : CustomRotation.CustomRotation +{ + private static DRKGauge JobGauge => Service.JobGauges.Get(); + + public override MedicineType MedicineType => MedicineType.Strength; + + private static float DarksideTimeRemaining => JobGauge.DarksideTimeRemaining / 1000f; + /// + /// 暗血 + /// + protected static byte Blood => JobGauge.Blood; + + /// + /// 有黑心心 + /// + protected static bool HasDarkArts => JobGauge.HasDarkArts; + + /// + /// 这个buff还剩多久就要没了啊 + /// + /// + /// + protected static bool DarkSideEndAfter(float time) + { + return EndAfter(DarksideTimeRemaining, time); + } + + /// + /// 这个buff还剩多久就要没了啊 + /// + /// + /// + /// + protected static bool DarkSideEndAfterGCD(uint gctCount = 0, uint abilityCount = 0) + { + return EndAfterGCD(DarksideTimeRemaining, gctCount, abilityCount); + } + + public sealed override ClassJobID[] JobIDs => new ClassJobID[] { ClassJobID.DarkKnight }; + private sealed protected override IBaseAction Shield => Grit; + + /// + /// 重斩 + /// + public static IBaseAction HardSlash { get; } = new BaseAction(ActionID.HardSlash); + + /// + /// 吸收斩 + /// + public static IBaseAction SyphonStrike { get; } = new BaseAction(ActionID.SyphonStrike); + + /// + /// 释放 + /// + public static IBaseAction Unleash { get; } = new BaseAction(ActionID.Unleash); + + /// + /// 深恶痛绝 + /// + public static IBaseAction Grit { get; } = new BaseAction(ActionID.Grit, shouldEndSpecial: true); + + /// + /// 伤残 + /// + public static IBaseAction Unmend { get; } = new BaseAction(ActionID.Unmend) + { + FilterForHostiles = TargetFilter.TankRangeTarget, + }; + + /// + /// 噬魂斩 + /// + public static IBaseAction Souleater { get; } = new BaseAction(ActionID.Souleater); + + /// + /// 暗黑波动 + /// + public static IBaseAction FloodofDarkness { get; } = new BaseAction(ActionID.FloodofDarkness); + + /// + /// 暗黑锋 + /// + public static IBaseAction EdgeofDarkness { get; } = new BaseAction(ActionID.EdgeofDarkness); + + /// + /// 嗜血 + /// + public static IBaseAction BloodWeapon { get; } = new BaseAction(ActionID.BloodWeapon); + + /// + /// 暗影墙 + /// + public static IBaseAction ShadowWall { get; } = new BaseAction(ActionID.ShadowWall, true, isTimeline: true) + { + StatusProvide = Rampart.StatusProvide, + ActionCheck = BaseAction.TankDefenseSelf, + }; + + /// + /// 弃明投暗 + /// + public static IBaseAction DarkMind { get; } = new BaseAction(ActionID.DarkMind, true, isTimeline: true) + { + ActionCheck = BaseAction.TankDefenseSelf, + }; + + /// + /// 行尸走肉 + /// + public static IBaseAction LivingDead { get; } = new BaseAction(ActionID.LivingDead, true, isTimeline: true); + + /// + /// 腐秽大地 + /// + public static IBaseAction SaltedEarth { get; } = new BaseAction(ActionID.SaltedEarth); + + /// + /// 跳斩 + /// + public static IBaseAction Plunge { get; } = new BaseAction(ActionID.Plunge, shouldEndSpecial: true) + { + ChoiceTarget = TargetFilter.FindTargetForMoving + }; + + /// + /// 吸血深渊 + /// + public static IBaseAction AbyssalDrain { get; } = new BaseAction(ActionID.AbyssalDrain); + + /// + /// 精雕怒斩 + /// + public static IBaseAction CarveandSpit { get; } = new BaseAction(ActionID.CarveandSpit); + + /// + /// 血溅 + /// + public static IBaseAction Bloodspiller { get; } = new BaseAction(ActionID.Bloodspiller) + { + ActionCheck = b => JobGauge.Blood >= 50 || Player.HasStatus(true, StatusID.Delirium), + }; + + /// + /// 寂灭 + /// + public static IBaseAction Quietus { get; } = new BaseAction(ActionID.Quietus) + { + ActionCheck = Bloodspiller.ActionCheck, + }; + + /// + /// 血乱 + /// + public static IBaseAction Delirium { get; } = new BaseAction(ActionID.Delirium); + + /// + /// 至黑之夜 + /// + public static IBaseAction TheBlackestNight { get; } = new BaseAction(ActionID.TheBlackestNight, isTimeline: true) + { + ChoiceTarget = TargetFilter.FindAttackedTarget, + }; + + /// + /// 刚魂 + /// + public static IBaseAction StalwartSoul { get; } = new BaseAction(ActionID.StalwartSoul); + + /// + /// 暗黑布道 + /// + public static IBaseAction DarkMissionary { get; } = new BaseAction(ActionID.DarkMissionary, true, isTimeline: true); + + /// + /// 掠影示现 + /// + public static IBaseAction LivingShadow { get; } = new BaseAction(ActionID.LivingShadow) + { + ActionCheck = b => JobGauge.Blood >= 50, + }; + + /// + /// 献奉 + /// + public static IBaseAction Oblation { get; } = new BaseAction(ActionID.Oblation, true, isTimeline: true) + { + ChoiceTarget = TargetFilter.FindAttackedTarget, + }; + + /// + /// 暗影使者 + /// + public static IBaseAction Shadowbringer { get; } = new BaseAction(ActionID.Shadowbringer) + { + ActionCheck = b => JobGauge.DarksideTimeRemaining > 0, + }; + + /// + /// 腐秽黑暗 + /// + public static IBaseAction SaltandDarkness { get; } = new BaseAction(ActionID.SaltandDarkness) + { + StatusNeed = new[] { StatusID.SaltedEarth }, + }; + + protected override bool EmergencyAbility(byte abilitiesRemaining, IAction nextGCD, out IAction act) + { + //行尸走肉 + if (LivingDead.CanUse(out act) && BaseAction.TankBreakOtherCheck(JobIDs[0])) return true; + + return base.EmergencyAbility(abilitiesRemaining, nextGCD, out act); + } + + [RotationDesc(ActionID.Plunge)] + protected sealed override bool MoveForwardAbility(byte abilitiesRemaining, out IAction act, bool recordTarget = true) + { + if (Plunge.CanUse(out act, emptyOrSkipCombo: true, recordTarget: recordTarget)) return true; + + return false; + } +} \ No newline at end of file diff --git a/RotationSolver.Basic/Rotations/Basic/GNB_Base.cs b/RotationSolver.Basic/Rotations/Basic/GNB_Base.cs new file mode 100644 index 000000000..23a282c19 --- /dev/null +++ b/RotationSolver.Basic/Rotations/Basic/GNB_Base.cs @@ -0,0 +1,251 @@ +using Dalamud.Game.ClientState.JobGauge.Types; +using Dalamud.Game.ClientState.Objects.Types; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.CustomRotation; + +namespace RotationSolver.Rotations.Basic; + +public abstract class GNB_Base : CustomRotation.CustomRotation +{ + private static GNBGauge JobGauge => Service.JobGauges.Get(); + + public override MedicineType MedicineType => MedicineType.Strength; + + /// + /// �������� + /// + protected static byte Ammo => JobGauge.Ammo; + + /// + /// �����ĵڼ���combo + /// + protected static byte AmmoComboStep => JobGauge.AmmoComboStep; + + public sealed override ClassJobID[] JobIDs => new ClassJobID[] { ClassJobID.Gunbreaker }; + private sealed protected override IBaseAction Shield => RoyalGuard; + + protected override bool CanHealSingleSpell => false; + protected override bool CanHealAreaSpell => false; + + protected static byte MaxAmmo => Level >= 88 ? (byte)3 : (byte)2; + + /// + /// �������� + /// + public static IBaseAction RoyalGuard { get; } = new BaseAction(ActionID.RoyalGuard, shouldEndSpecial: true); + + /// + /// ����ն + /// + public static IBaseAction KeenEdge { get; } = new BaseAction(ActionID.KeenEdge); + + /// + /// ���� + /// + public static IBaseAction NoMercy { get; } = new BaseAction(ActionID.NoMercy); + + /// + /// �б��� + /// + public static IBaseAction BrutalShell { get; } = new BaseAction(ActionID.BrutalShell); + + /// + /// αװ + /// + public static IBaseAction Camouflage { get; } = new BaseAction(ActionID.Camouflage, true, isTimeline: true) + { + ActionCheck = BaseAction.TankDefenseSelf, + }; + + /// + /// ��ħ�� + /// + public static IBaseAction DemonSlice { get; } = new BaseAction(ActionID.DemonSlice) + { + AOECount = 2, + }; + + /// + /// ���׵� + /// + public static IBaseAction LightningShot { get; } = new BaseAction(ActionID.LightningShot) + { + FilterForHostiles = TargetFilter.TankRangeTarget, + }; + + /// + /// Σ������ + /// + public static IBaseAction DangerZone { get; } = new BaseAction(ActionID.DangerZone); + + /// + /// Ѹ��ն + /// + public static IBaseAction SolidBarrel { get; } = new BaseAction(ActionID.SolidBarrel); + + /// + /// ������ + /// + public static IBaseAction BurstStrike { get; } = new BaseAction(ActionID.BurstStrike) + { + ActionCheck = b => JobGauge.Ammo > 0, + }; + + /// + /// ���� + /// + public static IBaseAction Nebula { get; } = new BaseAction(ActionID.Nebula, true, isTimeline: true) + { + StatusProvide = Rampart.StatusProvide, + ActionCheck = BaseAction.TankDefenseSelf, + }; + + /// + /// ��ħɱ + /// + public static IBaseAction DemonSlaughter { get; } = new BaseAction(ActionID.DemonSlaughter) + { + AOECount = 2, + }; + + /// + /// ���� + /// + public static IBaseAction Aurora { get; } = new BaseAction(ActionID.Aurora, true, isTimeline: true); + + /// + /// �������� + /// + public static IBaseAction Superbolide { get; } = new BaseAction(ActionID.Superbolide, true, isTimeline: true); + + /// + /// ������ + /// + public static IBaseAction SonicBreak { get; } = new BaseAction(ActionID.SonicBreak); + + /// + /// �ַ�ն + /// + public static IBaseAction RoughDivide { get; } = new BaseAction(ActionID.RoughDivide, shouldEndSpecial: true) + { + ChoiceTarget = TargetFilter.FindTargetForMoving, + }; + + /// + /// ���� + /// + public static IBaseAction GnashingFang { get; } = new BaseAction(ActionID.GnashingFang) + { + ActionCheck = b => JobGauge.AmmoComboStep == 0 && JobGauge.Ammo > 0, + }; + + /// + /// ���γ岨 + /// + public static IBaseAction BowShock { get; } = new BaseAction(ActionID.BowShock); + + /// + /// ��֮�� + /// + public static IBaseAction HeartofLight { get; } = new BaseAction(ActionID.HeartofLight, true, isTimeline: true); + + /// + /// ʯ֮�� + /// + public static IBaseAction HeartofStone { get; } = new BaseAction(ActionID.HeartofStone, true, isTimeline: true) + { + ChoiceTarget = TargetFilter.FindAttackedTarget, + }; + + /// + /// ����֮�� + /// + public static IBaseAction FatedCircle { get; } = new BaseAction(ActionID.FatedCircle) + { + ActionCheck = b => JobGauge.Ammo > 0, + }; + + /// + /// Ѫ�� + /// + public static IBaseAction Bloodfest { get; } = new BaseAction(ActionID.Bloodfest, true) + { + ActionCheck = b => MaxAmmo - JobGauge.Ammo > 1, + }; + + /// + /// ���� + /// + public static IBaseAction DoubleDown { get; } = new BaseAction(ActionID.DoubleDown) + { + ActionCheck = b => JobGauge.Ammo > 1, + }; + + /// + /// ����צ + /// + public static IBaseAction SavageClaw { get; } = new BaseAction(ActionID.SavageClaw) + { + ActionCheck = b => Service.GetAdjustedActionId(ActionID.GnashingFang) == ActionID.SavageClaw, + }; + + /// + /// ����צ + /// + public static IBaseAction WickedTalon { get; } = new BaseAction(ActionID.WickedTalon) + { + ActionCheck = b => Service.GetAdjustedActionId(ActionID.GnashingFang) == ActionID.WickedTalon, + }; + + /// + /// ˺�� + /// + public static IBaseAction JugularRip { get; } = new BaseAction(ActionID.JugularRip) + { + ActionCheck = b => Service.GetAdjustedActionId(ActionID.Continuation) == ActionID.JugularRip, + }; + + /// + /// ���� + /// + public static IBaseAction AbdomenTear { get; } = new BaseAction(ActionID.AbdomenTear) + { + ActionCheck = b => Service.GetAdjustedActionId(ActionID.Continuation) == ActionID.AbdomenTear, + }; + + /// + /// ��Ŀ + /// + public static IBaseAction EyeGouge { get; } = new BaseAction(ActionID.EyeGouge) + { + ActionCheck = b => Service.GetAdjustedActionId(ActionID.Continuation) == ActionID.EyeGouge, + }; + + /// + /// ������ + /// + public static IBaseAction Hypervelocity { get; } = new BaseAction(ActionID.Hypervelocity) + { + ActionCheck = b => Service.GetAdjustedActionId(ActionID.Continuation) + == ActionID.Hypervelocity, + }; + + protected override bool EmergencyAbility(byte abilitiesRemaining, IAction nextGCD, out IAction act) + { + if (Superbolide.CanUse(out act) && BaseAction.TankBreakOtherCheck(JobIDs[0])) return true; + return base.EmergencyAbility(abilitiesRemaining, nextGCD, out act); + } + + [RotationDesc(ActionID.RoughDivide)] + protected sealed override bool MoveForwardAbility(byte abilitiesRemaining, out IAction act, bool recordTarget = true) + { + if (RoughDivide.CanUse(out act, emptyOrSkipCombo: true, recordTarget: recordTarget)) return true; + return false; + } +} + diff --git a/RotationSolver.Basic/Rotations/Basic/MCH_Base.cs b/RotationSolver.Basic/Rotations/Basic/MCH_Base.cs new file mode 100644 index 000000000..1c868006c --- /dev/null +++ b/RotationSolver.Basic/Rotations/Basic/MCH_Base.cs @@ -0,0 +1,192 @@ +using Dalamud.Game.ClientState.JobGauge.Types; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.CustomRotation; + +namespace RotationSolver.Rotations.Basic; + +public abstract class MCH_Base : CustomRotation.CustomRotation +{ + private static MCHGauge JobGauge => Service.JobGauges.Get(); + + public override MedicineType MedicineType => MedicineType.Dexterity; + + /// + /// 处于过热中 + /// + protected static bool IsOverheated => JobGauge.IsOverheated; + + /// + /// 热量还有多少 + /// + protected static byte Heat => JobGauge.Heat; + + /// + /// 点量还有多少 + /// + protected static byte Battery => JobGauge.Battery; + + /// + /// 过热在这么久后,还有吗 + /// + /// + /// + protected static bool OverheatedEndAfter(float time) + { + return EndAfter(JobGauge.OverheatTimeRemaining / 1000f, time); + } + + /// + /// 过热在这么久后,还有吗 + /// + /// + /// + /// + protected static bool OverheatedEndAfterGCD(uint gctCount = 0, uint abilityCount = 0) + { + return EndAfterGCD(JobGauge.OverheatTimeRemaining / 1000f, gctCount, abilityCount); + } + + public sealed override ClassJobID[] JobIDs => new ClassJobID[] { ClassJobID.Machinist }; + + /// + /// 分裂弹 + /// + public static IBaseAction SplitShot { get; } = new BaseAction(ActionID.SplitShot); + + /// + /// 独头弹 + /// + public static IBaseAction SlugShot { get; } = new BaseAction(ActionID.SlugShot) + { + ComboIds = new[] { ActionID.HeatedSplitShot }, + }; + + /// + /// 狙击弹 + /// + public static IBaseAction CleanShot { get; } = new BaseAction(ActionID.CleanShot) + { + ComboIds = new[] { ActionID.HeatedSlugShot }, + }; + + /// + /// 热冲击 + /// + public static IBaseAction HeatBlast { get; } = new BaseAction(ActionID.HeatBlast) + { + ActionCheck = b => JobGauge.IsOverheated + && !OverheatedEndAfterGCD(), + }; + + /// + /// 散射 + /// + public static IBaseAction SpreadShot { get; } = new BaseAction(ActionID.SpreadShot); + + /// + /// 自动弩 + /// + public static IBaseAction AutoCrossbow { get; } = new BaseAction(ActionID.AutoCrossbow) + { + ActionCheck = HeatBlast.ActionCheck, + }; + + /// + /// 热弹 + /// + public static IBaseAction HotShot { get; } = new BaseAction(ActionID.HotShot); + + /// + /// 空气锚 + /// + public static IBaseAction AirAnchor { get; } = new BaseAction(ActionID.AirAnchor); + + /// + /// 钻头 + /// + public static IBaseAction Drill { get; } = new BaseAction(ActionID.Drill); + + /// + /// 回转飞锯 + /// + public static IBaseAction ChainSaw { get; } = new BaseAction(ActionID.ChainSaw); + + /// + /// 毒菌冲击 + /// + public static IBaseAction Bioblaster { get; } = new BaseAction(ActionID.Bioblaster, isEot: true); + + /// + /// 整备 + /// + public static IBaseAction Reassemble { get; } = new BaseAction(ActionID.Reassemble) + { + StatusProvide = new StatusID[] { StatusID.Reassemble }, + ActionCheck = b => HasHostilesInRange, + }; + + /// + /// 超荷 + /// + public static IBaseAction Hypercharge { get; } = new BaseAction(ActionID.Hypercharge) + { + ActionCheck = b => !JobGauge.IsOverheated && JobGauge.Heat >= 50, + }; + + /// + /// 野火 + /// + public static IBaseAction Wildfire { get; } = new BaseAction(ActionID.Wildfire); + + /// + /// 虹吸弹 + /// + public static IBaseAction GaussRound { get; } = new BaseAction(ActionID.GaussRound); + + /// + /// 弹射 + /// + public static IBaseAction Ricochet { get; } = new BaseAction(ActionID.Ricochet); + + /// + /// 枪管加热 + /// + public static IBaseAction BarrelStabilizer { get; } = new BaseAction(ActionID.BarrelStabilizer) + { + ActionCheck = b => JobGauge.Heat <= 50 && InCombat, + }; + + /// + /// 车式浮空炮塔 + /// + public static IBaseAction RookAutoturret { get; } = new BaseAction(ActionID.RookAutoturret) + { + ActionCheck = b => JobGauge.Battery >= 50 && !JobGauge.IsRobotActive, + }; + + /// + /// 策动 + /// + public static IBaseAction Tactician { get; } = new BaseAction(ActionID.Tactician, true, isTimeline: true) + { + ActionCheck = b => !Player.HasStatus(false, StatusID.Troubadour, + StatusID.Tactician1, + StatusID.Tactician2, + StatusID.ShieldSamba), + }; + + public static IBaseAction Dismantle { get; } = new BaseAction(ActionID.Dismantle, true, isTimeline: true); + + [RotationDesc(ActionID.Tactician, ActionID.Dismantle)] + protected sealed override bool DefenseAreaAbility(byte abilitiesRemaining, out IAction act) + { + if (Tactician.CanUse(out act, mustUse: true)) return true; + if (Dismantle.CanUse(out act, mustUse: true)) return true; + return false; + } +} diff --git a/RotationSolver.Basic/Rotations/Basic/MNK_Base.cs b/RotationSolver.Basic/Rotations/Basic/MNK_Base.cs new file mode 100644 index 000000000..60d44cd15 --- /dev/null +++ b/RotationSolver.Basic/Rotations/Basic/MNK_Base.cs @@ -0,0 +1,218 @@ +using Dalamud.Game.ClientState.JobGauge.Enums; +using Dalamud.Game.ClientState.JobGauge.Types; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.CustomRotation; + +namespace RotationSolver.Rotations.Basic; + +public abstract class MNK_Base : CustomRotation.CustomRotation +{ + private static MNKGauge JobGauge => Service.JobGauges.Get(); + + public override MedicineType MedicineType => MedicineType.Strength; + + + /// + /// 查克拉们 + /// + protected static BeastChakra[] BeastChakras => JobGauge.BeastChakra; + + /// + /// 查克拉数量 + /// + protected static byte Chakra => JobGauge.Chakra; + + protected static bool HasSolar => (JobGauge.Nadi & Nadi.SOLAR) != 0; + protected static bool HasLunar => (JobGauge.Nadi & Nadi.LUNAR) != 0; + + public sealed override ClassJobID[] JobIDs => new ClassJobID[] { ClassJobID.Monk, ClassJobID.Pugilist }; + + /// + /// 双龙脚 + /// + public static IBaseAction DragonKick { get; } = new BaseAction(ActionID.DragonKick) + { + StatusProvide = new[] { StatusID.LeadenFist }, + }; + + /// + /// 连击 + /// + public static IBaseAction Bootshine { get; } = new BaseAction(ActionID.Bootshine); + + /// + /// 破坏神冲 aoe + /// + public static IBaseAction ArmoftheDestroyer { get; } = new BaseAction(ActionID.ArmoftheDestroyer); + + /// + /// 破坏神脚 aoe + /// + public static IBaseAction ShadowoftheDestroyer { get; } = new BaseAction(ActionID.ShadowoftheDestroyer); + + /// + /// 双掌打 伤害提高 + /// + public static IBaseAction TwinSnakes { get; } = new BaseAction(ActionID.TwinSnakes, isEot: true); + + /// + /// 正拳 + /// + public static IBaseAction TrueStrike { get; } = new BaseAction(ActionID.TrueStrike); + + /// + /// 四面脚 aoe + /// + public static IBaseAction FourpointFury { get; } = new BaseAction(ActionID.FourpointFury); + + /// + /// 破碎拳 + /// + public static IBaseAction Demolish { get; } = new BaseAction(ActionID.Demolish, isEot: true) + { + TargetStatus = new StatusID[] { StatusID.Demolish }, + GetDotGcdCount = () => 2, + }; + + /// + /// 崩拳 + /// + public static IBaseAction SnapPunch { get; } = new BaseAction(ActionID.SnapPunch); + + /// + /// 地烈劲 aoe + /// + public static IBaseAction Rockbreaker { get; } = new BaseAction(ActionID.Rockbreaker); + + /// + /// 斗气 + /// + public static IBaseAction Meditation { get; } = new BaseAction(ActionID.Meditation, true); + + /// + /// 铁山靠 + /// + public static IBaseAction SteelPeak { get; } = new BaseAction(ActionID.SteelPeak) + { + ActionCheck = b => InCombat && Chakra == 5, + }; + + /// + /// 空鸣拳 + /// + public static IBaseAction HowlingFist { get; } = new BaseAction(ActionID.HowlingFist) + { + ActionCheck = SteelPeak.ActionCheck, + }; + + /// + /// 义结金兰 + /// + public static IBaseAction Brotherhood { get; } = new BaseAction(ActionID.Brotherhood, true); + + /// + /// 红莲极意 提高dps + /// + public static IBaseAction RiddleofFire { get; } = new BaseAction(ActionID.RiddleofFire, true); + + /// + /// 突进技能 + /// + public static IBaseAction Thunderclap { get; } = new BaseAction(ActionID.Thunderclap, shouldEndSpecial: true) + { + ChoiceTarget = TargetFilter.FindTargetForMoving, + }; + + /// + /// 真言 + /// + public static IBaseAction Mantra { get; } = new BaseAction(ActionID.Mantra, true, isTimeline: true); + + /// + /// 震脚 + /// + public static IBaseAction PerfectBalance { get; } = new BaseAction(ActionID.PerfectBalance) + { + ActionCheck = b => InCombat, + }; + + /// + /// 苍气炮 阴 + /// + public static IBaseAction ElixirField { get; } = new BaseAction(ActionID.ElixirField); + + /// + /// 爆裂脚 阳 + /// + public static IBaseAction FlintStrike { get; } = new BaseAction(ActionID.FlintStrike); + + /// + /// 翻天脚 兔 + /// + public static IBaseAction CelestialRevolution { get; } = new BaseAction(ActionID.CelestialRevolution); + + /// + /// 凤凰舞 + /// + public static IBaseAction RisingPhoenix { get; } = new BaseAction(ActionID.RisingPhoenix); + + /// + /// 斗魂旋风脚 阴阳 + /// + public static IBaseAction TornadoKick { get; } = new BaseAction(ActionID.TornadoKick); + public static IBaseAction PhantomRush { get; } = new BaseAction(ActionID.PhantomRush); + + /// + /// 演武 + /// + public static IBaseAction FormShift { get; } = new BaseAction(ActionID.FormShift, true) + { + StatusProvide = new[] { StatusID.FormlessFist, StatusID.PerfectBalance }, + }; + + /// + /// 金刚极意 盾 + /// + public static IBaseAction RiddleofEarth { get; } = new BaseAction(ActionID.RiddleofEarth, true, shouldEndSpecial: true, isTimeline: true) + { + StatusProvide = new[] { StatusID.RiddleofEarth }, + }; + + /// + /// 疾风极意 + /// + public static IBaseAction RiddleofWind { get; } = new BaseAction(ActionID.RiddleofWind, true); + + [RotationDesc(ActionID.Thunderclap)] + protected sealed override bool MoveForwardAbility(byte abilitiesRemaining, out IAction act, bool recordTarget = true) + { + if (Thunderclap.CanUse(out act, emptyOrSkipCombo: true, recordTarget: recordTarget)) return true; + return false; + } + + [RotationDesc(ActionID.Feint)] + protected sealed override bool DefenseAreaAbility(byte abilitiesRemaining, out IAction act) + { + if (Feint.CanUse(out act)) return true; + return false; + } + + [RotationDesc(ActionID.Mantra)] + protected sealed override bool HealAreaAbility(byte abilitiesRemaining, out IAction act) + { + if (Mantra.CanUse(out act)) return true; + return false; + } + + [RotationDesc(ActionID.RiddleofEarth)] + protected sealed override bool DefenseSingleAbility(byte abilitiesRemaining, out IAction act) + { + if (RiddleofEarth.CanUse(out act, emptyOrSkipCombo: true)) return true; + return false; + } +} diff --git a/RotationSolver.Basic/Rotations/Basic/NIN_Base.cs b/RotationSolver.Basic/Rotations/Basic/NIN_Base.cs new file mode 100644 index 000000000..be5053809 --- /dev/null +++ b/RotationSolver.Basic/Rotations/Basic/NIN_Base.cs @@ -0,0 +1,339 @@ +using Dalamud.Game.ClientState.JobGauge.Types; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.CustomRotation; + +namespace RotationSolver.Rotations.Basic; + +public interface INinAction : IBaseAction +{ + IBaseAction[] Ninjutsus { get; } +} + + +public abstract class NIN_Base : CustomRotation.CustomRotation +{ + private static NINGauge JobGauge => Service.JobGauges.Get(); + public override MedicineType MedicineType => MedicineType.Dexterity; + + + /// + /// 在风buff中 + /// + protected static bool InHuton => JobGauge.HutonTimer > 0; + + /// + /// 忍术点数 + /// + protected static byte Ninki => JobGauge.Ninki; + + public sealed override ClassJobID[] JobIDs => new ClassJobID[] { ClassJobID.Ninja, ClassJobID.Rogue }; + + + public class NinAction : BaseAction, INinAction + { + public IBaseAction[] Ninjutsus { get; } + internal NinAction(ActionID actionID, params IBaseAction[] ninjutsus) + : base(actionID, false, false) + { + Ninjutsus = ninjutsus; + } + } + + /// + /// 隐遁 + /// + public static IBaseAction Hide { get; } = new BaseAction(ActionID.Hide, true); + + /// + /// 双刃旋 + /// + public static IBaseAction SpinningEdge { get; } = new BaseAction(ActionID.SpinningEdge); + + /// + /// 残影 + /// + public static IBaseAction ShadeShift { get; } = new BaseAction(ActionID.ShadeShift, true); + + /// + /// 绝风 + /// + public static IBaseAction GustSlash { get; } = new BaseAction(ActionID.GustSlash); + + /// + /// 飞刀 + /// + public static IBaseAction ThrowingDagger { get; } = new BaseAction(ActionID.ThrowingDagger) + { + FilterForHostiles = TargetFilter.MeleeRangeTargetFilter, + }; + + /// + /// 夺取 + /// + public static IBaseAction Mug { get; } = new BaseAction(ActionID.Mug) + { + ActionCheck = b => JobGauge.Ninki <= 50, + }; + + /// + /// 攻其不备 + /// + public static IBaseAction TrickAttack { get; } = new BaseAction(ActionID.TrickAttack) + { + StatusNeed = new StatusID[] { StatusID.Suiton, StatusID.Hidden }, + }; + + /// + /// 旋风刃 + /// + public static IBaseAction AeolianEdge { get; } = new BaseAction(ActionID.AeolianEdge); + + /// + /// 血雨飞花 + /// + public static IBaseAction DeathBlossom { get; } = new BaseAction(ActionID.DeathBlossom); + + /// + /// 天之印 + /// + public static IBaseAction Ten { get; } = new BaseAction(ActionID.Ten, true); + + /// + /// 地之印 + /// + public static IBaseAction Chi { get; } = new BaseAction(ActionID.Chi, true); + + /// + /// 人之印 + /// + public static IBaseAction Jin { get; } = new BaseAction(ActionID.Jin, true); + + /// + /// 天地人 + /// + public static IBaseAction TenChiJin { get; } = new BaseAction(ActionID.TenChiJin, true) + { + StatusProvide = new[] { StatusID.Kassatsu, StatusID.TenChiJin }, + ActionCheck = b => JobGauge.HutonTimer > 0, + }; + + /// + /// 缩地 + /// + public static IBaseAction Shukuchi { get; } = new BaseAction(ActionID.Shukuchi, true); + + /// + /// 断绝 + /// + public static IBaseAction Assassinate { get; } = new BaseAction(ActionID.Assassinate); + + /// + /// 命水 + /// + public static IBaseAction Meisui { get; } = new BaseAction(ActionID.Meisui, true) + { + StatusNeed = new[] { StatusID.Suiton }, + ActionCheck = b => JobGauge.Ninki <= 50, + }; + + /// + /// 生杀予夺 + /// + public static IBaseAction Kassatsu { get; } = new BaseAction(ActionID.Kassatsu, true) + { + StatusProvide = TenChiJin.StatusProvide, + }; + + /// + /// 八卦无刃杀 + /// + public static IBaseAction HakkeMujinsatsu { get; } = new BaseAction(ActionID.HakkeMujinsatsu); + + /// + /// 强甲破点突 + /// + public static IBaseAction ArmorCrush { get; } = new BaseAction(ActionID.ArmorCrush) + { + ActionCheck = b => EndAfter(JobGauge.HutonTimer / 1000f, 29) && JobGauge.HutonTimer > 0, + }; + + /// + /// 分身之术 + /// + public static IBaseAction Bunshin { get; } = new BaseAction(ActionID.Bunshin, true) + { + ActionCheck = b => Ninki >= 50, + }; + + /// + /// 通灵之术·大虾蟆 + /// + public static IBaseAction HellfrogMedium { get; } = new BaseAction(ActionID.HellfrogMedium) + { + ActionCheck = Bunshin.ActionCheck, + }; + + /// + /// 六道轮回 + /// + public static IBaseAction Bhavacakra { get; } = new BaseAction(ActionID.Bhavacakra) + { + ActionCheck = Bunshin.ActionCheck, + }; + + /// + /// 残影镰鼬 + /// + public static IBaseAction PhantomKamaitachi { get; } = new BaseAction(ActionID.PhantomKamaitachi) + { + StatusNeed = new[] { StatusID.PhantomKamaitachiReady }, + }; + + /// + /// 月影雷兽牙 + /// + public static IBaseAction FleetingRaiju { get; } = new BaseAction(ActionID.FleetingRaiju) + { + StatusNeed = new[] { StatusID.RaijuReady }, + }; + + /// + /// 月影雷兽爪 + /// + public static IBaseAction ForkedRaiju { get; } = new BaseAction(ActionID.ForkedRaiju) + { + StatusNeed = FleetingRaiju.StatusNeed, + }; + + /// + /// 风来刃 + /// + public static IBaseAction Huraijin { get; } = new BaseAction(ActionID.Huraijin) + { + ActionCheck = b => JobGauge.HutonTimer == 0, + }; + + /// + /// 梦幻三段 + /// + public static IBaseAction DreamWithinaDream { get; } = new BaseAction(ActionID.DreamWithinaDream); + + /// + /// 风魔手里剑天 + /// + public static IBaseAction FumaShurikenTen { get; } = new BaseAction(ActionID.FumaShurikenTen); + + /// + /// 风魔手里剑人 + /// + public static IBaseAction FumaShurikenJin { get; } = new BaseAction(ActionID.FumaShurikenJin); + + /// + /// 火遁之术天 + /// + public static IBaseAction KatonTen { get; } = new BaseAction(ActionID.KatonTen); + + /// + /// 雷遁之术地 + /// + public static IBaseAction RaitonChi { get; } = new BaseAction(ActionID.RaitonChi); + + /// + /// 土遁之术地 + /// + public static IBaseAction DotonChi { get; } = new BaseAction(ActionID.DotonChi); + + /// + /// 水遁之术人 + /// + public static IBaseAction SuitonJin { get; } = new BaseAction(ActionID.SuitonJin); + + + /// + /// 通灵之术 + /// + public static INinAction RabbitMedium { get; } = new NinAction(ActionID.RabbitMedium); + + /// + /// 风魔手里剑 + /// + public static INinAction FumaShuriken { get; } = new NinAction(ActionID.FumaShuriken, Ten); + + /// + /// 火遁之术 + /// + public static INinAction Katon { get; } = new NinAction(ActionID.Katon, Chi, Ten); + + /// + /// 雷遁之术 + /// + public static INinAction Raiton { get; } = new NinAction(ActionID.Raiton, Ten, Chi); + + /// + /// 冰遁之术 + /// + public static INinAction Hyoton { get; } = new NinAction(ActionID.Hyoton, Ten, Jin); + + /// + /// 风遁之术 + /// + public static INinAction Huton { get; } = new NinAction(ActionID.Huton, Jin, Chi, Ten) + { + ActionCheck = b => JobGauge.HutonTimer == 0, + }; + + /// + /// 土遁之术 + /// + public static INinAction Doton { get; } = new NinAction(ActionID.Doton, Jin, Ten, Chi) + { + StatusProvide = new[] { StatusID.Doton }, + }; + + /// + /// 水遁之术 + /// + public static INinAction Suiton { get; } = new NinAction(ActionID.Suiton, Ten, Chi, Jin) + { + StatusProvide = new[] { StatusID.Suiton }, + ActionCheck = b => TrickAttack.WillHaveOneChargeGCD(1, 1), + }; + + /// + /// 劫火灭却之术 + /// + public static INinAction GokaMekkyaku { get; } = new NinAction(ActionID.GokaMekkyaku, Chi, Ten); + + /// + /// 冰晶乱流之术 + /// + public static INinAction HyoshoRanryu { get; } = new NinAction(ActionID.HyoshoRanryu, Ten, Jin); + + [RotationDesc(ActionID.Shukuchi)] + protected sealed override bool MoveForwardAbility(byte abilitiesRemaining, out IAction act, bool recordTarget = true) + { + if (Shukuchi.CanUse(out act, emptyOrSkipCombo: true, recordTarget: recordTarget)) return true; + + return false; + } + + [RotationDesc(ActionID.Feint)] + protected sealed override bool DefenseAreaAbility(byte abilitiesRemaining, out IAction act) + { + if (Feint.CanUse(out act)) return true; + return false; + } + + [RotationDesc(ActionID.ShadeShift)] + protected override bool DefenseSingleAbility(byte abilitiesRemaining, out IAction act) + { + if (ShadeShift.CanUse(out act)) return true; + + return false; + } +} diff --git a/RotationSolver.Basic/Rotations/Basic/PLD_Base.cs b/RotationSolver.Basic/Rotations/Basic/PLD_Base.cs new file mode 100644 index 000000000..b56572c7b --- /dev/null +++ b/RotationSolver.Basic/Rotations/Basic/PLD_Base.cs @@ -0,0 +1,219 @@ +using Dalamud.Game.ClientState.JobGauge.Types; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.CustomRotation; +using System.Linq; + +namespace RotationSolver.Rotations.Basic; + +public abstract class PLD_Base : CustomRotation.CustomRotation +{ + private static PLDGauge JobGauge => Service.JobGauges.Get(); + public override MedicineType MedicineType => MedicineType.Strength; + + + protected static bool HasDivineMight => !Player.WillStatusEndGCD(0, 0, true, StatusID.DivineMight); + + protected static bool HasFightOrFlight => !Player.WillStatusEndGCD(0, 0, true, StatusID.FightOrFlight); + + /// + /// ����� + /// + protected static byte OathGauge => JobGauge.OathGauge; + + public sealed override ClassJobID[] JobIDs => new ClassJobID[] { ClassJobID.Paladin, ClassJobID.Gladiator }; + + private sealed protected override IBaseAction Shield => IronWill; + + protected override bool CanHealSingleSpell => DataCenter.PartyMembers.Count() == 1 && base.CanHealSingleSpell; + protected override bool CanHealAreaAbility => false; + + /// + /// �������� + /// + public static IBaseAction IronWill { get; } = new BaseAction(ActionID.IronWill, shouldEndSpecial: true); + + /// + /// �ȷ潣 + /// + public static IBaseAction FastBlade { get; } = new BaseAction(ActionID.FastBlade); + + /// + /// ���ҽ� + /// + public static IBaseAction RiotBlade { get; } = new BaseAction(ActionID.RiotBlade); + + /// + /// ��Ѫ�� + /// + public static IBaseAction GoringBlade { get; } = new BaseAction(ActionID.GoringBlade, isEot: true) + { + TargetStatus = new[] + { + StatusID.GoringBlade, + } + }; + + /// + /// սŮ��֮ŭ(��Ȩ��) + /// + public static IBaseAction RageofHalone { get; } = new BaseAction(ActionID.RageofHalone); + + /// + /// Ͷ�� + /// + public static IBaseAction ShieldLob { get; } = new BaseAction(ActionID.ShieldLob) + { + FilterForHostiles = TargetFilter.TankRangeTarget, + }; + + /// + /// ս�ӷ�Ӧ + /// + public static IBaseAction FightorFlight { get; } = new BaseAction(ActionID.FightorFlight, true); + + /// + /// ȫʴն + /// + public static IBaseAction TotalEclipse { get; } = new BaseAction(ActionID.TotalEclipse); + + /// + /// ����ն + /// + public static IBaseAction Prominence { get; } = new BaseAction(ActionID.Prominence); + + /// + /// Ԥ�� + /// + public static IBaseAction Sentinel { get; } = new BaseAction(ActionID.Sentinel, isTimeline: true, isFriendly: true) + { + StatusProvide = Rampart.StatusProvide, + ActionCheck = BaseAction.TankDefenseSelf, + }; + + /// + /// ������ת + /// + public static IBaseAction CircleofScorn { get; } = new BaseAction(ActionID.CircleofScorn); + + /// + /// ���֮�� + /// + public static IBaseAction SpiritsWithin { get; } = new BaseAction(ActionID.SpiritsWithin); + + /// + /// ��ʥ���� + /// + public static IBaseAction HallowedGround { get; } = new BaseAction(ActionID.HallowedGround, true, isTimeline: true); + + /// + /// ʥ��Ļ�� + /// + public static IBaseAction DivineVeil { get; } = new BaseAction(ActionID.DivineVeil, true, isTimeline: true); + + /// + /// ���ʺ��� + /// + public static IBaseAction Clemency { get; } = new BaseAction(ActionID.Clemency, true, true, isTimeline: true); + + /// + /// ��Ԥ + /// + public static IBaseAction Intervention { get; } = new BaseAction(ActionID.Intervention, true, isTimeline: true) + { + ChoiceTarget = TargetFilter.FindAttackedTarget, + }; + + /// + /// ��ͣ + /// + public static IBaseAction Intervene { get; } = new BaseAction(ActionID.Intervene, shouldEndSpecial: true) + { + ChoiceTarget = TargetFilter.FindTargetForMoving, + }; + + /// + /// ���」 + /// + public static IBaseAction Atonement { get; } = new BaseAction(ActionID.Atonement) + { + StatusNeed = new[] { StatusID.SwordOath }, + }; + + /// + /// ���꽣 + /// + public static IBaseAction Expiacion { get; } = new BaseAction(ActionID.Expiacion); + + /// + /// �������� + /// + public static IBaseAction Requiescat { get; } = new BaseAction(ActionID.Requiescat, true); + + /// + /// ���� + /// + public static IBaseAction Confiteor { get; } = new BaseAction(ActionID.Confiteor); + + /// + /// ʥ�� + /// + public static IBaseAction HolyCircle { get; } = new BaseAction(ActionID.HolyCircle); + + /// + /// ʥ�� + /// + public static IBaseAction HolySpirit { get; } = new BaseAction(ActionID.HolySpirit); + + /// + /// ��װ���� + /// + public static IBaseAction PassageofArms { get; } = new BaseAction(ActionID.PassageofArms, true, isTimeline: true); + + /// + /// ���� + /// + public static IBaseAction Cover { get; } = new BaseAction(ActionID.Cover, true, isTimeline: true) + { + ChoiceTarget = TargetFilter.FindAttackedTarget, + ActionCheck = b => OathGauge >= 50, + }; + + /// + /// ���� + /// + public static IBaseAction Sheltron { get; } = new BaseAction(ActionID.Sheltron, true, isTimeline: true) + { + ActionCheck = Cover.ActionCheck, + }; + + public static IBaseAction Bulwark { get; } = new BaseAction(ActionID.Bulwark, true, isTimeline: true) + { + StatusProvide = Rampart.StatusProvide, + }; + + + protected override bool EmergencyAbility(byte abilitiesRemaining, IAction nextGCD, out IAction act) + { + if (HallowedGround.CanUse(out act) && BaseAction.TankBreakOtherCheck(JobIDs[0])) return true; + return base.EmergencyAbility(abilitiesRemaining, nextGCD, out act); + } + + [RotationDesc(ActionID.Intervene)] + protected sealed override bool MoveForwardAbility(byte abilitiesRemaining, out IAction act, bool recordTarget = true) + { + if (Intervene.CanUse(out act, emptyOrSkipCombo: true, recordTarget: recordTarget)) return true; + return false; + } + + [RotationDesc(ActionID.Clemency)] + protected sealed override bool HealSingleGCD(out IAction act) + { + if (Clemency.CanUse(out act)) return true; + return false; + } +} diff --git a/RotationSolver.Basic/Rotations/Basic/RDM_Base.cs b/RotationSolver.Basic/Rotations/Basic/RDM_Base.cs new file mode 100644 index 000000000..780073bfa --- /dev/null +++ b/RotationSolver.Basic/Rotations/Basic/RDM_Base.cs @@ -0,0 +1,247 @@ +using Dalamud.Game.ClientState.JobGauge.Types; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.CustomRotation; +using System.Linq; + +namespace RotationSolver.Rotations.Basic; + +public abstract class RDM_Base : CustomRotation.CustomRotation +{ + private static RDMGauge JobGauge => Service.JobGauges.Get(); + + public override MedicineType MedicineType => MedicineType.Intelligence; + + + /// + /// ��ħԪ + /// + protected static byte WhiteMana => JobGauge.WhiteMana; + + /// + /// ��ħԪ + /// + protected static byte BlackMana => JobGauge.BlackMana; + + /// + /// ħ���� + /// + protected static byte ManaStacks => JobGauge.ManaStacks; + + public sealed override ClassJobID[] JobIDs => new ClassJobID[] { ClassJobID.RedMage }; + protected override bool CanHealSingleSpell => DataCenter.PartyMembers.Count() == 1 && base.CanHealSingleSpell; + + private sealed protected override IBaseAction Raise => Verraise; + + /// + /// �ิ�� + /// + public static IBaseAction Verraise { get; } = new BaseAction(ActionID.Verraise, true); + + /// + /// �� + /// + public static IBaseAction Jolt { get; } = new BaseAction(ActionID.Jolt) + { + StatusProvide = Swiftcast.StatusProvide.Union(new[] { StatusID.Acceleration }).ToArray(), + }; + + /// + /// �ش� + /// + public static IBaseAction Riposte { get; } = new BaseAction(ActionID.Riposte) + { + ActionCheck = b => JobGauge.BlackMana >= 20 && JobGauge.WhiteMana >= 20, + }; + + /// + /// ������ + /// + public static IBaseAction Verthunder { get; } = new BaseAction(ActionID.Verthunder) + { + StatusNeed = Jolt.StatusProvide, + }; + + /// + /// �̱���� + /// + public static IBaseAction CorpsAcorps { get; } = new BaseAction(ActionID.CorpsAcorps, shouldEndSpecial: true) + { + ChoiceTarget = TargetFilter.FindTargetForMoving, + }; + + /// + /// �༲�� + /// + public static IBaseAction Veraero { get; } = new BaseAction(ActionID.Veraero) + { + StatusNeed = Jolt.StatusProvide, + }; + + /// + /// ɢ�� + /// + public static IBaseAction Scatter { get; } = new BaseAction(ActionID.Scatter) + { + StatusNeed = Jolt.StatusProvide, + AOECount = 2, + }; + + /// + /// ������ + /// + public static IBaseAction Verthunder2 { get; } = new BaseAction(ActionID.Verthunder2) + { + StatusProvide = Jolt.StatusProvide, + }; + + /// + /// ���ҷ� + /// + public static IBaseAction Veraero2 { get; } = new BaseAction(ActionID.Veraero2) + { + StatusProvide = Jolt.StatusProvide, + }; + + /// + /// ����� + /// + public static IBaseAction Verfire { get; } = new BaseAction(ActionID.Verfire) + { + StatusNeed = new[] { StatusID.VerfireReady }, + StatusProvide = Jolt.StatusProvide, + }; + + /// + /// ���ʯ + /// + public static IBaseAction Verstone { get; } = new BaseAction(ActionID.Verstone) + { + StatusNeed = new[] { StatusID.VerstoneReady }, + StatusProvide = Jolt.StatusProvide, + }; + + /// + /// ����ն + /// + public static IBaseAction Zwerchhau { get; } = new BaseAction(ActionID.Zwerchhau) + { + ActionCheck = b => BlackMana >= 15 && WhiteMana >= 15, + }; + + /// + /// ���� + /// + public static IBaseAction Engagement { get; } = new BaseAction(ActionID.Engagement); + + /// + /// �ɽ� + /// + public static IBaseAction Fleche { get; } = new BaseAction(ActionID.Fleche); + + /// + /// ���� + /// + public static IBaseAction Redoublement { get; } = new BaseAction(ActionID.Redoublement) + { + ActionCheck = b => BlackMana >= 15 && WhiteMana >= 15, + }; + + + /// + /// �ٽ� + /// + public static IBaseAction Acceleration { get; } = new BaseAction(ActionID.Acceleration, true) + { + StatusProvide = new[] { StatusID.Acceleration }, + }; + + /// + /// ��Բն + /// + public static IBaseAction Moulinet { get; } = new BaseAction(ActionID.Moulinet) + { + ActionCheck = b => BlackMana >= 20 && WhiteMana >= 20, + }; + + /// + /// ������ + /// + public static IBaseAction Vercure { get; } = new BaseAction(ActionID.Vercure, true) + { + StatusProvide = Swiftcast.StatusProvide.Union(Acceleration.StatusProvide).ToArray(), + }; + + /// + /// ���ַ��� + /// + public static IBaseAction ContreSixte { get; } = new BaseAction(ActionID.ContreSixte); + + /// + /// ���� + /// + public static IBaseAction Embolden { get; } = new BaseAction(ActionID.Embolden, true); + + /// + /// ���� + /// + public static IBaseAction MagickBarrier { get; } = new BaseAction(ActionID.MagickBarrier, true, isTimeline: true); + + /// + /// ��˱� + /// + public static IBaseAction Verflare { get; } = new BaseAction(ActionID.Verflare); + + /// + /// ����ʥ + /// + public static IBaseAction Verholy { get; } = new BaseAction(ActionID.Verholy); + + /// + /// ���� + /// + public static IBaseAction Scorch { get; } = new BaseAction(ActionID.Scorch) + { + ComboIds = new[] { ActionID.Verholy }, + }; + + /// + /// ���� + /// + public static IBaseAction Resolution { get; } = new BaseAction(ActionID.Resolution); + + /// + /// ħԪ�� + /// + public static IBaseAction Manafication { get; } = new BaseAction(ActionID.Manafication) + { + ActionCheck = b => WhiteMana <= 50 && BlackMana <= 50 && InCombat && ManaStacks == 0, + ComboIdsNot = new[] { ActionID.Riposte, ActionID.Zwerchhau, ActionID.Scorch, ActionID.Verflare, ActionID.Verholy }, + }; + + [RotationDesc(ActionID.Vercure)] + protected sealed override bool HealSingleGCD(out IAction act) + { + if (Vercure.CanUse(out act, mustUse: true)) return true; + return false; + } + + [RotationDesc(ActionID.CorpsAcorps)] + protected sealed override bool MoveForwardAbility(byte abilitiesRemaining, out IAction act, bool recordTarget = true) + { + if (CorpsAcorps.CanUse(out act, emptyOrSkipCombo: true, recordTarget: recordTarget)) return true; + return false; + } + + [RotationDesc(ActionID.Addle, ActionID.MagickBarrier)] + protected sealed override bool DefenseAreaAbility(byte abilitiesRemaining, out IAction act) + { + if (Addle.CanUse(out act)) return true; + if (MagickBarrier.CanUse(out act, mustUse: true)) return true; + return false; + } +} diff --git a/RotationSolver.Basic/Rotations/Basic/RPR_Base.cs b/RotationSolver.Basic/Rotations/Basic/RPR_Base.cs new file mode 100644 index 000000000..c3daa6c14 --- /dev/null +++ b/RotationSolver.Basic/Rotations/Basic/RPR_Base.cs @@ -0,0 +1,345 @@ +using Dalamud.Game.ClientState.JobGauge.Types; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.CustomRotation; + +namespace RotationSolver.Rotations.Basic; + +public abstract class RPR_Base : CustomRotation.CustomRotation +{ + private static RPRGauge JobGauge => Service.JobGauges.Get(); + + public override MedicineType MedicineType => MedicineType.Strength; + + + /// + /// 红色灵魂 + /// + protected static byte Soul => JobGauge.Soul; + /// + /// 蓝色灵魂 + /// + protected static byte Shroud => JobGauge.Shroud; + /// + /// 夜游魂 + /// + protected static byte LemureShroud => JobGauge.LemureShroud; + /// + /// 虚无魂 + /// + protected static byte VoidShroud => JobGauge.VoidShroud; + /// + /// 夜游魂附体 + /// + protected static bool Enshrouded => Player.HasStatus(true, StatusID.Enshrouded); + /// + /// 妖异之镰状态 + /// + protected static bool SoulReaver => Player.HasStatus(true, StatusID.SoulReaver); + + public sealed override ClassJobID[] JobIDs => new ClassJobID[] { ClassJobID.Reaper }; + + public class PRPAction : BaseAction + { + public override EnemyPositional EnemyPositional => Player.HasStatus(true, StatusID.Enshrouded) + ? EnemyPositional.None : base.EnemyPositional; + internal PRPAction(ActionID actionID, bool isFriendly = false, bool shouldEndSpecial = false) + : base(actionID, isFriendly, shouldEndSpecial) + { + } + } + + #region 单体 + /// + /// 切割 + /// + public static IBaseAction Slice { get; } = new BaseAction(ActionID.Slice) + { + ActionCheck = b => !Enshrouded && !SoulReaver, + }; + + /// + /// 增盈切割 + /// + public static IBaseAction WaxingSlice { get; } = new BaseAction(ActionID.WaxingSlice) + { + ActionCheck = Slice.ActionCheck, + }; + + /// + /// 地狱切割 + /// + public static IBaseAction InfernalSlice { get; } = new BaseAction(ActionID.InfernalSlice) + { + ActionCheck = Slice.ActionCheck, + }; + + /// + /// 死亡之影 + /// + public static IBaseAction ShadowofDeath { get; } = new BaseAction(ActionID.ShadowofDeath, isEot: true) + { + TargetStatus = new[] { StatusID.DeathsDesign }, + ActionCheck = b => !SoulReaver, + }; + + /// + /// 灵魂切割 + /// + public static IBaseAction SoulSlice { get; } = new BaseAction(ActionID.SoulSlice) + { + ActionCheck = b => !Enshrouded && !SoulReaver && Soul <= 50, + }; + #endregion + #region AoE + /// + /// 旋转钐割 + /// + public static IBaseAction SpinningScythe { get; } = new BaseAction(ActionID.SpinningScythe) + { + ActionCheck = Slice.ActionCheck, + }; + + /// + /// 噩梦钐割 + /// + public static IBaseAction NightmareScythe { get; } = new BaseAction(ActionID.NightmareScythe) + { + ActionCheck = Slice.ActionCheck, + }; + + /// + /// 死亡之涡 + /// + public static IBaseAction WhorlofDeath { get; } = new BaseAction(ActionID.WhorlofDeath, isEot: true) + { + TargetStatus = new[] { StatusID.DeathsDesign }, + ActionCheck = ShadowofDeath.ActionCheck, + }; + + /// + /// 灵魂钐割 + /// + public static IBaseAction SoulScythe { get; } = new BaseAction(ActionID.SoulScythe) + { + ActionCheck = SoulSlice.ActionCheck, + }; + #endregion + #region 妖异之镰状态 + /// + /// 绞决 + /// + public static IBaseAction Gibbet { get; } = new BaseAction(ActionID.Gibbet) + { + StatusNeed = new[] { StatusID.SoulReaver } + }; + + /// + /// 缢杀 + /// + public static IBaseAction Gallows { get; } = new BaseAction(ActionID.Gallows) + { + StatusNeed = new[] { StatusID.SoulReaver } + }; + + /// + /// 断首 + /// + public static IBaseAction Guillotine { get; } = new BaseAction(ActionID.Guillotine) + { + StatusNeed = new[] { StatusID.SoulReaver } + }; + #endregion + #region 红条50灵魂 + /// + /// 隐匿挥割 + /// + public static IBaseAction BloodStalk { get; } = new BaseAction(ActionID.BloodStalk) + { + StatusProvide = new[] { StatusID.SoulReaver }, + ActionCheck = b => !SoulReaver && !Enshrouded && Soul >= 50 + }; + + /// + /// 束缚挥割 + /// + public static IBaseAction GrimSwathe { get; } = new BaseAction(ActionID.GrimSwathe) + { + StatusProvide = new[] { StatusID.SoulReaver }, + ActionCheck = BloodStalk.ActionCheck, + }; + + /// + /// 暴食 + /// + public static IBaseAction Gluttony { get; } = new BaseAction(ActionID.Gluttony) + { + StatusProvide = new[] { StatusID.SoulReaver }, + ActionCheck = BloodStalk.ActionCheck, + }; + #endregion + #region 大爆发 + /// + /// 神秘环 + /// + public static IBaseAction ArcaneCircle { get; } = new BaseAction(ActionID.ArcaneCircle, true) + { + StatusProvide = new[] { StatusID.CircleofSacrifice, StatusID.BloodsownCircle } + }; + + /// + /// 大丰收 + /// + public static IBaseAction PlentifulHarvest { get; } = new BaseAction(ActionID.PlentifulHarvest) + { + StatusNeed = new[] { StatusID.ImmortalSacrifice }, + ActionCheck = b => !Player.HasStatus(true, StatusID.BloodsownCircle) + }; + #endregion + #region 蓝条50附体 + /// + /// 夜游魂衣 + /// + public static IBaseAction Enshroud { get; } = new BaseAction(ActionID.Enshroud) + { + StatusProvide = new[] { StatusID.Enshrouded }, + ActionCheck = b => Shroud >= 50 && !SoulReaver && !Enshrouded + }; + + /// + /// 团契 + /// + public static IBaseAction Communio { get; } = new BaseAction(ActionID.Communio) + { + StatusNeed = new[] { StatusID.Enshrouded }, + ActionCheck = b => LemureShroud == 1 + }; + + /// + /// 夜游魂切割 + /// + public static IBaseAction LemuresSlice { get; } = new BaseAction(ActionID.LemuresSlice) + { + StatusNeed = new[] { StatusID.Enshrouded }, + ActionCheck = b => VoidShroud >= 2, + }; + + /// + /// 夜游魂钐割 + /// + public static IBaseAction LemuresScythe { get; } = new BaseAction(ActionID.LemuresScythe) + { + StatusNeed = new[] { StatusID.Enshrouded }, + ActionCheck = b => VoidShroud >= 2, + }; + + /// + /// 虚无收割 + /// + public static IBaseAction VoidReaping { get; } = new BaseAction(ActionID.VoidReaping) + { + StatusNeed = new[] { StatusID.Enshrouded }, + }; + + /// + /// 交错收割 + /// + public static IBaseAction CrossReaping { get; } = new BaseAction(ActionID.CrossReaping) + { + StatusNeed = new[] { StatusID.Enshrouded }, + }; + + /// + /// 阴冷收割 + /// + public static IBaseAction GrimReaping { get; } = new BaseAction(ActionID.GrimReaping) + { + StatusNeed = new[] { StatusID.Enshrouded }, + }; + #endregion + #region 杂项 + /// + /// 勾刃 + /// + public static IBaseAction Harpe { get; } = new BaseAction(ActionID.Harpe) + { + ActionCheck = b => !SoulReaver, + FilterForHostiles = TargetFilter.MeleeRangeTargetFilter, + }; + + /// + /// 地狱入境 + /// + public static IBaseAction HellsIngress { get; } = new BaseAction(ActionID.HellsIngress) + { + StatusProvide = new[] { StatusID.EnhancedHarpe }, + ActionCheck = b => !Player.HasStatus(true, StatusID.Bind1, StatusID.Bind2) + }; + + /// + /// 地狱出境 + /// + public static IBaseAction HellsEgress { get; } = new BaseAction(ActionID.HellsEgress) + { + StatusProvide = HellsIngress.StatusProvide, + ActionCheck = HellsIngress.ActionCheck + }; + + /// + /// 播魂种 + /// + public static IBaseAction Soulsow { get; } = new BaseAction(ActionID.Soulsow) + { + StatusProvide = new[] { StatusID.Soulsow }, + ActionCheck = b => !InCombat, + }; + + /// + /// 收获月 + /// + public static IBaseAction HarvestMoon { get; } = new BaseAction(ActionID.HarvestMoon) + { + StatusNeed = new[] { StatusID.Soulsow }, + }; + + /// + /// 神秘纹 加盾 + /// + public static IBaseAction ArcaneCrest { get; } = new BaseAction(ActionID.ArcaneCrest, true, isTimeline: true); + #endregion + + [RotationDesc(ActionID.HellsIngress)] + protected sealed override bool MoveForwardAbility(byte abilitiesRemaining, out IAction act, bool recordTarget = true) + { + //E上去 + if (HellsIngress.CanUse(out act, emptyOrSkipCombo: true, recordTarget: recordTarget)) return true; + return false; + } + + [RotationDesc(ActionID.Feint)] + protected sealed override bool DefenseAreaAbility(byte abilitiesRemaining, out IAction act) + { + if (!SoulReaver && !Enshrouded) + { + if (Feint.CanUse(out act)) return true; + } + + act = null; + return false; + } + + [RotationDesc(ActionID.ArcaneCrest)] + protected override bool DefenseSingleAbility(byte abilitiesRemaining, out IAction act) + { + if (!SoulReaver && !Enshrouded) + { + if (ArcaneCrest.CanUse(out act)) return true; + } + + return base.DefenseSingleAbility(abilitiesRemaining, out act); + } +} diff --git a/RotationSolver.Basic/Rotations/Basic/SAM_Base.cs b/RotationSolver.Basic/Rotations/Basic/SAM_Base.cs new file mode 100644 index 000000000..9fbdd5af0 --- /dev/null +++ b/RotationSolver.Basic/Rotations/Basic/SAM_Base.cs @@ -0,0 +1,312 @@ +using Dalamud.Game.ClientState.JobGauge.Types; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.CustomRotation; + +namespace RotationSolver.Rotations.Basic; + +public abstract class SAM_Base : CustomRotation.CustomRotation +{ + private static SAMGauge JobGauge => Service.JobGauges.Get(); + public override MedicineType MedicineType => MedicineType.Strength; + + + /// + /// ѩ�� + /// + protected static bool HasSetsu => JobGauge.HasSetsu; + + /// + /// ���� + /// + protected static bool HasGetsu => JobGauge.HasGetsu; + + /// + /// ���� + /// + protected static bool HasKa => JobGauge.HasKa; + + /// + /// ���� + /// + protected static byte Kenki => JobGauge.Kenki; + + /// + /// ��ѹ + /// + protected static byte MeditationStacks => JobGauge.MeditationStacks; + + public sealed override ClassJobID[] JobIDs => new ClassJobID[] { ClassJobID.Samurai }; + + /// + /// �������� + /// + protected static byte SenCount => (byte)((HasGetsu ? 1 : 0) + (HasSetsu ? 1 : 0) + (HasKa ? 1 : 0)); + + protected static bool HaveMoon => Player.HasStatus(true, StatusID.Fugetsu); + protected static float MoonTime => Player.StatusTime(true, StatusID.Fugetsu); + protected static bool HaveFlower => Player.HasStatus(true, StatusID.Fuka); + protected static float FlowerTime => Player.StatusTime(true, StatusID.Fuka); + + #region ���� + /// + /// �з� + /// + public static IBaseAction Hakaze { get; } = new BaseAction(ActionID.Hakaze); + + /// + /// ��� + /// + public static IBaseAction Jinpu { get; } = new BaseAction(ActionID.Jinpu); + + /// + /// �¹� + /// + public static IBaseAction Gekko { get; } = new BaseAction(ActionID.Gekko); + + /// + /// ʿ�� + /// + public static IBaseAction Shifu { get; } = new BaseAction(ActionID.Shifu); + + /// + /// ���� + /// + public static IBaseAction Kasha { get; } = new BaseAction(ActionID.Kasha); + + /// + /// ѩ�� + /// + public static IBaseAction Yukikaze { get; } = new BaseAction(ActionID.Yukikaze); + + /// + /// ���� + /// + public static IBaseAction Shoha { get; } = new BaseAction(ActionID.Shoha) + { + ActionCheck = b => MeditationStacks == 3 + }; + #endregion + + #region AoE + + /// + /// ���� + /// + public static IBaseAction Fuga { get; } = new BaseAction(ActionID.Fuga); + + /// + /// ��� + /// + public static IBaseAction Fuko { get; } = new BaseAction(ActionID.Fuko); + + /// + /// ���� + /// + public static IBaseAction Mangetsu { get; } = new BaseAction(ActionID.Mangetsu) + { + ComboIds = new[] + { + ActionID.Fuga,ActionID.Fuko + } + }; + /// + /// ӣ�� + /// + public static IBaseAction Oka { get; } = new BaseAction(ActionID.Oka) + { + ComboIds = new[] + { + ActionID.Fuga,ActionID.Fuko + } + }; + + /// + /// �������� + /// + public static IBaseAction Shoha2 { get; } = new BaseAction(ActionID.Shoha2) + { + ActionCheck = b => MeditationStacks == 3 + }; + + /// + /// ����ն�� + /// + public static IBaseAction OgiNamikiri { get; } = new BaseAction(ActionID.OgiNamikiri) + { + StatusNeed = new[] { StatusID.OgiNamikiriReady }, + ActionCheck = b => !IsMoving + }; + + /// + /// �ط�ն�� + /// + public static IBaseAction KaeshiNamikiri { get; } = new BaseAction(ActionID.KaeshiNamikiri) + { + ActionCheck = b => JobGauge.Kaeshi == Dalamud.Game.ClientState.JobGauge.Enums.Kaeshi.NAMIKIRI + }; + #endregion + + #region �Ӻ��� + /// + /// �˰��� + /// + public static IBaseAction Higanbana { get; } = new BaseAction(ActionID.Higanbana, isEot: true) + { + ActionCheck = b => !IsMoving && SenCount == 1, + TargetStatus = new[] { StatusID.Higanbana }, + }; + + /// + /// �����彣 + /// + public static IBaseAction TenkaGoken { get; } = new BaseAction(ActionID.TenkaGoken) + { + ActionCheck = b => !IsMoving && SenCount == 2, + }; + + /// + /// ����ѩ�»� + /// + public static IBaseAction MidareSetsugekka { get; } = new BaseAction(ActionID.MidareSetsugekka) + { + ActionCheck = b => !IsMoving && SenCount == 3, + }; + + /// + /// ��ط� + /// + public static IBaseAction TsubameGaeshi { get; } = new BaseAction(ActionID.TsubameGaeshi); + + /// + /// �ط��彣 + /// + public static IBaseAction KaeshiGoken { get; } = new BaseAction(ActionID.KaeshiGoken) + { + ActionCheck = b => JobGauge.Kaeshi == Dalamud.Game.ClientState.JobGauge.Enums.Kaeshi.GOKEN + }; + + /// + /// �ط�ѩ�»� + /// + public static IBaseAction KaeshiSetsugekka { get; } = new BaseAction(ActionID.KaeshiSetsugekka) + { + ActionCheck = b => JobGauge.Kaeshi == Dalamud.Game.ClientState.JobGauge.Enums.Kaeshi.SETSUGEKKA + }; + #endregion + + #region ���� + /// + /// ���� + /// + public static IBaseAction ThirdEye { get; } = new BaseAction(ActionID.ThirdEye, true, isTimeline: true); + + /// + /// ��� + /// + public static IBaseAction Enpi { get; } = new BaseAction(ActionID.Enpi) + { + FilterForHostiles = TargetFilter.MeleeRangeTargetFilter, + }; + + /// + /// ����ֹˮ + /// + public static IBaseAction MeikyoShisui { get; } = new BaseAction(ActionID.MeikyoShisui) + { + StatusProvide = new[] { StatusID.MeikyoShisui }, + }; + + /// + /// Ҷ�� + /// + public static IBaseAction Hagakure { get; } = new BaseAction(ActionID.Hagakure) + { + ActionCheck = b => SenCount > 0 + }; + + /// + /// �������� + /// + public static IBaseAction Ikishoten { get; } = new BaseAction(ActionID.Ikishoten) + { + StatusProvide = new[] { StatusID.OgiNamikiriReady }, + ActionCheck = b => InCombat + }; + #endregion + + #region ��ɱ�� + /// + /// ��ɱ�������� + /// + public static IBaseAction HissatsuShinten { get; } = new BaseAction(ActionID.HissatsuShinten) + { + ActionCheck = b => Kenki >= 25 + }; + + /// + /// ��ɱ�������� + /// + public static IBaseAction HissatsuGyoten { get; } = new BaseAction(ActionID.HissatsuGyoten) + { + ActionCheck = b => Kenki >= 10 && !Player.HasStatus(true, StatusID.Bind1, StatusID.Bind2) + }; + + /// + /// ��ɱ����ҹ�� + /// + public static IBaseAction HissatsuYaten { get; } = new BaseAction(ActionID.HissatsuYaten) + { + ActionCheck = HissatsuGyoten.ActionCheck + }; + + /// + /// ��ɱ�������� + /// + public static IBaseAction HissatsuKyuten { get; } = new BaseAction(ActionID.HissatsuKyuten) + { + ActionCheck = b => Kenki >= 25 + }; + + /// + /// ��ɱ�������� + /// + public static IBaseAction HissatsuGuren { get; } = new BaseAction(ActionID.HissatsuGuren) + { + ActionCheck = b => Kenki >= 25 + }; + + /// + /// ��ɱ������Ӱ + /// + public static IBaseAction HissatsuSenei { get; } = new BaseAction(ActionID.HissatsuSenei) + { + ActionCheck = b => Kenki >= 25 + }; + #endregion + + [RotationDesc(ActionID.HissatsuGyoten)] + protected sealed override bool MoveForwardAbility(byte abilitiesRemaining, out IAction act, bool recordTarget = true) + { + if (HissatsuGyoten.CanUse(out act, emptyOrSkipCombo: true, recordTarget: recordTarget)) return true; + return false; + } + + [RotationDesc(ActionID.Feint)] + protected sealed override bool DefenseAreaAbility(byte abilitiesRemaining, out IAction act) + { + if (Feint.CanUse(out act)) return true; + return false; + } + + [RotationDesc(ActionID.ThirdEye)] + protected override bool DefenseSingleAbility(byte abilitiesRemaining, out IAction act) + { + if (ThirdEye.CanUse(out act)) return true; + return false; + } +} \ No newline at end of file diff --git a/RotationSolver.Basic/Rotations/Basic/SCH_Base.cs b/RotationSolver.Basic/Rotations/Basic/SCH_Base.cs new file mode 100644 index 000000000..993fe6442 --- /dev/null +++ b/RotationSolver.Basic/Rotations/Basic/SCH_Base.cs @@ -0,0 +1,247 @@ +using Dalamud.Game.ClientState.JobGauge.Types; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.CustomRotation; + +namespace RotationSolver.Rotations.Basic; + +public abstract class SCH_Base : CustomRotation.CustomRotation +{ + private static SCHGauge JobGauge => Service.JobGauges.Get(); + + public override MedicineType MedicineType => MedicineType.Mind; + + + /// + /// 契约槽 + /// + protected static byte FairyGauge => JobGauge.FairyGauge; + + public sealed override ClassJobID[] JobIDs => new ClassJobID[] { ClassJobID.Scholar }; + + private sealed protected override IBaseAction Raise => Resurrection; + /// + /// 有豆子 + /// + protected static bool HasAetherflow => JobGauge.Aetherflow > 0; + /// + /// 有大天使 + /// + protected static bool HasSeraph => JobGauge.SeraphTimer > 0; + + #region 治疗 + /// + /// 医术 + /// + public static IBaseAction Physick { get; } = new BaseAction(ActionID.Physick, true, isTimeline: true); + + /// + /// 鼓舞激励之策 + /// + public static IBaseAction Adloquium { get; } = new BaseAction(ActionID.Adloquium, true, isTimeline: true) + { + ActionCheck = b => !b.HasStatus(false, StatusID.EukrasianDiagnosis, + StatusID.EukrasianPrognosis, + StatusID.Galvanize), + }; + + + /// + /// 复生 + /// + public static IBaseAction Resurrection { get; } = new BaseAction(ActionID.Resurrection, true); + + /// + /// 士气高扬之策 + /// + public static IBaseAction Succor { get; } = new BaseAction(ActionID.Succor, true, isTimeline: true) + { + StatusProvide = new[] { StatusID.Galvanize }, + }; + + /// + /// 生命活性法 + /// + public static IBaseAction Lustrate { get; } = new BaseAction(ActionID.Lustrate, true, isTimeline: true) + { + ActionCheck = b => HasAetherflow + }; + + /// + /// 野战治疗阵 + /// + public static IBaseAction SacredSoil { get; } = new BaseAction(ActionID.SacredSoil, true, isTimeline: true) + { + ActionCheck = b => HasAetherflow && !IsMoving, + }; + + /// + /// 不屈不挠之策 + /// + public static IBaseAction Indomitability { get; } = new BaseAction(ActionID.Indomitability, true, isTimeline: true) + { + ActionCheck = b => HasAetherflow + }; + + /// + /// 深谋远虑之策 + /// + public static IBaseAction Excogitation { get; } = new BaseAction(ActionID.Excogitation, true, isTimeline: true) + { + ActionCheck = b => HasAetherflow + }; + + /// + /// 慰藉 + /// + public static IBaseAction Consolation { get; } = new BaseAction(ActionID.Consolation, true, isTimeline: true) + { + ActionCheck = b => HasSeraph, + }; + + /// + /// 生命回生法 + /// + public static IBaseAction Protraction { get; } = new BaseAction(ActionID.Protraction, true, isTimeline: true); + #endregion + #region 进攻 + /// + /// 毒菌 猛毒菌 蛊毒法 + /// + public static IBaseAction Bio { get; } = new BaseAction(ActionID.Bio, isEot: true) + { + TargetStatus = new StatusID[] { StatusID.Bio, StatusID.Bio2, StatusID.Biolysis }, + }; + + /// + /// 毁灭 气炎法 魔炎法 死炎法 极炎法 + /// + public static IBaseAction Ruin { get; } = new BaseAction(ActionID.Ruin); + + /// + /// 毁坏 + /// + public static IBaseAction Ruin2 { get; } = new BaseAction(ActionID.Ruin2); + + /// + /// 能量吸收 + /// + public static IBaseAction EnergyDrain { get; } = new BaseAction(ActionID.EnergyDrain) + { + ActionCheck = b => HasAetherflow + }; + + /// + /// 破阵法 + /// + public static IBaseAction ArtofWar { get; } = new BaseAction(ActionID.ArtofWar);//裂阵法 25866 + #endregion + #region 仙女 + /// + /// 炽天召唤 + /// + public static IBaseAction SummonSeraph { get; } = new BaseAction(ActionID.SummonSeraph, true, isTimeline: true) + { + ActionCheck = b => DataCenter.HasPet, + }; + + /// + /// 朝日召唤 + /// + public static IBaseAction SummonEos { get; } = new BaseAction(ActionID.SummonEos)//夕月召唤 17216 + { + ActionCheck = b => !DataCenter.HasPet && (!Player.HasStatus(true, StatusID.Dissipation) || Dissipation.WillHaveOneCharge(30) && Dissipation.EnoughLevel), + }; + + /// + /// 仙光的低语/天使的低语 + /// + public static IBaseAction WhisperingDawn { get; } = new BaseAction(ActionID.WhisperingDawn, isTimeline: true) + { + ActionCheck = b => DataCenter.HasPet, + }; + + /// + /// 异想的幻光/炽天的幻光 + /// + public static IBaseAction FeyIllumination { get; } = new BaseAction(ActionID.FeyIllumination, isTimeline: true) + { + ActionCheck = b => DataCenter.HasPet, + }; + + /// + /// 转化 + /// + public static IBaseAction Dissipation { get; } = new BaseAction(ActionID.Dissipation) + { + StatusProvide = new[] { StatusID.Dissipation }, + ActionCheck = b => !HasAetherflow && !HasSeraph && InCombat && DataCenter.HasPet, + }; + + /// + /// 以太契约-异想的融光 + /// + public static IBaseAction Aetherpact { get; } = new BaseAction(ActionID.Aetherpact, true, isTimeline: true) + { + ActionCheck = b => JobGauge.FairyGauge >= 10 && DataCenter.HasPet && !HasSeraph + }; + + /// + /// 异想的祥光 + /// + public static IBaseAction FeyBlessing { get; } = new BaseAction(ActionID.FeyBlessing, isTimeline: true) + { + ActionCheck = b => !HasSeraph && DataCenter.HasPet, + }; + #endregion + #region 其他 + /// + /// 以太超流 + /// + public static IBaseAction Aetherflow { get; } = new BaseAction(ActionID.Aetherflow) + { + ActionCheck = b => InCombat && !HasAetherflow + }; + + /// + /// 秘策 + /// + public static IBaseAction Recitation { get; } = new BaseAction(ActionID.Recitation, isTimeline: true); + + /// + /// 连环计 + /// + public static IBaseAction ChainStratagem { get; } = new BaseAction(ActionID.ChainStratagem) + { + ActionCheck = b => InCombat && IsTargetBoss + }; + + /// + /// 展开战术 + /// + public static IBaseAction DeploymentTactics { get; } = new BaseAction(ActionID.DeploymentTactics, true, isTimeline: true) + { + ChoiceTarget = (friends, mustUse) => + { + foreach (var friend in friends) + { + if (friend.HasStatus(true, StatusID.Galvanize)) return friend; + } + return null; + }, + }; + + /// + /// 应急战术 + /// + public static IBaseAction EmergencyTactics { get; } = new BaseAction(ActionID.EmergencyTactics, isTimeline: true); + + /// + /// 疾风怒涛之计 + /// + public static IBaseAction Expedient { get; } = new BaseAction(ActionID.Expedient, isTimeline: true); + #endregion +} \ No newline at end of file diff --git a/RotationSolver.Basic/Rotations/Basic/SGE_Base.cs b/RotationSolver.Basic/Rotations/Basic/SGE_Base.cs new file mode 100644 index 000000000..900445ad0 --- /dev/null +++ b/RotationSolver.Basic/Rotations/Basic/SGE_Base.cs @@ -0,0 +1,262 @@ +using Dalamud.Game.ClientState.JobGauge.Types; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.CustomRotation; + +namespace RotationSolver.Rotations.Basic; + +public abstract class SGE_Base : CustomRotation.CustomRotation +{ + private static SGEGauge JobGauge => Service.JobGauges.Get(); + + protected static bool HasEukrasia => JobGauge.Eukrasia; + protected static byte Addersgall => JobGauge.Addersgall; + + protected static byte Addersting => JobGauge.Addersting; + public override MedicineType MedicineType => MedicineType.Strength; + + + /// + /// ���ӵ���ʱ���ж������һ�Ű� + /// + /// + /// + protected static bool AddersgallEndAfter(float time) + { + return EndAfter(JobGauge.AddersgallTimer / 1000f, time); + } + + /// + /// ���ӵ���ʱ���ж������һ�Ű� + /// + /// + /// + /// + protected static bool AddersgallEndAfterGCD(uint gctCount = 0, uint abilityCount = 0) + { + return EndAfterGCD(JobGauge.AddersgallTimer / 1000f, gctCount, abilityCount); + } + + public sealed override ClassJobID[] JobIDs => new ClassJobID[] { ClassJobID.Sage }; + private sealed protected override IBaseAction Raise => Egeiro; + + /// + /// ���� + /// + public static IBaseAction Egeiro { get; } = new BaseAction(ActionID.Egeiro, true); + + /// + /// עҩ + /// + public static IBaseAction Dosis { get; } = new BaseAction(ActionID.Dosis); + + /// + /// ����עҩ + /// + public static IBaseAction EukrasianDosis { get; } = new BaseAction(ActionID.EukrasianDosis, isEot: true) + { + TargetStatus = new StatusID[] + { + StatusID.EukrasianDosis, + StatusID.EukrasianDosis2, + StatusID.EukrasianDosis3 + }, + }; + + /// + /// ���� + /// + public static IBaseAction Phlegma { get; } = new BaseAction(ActionID.Phlegma); + + /// + /// ����2 + /// + public static IBaseAction Phlegma2 { get; } = new BaseAction(ActionID.Phlegma2); + + /// + /// ����3 + /// + public static IBaseAction Phlegma3 { get; } = new BaseAction(ActionID.Phlegma3); + + /// + /// ��� + /// + public static IBaseAction Diagnosis { get; } = new BaseAction(ActionID.Diagnosis, true); + + /// + /// �Ĺ� + /// + public static IBaseAction Kardia { get; } = new BaseAction(ActionID.Kardia, true) + { + StatusProvide = new StatusID[] { StatusID.Kardia }, + ChoiceTarget = (Targets, mustUse) => + { + var targets = Targets.GetJobCategory(JobRole.Tank); + targets = targets.Any() ? targets : Targets; + + if (!targets.Any()) return null; + + return TargetFilter.FindAttackedTarget(targets, mustUse); + }, + ActionCheck = b => !b.HasStatus(true, StatusID.Kardion), + }; + + /// + /// Ԥ�� + /// + public static IBaseAction Prognosis { get; } = new BaseAction(ActionID.Prognosis, true, shouldEndSpecial: true, isTimeline: true); + + /// + /// ���� + /// + public static IBaseAction Physis { get; } = new BaseAction(ActionID.Physis, true, isTimeline: true); + + /// + /// ���� + /// + public static IBaseAction Eukrasia { get; } = new BaseAction(ActionID.Eukrasia, true, isTimeline: true) + { + ActionCheck = b => !JobGauge.Eukrasia, + }; + + /// + /// ���� + /// + public static IBaseAction Soteria { get; } = new BaseAction(ActionID.Soteria, true, isTimeline: true); + + /// + /// ���� + /// + public static IBaseAction Icarus { get; } = new BaseAction(ActionID.Icarus, shouldEndSpecial: true) + { + ChoiceTarget = TargetFilter.FindTargetForMoving, + }; + + /// + /// ������֭ + /// + public static IBaseAction Druochole { get; } = new BaseAction(ActionID.Druochole, true, isTimeline: true) + { + ActionCheck = b => JobGauge.Addersgall > 0, + }; + + /// + /// ʧ�� + /// + public static IBaseAction Dyskrasia { get; } = new BaseAction(ActionID.Dyskrasia); + + /// + /// �����֭ + /// + public static IBaseAction Kerachole { get; } = new BaseAction(ActionID.Kerachole, true, isTimeline: true) + { + ActionCheck = b => JobGauge.Addersgall > 0, + }; + + /// + /// ������֭ + /// + public static IBaseAction Ixochole { get; } = new BaseAction(ActionID.Ixochole, true, isTimeline: true) + { + ActionCheck = b => JobGauge.Addersgall > 0, + }; + + /// + /// � + /// + public static IBaseAction Zoe { get; } = new BaseAction(ActionID.Zoe, isTimeline: true); + + /// + /// ��ţ��֭ + /// + public static IBaseAction Taurochole { get; } = new BaseAction(ActionID.Taurochole, true, isTimeline: true) + { + ChoiceTarget = TargetFilter.FindAttackedTarget, + ActionCheck = b => JobGauge.Addersgall > 0, + }; + + /// + /// ���� + /// + public static IBaseAction Toxikon { get; } = new BaseAction(ActionID.Toxikon) + { + ActionCheck = b => JobGauge.Addersting > 0, + }; + + /// + /// ��Ѫ + /// + public static IBaseAction Haima { get; } = new BaseAction(ActionID.Haima, true, isTimeline: true) + { + ChoiceTarget = TargetFilter.FindAttackedTarget, + }; + + /// + /// ������� + /// + public static IBaseAction EukrasianDiagnosis { get; } = new BaseAction(ActionID.EukrasianDiagnosis, true, isTimeline: true) + { + ChoiceTarget = TargetFilter.FindAttackedTarget, + }; + + /// + /// ����Ԥ�� + /// + public static IBaseAction EukrasianPrognosis { get; } = new BaseAction(ActionID.EukrasianPrognosis, true, isTimeline: true); + + /// + /// ���� + /// + public static IBaseAction Rhizomata { get; } = new BaseAction(ActionID.Rhizomata, isTimeline: true) + { + ActionCheck = b => JobGauge.Addersgall < 3, + }; + + /// + /// ������ + /// + public static IBaseAction Holos { get; } = new BaseAction(ActionID.Holos, true, isTimeline: true); + + /// + /// ����Ѫ + /// + public static IBaseAction Panhaima { get; } = new BaseAction(ActionID.Panhaima, true, isTimeline: true); + + /// + /// ��� + /// + public static IBaseAction Krasis { get; } = new BaseAction(ActionID.Krasis, true, isTimeline: true); + + /// + /// �����Ϣ + /// + public static IBaseAction Pneuma { get; } = new BaseAction(ActionID.Pneuma, isTimeline: true); + + /// + /// ���� + /// + public static IBaseAction Pepsis { get; } = new BaseAction(ActionID.Pepsis, true, isTimeline: true) + { + ActionCheck = b => + { + foreach (var chara in DataCenter.PartyMembers) + { + if (chara.HasStatus(true, StatusID.EukrasianDiagnosis, StatusID.EukrasianPrognosis) + && b.WillStatusEndGCD(2, 0, true, StatusID.EukrasianDiagnosis, StatusID.EukrasianPrognosis) + && chara.GetHealthRatio() < 0.9) return true; + } + + return false; + }, + }; + + protected sealed override bool MoveForwardAbility(byte abilitiesRemaining, out IAction act, bool recordTarget = true) + { + //���� + if (Icarus.CanUse(out act, emptyOrSkipCombo: true, recordTarget: recordTarget)) return true; + return false; + } +} diff --git a/RotationSolver.Basic/Rotations/Basic/SMN_Base.cs b/RotationSolver.Basic/Rotations/Basic/SMN_Base.cs new file mode 100644 index 000000000..a468c37c7 --- /dev/null +++ b/RotationSolver.Basic/Rotations/Basic/SMN_Base.cs @@ -0,0 +1,323 @@ +using Dalamud.Game.ClientState.JobGauge.Types; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Rotations.CustomRotation; + +namespace RotationSolver.Rotations.Basic; + +public abstract class SMN_Base : CustomRotation.CustomRotation +{ + private static SMNGauge JobGauge => Service.JobGauges.Get(); + + public override MedicineType MedicineType => MedicineType.Strength; + + + /// + /// 有以太超流 + /// + protected static bool HasAetherflowStacks => JobGauge.HasAetherflowStacks; + + /// + /// 属性以太 + /// + protected static byte Attunement => JobGauge.Attunement; + + /// + /// 蛮神多久后会消失 + /// + /// + /// + protected static bool SummonTimeEndAfter(float time) + { + return EndAfter(JobGauge.SummonTimerRemaining / 1000f, time); + } + + /// + /// 蛮神剩余时间 + /// + protected static ushort SummonTimerRemaining => JobGauge.SummonTimerRemaining; + + /// + /// 属性剩余时间 + /// + protected static ushort AttunmentTimerRemaining => JobGauge.AttunmentTimerRemaining; + + public sealed override ClassJobID[] JobIDs => new ClassJobID[] { ClassJobID.Summoner, ClassJobID.Arcanist }; + protected override bool CanHealSingleSpell => false; + private sealed protected override IBaseAction Raise => Resurrection; + + /// + /// 宝石兽处于同行状态 + /// + protected static bool HaveSummon => DataCenter.HasPet && JobGauge.SummonTimerRemaining == 0; + + /// + /// 龙神附体状态 + /// + protected static bool InBahamut => Service.GetAdjustedActionId(ActionID.AstralFlow) == ActionID.Deathflare; + + /// + /// 不死鸟附体状态 + /// + protected static bool InPhoenix => Service.GetAdjustedActionId(ActionID.AstralFlow) == ActionID.Rekindle; + + /// + /// 火神可用 + /// + protected static bool IsIfritReady => JobGauge.IsIfritReady; + + /// + /// 土神可用 + /// + protected static bool IsTitanReady => JobGauge.IsTitanReady; + + /// + /// 风神可用 + /// + protected static bool IsGarudaReady => JobGauge.IsGarudaReady; + + /// + /// 火神阶段 + /// + protected static bool InIfrit => JobGauge.IsIfritAttuned; + + /// + /// 土神阶段 + /// + protected static bool InTitan => JobGauge.IsTitanAttuned; + + /// + /// 风神阶段 + /// + protected static bool InGaruda => JobGauge.IsGarudaAttuned; + + #region 召唤兽 + /// + /// 红宝石召唤 火神召唤 + /// + public static IBaseAction SummonRuby { get; } = new BaseAction(ActionID.SummonRuby) + { + StatusProvide = new[] { StatusID.IfritsFavor }, + ActionCheck = b => HaveSummon && IsIfritReady + }; + + /// + /// 黄宝石召唤 土神召唤 + /// + public static IBaseAction SummonTopaz { get; } = new BaseAction(ActionID.SummonTopaz) + { + ActionCheck = b => HaveSummon && IsTitanReady, + }; + + /// + /// 绿宝石召唤 风神召唤 + /// + public static IBaseAction SummonEmerald { get; } = new BaseAction(ActionID.SummonEmerald) + { + StatusProvide = new[] { StatusID.GarudasFavor }, + ActionCheck = b => HaveSummon && IsGarudaReady, + }; + + /// + /// 宝石兽召唤 + /// + public static IBaseAction SummonCarbuncle { get; } = new BaseAction(ActionID.SummonCarbuncle) + { + ActionCheck = b => !DataCenter.HasPet, + }; + #endregion + #region 召唤兽能力 + /// + /// 宝石耀 单体 + /// + public static IBaseAction Gemshine { get; } = new BaseAction(ActionID.Gemshine) + { + ActionCheck = b => Attunement > 0, + }; + + /// + /// 宝石辉 AoE + /// + public static IBaseAction PreciousBrilliance { get; } = new BaseAction(ActionID.PreciousBrilliance) + { + ActionCheck = b => Attunement > 0, + }; + + /// + /// 以太蓄能 龙神附体 + /// + public static IBaseAction Aethercharge { get; } = new BaseAction(ActionID.Aethercharge) + { + ActionCheck = b => InCombat && HaveSummon + }; + + /// + /// 龙神召唤 不死鸟召唤 + /// + public static IBaseAction SummonBahamut { get; } = new BaseAction(ActionID.SummonBahamut) + { + ActionCheck = b => InCombat && HaveSummon + }; + /// + /// 龙神迸发 不死鸟迸发 + /// + public static IBaseAction EnkindleBahamut { get; } = new BaseAction(ActionID.EnkindleBahamut) + { + ActionCheck = b => InBahamut || InPhoenix, + }; + + #endregion + #region 召唤兽星极超流 + /// + /// 死星核爆 + /// + public static IBaseAction Deathflare { get; } = new BaseAction(ActionID.Deathflare) + { + ActionCheck = b => InBahamut, + }; + + /// + /// 苏生之炎 + /// + public static IBaseAction Rekindle { get; } = new BaseAction(ActionID.Rekindle, true) + { + ActionCheck = b => InPhoenix, + }; + + /// + /// 深红旋风 + /// + public static IBaseAction CrimsonCyclone { get; } = new BaseAction(ActionID.CrimsonCyclone) + { + StatusNeed = new[] { StatusID.IfritsFavor }, + }; + + /// + /// 深红强袭 + /// + public static IBaseAction CrimsonStrike { get; } = new BaseAction(ActionID.CrimsonStrike); + + /// + /// 山崩 + /// + public static IBaseAction MountainBuster { get; } = new BaseAction(ActionID.MountainBuster) + { + StatusNeed = new[] { StatusID.TitansFavor }, + }; + + /// + /// 螺旋气流 + /// + public static IBaseAction Slipstream { get; } = new BaseAction(ActionID.Slipstream) + { + StatusNeed = new[] { StatusID.GarudasFavor }, + }; + + #endregion + #region 基础技能 + /// + /// 毁灭 毁坏 毁荡 + /// + public static IBaseAction Ruin { get; } = new BaseAction(ActionID.RuinSMN); + + /// + /// 毁绝 + /// + public static IBaseAction RuinIV { get; } = new BaseAction(ActionID.RuinIV) + { + StatusNeed = new[] { StatusID.FurtherRuin }, + }; + + /// + /// 迸裂 三重灾祸 + /// + public static IBaseAction Outburst { get; } = new BaseAction(ActionID.Outburst); + + #endregion + #region 能力技 + /// + /// 灼热之光 团辅 + /// + public static IBaseAction SearingLight { get; } = new BaseAction(ActionID.SearingLight, true) + { + StatusProvide = new[] { StatusID.SearingLight }, + ActionCheck = b => InCombat, + }; + + /// + /// 守护之光 + /// + public static IBaseAction RadiantAegis { get; } = new BaseAction(ActionID.RadiantAegis, true, isTimeline: true) + { + ActionCheck = b => HaveSummon + }; + + /// + /// 能量吸收 + /// + public static IBaseAction EnergyDrain { get; } = new BaseAction(ActionID.EnergyDrainSMN) + { + StatusProvide = new[] { StatusID.FurtherRuin }, + ActionCheck = b => !HasAetherflowStacks + }; + + /// + /// 溃烂爆发 + /// + public static IBaseAction Fester { get; } = new BaseAction(ActionID.Fester) + { + ActionCheck = b => HasAetherflowStacks + }; + + /// + /// 能量抽取 + /// + public static IBaseAction EnergySiphon { get; } = new BaseAction(ActionID.EnergySiphon) + { + StatusProvide = new[] { StatusID.FurtherRuin }, + ActionCheck = b => !HasAetherflowStacks + }; + + /// + /// 痛苦核爆 + /// + public static IBaseAction Painflare { get; } = new BaseAction(ActionID.Painflare) + { + ActionCheck = b => HasAetherflowStacks + }; + #endregion + + /// + /// 复生 + /// + public static IBaseAction Resurrection { get; } = new BaseAction(ActionID.ResurrectionSMN, true); + + /// + /// 医术 + /// + public static IBaseAction Physick { get; } = new BaseAction(ActionID.Physick, true); + + [RotationDesc(ActionID.RadiantAegis)] + protected sealed override bool DefenseSingleAbility(byte abilitiesRemaining, out IAction act) + { + if (RadiantAegis.CanUse(out act)) return true; + return false; + } + + [RotationDesc(ActionID.Physick)] + protected override bool HealSingleGCD(out IAction act) + { + if (Physick.CanUse(out act)) return true; + return false; + } + + [RotationDesc(ActionID.Addle)] + protected override bool DefenseAreaAbility(byte abilitiesRemaining, out IAction act) + { + if (Addle.CanUse(out act)) return true; + return false; + } +} \ No newline at end of file diff --git a/RotationSolver.Basic/Rotations/Basic/WAR_Base.cs b/RotationSolver.Basic/Rotations/Basic/WAR_Base.cs new file mode 100644 index 000000000..9acfded06 --- /dev/null +++ b/RotationSolver.Basic/Rotations/Basic/WAR_Base.cs @@ -0,0 +1,198 @@ +using Dalamud.Game.ClientState.JobGauge.Types; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.CustomRotation; + +namespace RotationSolver.Rotations.Basic; + +public abstract class WAR_Base : CustomRotation.CustomRotation +{ + private static WARGauge JobGauge => Service.JobGauges.Get(); + public override MedicineType MedicineType => MedicineType.Strength; + + + public sealed override ClassJobID[] JobIDs => new ClassJobID[] { ClassJobID.Warrior, ClassJobID.Marauder }; + private sealed protected override IBaseAction Shield => Defiance; + + /// + /// �ػ� + /// + public static IBaseAction Defiance { get; } = new BaseAction(ActionID.Defiance, shouldEndSpecial: true); + + /// + /// ���� + /// + public static IBaseAction HeavySwing { get; } = new BaseAction(ActionID.HeavySwing); + + /// + /// �ײ��� + /// + public static IBaseAction Maim { get; } = new BaseAction(ActionID.Maim); + + /// + /// ����ն �̸� + /// + public static IBaseAction StormsPath { get; } = new BaseAction(ActionID.StormsPath); + + /// + /// ������ �츫 + /// + public static IBaseAction StormsEye { get; } = new BaseAction(ActionID.StormsEye) + { + ActionCheck = b => Player.WillStatusEndGCD(3, 0, true, StatusID.SurgingTempest), + }; + + /// + /// �ɸ� + /// + public static IBaseAction Tomahawk { get; } = new BaseAction(ActionID.Tomahawk) + { + FilterForHostiles = TargetFilter.TankRangeTarget, + }; + + /// + /// �͹� + /// + public static IBaseAction Onslaught { get; } = new BaseAction(ActionID.Onslaught, shouldEndSpecial: true) + { + ChoiceTarget = TargetFilter.FindTargetForMoving, + }; + + /// + /// ���� + /// + public static IBaseAction Upheaval { get; } = new BaseAction(ActionID.Upheaval) + { + StatusNeed = new StatusID[] { StatusID.SurgingTempest }, + }; + + /// + /// ��ѹ�� + /// + public static IBaseAction Overpower { get; } = new BaseAction(ActionID.Overpower); + + /// + /// �������� + /// + public static IBaseAction MythrilTempest { get; } = new BaseAction(ActionID.MythrilTempest); + + /// + /// Ⱥɽ¡�� + /// + public static IBaseAction Orogeny { get; } = new BaseAction(ActionID.Orogeny); + + /// + /// ԭ��֮�� + /// + public static IBaseAction InnerBeast { get; } = new BaseAction(ActionID.InnerBeast) + { + ActionCheck = b => JobGauge.BeastGauge >= 50 || Player.HasStatus(true, StatusID.InnerRelease), + }; + + /// + /// ԭ���Ľ�� + /// + public static IBaseAction InnerRelease { get; } = new BaseAction(ActionID.InnerRelease) + { + ActionCheck = InnerBeast.ActionCheck, + }; + + /// + /// �������� + /// + public static IBaseAction SteelCyclone { get; } = new BaseAction(ActionID.SteelCyclone) + { + ActionCheck = InnerBeast.ActionCheck, + }; + + /// + /// ս�� + /// + public static IBaseAction Infuriate { get; } = new BaseAction(ActionID.Infuriate) + { + StatusProvide = new[] { StatusID.InnerRelease }, + ActionCheck = b => HasHostilesInRange && JobGauge.BeastGauge < 50 && InCombat, + }; + + /// + /// �� + /// + public static IBaseAction Berserk { get; } = new BaseAction(ActionID.Berserk) + { + ActionCheck = b => HasHostilesInRange && !InnerRelease.IsCoolingDown, + }; + + /// + /// ս�� + /// + public static IBaseAction ThrillofBattle { get; } = new BaseAction(ActionID.ThrillofBattle, true, isTimeline: true); + + /// + /// ̩Ȼ���� + /// + public static IBaseAction Equilibrium { get; } = new BaseAction(ActionID.Equilibrium, true, isTimeline: true); + + /// + /// ԭ�������� + /// + public static IBaseAction NascentFlash { get; } = new BaseAction(ActionID.NascentFlash, isTimeline: true) + { + ChoiceTarget = TargetFilter.FindAttackedTarget, + }; + + /// + /// ���� + /// + public static IBaseAction Vengeance { get; } = new BaseAction(ActionID.Vengeance, isTimeline: true) + { + StatusProvide = Rampart.StatusProvide, + ActionCheck = BaseAction.TankDefenseSelf, + }; + + /// + /// ԭ����ֱ�� + /// + public static IBaseAction RawIntuition { get; } = new BaseAction(ActionID.RawIntuition, isTimeline: true) + { + ActionCheck = BaseAction.TankDefenseSelf, + }; + + /// + /// ���� + /// + public static IBaseAction ShakeItOff { get; } = new BaseAction(ActionID.ShakeItOff, true, isTimeline: true); + + /// + /// ���� + /// + public static IBaseAction Holmgang { get; } = new BaseAction(ActionID.Holmgang, isTimeline: true) + { + ChoiceTarget = (tars, mustUse) => Player, + }; + + /// + /// ���ı��� + /// + public static IBaseAction PrimalRend { get; } = new BaseAction(ActionID.PrimalRend) + { + StatusNeed = new[] { StatusID.PrimalRendReady } + }; + + protected override bool EmergencyAbility(byte abilitiesRemaining, IAction nextGCD, out IAction act) + { + //���� ���Ѫ�����ˡ� + if (Holmgang.CanUse(out act) && BaseAction.TankBreakOtherCheck(JobIDs[0])) return true; + return base.EmergencyAbility(abilitiesRemaining, nextGCD, out act); + } + + [RotationDesc(ActionID.Onslaught)] + protected sealed override bool MoveForwardAbility(byte abilitiesRemaining, out IAction act, bool recordTarget = true) + { + if (Onslaught.CanUse(out act, emptyOrSkipCombo: true, recordTarget: recordTarget)) return true; + return false; + } +} diff --git a/RotationSolver.Basic/Rotations/Basic/WHM_Base.cs b/RotationSolver.Basic/Rotations/Basic/WHM_Base.cs new file mode 100644 index 000000000..61875ff13 --- /dev/null +++ b/RotationSolver.Basic/Rotations/Basic/WHM_Base.cs @@ -0,0 +1,210 @@ +using Dalamud.Game.ClientState.JobGauge.Types; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.CustomRotation; + +namespace RotationSolver.Rotations.Basic; + +public abstract class WHM_Base : CustomRotation.CustomRotation +{ + private static WHMGauge JobGauge => Service.JobGauges.Get(); + public override MedicineType MedicineType => MedicineType.Mind; + + + /// + /// 百合花的数量 + /// + protected static byte Lily => JobGauge.Lily; + + /// + /// 血百合花的数量 + /// + protected static byte BloodLily => JobGauge.BloodLily; + + /// + /// 百合花多久后 + /// + /// + /// + protected static bool LilyAfter(float time) + { + return EndAfter(JobGauge.LilyTimer / 1000f, time); + } + + /// + /// 这首歌啊在多久后还在唱嘛 + /// + /// + /// + /// + protected static bool LilyAfterGCD(uint gctCount = 0, uint abilityCount = 0) + { + return EndAfterGCD(JobGauge.LilyTimer / 1000f, gctCount, abilityCount); + } + + public sealed override ClassJobID[] JobIDs => new ClassJobID[] { ClassJobID.WhiteMage, ClassJobID.Conjurer }; + private sealed protected override IBaseAction Raise => Raise1; + + #region 治疗 + /// + /// 治疗 + /// + public static IBaseAction Cure { get; } = new BaseAction(ActionID.Cure, true, isTimeline: true); + + /// + /// 医治 + /// + public static IBaseAction Medica { get; } = new BaseAction(ActionID.Medica, true, isTimeline: true); + + /// + /// 复活 + /// + public static IBaseAction Raise1 { get; } = new BaseAction(ActionID.Raise1, true); + + /// + /// 救疗 + /// + public static IBaseAction Cure2 { get; } = new BaseAction(ActionID.Cure2, true, isTimeline: true); + + /// + /// 医济 + /// + public static IBaseAction Medica2 { get; } = new BaseAction(ActionID.Medica2, true, isEot: true, isTimeline: true) + { + StatusProvide = new[] { StatusID.Medica2, StatusID.TrueMedica2 }, + }; + + /// + /// 再生 + /// + public static IBaseAction Regen { get; } = new BaseAction(ActionID.Regen, true, isEot: true, isTimeline: true) + { + TargetStatus = new[] + { + StatusID.Regen1, + StatusID.Regen2, + StatusID.Regen3, + } + }; + + /// + /// 愈疗 + /// + public static IBaseAction Cure3 { get; } = new BaseAction(ActionID.Cure3, true, shouldEndSpecial: true, isTimeline: true); + + /// + /// 天赐祝福 + /// + public static IBaseAction Benediction { get; } = new BaseAction(ActionID.Benediction, true, isTimeline: true); + + /// + /// 庇护所 + /// + public static IBaseAction Asylum { get; } = new BaseAction(ActionID.Asylum, true, isTimeline: true); + + /// + /// 安慰之心 + /// + public static IBaseAction AfflatusSolace { get; } = new BaseAction(ActionID.AfflatusSolace, true, isTimeline: true) + { + ActionCheck = b => JobGauge.Lily > 0, + }; + + /// + /// 神名 + /// + public static IBaseAction Tetragrammaton { get; } = new BaseAction(ActionID.Tetragrammaton, true, isTimeline: true); + + /// + /// 神祝祷 + /// + public static IBaseAction DivineBenison { get; } = new BaseAction(ActionID.DivineBenison, true, isTimeline: true) + { + StatusProvide = new StatusID[] { StatusID.DivineBenison }, + ChoiceTarget = TargetFilter.FindAttackedTarget, + }; + + /// + /// 狂喜之心 + /// + public static IBaseAction AfflatusRapture { get; } = new BaseAction(ActionID.AfflatusRapture, true, isTimeline: true) + { + ActionCheck = b => JobGauge.Lily > 0, + }; + + /// + /// 水流幕 + /// + public static IBaseAction Aquaveil { get; } = new BaseAction(ActionID.Aquaveil, true, isTimeline: true); + + /// + /// 礼仪之铃 + /// + public static IBaseAction LiturgyoftheBell { get; } = new BaseAction(ActionID.LiturgyoftheBell, true, isTimeline: true); + #endregion + #region 输出 + /// + /// 飞石 坚石 垒石 崩石 闪耀 闪灼 + /// + public static IBaseAction Stone { get; } = new BaseAction(ActionID.Stone); + + /// + /// 疾风 烈风 天辉 + /// + public static IBaseAction Aero { get; } = new BaseAction(ActionID.Aero, isEot: true) + { + TargetStatus = new StatusID[] + { + StatusID.Aero, + StatusID.Aero2, + StatusID.Dia, + } + }; + + /// + /// 神圣 豪圣 + /// + public static IBaseAction Holy { get; } = new BaseAction(ActionID.Holy); + + /// + /// 法令 + /// + public static IBaseAction Assize { get; } = new BaseAction(ActionID.Assize); + + /// + /// 苦难之心 + /// + public static IBaseAction AfflatusMisery { get; } = new BaseAction(ActionID.AfflatusMisery) + { + ActionCheck = b => JobGauge.BloodLily == 3, + }; + #endregion + #region buff + /// + /// 神速咏唱 + /// + public static IBaseAction PresenseOfMind { get; } = new BaseAction(ActionID.PresenseOfMind, true) + { + ActionCheck = b => !IsMoving + }; + + /// + /// 无中生有 + /// + public static IBaseAction ThinAir { get; } = new BaseAction(ActionID.ThinAir, true); + + /// + /// 全大赦 + /// + public static IBaseAction PlenaryIndulgence { get; } = new BaseAction(ActionID.PlenaryIndulgence, true, isTimeline: true); + + /// + /// 节制 + /// + public static IBaseAction Temperance { get; } = new BaseAction(ActionID.Temperance, true, isTimeline: true); + #endregion + +} \ No newline at end of file diff --git a/RotationSolver.Basic/Rotations/CustomRotation_Ability.cs b/RotationSolver.Basic/Rotations/CustomRotation_Ability.cs new file mode 100644 index 000000000..d997c4913 --- /dev/null +++ b/RotationSolver.Basic/Rotations/CustomRotation_Ability.cs @@ -0,0 +1,311 @@ +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Commands; +using RotationSolver.Data; +using RotationSolver.Helpers; + +namespace RotationSolver.Rotations.CustomRotation; + +public abstract partial class CustomRotation +{ + private bool Ability(byte abilitiesRemaining, IAction nextGCD, out IAction act, bool helpDefenseAOE, bool helpDefenseSingle) + { + act = DataCenter.CommandNextAction; + if (act is IBaseAction a && a != null && !a.IsRealGCD && a.CanUse(out _, mustUse: true, skipDisable: true)) return true; + + if (!Service.Config.UseAbility || Player.TotalCastTime - Player.CurrentCastTime > Service.Config.AbilitiesInterval) + { + act = null; + return false; + } + + if (EmergencyAbility(abilitiesRemaining, nextGCD, out act)) return true; + var role = Job.GetJobRole(); + + if (InterruptAbility(role, out act)) return true; + + var specialType = DataCenter.SpecialType; + + if (ShirkOrShield(role, specialType, out act)) return true; + if (AntiKnockback(role, specialType, out act)) return true; + + if (specialType == SpecialCommandType.EsunaStanceNorth && role == JobRole.Melee) + { + if (TrueNorth.CanUse(out act)) return true; + } + + if (GeneralHealAbility(abilitiesRemaining, specialType, out act)) return true; + + if (AutoDefense(abilitiesRemaining, role, helpDefenseAOE, helpDefenseSingle, out act)) return true; + + if (MovingAbility(abilitiesRemaining, specialType, out act)) return true; + + if (GeneralUsingAbility(role, out act)) return true; + + if (GeneralAbility(abilitiesRemaining, out act)) return true; + if (HasHostilesInRange && AttackAbility(abilitiesRemaining, out act)) return true; + + //Run! + if (!InCombat && IsMoving && role == JobRole.RangedPhysical + && Peloton.CanUse(out act, mustUse: true)) return true; + + return false; + } + + private bool InterruptAbility(JobRole role, out IAction act) + { + act = null; + if (!DataCenter.CanInterruptTargets.Any()) return false; + + switch (role) + { + case JobRole.Tank: + if (Interject.CanUse(out act)) return true; + break; + + case JobRole.Melee: + if (LegSweep.CanUse(out act)) return true; + break; + + case JobRole.RangedPhysical: + if (HeadGraze.CanUse(out act)) return true; + break; + } + return false; + } + + private bool ShirkOrShield(JobRole role, SpecialCommandType specialType, out IAction act) + { + act = null; + if (role != JobRole.Tank) return false; + + switch (specialType) + { + case SpecialCommandType.RaiseShirk: + if (Shirk.CanUse(out act)) return true; + break; + + case SpecialCommandType.EsunaStanceNorth: + if (Shield.CanUse(out act)) return true; + break; + } + + if (Service.Config.AutoShield) + { + if (!DataCenter.AllianceTanks.Any(t => t.CurrentHp != 0 && t.HasStatus(false, StatusHelper.TankStanceStatus))) + { + if (!HasTankStance && Shield.CanUse(out act)) return true; + } + } + + return false; + } + + private bool AntiKnockback(JobRole role, SpecialCommandType specialType, out IAction act) + { + act = null; + + if (specialType != SpecialCommandType.AntiKnockback) return false; + + switch (role) + { + case JobRole.Tank: + case JobRole.Melee: + if (ArmsLength.CanUse(out act)) return true; + break; + case JobRole.Healer: + if (Surecast.CanUse(out act)) return true; + break; + case JobRole.RangedPhysical: + if (ArmsLength.CanUse(out act)) return true; + break; + case JobRole.RangedMagical: + if (Surecast.CanUse(out act)) return true; + break; + } + + return false; + } + + private bool GeneralHealAbility(byte abilitiesRemaining, SpecialCommandType specialType, out IAction act) + { + act = null; + switch (specialType) + { + case SpecialCommandType.DefenseArea: + if (DefenseAreaAbility(abilitiesRemaining, out act)) return true; + break; + + case SpecialCommandType.DefenseSingle: + if (DefenseSingleAbility(abilitiesRemaining, out act)) return true; + break; + } + + if ((DataCenter.HPNotFull || Job.RowId == (uint)ClassJobID.BlackMage) && InCombat) + { + if ((DataCenter.SpecialType == SpecialCommandType.HealArea || CanHealAreaAbility) && HealAreaAbility(abilitiesRemaining, out act)) return true; + if ((DataCenter.SpecialType == SpecialCommandType.HealSingle || CanHealSingleAbility) && HealSingleAbility(abilitiesRemaining, out act)) return true; + } + + return false; + } + + private bool AutoDefense(byte abilitiesRemaining, JobRole role, bool helpDefenseAOE, bool helpDefenseSingle, out IAction act) + { + act = null; + + if (!InCombat || !HasHostilesInRange) return false; + + //Auto Provoke + if (role == JobRole.Tank + && (Service.Config.AutoProvokeForTank || DataCenter.AllianceTanks.Count() < 2) + && TargetFilter.ProvokeTarget(DataCenter.HostileTargets, true).Count() != DataCenter.HostileTargets.Count()) + + { + if (!HasTankStance && Shield.CanUse(out act)) return true; + if (Provoke.CanUse(out act, mustUse: true)) return true; + } + + //No using defence abilities. + if (!Service.Config.UseDefenseAbility) return false; + + if (helpDefenseAOE) + { + if (DefenseAreaAbility(abilitiesRemaining, out act)) return true; + if (role is JobRole.Melee or JobRole.RangedPhysical or JobRole.RangedMagical) + { + if (DefenseSingleAbility(abilitiesRemaining, out act)) return true; + } + } + + //Defnece himself. + if (role == JobRole.Tank && HasTankStance) + { + var tarOnmeCount = DataCenter.TarOnMeTargets.Count(); + + //A lot targets are targeting on me. + if (tarOnmeCount > 1 && !IsMoving) + { + if (ArmsLength.CanUse(out act)) return true; + if (DefenseSingleAbility(abilitiesRemaining, out act)) return true; + } + + //Big damage cating action. + if (tarOnmeCount == 1 && DataCenter.IsHostileCastingToTank) + { + if (DefenseSingleAbility(abilitiesRemaining, out act)) return true; + } + } + + if (helpDefenseSingle && DefenseSingleAbility(abilitiesRemaining, out act)) return true; + + return false; + } + + private bool MovingAbility(byte abilitiesRemaining, SpecialCommandType specialType, out IAction act) + { + act = null; + if (specialType == SpecialCommandType.MoveForward && MoveForwardAbility(abilitiesRemaining, out act)) + { + if (act is BaseAction b && ObjectHelper.DistanceToPlayer(b.Target) > 5) return true; + } + else if (specialType == SpecialCommandType.MoveBack) + { + if (MoveBackAbility(abilitiesRemaining, out act)) return true; + } + return false; + } + + private bool GeneralUsingAbility(JobRole role, out IAction act) + { + act = null; + switch (role) + { + case JobRole.Tank: + if (LowBlow.CanUse(out act)) return true; + break; + + case JobRole.Melee: + if (SecondWind.CanUse(out act)) return true; + if (Bloodbath.CanUse(out act)) return true; + break; + + case JobRole.Healer: + case JobRole.RangedMagical: + if (JobIDs[0] == ClassJobID.BlackMage) break; + if (LucidDreaming.CanUse(out act)) return true; + break; + + case JobRole.RangedPhysical: + if (SecondWind.CanUse(out act)) return true; + break; + } + return false; + } + + + protected virtual bool EmergencyAbility(byte abilitiesRemaining, IAction nextGCD, out IAction act) + { + if (nextGCD is BaseAction action) + { + if (Job.GetJobRole() is JobRole.Healer or JobRole.RangedMagical && + action.CastTime >= 5 && Swiftcast.CanUse(out act, emptyOrSkipCombo: true)) return true; + + if (Service.Config.AutoUseTrueNorth && abilitiesRemaining == 1 && action.EnemyPositional != EnemyPositional.None && action.Target != null) + { + if (action.EnemyPositional != action.Target.FindEnemyPositional() && action.Target.HasPositional()) + { + if (TrueNorth.CanUse(out act, emptyOrSkipCombo: true)) return true; + } + } + } + + act = null; + return false; + } + + [RotationDesc(DescType.MoveForwardAbility)] + protected virtual bool MoveForwardAbility(byte abilitiesRemaining, out IAction act, bool recordTarget = true) + { + act = null; return false; + } + + [RotationDesc(DescType.MoveBackAbility)] + protected virtual bool MoveBackAbility(byte abilitiesRemaining, out IAction act) + { + act = null; return false; + } + + [RotationDesc(DescType.HealSingleAbility)] + protected virtual bool HealSingleAbility(byte abilitiesRemaining, out IAction act) + { + act = null; return false; + } + + [RotationDesc(DescType.HealAreaAbility)] + protected virtual bool HealAreaAbility(byte abilitiesRemaining, out IAction act) + { + act = null; return false; + } + + [RotationDesc(DescType.DefenseSingleAbility)] + protected virtual bool DefenseSingleAbility(byte abilitiesRemaining, out IAction act) + { + act = null; return false; + } + + [RotationDesc(DescType.DefenseAreaAbility)] + protected virtual bool DefenseAreaAbility(byte abilitiesRemaining, out IAction act) + { + act = null; return false; + } + + protected virtual bool GeneralAbility(byte abilitiesRemaining, out IAction act) + { + act = null; return false; + } + + protected abstract bool AttackAbility(byte abilitiesRemaining, out IAction act); +} diff --git a/RotationSolver.Basic/Rotations/CustomRotation_Actions.cs b/RotationSolver.Basic/Rotations/CustomRotation_Actions.cs new file mode 100644 index 000000000..ab5fcd411 --- /dev/null +++ b/RotationSolver.Basic/Rotations/CustomRotation_Actions.cs @@ -0,0 +1,265 @@ +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using System.Reflection; + +namespace RotationSolver.Rotations.CustomRotation; + +public abstract partial class CustomRotation +{ + internal class RoleAction : BaseAction + { + private JobRole[] _roles; + internal RoleAction(ActionID actionID, JobRole[] roles, bool isFriendly = false, bool shouldEndSpecial = false, bool isEot = false, bool isTimeline = false) : base(actionID, isFriendly, shouldEndSpecial, isEot, isTimeline) + { + _roles = roles; + } + + internal bool InRole(JobRole role) => _roles.Contains(role); + } + + /// + /// 昏乱 + /// + public static IBaseAction Addle { get; } = new RoleAction(ActionID.Addle, new JobRole[] { JobRole.RangedMagical }, isFriendly: true, isTimeline: true) + { + ActionCheck = b => !b.HasStatus(false, StatusID.Addle), + }; + + /// + /// 即刻咏唱 + /// + public static IBaseAction Swiftcast { get; } = new RoleAction(ActionID.Swiftcast, new JobRole[] { JobRole.RangedMagical, JobRole.Healer }, true) + { + StatusProvide = new StatusID[] + { + StatusID.Swiftcast, + StatusID.Triplecast, + StatusID.Dualcast, + } + }; + + /// + /// 康复 + /// + public static IBaseAction Esuna { get; } = new RoleAction(ActionID.Esuna, new JobRole[] { JobRole.Healer }, true) + { + ChoiceTarget = (tars, mustUse) => + { + if (DyingPeople.Any()) + { + return DyingPeople.OrderBy(ObjectHelper.DistanceToPlayer).First(); + } + else if (WeakenPeople.Any()) + { + return WeakenPeople.OrderBy(ObjectHelper.DistanceToPlayer).First(); + } + return null; + }, + }; + + /// + /// 营救 + /// + public static IBaseAction Rescue { get; } = new RoleAction(ActionID.Rescue, new JobRole[] { JobRole.Healer }, true, isTimeline: true); + + /// + /// 沉静 + /// + public static IBaseAction Repose { get; } = new RoleAction(ActionID.Repose, new JobRole[] { JobRole.Healer }); + + /// + /// 醒梦(如果MP低于6000那么使用) + /// + public static IBaseAction LucidDreaming { get; } = new RoleAction(ActionID.LucidDreaming, + new JobRole[] { JobRole.Healer, JobRole.RangedMagical }, true) + { + ActionCheck = b => Player.CurrentMp < 6000 && InCombat, + }; + + /// + /// 内丹 + /// + public static IBaseAction SecondWind { get; } = new RoleAction(ActionID.SecondWind, + new JobRole[] { JobRole.RangedPhysical, JobRole.Melee }, true, isTimeline: true) + { + ActionCheck = b => Player?.GetHealthRatio() < Service.Config.HealthSingleAbility && InCombat, + }; + + /// + /// 亲疏自行 + /// + public static IBaseAction ArmsLength { get; } = new RoleAction(ActionID.ArmsLength, new JobRole[] { JobRole.Tank, JobRole.Melee, JobRole.RangedPhysical }, true, shouldEndSpecial: true, isTimeline: true); + + /// + /// 铁壁 + /// + public static IBaseAction Rampart { get; } = new RoleAction(ActionID.Rampart, new JobRole[] { JobRole.Tank }, true, isTimeline: true) + { + StatusProvide = new StatusID[] + { + StatusID.Superbolide, StatusID.HallowedGround, + StatusID.Rampart, StatusID.Bulwark, + //原初的直觉和血气 + StatusID.RawIntuition, StatusID.Bloodwhetting, + //复仇 + StatusID.Vengeance, + //预警 + StatusID.Sentinel, + //暗影墙 + StatusID.ShadowWall, + //星云 + StatusID.Nebula, + + //TODO:BLU的减伤技能 + }.Union(StatusHelper.NoNeedHealingStatus).ToArray(), + ActionCheck = BaseAction.TankDefenseSelf, + }; + + /// + /// 挑衅 + /// + public static IBaseAction Provoke { get; } = new RoleAction(ActionID.Provoke, new JobRole[] { JobRole.Tank }, isTimeline: true) + { + FilterForHostiles = b => TargetFilter.ProvokeTarget(b), + }; + + /// + /// 雪仇 + /// + public static IBaseAction Reprisal { get; } = new RoleAction(ActionID.Reprisal, new JobRole[] { JobRole.Tank }, isTimeline: true); + + /// + /// 退避 + /// + public static IBaseAction Shirk { get; } = new RoleAction(ActionID.Shirk, new JobRole[] { JobRole.Tank }, true) + { + ChoiceTarget = (friends, mustUse) => TargetFilter.GetJobCategory(friends, JobRole.Tank)?.FirstOrDefault(), + }; + + /// + /// 浴血 + /// + public static IBaseAction Bloodbath { get; } = new RoleAction(ActionID.Bloodbath, new JobRole[] { JobRole.Melee }, true, isTimeline: true) + { + ActionCheck = SecondWind.ActionCheck, + }; + + /// + /// 牵制 + /// + public static IBaseAction Feint { get; } = new RoleAction(ActionID.Feint, new JobRole[] { JobRole.Melee }, isTimeline: true) + { + ActionCheck = b => !b.HasStatus(false, StatusID.Feint), + }; + + /// + /// 插言 + /// + public static IBaseAction Interject { get; } = new RoleAction(ActionID.Interject, new JobRole[] { JobRole.Tank }); + + /// + /// 下踢 + /// + public static IBaseAction LowBlow { get; } = new RoleAction(ActionID.LowBlow, new JobRole[] { JobRole.Tank }) + { + ActionCheck = b => + { + if (b.IsBoss() || IsMoving || b.CastActionId == 0) return false; + + if (!b.IsCastInterruptible || Interject.IsCoolingDown) return true; + return false; + } + }; + + /// + /// 扫腿 + /// + public static IBaseAction LegSweep { get; } = new RoleAction(ActionID.LegSweep, new JobRole[] { JobRole.Melee }); + + /// + /// 伤头 + /// + public static IBaseAction HeadGraze { get; } = new RoleAction(ActionID.HeadGraze, new JobRole[] { JobRole.RangedPhysical }); + + /// + /// 沉稳咏唱 + /// + public static IBaseAction Surecast { get; } = new RoleAction(ActionID.Surecast, + new JobRole[] { JobRole.RangedMagical, JobRole.Healer }, true, shouldEndSpecial: true); + + /// + /// 真北 + /// + public static IBaseAction TrueNorth { get; } = new RoleAction(ActionID.TrueNorth, + new JobRole[] { JobRole.Melee }, true, shouldEndSpecial: true) + { + StatusProvide = new StatusID[] { StatusID.TrueNorth }, + }; + + /// + /// 速行 + /// + public static IBaseAction Peloton { get; } = new RoleAction(ActionID.Peloton, new JobRole[] { JobRole.RangedPhysical }, true) + { + ActionCheck = b => NotInCombatDelay && PartyMembers.GetObjectInRadius(20) + .Any(p => p.WillStatusEnd(3, false, StatusID.Peloton)), + }; + + private protected virtual IBaseAction Raise => null; + private protected virtual IBaseAction Shield => null; + + /// + /// 当前这个类所有的BaseAction + /// + public virtual IBaseAction[] AllBaseActions => GetBaseActions(GetType()).ToArray(); + + public IAction[] AllActions => new IAction[0].Union(GetBaseItems(GetType())).Union(AllBaseActions).ToArray(); + + /// + /// 这个类所有的公开bool值 + /// + public PropertyInfo[] AllBools => GetType().GetStaticProperties(); + + /// + /// 这个类所有的公开float值 + /// + public PropertyInfo[] AllBytes => GetType().GetStaticProperties(); + + public MethodInfo[] AllTimes => GetType().GetStaticBoolMethodInfo(m => + { + var types = m.GetParameters(); + return types.Length == 1 && types[0].ParameterType == typeof(float); + }); + + public MethodInfo[] AllGCDs => GetType().GetStaticBoolMethodInfo(m => + { + var types = m.GetParameters(); + return types.Length == 2 && types[0].ParameterType == typeof(uint) && types[1].ParameterType == typeof(uint); + }); + + private IEnumerable GetBaseActions(Type type) + { + return GetIActions(type).OfType().Where(a => a is RoleAction role ? role.InRole(Job.GetJobRole()) : true); + } + + private IEnumerable GetBaseItems(Type type) + { + return GetIActions(type).OfType().Where(a => a is MedicineItem medicine ? medicine.InType(this) : true).Reverse(); + } + + private IEnumerable GetIActions(Type type) + { + if (type == null) return new IAction[0]; + + var acts = from prop in type.GetProperties() + where typeof(IAction).IsAssignableFrom(prop.PropertyType) && !(prop.GetMethod?.IsPrivate ?? true) + select (IAction)prop.GetValue(this) into act + orderby act.ID + select act; + + return acts.Union(GetIActions(type.BaseType)); + } +} diff --git a/RotationSolver.Basic/Rotations/CustomRotation_BasicInfo.cs b/RotationSolver.Basic/Rotations/CustomRotation_BasicInfo.cs new file mode 100644 index 000000000..8df183f02 --- /dev/null +++ b/RotationSolver.Basic/Rotations/CustomRotation_BasicInfo.cs @@ -0,0 +1,70 @@ +using Dalamud.Game.ClientState.Objects.Types; +using Lumina.Excel.GeneratedSheets; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Basic.Rotations; +using RotationSolver.Configuration.RotationConfig; +using RotationSolver.Data; +namespace RotationSolver.Rotations.CustomRotation; + +[RotationDesc(DescType.BurstActions)] +public abstract partial class CustomRotation : ICustomRotation +{ + public abstract ClassJobID[] JobIDs { get; } + + public abstract string GameVersion { get; } + + public ClassJob Job => Service.GetSheet().GetRow((uint)JobIDs[0]); + + public string Name => Job.Abbreviation + " - " + Job.Name; + + /// + /// 作者 + /// + public abstract string RotationName { get; } + + public bool IsEnabled + { + get => !Service.Config.DisabledCombos.Contains(Name); + set + { + if (value) + { + Service.Config.DisabledCombos.Remove(Name); + } + else + { + Service.Config.DisabledCombos.Add(Name); + } + } + } + + public uint IconID { get; } + + public IRotationConfigSet Configs { get; } + + public BattleChara MoveTarget { get; private set; } + + public virtual string Description { get; } = string.Empty; + + /// + /// Description about the actions. + /// + //public virtual SortedList DescriptionDict { get; } = new SortedList(); + private protected CustomRotation() + { + IconID = IconSet.GetJobIcon(this); + Configs = CreateConfiguration(); + } + + protected virtual IRotationConfigSet CreateConfiguration() + { + return new RotationConfigSet(JobIDs[0], RotationName); + } + + + /// + /// Update your customized field. + /// + protected virtual void UpdateInfo() { } +} diff --git a/RotationSolver.Basic/Rotations/CustomRotation_GCD.cs b/RotationSolver.Basic/Rotations/CustomRotation_GCD.cs new file mode 100644 index 000000000..e52052be4 --- /dev/null +++ b/RotationSolver.Basic/Rotations/CustomRotation_GCD.cs @@ -0,0 +1,122 @@ +using RotationSolver.Actions; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Commands; +using RotationSolver.Data; +using RotationSolver.Helpers; + +namespace RotationSolver.Rotations.CustomRotation; + +public abstract partial class CustomRotation +{ + private IAction GCD(byte abilityRemain, bool helpDefenseAOE, bool helpDefenseSingle) + { + IAction act = DataCenter.CommandNextAction; + if (act is IBaseAction a && a != null && a.IsRealGCD && a.CanUse(out _, mustUse: true, skipDisable: true)) return act; + + if (EmergencyGCD(out act)) return act; + + var specialType = DataCenter.SpecialType; + + if (RaiseSpell(specialType, out act, abilityRemain, false)) return act; + + if (specialType == SpecialCommandType.MoveForward && MoveForwardGCD(out act)) + { + if (act is IBaseAction b && ObjectHelper.DistanceToPlayer(b.Target) > 5) return act; + } + + //General Heal + if (DataCenter.HPNotFull && (DataCenter.InCombat || Service.Config.HealOutOfCombat)) + { + if ((specialType == SpecialCommandType.HealArea || CanHealAreaSpell) && HealAreaGCD(out act)) return act; + if ((specialType == SpecialCommandType.HealSingle || CanHealSingleSpell) && HealSingleGCD(out act)) return act; + } + if (specialType == SpecialCommandType.DefenseArea && DefenseAreaGCD(out act)) return act; + if (specialType == SpecialCommandType.DefenseSingle && DefenseSingleGCD(out act)) return act; + + //Auto Defense + if (helpDefenseAOE && DefenseAreaGCD(out act)) return act; + if (helpDefenseSingle && DefenseSingleGCD(out act)) return act; + + //Esuna + if ((specialType == SpecialCommandType.EsunaStanceNorth || !HasHostilesInRange || Service.Config.EsunaAll) + && DataCenter.WeakenPeople.Any() + || DataCenter.DyingPeople.Any()) + { + if (Job.GetJobRole() == JobRole.Healer && Esuna.CanUse(out act, mustUse: true)) return act; + } + + if (GeneralGCD(out var action)) return action; + + if (Service.Config.RaisePlayerByCasting && RaiseSpell(specialType, out act, abilityRemain, true)) return act; + + return null; + } + + private bool RaiseSpell(SpecialCommandType specialType, out IAction act, byte actabilityRemain, bool mustUse) + { + act = null; + if (Raise == null) return false; + if (Player.CurrentMp <= Service.Config.LessMPNoRaise) return false; + + if ((Service.Config.RaiseAll ? DataCenter.DeathPeopleAll.Any() : DataCenter.DeathPeopleParty.Any()) + && Raise.CanUse(out act)) + { + if (specialType == SpecialCommandType.RaiseShirk || HasSwift) + { + return true; + } + else if (mustUse) + { + if(Swiftcast.CanUse(out act)) return true; + else + { + act = Raise; + return true; + } + } + else if (Service.Config.RaisePlayerBySwift && !Swiftcast.IsCoolingDown && actabilityRemain > 0) + { + return true; + } + } + return false; + } + + protected virtual bool EmergencyGCD(out IAction act) + { + act = null; return false; + } + + [RotationDesc(DescType.MoveForwardGCD)] + protected virtual bool MoveForwardGCD(out IAction act) + { + act = null; return false; + } + + [RotationDesc(DescType.HealSingleGCD)] + protected virtual bool HealSingleGCD(out IAction act) + { + act = null; return false; + } + + [RotationDesc(DescType.HealAreaGCD)] + protected virtual bool HealAreaGCD(out IAction act) + { + act = null; return false; + } + + [RotationDesc(DescType.DefenseSingleGCD)] + protected virtual bool DefenseSingleGCD(out IAction act) + { + act = null; return false; + } + + [RotationDesc(DescType.DefenseAreaGCD)] + protected virtual bool DefenseAreaGCD(out IAction act) + { + act = null; return false; + } + + protected abstract bool GeneralGCD(out IAction act); +} diff --git a/RotationSolver.Basic/Rotations/CustomRotation_Invoke.cs b/RotationSolver.Basic/Rotations/CustomRotation_Invoke.cs new file mode 100644 index 000000000..d30540bfe --- /dev/null +++ b/RotationSolver.Basic/Rotations/CustomRotation_Invoke.cs @@ -0,0 +1,106 @@ +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Basic; +using RotationSolver.Commands; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.SigReplacers; + +namespace RotationSolver.Rotations.CustomRotation; + +public abstract partial class CustomRotation +{ + public bool TryInvoke(out IAction newAction, out IAction gcdAction) + { + newAction = gcdAction = null; + if (!IsEnabled) + { + return false; + } + + MoveTarget = (MoveForwardAbility(1, out var act, recordTarget: false) && act is BaseAction a) ? a.Target : null; + UpdateInfo(); + + newAction = Invoke(out gcdAction); + + return newAction != null; + } + + private IAction Invoke(out IAction gcdAction) + { + var countDown = CountDown.CountDownTime; + if (countDown > 0) + { + gcdAction = null; + return CountDownAction(countDown); + } + + byte abilityRemain = DataCenter.AbilityRemainCount; + var helpDefenseAOE = Service.Config.UseDefenseAbility && DataCenter.IsHostileCastingAOE; + + bool helpDefenseSingle = false; + if (Job.GetJobRole() == JobRole.Healer || Job.RowId == (uint)ClassJobID.Paladin) + { + if (DataCenter.PartyTanks.Any((tank) => + { + var attackingTankObj = DataCenter.HostileTargets.Where(t => t.TargetObjectId == tank.ObjectId); + + if (attackingTankObj.Count() != 1) return false; + + return DataCenter.IsHostileCastingToTank; + })) helpDefenseSingle = true; + } + + gcdAction = GCD(abilityRemain, helpDefenseAOE, helpDefenseSingle); + + if (gcdAction != null && gcdAction is IBaseAction GcdAction) + { + //if (GcdAction.IsMeleeAction()) + //{ + // OverlayWindow.MeleeAction = GcdAction; + // //Sayout! + // if (GcdAction.EnemyPositional != EnemyPositional.None && GcdAction.Target.HasPositional() + // && !Player.HasStatus(true, StatusID.TrueNorth)) + // { + // if (CheckAction(GcdAction.ID)) + // { + // string positional = GcdAction.EnemyPositional.ToName(); + // if (Service.Config.SayPositional) Watcher.Speak(positional); + // if (Service.Config.FlytextPositional) Service.ToastGui.ShowQuest(" " + positional, new Dalamud.Game.Gui.Toast.QuestToastOptions() + // { + // IconId = GcdAction.IconID, + // }); + // } + // } + //} + + if (abilityRemain == 0 || DataCenter.WeaponTotal < DataCenter.CastingTotal) return GcdAction; + + if (Ability(abilityRemain, GcdAction, out IAction ability, helpDefenseAOE, helpDefenseSingle)) return ability; + + return GcdAction; + } + else if (gcdAction == null) + { + if (Ability(abilityRemain, Addle, out IAction ability, helpDefenseAOE, helpDefenseSingle)) return ability; + return null; + } + return gcdAction; + } + + protected virtual IAction CountDownAction(float remainTime) => null; + + + uint _lastSayingGCDAction; + DateTime lastTime; + bool CheckAction(uint actionID) + { + if ((_lastSayingGCDAction != actionID || DateTime.Now - lastTime > new TimeSpan(0, 0, 3)) && DataCenter.StateType != StateCommandType.Cancel) + { + _lastSayingGCDAction = actionID; + lastTime = DateTime.Now; + return true; + } + else return false; + } +} diff --git a/RotationSolver.Basic/Rotations/CustomRotation_Medicine.cs b/RotationSolver.Basic/Rotations/CustomRotation_Medicine.cs new file mode 100644 index 000000000..f2d84bee1 --- /dev/null +++ b/RotationSolver.Basic/Rotations/CustomRotation_Medicine.cs @@ -0,0 +1,82 @@ +using RotationSolver.Actions; +using RotationSolver.Basic; +using RotationSolver.Data; +using System.Linq; + +namespace RotationSolver.Rotations.CustomRotation; +public enum MedicineType : byte +{ + Strength, + Dexterity, + Intelligence, + Mind, +} +public abstract partial class CustomRotation +{ + public abstract MedicineType MedicineType { get; } + public static IBaseItem TinctureOfStrength6 { get; } = new MedicineItem(36109, + MedicineType.Strength, 196625); + public static IBaseItem TinctureOfDexterity6 { get; } = new MedicineItem(36110, + MedicineType.Dexterity); + public static IBaseItem TinctureOfIntelligence6 { get; } = new MedicineItem(36112, + MedicineType.Intelligence); + public static IBaseItem TinctureOfMind6 { get; } = new MedicineItem(36113, + MedicineType.Mind); + + public static IBaseItem TinctureOfStrength7 { get; } = new MedicineItem(37840, + MedicineType.Strength); + public static IBaseItem TinctureOfDexterity7 { get; } = new MedicineItem(37841, + MedicineType.Dexterity); + public static IBaseItem TinctureOfIntelligence7 { get; } = new MedicineItem(37843, + MedicineType.Intelligence); + public static IBaseItem TinctureOfMind7 { get; } = new MedicineItem(37844, + MedicineType.Mind); + + public static IBaseItem EchoDrops { get; } = new BaseItem(4566); + + static bool UseStrength(out IAction act) + { + if (TinctureOfStrength7.CanUse(out act)) return true; + if (TinctureOfStrength6.CanUse(out act)) return true; + return false; + } + + static bool UseDexterity(out IAction act) + { + if (TinctureOfDexterity7.CanUse(out act)) return true; + if (TinctureOfDexterity6.CanUse(out act)) return true; + return false; + } + static bool UseIntelligence(out IAction act) + { + if (TinctureOfIntelligence7.CanUse(out act)) return true; + if (TinctureOfIntelligence6.CanUse(out act)) return true; + return false; + } + static bool UseMind(out IAction act) + { + if (TinctureOfMind7.CanUse(out act)) return true; + if (TinctureOfMind6.CanUse(out act)) return true; + return false; + } + protected bool UseBurstMedicine(out IAction act) + { + act = null; + + if (!IsFullParty || !InCombat) return false; + if (Service.Player?.Level < 90) return false; + + switch (MedicineType) + { + case MedicineType.Strength: + return UseStrength(out act); + case MedicineType.Dexterity: + return UseDexterity(out act); + case MedicineType.Intelligence: + return UseIntelligence(out act); + case MedicineType.Mind: + return UseMind(out act); + } + return false; + } +} diff --git a/RotationSolver.Basic/Rotations/CustomRotation_OtherInfo.cs b/RotationSolver.Basic/Rotations/CustomRotation_OtherInfo.cs new file mode 100644 index 000000000..a2811adda --- /dev/null +++ b/RotationSolver.Basic/Rotations/CustomRotation_OtherInfo.cs @@ -0,0 +1,241 @@ +using Dalamud.Game.ClientState; +using Dalamud; +using Dalamud.Game.ClientState.Objects.SubKinds; +using Dalamud.Game.ClientState.Objects.Types; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using RotationSolver.Actions; +using RotationSolver.Basic; +using RotationSolver.Basic.Data; +using RotationSolver.Commands; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.SigReplacers; +using System.Reflection; + +namespace RotationSolver.Rotations.CustomRotation; +public abstract partial class CustomRotation +{ + #region Player + /// + /// This is the player. + /// + protected static PlayerCharacter Player => Service.Player; + + /// + /// The level of the player. + /// + protected static byte Level => Player?.Level ?? 0; + + /// + /// Does player have swift cast, dual cast or triple cast. + /// + protected static bool HasSwift => Player?.HasStatus(true, Swiftcast.StatusProvide) ?? false; + + /// + /// + /// + protected static bool HasTankStance => Player?.HasStatus(true, StatusHelper.TankStanceStatus) ?? false; + + /// + /// Check the player is moving, such as running, walking or jumping. + /// + protected static bool IsMoving => DataCenter.IsMoving; + + + /// + /// Is in combat. + /// + protected static bool InCombat => DataCenter.InCombat; + + static RandomDelay _notInCombatDelay = new RandomDelay(() => + (Service.Config.NotInCombatDelayMin, Service.Config.NotInCombatDelayMax)); + protected static bool NotInCombatDelay => _notInCombatDelay.Delay(!InCombat); + + #endregion + + #region Friends + + protected static bool HasCompanion => DataCenter.HasCompanion; + protected static IEnumerable PartyMembers => DataCenter.PartyMembers; + protected static IEnumerable PartyTanks => DataCenter.PartyTanks; + protected static IEnumerable PartyHealers => DataCenter.PartyHealers; + protected static IEnumerable AllianceMembers => DataCenter.AllianceMembers; + protected static IEnumerable AllianceTanks => DataCenter.AllianceTanks; + protected static IEnumerable WeakenPeople => DataCenter.WeakenPeople; + protected static IEnumerable DyingPeople => DataCenter.DyingPeople; + + /// + /// Whether the number of party members is 8. + /// + protected static bool IsFullParty => DataCenter.PartyMembers.Count() is 8; + + protected static IEnumerable PartyMembersHP => DataCenter.PartyMembersHP; + protected static float PartyMembersMinHP => DataCenter.PartyMembersMinHP; + protected static float PartyMembersAverHP => DataCenter.PartyMembersAverHP; + #endregion + + + #region Target + /// + /// The player's target. + /// + protected static BattleChara Target => Service.TargetManager.Target is BattleChara b ? b : Player; + + /// + /// Shortcut for Target.IsDying(); + /// + protected static bool IsTargetDying => Target?.IsDying() ?? false; + + /// + /// Shortcut for Target.IsBoss(); + /// + protected static bool IsTargetBoss => Target?.IsBoss() ?? false; + + + /// + /// Is there any hostile target in the range? 25 for ranged jobs and healer, 3 for melee and tank. + /// + protected static bool HasHostilesInRange => DataCenter.HasHostilesInRange; + + protected static IEnumerable HostileTargets => DataCenter.HostileTargets; + + #endregion + + #region Command + + /// + /// Is in burst right now? Usually it used with team support actions. + /// + protected static bool InBurst => DataCenter.SpecialType == SpecialCommandType.Burst || Service.Config.AutoBurst; + + bool _canUseHealAction => Job.GetJobRole() == JobRole.Healer || Service.Config.UseHealWhenNotAHealer; + + protected virtual bool CanHealAreaAbility => DataCenter.CanHealAreaAbility && _canUseHealAction; + + protected virtual bool CanHealAreaSpell => DataCenter.CanHealAreaSpell && _canUseHealAction; + + protected virtual bool CanHealSingleAbility => DataCenter.CanHealSingleAbility && _canUseHealAction; + + protected virtual bool CanHealSingleSpell => DataCenter.CanHealSingleSpell && _canUseHealAction; + + protected static SpecialCommandType SpecialType => DataCenter.SpecialType; + protected static StateCommandType StateType => DataCenter.StateType; + #endregion + + #region GCD + protected static float WeaponRemain => DataCenter.WeaponRemain; + + protected static float WeaponTotal => DataCenter.WeaponTotal; + + protected static float WeaponElapsed => DataCenter.WeaponElapsed; + #endregion + + + protected static ClientLanguage Language => Service.Language; + + + public static uint AdjustId(uint id) => Service.GetAdjustedActionId(id); + public static ActionID AdjustId(ActionID id) => Service.GetAdjustedActionId(id); + + + /// + /// Actions successfully released. The first one is the latest one. + /// + protected static ActionRec[] RecordActions => DataCenter.RecordActions; + + /// + /// How much time has passed since the last action was released. + /// + protected static TimeSpan TimeSinceLastAction => DataCenter.TimeSinceLastAction; + + /// + /// Check for GCD Record. + /// + /// Check for adjust id not raw id. + /// True if any of this is matched. + /// + protected static bool IsLastGCD(bool isAdjust, params IAction[] actions) + => IActionHelper.IsLastGCD(isAdjust, actions); + + /// + /// Check for GCD Record. + /// + /// True if any of this is matched. + /// + protected static bool IsLastGCD(params ActionID[] ids) + => IActionHelper.IsLastGCD(ids); + + /// + /// Check for ability Record. + /// + /// Check for adjust id not raw id. + /// True if any of this is matched. + /// + protected static bool IsLastAbility(bool isAdjust, params IAction[] actions) + => IActionHelper.IsLastAbility(isAdjust, actions); + + /// + /// Check for ability Record. + /// + /// True if any of this is matched. + /// + protected static bool IsLastAbility(params ActionID[] ids) + => IActionHelper.IsLastAbility(ids); + + /// + /// Check for action Record. + /// + /// Check for adjust id not raw id. + /// True if any of this is matched. + /// + protected static bool IsLastAction(bool isAdjust, params IAction[] actions) + => IActionHelper.IsLastAction(isAdjust, actions); + + /// + /// Check for action Record. + /// + /// True if any of this is matched. + /// + protected static bool IsLastAction(params ActionID[] ids) + => IActionHelper.IsLastAction(ids); + + /// + /// Is the thing still there after gcds and abilities. + /// + /// jobgauge time + /// + /// + /// + protected static bool EndAfterGCD(float remain, uint gcdCount = 0, uint abilityCount = 0) + => CooldownHelper.RecastAfterGCD(remain, gcdCount, abilityCount); + + /// + /// Is the thing still there after seconds + /// + /// jobgauge time + /// seconds + /// + protected static bool EndAfter(float remain, float remainNeed) + => CooldownHelper.RecastAfter(remain, remainNeed); + + /// + /// Whether the battle lasted less than seconds + /// + /// time in second. + /// + public static bool CombatElapsedLess(float time) + { + if (!InCombat) return true; + return CooldownHelper.ElapsedAfter(time, DataCenter.CombatTime + DataCenter.WeaponRemain); + } + + public MethodInfo[] AllLast => GetType().GetStaticBoolMethodInfo(m => + { + var types = m.GetParameters(); + return types.Length == 2 + && types[0].ParameterType == typeof(bool) + && types[1].ParameterType == typeof(IAction[]); + }); + + +} diff --git a/RotationSolver.Basic/Rotations/ICustomRotation.cs b/RotationSolver.Basic/Rotations/ICustomRotation.cs new file mode 100644 index 000000000..c0d8fbb69 --- /dev/null +++ b/RotationSolver.Basic/Rotations/ICustomRotation.cs @@ -0,0 +1,33 @@ +using Dalamud.Game.ClientState.Objects.Types; +using Lumina.Excel.GeneratedSheets; +using RotationSolver.Actions; +using RotationSolver.Configuration.RotationConfig; +using RotationSolver.Data; +using RotationSolver.Rotations.CustomRotation; +using System.Collections.Generic; +using System.Reflection; + +namespace RotationSolver.Basic.Rotations; + +public interface ICustomRotation : ITexture +{ + ClassJob Job { get; } + ClassJobID[] JobIDs { get; } + + string GameVersion { get; } + string RotationName { get; } + IRotationConfigSet Configs { get; } + MedicineType MedicineType { get; } + BattleChara MoveTarget { get; } + + IBaseAction[] AllBaseActions { get; } + IAction[] AllActions { get; } + PropertyInfo[] AllBools { get; } + PropertyInfo[] AllBytes { get; } + + MethodInfo[] AllTimes { get; } + MethodInfo[] AllLast { get; } + MethodInfo[] AllGCDs { get; } + + bool TryInvoke(out IAction newAction, out IAction gcdAction); +} diff --git a/RotationSolver/Rotations/Rotations.cd b/RotationSolver.Basic/Rotations/Rotations.cd similarity index 100% rename from RotationSolver/Rotations/Rotations.cd rename to RotationSolver.Basic/Rotations/Rotations.cd diff --git a/RotationSolver.Basic/Service.cs b/RotationSolver.Basic/Service.cs new file mode 100644 index 000000000..325a04ef9 --- /dev/null +++ b/RotationSolver.Basic/Service.cs @@ -0,0 +1,137 @@ +using Dalamud; +using Dalamud.Data; +using Dalamud.Game; +using Dalamud.Game.ClientState; +using Dalamud.Game.ClientState.Conditions; +using Dalamud.Game.ClientState.GamePad; +using Dalamud.Game.ClientState.JobGauge; +using Dalamud.Game.ClientState.Keys; +using Dalamud.Game.ClientState.Objects; +using Dalamud.Game.ClientState.Objects.SubKinds; +using Dalamud.Game.ClientState.Party; +using Dalamud.Game.Command; +using Dalamud.Game.DutyState; +using Dalamud.Game.Gui; +using Dalamud.Game.Gui.Dtr; +using Dalamud.Game.Gui.FlyText; +using Dalamud.Game.Gui.Toast; +using Dalamud.Hooking; +using Dalamud.IoC; +using Dalamud.Plugin; +using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.Control; +using ImGuiScene; +using Lumina.Excel; +using RotationSolver.Configuration; +using RotationSolver.Data; +using System.Runtime.InteropServices; + +namespace RotationSolver.Basic; + +public class Service +{ + public const string Command = "/rotation"; + public static GetChatBoxModuleDelegate GetChatBox { get; private set; } + + public Service(DalamudPluginInterface pluginInterface, SigScanner scanner) + { + pluginInterface.Create(); + + //https://github.com/BardMusicPlayer/Hypnotoad-Plugin/blob/7928be6735daf28e94121c3cf1c1dbbef0d97bcf/HypnotoadPlugin/Offsets/Offsets.cs#L18 + GetChatBox = Marshal.GetDelegateForFunctionPointer( + scanner.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 45 84 C9")); + } + public static PluginConfiguration Config { get; set; } + + internal static unsafe FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara* RawPlayer + => Control.Instance()->LocalPlayer; + + + public static ActionID GetAdjustedActionId(ActionID id) + => (ActionID)GetAdjustedActionId((uint)id); + + public static unsafe uint GetAdjustedActionId(uint id) + => ActionManager.Instance()->GetAdjustedActionId(id); + + [PluginService] + public static DalamudPluginInterface Interface { get; private set; } + + [PluginService] + public static ChatGui ChatGui { get; private set; } + + [PluginService] + public static GameGui GameGui { get; private set; } + + public static PlayerCharacter Player => ClientState.LocalPlayer; + [PluginService] + public static ClientState ClientState { get; set; } + + public static ExcelSheet GetSheet() where T : ExcelRow => DataManager.GetExcelSheet(); + + internal static TextureWrap GetTextureIcon(uint id) => DataManager.GetImGuiTextureIcon(id); + + [PluginService] + private static DataManager DataManager { get; set; } + + + [PluginService] + public static CommandManager CommandManager { get; private set; } + + [PluginService] + public static Condition Conditions { get; private set; } + + [PluginService] + public static JobGauges JobGauges { get; private set; } + + [PluginService] + public static ObjectTable ObjectTable { get; private set; } + + [PluginService] + public static TargetManager TargetManager { get; private set; } + + [PluginService] + public static PartyList PartyList { get; private set; } + + [PluginService] + public static DtrBar DtrBar { get; private set; } + + [PluginService] + public static ToastGui ToastGui { get; private set; } + [PluginService] + public static FlyTextGui FlyTextGui { get; private set; } + [PluginService] + public static KeyState KeyState { get; private set; } + [PluginService] + public static GamepadState GamepadState { get; private set; } + [PluginService] + public static Framework Framework { get; private set; } + + [PluginService] + public static DutyState DutyState { get; private set; } + public static ClientLanguage Language => ClientState.ClientLanguage; + + + public delegate void GetChatBoxModuleDelegate(IntPtr uiModule, IntPtr message, IntPtr unused, byte a4); + + + /// + /// Submit text/command to outgoing chat. + /// Can be used to enter chat commands. + /// + /// Text to submit. + public unsafe static void SubmitToChat(string text) + { + IntPtr uiModule = GameGui.GetUIModule(); + + using (ChatPayload payload = new ChatPayload(text)) + { + IntPtr mem1 = Marshal.AllocHGlobal(400); + Marshal.StructureToPtr(payload, mem1, false); + + GetChatBox(uiModule, mem1, IntPtr.Zero, 0); + + Marshal.FreeHGlobal(mem1); + } + } +} diff --git a/RotationSolver.Default/Healer/AST_Default.cs b/RotationSolver.Default/Healer/AST_Default.cs new file mode 100644 index 000000000..0e4b376a6 --- /dev/null +++ b/RotationSolver.Default/Healer/AST_Default.cs @@ -0,0 +1,252 @@ +using Dalamud.Game.ClientState.JobGauge.Enums; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Configuration.RotationConfig; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.Basic; + +namespace RotationSolver.Default.Healer; + +[RotationDesc(ActionID.Divination)] +internal sealed class AST_Default : AST_Base +{ + public override string GameVersion => "6.28"; + + public override string RotationName => "Default"; + + protected override IRotationConfigSet CreateConfiguration() + => base.CreateConfiguration() + .SetFloat("UseEarthlyStarTime", 15, "Use the Earthly Star in Count down time", 4, 20); + + static IBaseAction AspectedBeneficDefense { get; } = new BaseAction(ActionID.AspectedBenefic, true, isEot: true) + { + ChoiceTarget = TargetFilter.FindAttackedTarget, + ActionCheck = b => b.IsJobCategory(JobRole.Tank), + TargetStatus = new StatusID[] { StatusID.AspectedBenefic }, + }; + + protected override IAction CountDownAction(float remainTime) + { + if (remainTime < Malefic.CastTime + Service.Config.CountDownAhead + && Malefic.CanUse(out var act)) return act; + if (remainTime < 3 && UseBurstMedicine(out act)) return act; + if (remainTime < 4 && AspectedBeneficDefense.CanUse(out act)) return act; + if (remainTime < Configs.GetFloat("UseEarthlyStarTime") + && EarthlyStar.CanUse(out act)) return act; + if (remainTime < 30 && Draw.CanUse(out act)) return act; + + return base.CountDownAction(remainTime); + } + + [RotationDesc(ActionID.CelestialIntersection, ActionID.Exaltation)] + protected override bool DefenseSingleAbility(byte abilitiesRemaining, out IAction act) + { + //天星交错 + if (CelestialIntersection.CanUse(out act, emptyOrSkipCombo: true)) return true; + + //给T减伤,这个很重要。 + if (Exaltation.CanUse(out act)) return true; + return false; + } + + [RotationDesc(ActionID.CollectiveUnconscious)] + protected override bool DefenseAreaAbility(byte abilitiesRemaining, out IAction act) + { + //来个命运之轮 + if (CollectiveUnconscious.CanUse(out act)) return true; + + return base.DefenseAreaAbility(abilitiesRemaining, out act); + } + + protected override bool GeneralGCD(out IAction act) + { + //Add AspectedBeneficwhen not in combat. + if (NotInCombatDelay && AspectedBeneficDefense.CanUse(out act)) return true; + + //群体输出 + if (Gravity.CanUse(out act)) return true; + + //单体输出 + if (Combust.CanUse(out act)) return true; + if (Malefic.CanUse(out act)) return true; + if (Combust.CanUse(out act, mustUse: true)) return true; + + act = null!; + return false; + } + + [RotationDesc(ActionID.AspectedHelios, ActionID.Helios)] + protected override bool HealAreaGCD(out IAction act) + { + //阳星相位 + if (AspectedHelios.CanUse(out act)) return true; + + //阳星 + if (Helios.CanUse(out act)) return true; + + act = null!; + return false; + } + + protected override bool EmergencyAbility(byte abilityRemain, IAction nextGCD, out IAction act) + { + if (base.EmergencyAbility(abilityRemain, nextGCD, out act)) return true; + + if (PartyHealers.Count() == 1 && Player.HasStatus(false, StatusID.Silence) + && HasHostilesInRange && EchoDrops.CanUse(out act)) return true; + + + if (!InCombat) return false; + + //如果要群奶了,先上个天宫图! + if (nextGCD.IsTheSameTo(true, AspectedHelios, Helios)) + { + if (Horoscope.CanUse(out act)) return true; + + //中间学派 + if (NeutralSect.CanUse(out act)) return true; + } + + //如果要单奶了,先上星位合图! + if (nextGCD.IsTheSameTo(true, Benefic, Benefic2, AspectedBenefic)) + { + if (Synastry.CanUse(out act)) return true; + } + return false; + } + + protected override bool GeneralAbility(byte abilitiesRemaining, out IAction act) + { + //如果当前还没有卡牌,那就抽一张 + if (Draw.CanUse(out act)) return true; + + bool canUse = Astrodyne.ActionCheck(Service.Player); + + //如果当前卡牌已经拥有了,就重抽 + if (!canUse && Redraw.CanUse(out act)) return true; + + act = null; + return false; + } + + [RotationDesc(ActionID.AspectedBenefic, ActionID.Benefic2, ActionID.Benefic)] + protected override bool HealSingleGCD(out IAction act) + { + //吉星相位 + if (AspectedBenefic.CanUse(out act) + && (IsMoving || AspectedBenefic.Target.GetHealthRatio() > 0.4)) return true; + + //福星 + if (Benefic2.CanUse(out act)) return true; + + //吉星 + if (Benefic.CanUse(out act)) return true; + + act = null; + return false; + } + + protected override bool AttackAbility(byte abilitiesRemaining, out IAction act) + { + if (InBurst && Divination.CanUse(out act)) return true; + + //如果当前还没有皇冠卡牌,那就抽一张 + if (MinorArcana.CanUse(out act, emptyOrSkipCombo: true)) return true; + + //如果当前还没有卡牌,那就抽一张 + if (Draw.CanUse(out act, emptyOrSkipCombo: InBurst)) return true; + + //光速,创造更多的内插能力技的机会。 + if (IsMoving && Lightspeed.CanUse(out act)) return true; + + + if (!IsMoving) + { + //如果没有地星也没有巨星,那就试试看能不能放个。 + if (!Player.HasStatus(true, StatusID.EarthlyDominance, StatusID.GiantDominance)) + { + if (EarthlyStar.CanUse(out act, mustUse: true)) return true; + } + //加星星的进攻Buff + if (Astrodyne.CanUse(out act)) return true; + } + + if (DrawnCrownCard == CardType.LORD || MinorArcana.WillHaveOneChargeGCD(1)) + { + //进攻牌,随便发。或者CD要转好了,赶紧发掉。 + if (MinorArcana.CanUse(out act)) return true; + } + + //发牌 + if (abilitiesRemaining == 1) + { + if (PlayCard(out act)) return true; + } + + return false; + } + + [RotationDesc(ActionID.EssentialDignity, ActionID.CelestialIntersection, ActionID.CelestialOpposition, + ActionID.EarthlyStar, ActionID.Horoscope)] + protected override bool HealSingleAbility(byte abilitiesRemaining, out IAction act) + { + //常规奶 + if (EssentialDignity.CanUse(out act)) return true; + //带盾奶 + if (CelestialIntersection.CanUse(out act, emptyOrSkipCombo: true)) return true; + + //奶量牌,要看情况。 + if (DrawnCrownCard == CardType.LADY && MinorArcana.CanUse(out act)) return true; + + var tank = PartyTanks; + var isBoss = Malefic.IsTargetBoss; + if (EssentialDignity.IsCoolingDown && tank.Count() == 1 && tank.Any(t => t.GetHealthRatio() < 0.5) && !isBoss) + { + //群Hot + if (CelestialOpposition.CanUse(out act)) return true; + + //如果有巨星主宰 + if (Player.HasStatus(true, StatusID.GiantDominance)) + { + //需要回血的时候炸了。 + act = EarthlyStar; + return true; + } + + //天宫图 + if (!Player.HasStatus(true, StatusID.HoroscopeHelios, StatusID.Horoscope) && Horoscope.CanUse(out act)) return true; + //阳星天宫图 + if (Player.HasStatus(true, StatusID.HoroscopeHelios) && Horoscope.CanUse(out act)) return true; + //超紧急情况天宫图 + if (tank.Any(t => t.GetHealthRatio() < 0.3) && Horoscope.CanUse(out act)) return true; + } + + return false; + } + + [RotationDesc(ActionID.CelestialOpposition, ActionID.EarthlyStar, ActionID.Horoscope)] + protected override bool HealAreaAbility(byte abilitiesRemaining, out IAction act) + { + //群Hot + if (CelestialOpposition.CanUse(out act)) return true; + + //如果有巨星主宰 + if (Player.HasStatus(true, StatusID.GiantDominance)) + { + //需要回血的时候炸了。 + act = EarthlyStar; + return true; + } + + //天宫图 + if (Player.HasStatus(true, StatusID.HoroscopeHelios) && Horoscope.CanUse(out act)) return true; + + //奶量牌,要看情况。 + if (DrawnCrownCard == CardType.LADY && MinorArcana.CanUse(out act)) return true; + + return false; + } +} diff --git a/RotationSolver.Default/Healer/SCH_Default.cs b/RotationSolver.Default/Healer/SCH_Default.cs new file mode 100644 index 000000000..3498d0904 --- /dev/null +++ b/RotationSolver.Default/Healer/SCH_Default.cs @@ -0,0 +1,230 @@ +using RotationSolver.Actions; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Configuration.RotationConfig; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.Basic; + +namespace RotationSolver.Default.Healer; + +[RotationDesc(ActionID.ChainStratagem)] +internal sealed class SCH_Default : SCH_Base +{ + public override string GameVersion => "6.28"; + + public override string RotationName => "Default"; + + public SCH_Default() + { + SummonSeraph.RotationCheck = b => WhisperingDawn.ElapsedAfterGCD(1) || FeyIllumination.ElapsedAfterGCD(1) || FeyBlessing.ElapsedAfterGCD(1); + } + protected override bool CanHealSingleSpell => base.CanHealSingleSpell && (Configs.GetBool("GCDHeal") || PartyHealers.Count() < 2); + protected override bool CanHealAreaSpell => base.CanHealAreaSpell && (Configs.GetBool("GCDHeal") || PartyHealers.Count() < 2); + + protected override IRotationConfigSet CreateConfiguration() + { + return base.CreateConfiguration().SetBool("GCDHeal", false, "Aut use GCD to heal") + .SetBool("prevDUN", false, "Recitation in 15 seconds.") + .SetBool("GiveT", false, "Give Recitation to Tank"); + } + + protected override bool EmergencyAbility(byte abilityRemain, IAction nextGCD, out IAction act) + { + //秘策绑定单盾群盾 + if (nextGCD.IsTheSameTo(true, Succor, Adloquium)) + { + if (Recitation.CanUse(out act)) return true; + } + + //Remove Aetherpact + foreach (var item in PartyMembers) + { + if (item.GetHealthRatio() < 0.9) continue; + if (item.HasStatus(true, StatusID.Aetherpact)) + { + act = Aetherpact; + return true; + } + } + + return base.EmergencyAbility(abilityRemain, nextGCD, out act); + } + + protected override bool GeneralGCD(out IAction act) + { + //召唤小仙女 + if (SummonEos.CanUse(out act)) return true; + + //DoT + if (Bio.CanUse(out act)) return true; + + //AOE + if (ArtofWar.CanUse(out act)) return true; + + //Single + if (Ruin.CanUse(out act)) return true; + if (Ruin2.CanUse(out act)) return true; + + //Add dot. + if (Bio.CanUse(out act, true)) return true; + + return false; + } + + [RotationDesc(ActionID.Adloquium, ActionID.Physick)] + protected override bool HealSingleGCD(out IAction act) + { + //鼓舞激励之策 + if (Adloquium.CanUse(out act)) return true; + + //医术 + if (Physick.CanUse(out act)) return true; + + return false; + } + + [RotationDesc(ActionID.Aetherpact, ActionID.Protraction, ActionID.SacredSoil, ActionID.Excogitation, ActionID.Lustrate, ActionID.Aetherpact)] + protected override bool HealSingleAbility(byte abilitiesRemaining, out IAction act) + { + //判断是否有人有线 + var haveLink = PartyMembers.Any(p => p.HasStatus(true, StatusID.Aetherpact)); + + //以太契约 + if (Aetherpact.CanUse(out act) && FairyGauge >= 70 && !haveLink) return true; + + //生命回生法 + if (Protraction.CanUse(out act)) return true; + + //野战治疗阵 + if (SacredSoil.CanUse(out act)) return true; + + //深谋远虑之策 + if (Excogitation.CanUse(out act)) return true; + + //生命活性法 + if (Lustrate.CanUse(out act)) return true; + + //以太契约 + if (Aetherpact.CanUse(out act) && !haveLink) return true; + + return false; + } + + [RotationDesc(ActionID.Excogitation)] + protected override bool DefenseSingleAbility(byte abilitiesRemaining, out IAction act) + { + if (Excogitation.CanUse(out act)) return true; + return false; + } + + [RotationDesc(ActionID.Succor)] + protected override bool HealAreaGCD(out IAction act) + { + //士气高扬之策 + if (Succor.CanUse(out act)) return true; + + return false; + } + + + [RotationDesc(ActionID.SummonSeraph, ActionID.Consolation, ActionID.WhisperingDawn, ActionID.SacredSoil, ActionID.Indomitability)] + protected override bool HealAreaAbility(byte abilitiesRemaining, out IAction act) + { + //慰藉 + if (SummonSeraph.CanUse(out act)) return true; + if (Consolation.CanUse(out act, emptyOrSkipCombo: true)) return true; + + //异想的祥光 + if (FeyBlessing.CanUse(out act)) return true; + + //仙光的低语 + if (WhisperingDawn.CanUse(out act)) return true; + + //野战治疗阵 + if (SacredSoil.CanUse(out act)) return true; + + //不屈不挠之策 + if (Indomitability.CanUse(out act)) return true; + + act = null; + return false; + } + + [RotationDesc(ActionID.Succor)] + protected override bool DefenseAreaGCD(out IAction act) + { + if (Succor.CanUse(out act)) return true; + return false; + } + + [RotationDesc(ActionID.FeyIllumination, ActionID.Expedient, ActionID.SummonSeraph, ActionID.Consolation, ActionID.SacredSoil)] + protected override bool DefenseAreaAbility(byte abilitiesRemaining, out IAction act) + { + //异想的幻光 + if (FeyIllumination.CanUse(out act)) return true; + + //疾风怒涛之计 + if (Expedient.CanUse(out act)) return true; + + //慰藉 + if (SummonSeraph.CanUse(out act)) return true; + if (Consolation.CanUse(out act, emptyOrSkipCombo: true)) return true; + + //野战治疗阵 + if (SacredSoil.CanUse(out act)) return true; + + return false; + } + + + protected override bool AttackAbility(byte abilitiesRemaining, out IAction act) + { + if (InBurst) + { + //连环计 + if (ChainStratagem.CanUse(out act)) return true; + } + + if (Dissipation.EnoughLevel && Dissipation.WillHaveOneChargeGCD(3) && Dissipation.IsEnabled || Aetherflow.WillHaveOneChargeGCD(3)) + { + //能量吸收 + if (EnergyDrain.CanUse(out act, emptyOrSkipCombo: true)) return true; + } + + //转化 + if (Dissipation.CanUse(out act)) return true; + + //以太超流 + if (Aetherflow.CanUse(out act)) return true; + + act = null; + return false; + } + + //15秒秘策单盾扩散 + protected override IAction CountDownAction(float remainTime) + { + if (remainTime < Ruin.CastTime + Service.Config.CountDownAhead + && Ruin.CanUse(out var act)) return act; + + if (Configs.GetBool("prevDUN") && remainTime <= 15 && !DeploymentTactics.IsCoolingDown && PartyMembers.Count() > 1) + { + + if (!Recitation.IsCoolingDown) return Recitation; + if (!PartyMembers.Any((n) => n.HasStatus(true, StatusID.Galvanize))) + { + //如果还没上激励就给t一个激励 + if (Configs.GetBool("GiveT")) + { + return Adloquium; + } + } + else + { + return DeploymentTactics; + } + } + return base.CountDownAction(remainTime); + } +} \ No newline at end of file diff --git a/RotationSolver.Default/Healer/SGE_Default.cs b/RotationSolver.Default/Healer/SGE_Default.cs new file mode 100644 index 000000000..b9e408cba --- /dev/null +++ b/RotationSolver.Default/Healer/SGE_Default.cs @@ -0,0 +1,285 @@ +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Configuration.RotationConfig; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.Basic; + +namespace RotationSolver.Default.Healer; + +internal sealed class SGE_Default : SGE_Base +{ + public override string GameVersion => "6.18"; + + public override string RotationName => "Default"; + + + /// + /// ���þ������ + /// + private static BaseAction MEukrasianDiagnosis { get; } = new(ActionID.EukrasianDiagnosis, true) + { + ChoiceTarget = (Targets, mustUse) => + { + var targets = Targets.GetJobCategory(JobRole.Tank); + if (!targets.Any()) return null; + return targets.First(); + }, + ActionCheck = b => + { + if (InCombat) return false; + if (b == Player) return false; + if (b.HasStatus(false, StatusID.EukrasianDiagnosis, StatusID.EukrasianPrognosis, StatusID.Galvanize)) return false; + return true; + } + }; + + protected override bool CanHealSingleSpell => base.CanHealSingleSpell && (Configs.GetBool("GCDHeal") || PartyHealers.Count() < 2); + protected override bool CanHealAreaSpell => base.CanHealAreaSpell && (Configs.GetBool("GCDHeal") || PartyHealers.Count() < 2); + + protected override IRotationConfigSet CreateConfiguration() + { + return base.CreateConfiguration().SetBool("GCDHeal", false, "Auto Use GCD to heal."); + } + + protected override bool AttackAbility(byte abilitiesRemaining, out IAction act) + { + act = null!; + return false; + } + + protected override bool EmergencyAbility(byte abilitiesRemaining, IAction nextGCD, out IAction act) + { + if (base.EmergencyAbility(abilitiesRemaining, nextGCD, out act)) return true; + + //�¸������� + if (nextGCD.IsTheSameTo(false, Pneuma, EukrasianDiagnosis, + EukrasianPrognosis, Diagnosis, Prognosis)) + { + //� + if (Zoe.CanUse(out act)) return true; + } + + if (nextGCD == Diagnosis) + { + //��� + if (Krasis.CanUse(out act)) return true; + } + + return base.EmergencyAbility(abilitiesRemaining, nextGCD, out act); + } + + [RotationDesc(ActionID.Haima, ActionID.Taurochole)] + protected override bool DefenseSingleAbility(byte abilitiesRemaining, out IAction act) + { + if (Addersgall == 0 || Dyskrasia.CanUse(out _)) + { + if (Haima.CanUse(out act)) return true; + } + + //��ţ��֭ + if (Taurochole.CanUse(out act) && Taurochole.Target.GetHealthRatio() < 0.8) return true; + + return base.DefenseSingleAbility(abilitiesRemaining, out act); + } + + [RotationDesc(ActionID.EukrasianDiagnosis)] + protected override bool DefenseSingleGCD(out IAction act) + { + //��� + if (EukrasianDiagnosis.CanUse(out act)) + { + if (EukrasianDiagnosis.Target.HasStatus(false, + StatusID.EukrasianDiagnosis, + StatusID.EukrasianPrognosis, + StatusID.Galvanize + )) return false; + + //���� + if (Eukrasia.CanUse(out act)) return true; + + act = EukrasianDiagnosis; + return true; + } + + return base.DefenseSingleGCD(out act); + } + + [RotationDesc(ActionID.Panhaima, ActionID.Kerachole, ActionID.Holos)] + protected override bool DefenseAreaAbility(byte abilityRemain, out IAction act) + { + //����Ѫ + if (Addersgall == 0 && PartyMembersAverHP < 0.7) + { + if (Panhaima.CanUse(out act)) return true; + } + + //�����֭ + if (Kerachole.CanUse(out act)) return true; + + //������ + if (Holos.CanUse(out act)) return true; + + return base.DefenseAreaAbility(abilityRemain, out act); + } + + [RotationDesc(ActionID.EukrasianPrognosis)] + protected override bool DefenseAreaGCD(out IAction act) + { + //Ԥ�� + if (EukrasianPrognosis.CanUse(out act)) + { + if (EukrasianDiagnosis.Target.HasStatus(false, + StatusID.EukrasianDiagnosis, + StatusID.EukrasianPrognosis, + StatusID.Galvanize + )) return false; + + //���� + if (Eukrasia.CanUse(out act)) return true; + + act = EukrasianPrognosis; + return true; + } + + return base.DefenseAreaGCD(out act); + } + + protected override bool GeneralAbility(byte abilitiesRemaining, out IAction act) + { + //�Ĺ� + if (Kardia.CanUse(out act)) return true; + + //���� + if (Addersgall == 0 && Rhizomata.CanUse(out act)) return true; + + //���� + if (Soteria.CanUse(out act) && PartyMembers.Any(b => b.HasStatus(true, StatusID.Kardion) && b.GetHealthRatio() < Service.Config.HealthSingleAbility)) return true; + + //���� + if (Pepsis.CanUse(out act)) return true; + + act = null!; + return false; + } + + protected override bool GeneralGCD(out IAction act) + { + //���� ��һ����λ + if (Phlegma3.CanUse(out act, mustUse: true, emptyOrSkipCombo: IsMoving || Dyskrasia.CanUse(out _))) return true; + if (!Phlegma3.EnoughLevel && Phlegma2.CanUse(out act, mustUse: true, emptyOrSkipCombo: IsMoving || Dyskrasia.CanUse(out _))) return true; + if (!Phlegma2.EnoughLevel && Phlegma.CanUse(out act, mustUse: true, emptyOrSkipCombo: IsMoving || Dyskrasia.CanUse(out _))) return true; + + //ʧ�� + if (Dyskrasia.CanUse(out act)) return true; + + if (EukrasianDosis.CanUse(out var enAct)) + { + //����Dot + if (Eukrasia.CanUse(out act)) return true; + act = enAct; + return true; + } + + //עҩ + if (Dosis.CanUse(out act)) return true; + + //���� + if (Toxikon.CanUse(out act, mustUse: true)) return true; + + //��ս��Tˢ�����ζ��� + if (MEukrasianDiagnosis.CanUse(out _)) + { + //���� + if (Eukrasia.CanUse(out act)) return true; + + act = MEukrasianDiagnosis; + return true; + } + if (Eukrasia.CanUse(out act)) return true; + + return false; + } + + [RotationDesc(ActionID.Taurochole, ActionID.Druochole, ActionID.Holos, ActionID.Physis, ActionID.Panhaima)] + protected override bool HealSingleAbility(byte abilitiesRemaining, out IAction act) + { + //��ţ��֭ + if (Taurochole.CanUse(out act)) return true; + + //������֭ + if (Druochole.CanUse(out act)) return true; + + //����Դ����ʱ���뷶Χ���ƻ���ѹ�� + var tank = PartyTanks; + var isBoss = Dosis.Target.IsBoss(); + if (Addersgall == 0 && tank.Count() == 1 && tank.Any(t => t.GetHealthRatio() < 0.6f) && !isBoss) + { + //������ + if (Holos.CanUse(out act)) return true; + + //���� + if (Physis.CanUse(out act)) return true; + + //����Ѫ + if (Panhaima.CanUse(out act)) return true; + } + + return base.HealSingleAbility(abilitiesRemaining, out act); + } + + [RotationDesc(ActionID.Diagnosis)] + protected override bool HealSingleGCD(out IAction act) + { + if (Diagnosis.CanUse(out act)) return true; + return false; + } + + [RotationDesc(ActionID.Pneuma, ActionID.Prognosis, ActionID.EukrasianPrognosis)] + protected override bool HealAreaGCD(out IAction act) + { + if (PartyMembersAverHP < 0.65f || Dyskrasia.CanUse(out _) && PartyTanks.Any(t => t.GetHealthRatio() < 0.6f)) + { + //�����Ϣ + if (Pneuma.CanUse(out act, mustUse: true)) return true; + } + + //Ԥ�� + if (EukrasianPrognosis.Target.HasStatus(false, StatusID.EukrasianDiagnosis, StatusID.EukrasianPrognosis, StatusID.Galvanize)) + { + if (Prognosis.CanUse(out act)) return true; + } + + if (EukrasianPrognosis.CanUse(out _)) + { + //���� + if (Eukrasia.CanUse(out act)) return true; + + act = EukrasianPrognosis; + return true; + } + + act = null; + return false; + } + + [RotationDesc(ActionID.Kerachole, ActionID.Physis, ActionID.Holos, ActionID.Ixochole)] + protected override bool HealAreaAbility(byte abilitiesRemaining, out IAction act) + { + //�����֭ + if (Kerachole.CanUse(out act) && Level >= 78) return true; + + //���� + if (Physis.CanUse(out act)) return true; + + //������ + if (Holos.CanUse(out act) && PartyMembersAverHP < 0.65f) return true; + + //������֭ + if (Ixochole.CanUse(out act)) return true; + + return false; + } +} diff --git a/RotationSolver.Default/Healer/WHM_Default.cs b/RotationSolver.Default/Healer/WHM_Default.cs new file mode 100644 index 000000000..5ad252476 --- /dev/null +++ b/RotationSolver.Default/Healer/WHM_Default.cs @@ -0,0 +1,186 @@ +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Configuration.RotationConfig; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.Basic; + +namespace RotationSolver.Default.Healer; + +internal sealed class WHM_Default : WHM_Base +{ + public override string GameVersion => "6.28"; + + public override string RotationName => "Default"; + + protected override IRotationConfigSet CreateConfiguration() + { + return base.CreateConfiguration().SetBool("UseLilyWhenFull", true, "Auto use Lily when full") + .SetBool("UsePreRegen", false, "Regen on Tank in 5 seconds."); + } + public static IBaseAction RegenDefense { get; } = new BaseAction(ActionID.Regen, true, isEot: true, isTimeline: true) + { + ChoiceTarget = TargetFilter.FindAttackedTarget, + TargetStatus = new[] + { + StatusID.Regen1, + StatusID.Regen2, + StatusID.Regen3, + } + }; + + protected override bool GeneralGCD(out IAction act) + { + //苦难之心 + if (AfflatusMisery.CanUse(out act, mustUse: true)) return true; + + //泄蓝花 团队缺血时优先狂喜之心 + bool liliesNearlyFull = Lily == 2 && LilyAfter(17); + bool liliesFullNoBlood = Lily == 3 && BloodLily < 3; + if (Configs.GetBool("UseLilyWhenFull") && (liliesNearlyFull || liliesFullNoBlood) && AfflatusMisery.EnoughLevel) + { + if (PartyMembersAverHP < 0.7) + { + if (AfflatusRapture.CanUse(out act)) return true; + } + if (AfflatusSolace.CanUse(out act)) return true; + + } + + //群体输出 + if (Holy.CanUse(out act)) return true; + + //单体输出 + if (Aero.CanUse(out act, mustUse: IsMoving)) return true; + if (Stone.CanUse(out act)) return true; + + act = null; + return false; + } + + protected override bool AttackAbility(byte abilitiesRemaining, out IAction act) + { + //加个神速咏唱 + if (PresenseOfMind.CanUse(out act)) return true; + + //加个法令 + if (HasHostilesInRange && Assize.CanUse(out act, mustUse: true)) return true; + + return false; + } + + protected override bool EmergencyAbility(byte abilitiesRemaining, IAction nextGCD, out IAction act) + { + //加个无中生有 + if (nextGCD is BaseAction action && action.MPNeed >= 1000 && + ThinAir.CanUse(out act)) return true; + + //加个全大赦,狂喜之心 医济医治愈疗 + if (nextGCD.IsTheSameTo(true, AfflatusRapture, Medica, Medica2, Cure3)) + { + if (PlenaryIndulgence.CanUse(out act)) return true; + } + + return base.EmergencyAbility(abilitiesRemaining, nextGCD, out act); + } + + [RotationDesc(ActionID.AfflatusSolace, ActionID.Regen, ActionID.Cure2, ActionID.Cure)] + protected override bool HealSingleGCD(out IAction act) + { + //安慰之心 + if (AfflatusSolace.CanUse(out act)) return true; + + //再生 + if (Regen.CanUse(out act) + && (IsMoving || Regen.Target.GetHealthRatio() > 0.4)) return true; + + //救疗 + if (Cure2.CanUse(out act)) return true; + + //治疗 + if (Cure.CanUse(out act)) return true; + + return false; + } + + [RotationDesc(ActionID.Benediction, ActionID.Asylum, ActionID.DivineBenison, ActionID.Tetragrammaton)] + protected override bool HealSingleAbility(byte abilitiesRemaining, out IAction act) + { + if (Benediction.CanUse(out act) && + Benediction.Target.GetHealthRatio() < 0.3) return true; + + //庇护所 + if (!IsMoving && Asylum.CanUse(out act)) return true; + + //神祝祷 + if (DivineBenison.CanUse(out act)) return true; + + //神名 + if (Tetragrammaton.CanUse(out act)) return true; + return false; + } + + [RotationDesc(ActionID.AfflatusRapture, ActionID.Medica2, ActionID.Cure3, ActionID.Medica)] + protected override bool HealAreaGCD(out IAction act) + { + //狂喜之心 + if (AfflatusRapture.CanUse(out act)) return true; + + int hasMedica2 = PartyMembers.Count((n) => n.HasStatus(true, StatusID.Medica2)); + + //医济 在小队半数人都没有医济buff and 上次没放医济时使用 + if (Medica2.CanUse(out act) && hasMedica2 < PartyMembers.Count() / 2 && !IsLastAction(true, Medica2)) return true; + + //愈疗 + if (Cure3.CanUse(out act)) return true; + + //医治 + if (Medica.CanUse(out act)) return true; + + return false; + } + + [RotationDesc(ActionID.Asylum)] + protected override bool HealAreaAbility(byte abilitiesRemaining, out IAction act) + { + if (Asylum.CanUse(out act)) return true; + return false; + } + + [RotationDesc(ActionID.DivineBenison, ActionID.Aquaveil)] + protected override bool DefenseSingleAbility(byte abilitiesRemaining, out IAction act) + { + //神祝祷 + if (DivineBenison.CanUse(out act)) return true; + + //水流幕 + if (Aquaveil.CanUse(out act)) return true; + return false; + } + + [RotationDesc(ActionID.Temperance, ActionID.LiturgyoftheBell)] + protected override bool DefenseAreaAbility(byte abilitiesRemaining, out IAction act) + { + //节制 + if (Temperance.CanUse(out act)) return true; + + //礼仪之铃 + if (LiturgyoftheBell.CanUse(out act)) return true; + return false; + } + + protected override IAction CountDownAction(float remainTime) + { + if (remainTime < Stone.CastTime + Service.Config.CountDownAhead + && Stone.CanUse(out var act)) return act; + + if (Configs.GetBool("UsePreRegen") && remainTime <= 5 && remainTime > 3) + { + if (RegenDefense.CanUse(out act)) return act; + if (DivineBenison.CanUse(out act)) return act; + } + return base.CountDownAction(remainTime); + } +} \ No newline at end of file diff --git a/RotationSolver.Default/Magical/BLM_Default.cs b/RotationSolver.Default/Magical/BLM_Default.cs new file mode 100644 index 000000000..b43d13f7b --- /dev/null +++ b/RotationSolver.Default/Magical/BLM_Default.cs @@ -0,0 +1,374 @@ +using RotationSolver.Actions; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Configuration.RotationConfig; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.Basic; +using RotationSolver.Rotations.CustomRotation; +using System.Collections.Generic; + +namespace RotationSolver.Default.Magical; + +internal class BLM_Default : BLM_Base +{ + public override string GameVersion => "6.31"; + + public override string RotationName => "Default"; + + private static bool NeedToGoIce + { + get + { + //Can use Despair. + if (Despair.EnoughLevel && Player.CurrentMp >= Despair.MPNeed) return false; + + //Can use Fire1 + if (Fire.EnoughLevel && Player.CurrentMp >= Fire.MPNeed) return false; + + return true; + } + } + + private static bool NeedToTransposeGoIce(bool usedOne) + { + if (!NeedToGoIce) return false; + if (!Paradox.EnoughLevel) return false; + var compare = usedOne ? -1 : 0; + var count = PolyglotStacks; + if (count == compare++) return false; + if (count == compare++ && !EnchinaEndAfterGCD(2)) return false; + if (count >= compare && (HasFire || Swiftcast.WillHaveOneChargeGCD(2) || Triplecast.WillHaveOneChargeGCD(2))) return true; + if (!HasFire && !Swiftcast.WillHaveOneChargeGCD(2) && !Triplecast.CanUse(out _, gcdCountForAbility: 8)) return false; + return true; + } + + protected override IRotationConfigSet CreateConfiguration() + => base.CreateConfiguration() + .SetBool("UseTransposeForParadox", true, "Use Transpose to Fire for Paradox") + .SetBool("ExtendTimeSafely", false, "Extend Fire Element Time Safely") + .SetBool("UseN15", false, "Use N15"); + + protected override IAction CountDownAction(float remainTime) + { + IAction act; + if (remainTime < Fire3.CastTime + Service.Config.CountDownAhead) + { + if (Fire3.CanUse(out act)) return act; + } + if (remainTime <= 12 && Sharpcast.CanUse(out act, emptyOrSkipCombo: true)) return act; + return base.CountDownAction(remainTime); + } + + protected override bool AttackAbility(byte abilitiesRemaining, out IAction act) + { + if (InBurst && UseBurstMedicine(out act)) return true; + if (InUmbralIce) + { + if (UmbralIceStacks == 2 && !HasFire + && !IsLastGCD(ActionID.Paradox)) + { + if (Swiftcast.CanUse(out act)) return true; + if (Triplecast.CanUse(out act, emptyOrSkipCombo: true)) return true; + } + + if (UmbralIceStacks < 3 && LucidDreaming.CanUse(out act)) return true; + if (Sharpcast.CanUse(out act, emptyOrSkipCombo: true)) return true; + } + if (InAstralFire) + { + if (!CombatElapsedLess(6) && CombatElapsedLess(9) && Leylines.CanUse(out act)) return true; + if (Triplecast.CanUse(out act, gcdCountForAbility: 5)) return true; + } + if (Amplifier.CanUse(out act)) return true; + return false; + } + + protected override bool EmergencyAbility(byte abilitiesRemaining, IAction nextGCD, out IAction act) + { + //To Fire + if (Player.CurrentMp >= 7200 && UmbralIceStacks == 2 && Paradox.EnoughLevel) + { + if ((HasFire || HasSwift) && abilitiesRemaining == 1 && Transpose.CanUse(out act)) return true; + } + if (nextGCD.IsTheSameTo(false, Fire3) && HasFire && abilitiesRemaining == 1) + { + if (Transpose.CanUse(out act)) return true; + } + + //Using Manafont + if (InAstralFire) + { + if (Player.CurrentMp == 0 && abilitiesRemaining == 2 && Manafont.CanUse(out act)) return true; + //To Ice + if (NeedToTransposeGoIce(true) && Transpose.CanUse(out act)) return true; + } + + return base.EmergencyAbility(abilitiesRemaining, nextGCD, out act); + } + + protected override bool GeneralGCD(out IAction act) + { + if (InFireOrIce(out act, out var mustGo)) return true; + if (mustGo) return false; + //Triplecast for moving. + if (IsMoving && Triplecast.CanUse(out act, emptyOrSkipCombo: true)) return true; + + if (AddElementBase(out act)) return true; + if (Scathe.CanUse(out act)) return true; + if (MaintainStatus(out act)) return true; + + return false; + } + + private bool InFireOrIce(out IAction act, out bool mustGo) + { + act = null; + mustGo = false; + if (InUmbralIce) + { + if (GoFire(out act)) return true; + if (MaintainIce(out act)) return true; + if (DoIce(out act)) return true; + } + if (InAstralFire) + { + if (GoIce(out act)) return true; + if (MaintainFire(out act)) return true; + if (DoFire(out act)) return true; + } + return false; + } + + private static bool GoIce(out IAction act) + { + act = null; + + if (!NeedToGoIce) return false; + + //Use Manafont or transpose. + if ((!Manafont.IsCoolingDown || NeedToTransposeGoIce(false)) + && UseInstanceSpell(out act)) return true; + + //Go to Ice. + if (Blizzard2.CanUse(out act)) return true; + if (Blizzard3.CanUse(out act)) return true; + if (Transpose.CanUse(out act)) return true; + if (Blizzard.CanUse(out act)) return true; + return false; + } + + private static bool MaintainIce(out IAction act) + { + act = null; + if (UmbralIceStacks == 1) + { + if (Blizzard2.CanUse(out act)) return true; + + if (Player.Level == 90 && Blizzard.CanUse(out act)) return true; + if (Blizzard3.CanUse(out act)) return true; + } + if (UmbralIceStacks == 2 && Player.Level < 90) + { + if (Blizzard2.CanUse(out act)) return true; + if (Blizzard.CanUse(out act)) return true; + } + return false; + } + + private static bool DoIce(out IAction act) + { + if (IsLastAction(ActionID.UmbralSoul, ActionID.Transpose) + && IsParadoxActive && Blizzard.CanUse(out act)) return true; + + if (UmbralIceStacks == 3 && UsePolyglot(out act)) return true; + + //Add Hearts + if (UmbralIceStacks == 3 && + Blizzard4.EnoughLevel && UmbralHearts < 3 && !IsLastGCD + (ActionID.Blizzard4, ActionID.Freeze)) + { + if (Freeze.CanUse(out act)) return true; + if (Blizzard4.CanUse(out act)) return true; + } + + if (AddThunder(out act, 5)) return true; + if (UmbralIceStacks == 2 && UsePolyglot(out act, 0)) return true; + + if (IsParadoxActive) + { + if (Blizzard.CanUse(out act)) return true; + } + + if (Blizzard2.CanUse(out act)) return true; + if (Blizzard4.CanUse(out act)) return true; + if (Blizzard.CanUse(out act)) return true; + return false; + } + + private static bool GoFire(out IAction act) + { + act = null; + + //Transpose line + if (UmbralIceStacks < 3) return false; + + //Need more MP + if (Player.CurrentMp < 9600) return false; + + if (IsParadoxActive) + { + if (Blizzard.CanUse(out act)) return true; + } + + //Go to Fire. + if (Fire2.CanUse(out act)) return true; + if (Fire3.CanUse(out act)) return true; + if (Transpose.CanUse(out act)) return true; + if (Fire.CanUse(out act)) return true; + + return false; + } + + private bool MaintainFire(out IAction act) + { + switch (AstralFireStacks) + { + case 1: + if (Fire2.CanUse(out act)) return true; + if (Configs.GetBool("UseN15")) + { + if (HasFire && Fire3.CanUse(out act)) return true; + if (IsParadoxActive && Fire.CanUse(out act)) return true; + } + if (Fire3.CanUse(out act)) return true; + break; + case 2: + if (Fire2.CanUse(out act)) return true; + if (Fire.CanUse(out act)) return true; + break; + } + + if (ElementTimeEndAfterGCD(Configs.GetBool("ExtendTimeSafely") ? 3u : 2u)) + { + if (Player.CurrentMp >= Fire.MPNeed * 2 + 800 && Fire.CanUse(out act)) return true; + if (Flare.CanUse(out act)) return true; + if (Despair.CanUse(out act)) return true; + } + + act = null; + return false; + } + + private static bool DoFire(out IAction act) + { + if (UsePolyglot(out act)) return true; + + // Add thunder only at combat start. + if (CombatElapsedLess(5)) + { + if (AddThunder(out act, 0)) return true; + } + + if (Triplecast.CanUse(out act)) return true; + + if (AddThunder(out act, 0) && Player.WillStatusEndGCD(1, 0, true, + StatusID.Thundercloud)) return true; + + if (UmbralHearts < 2 && Flare.CanUse(out act)) return true; + if (Fire2.CanUse(out act)) return true; + + if (Player.CurrentMp >= Fire.MPNeed + 800) + { + if (Fire4.EnoughLevel) + { + if (Fire4.CanUse(out act)) return true; + } + else if (HasFire) + { + if(Fire3.CanUse(out act)) return true; + } + if (Fire.CanUse(out act)) return true; + } + + if (Despair.CanUse(out act)) return true; + + return false; + } + + private static bool UseInstanceSpell(out IAction act) + { + if (UsePolyglot(out act)) return true; + if (HasThunder && AddThunder(out act, 1)) return true; + if (UsePolyglot(out act, 0)) return true; + return false; + } + + private static bool AddThunder(out IAction act, uint gcdCount = 3) + { + act = null; + //Return if just used. + if (IsLastGCD(ActionID.Thunder, ActionID.Thunder2, ActionID.Thunder3, ActionID.Thunder4)) return false; + + //So long for thunder. + if (Thunder.CanUse(out _) && !Thunder.Target.WillStatusEndGCD(gcdCount, 0, true, + StatusID.Thunder, StatusID.Thunder2, StatusID.Thunder3, StatusID.Thunder4)) + return false; + + if (Thunder2.CanUse(out act)) return true; + if (Thunder.CanUse(out act)) return true; + + return false; + } + + private static bool AddElementBase(out IAction act) + { + if (Player.CurrentMp >= 7200) + { + if (Fire2.CanUse(out act)) return true; + if (Fire3.CanUse(out act)) return true; + if (Fire.CanUse(out act)) return true; + } + else + { + if (Blizzard2.CanUse(out act)) return true; + if (Blizzard3.CanUse(out act)) return true; + if (Blizzard.CanUse(out act)) return true; + } + return false; + } + + private static bool UsePolyglot(out IAction act, uint gcdCount = 3) + { + if (gcdCount == 0 || IsPolyglotStacksMaxed && EnchinaEndAfterGCD(gcdCount)) + { + if (Foul.CanUse(out act)) return true; + if (Xenoglossy.CanUse(out act)) return true; + } + + act = null; + return false; + } + + private bool MaintainStatus(out IAction act) + { + act = null; + if (CombatElapsedLess(6)) return false; + if (UmbralSoul.CanUse(out act)) return true; + if (InAstralFire && Transpose.CanUse(out act)) return true; + if (Configs.GetBool("UseTransposeForParadox") && + InUmbralIce && !IsParadoxActive && UmbralIceStacks == 3 + && Transpose.CanUse(out act)) return true; + + return false; + } + + [RotationDesc(ActionID.BetweenTheLines, ActionID.Leylines)] + protected override bool HealSingleAbility(byte abilitiesRemaining, out IAction act) + { + if (BetweenTheLines.CanUse(out act)) return true; + if (Leylines.CanUse(out act, mustUse: true)) return true; + + return base.HealSingleAbility(abilitiesRemaining, out act); + } +} diff --git a/RotationSolver.Default/Magical/BLU_Default.cs b/RotationSolver.Default/Magical/BLU_Default.cs new file mode 100644 index 000000000..1fb85d40e --- /dev/null +++ b/RotationSolver.Default/Magical/BLU_Default.cs @@ -0,0 +1,463 @@ +using RotationSolver.Actions; +using RotationSolver.Attributes; +using RotationSolver.Commands; +using RotationSolver.Configuration.RotationConfig; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.Basic; + +namespace RotationSolver.Default.Magical; + +internal sealed class BLU_Default : BLU_Base +{ + public override string GameVersion => "6.18"; + + public override string RotationName => "Default"; + + protected override bool CanHealAreaSpell => base.CanHealAreaSpell && BlueId == BLUID.Healer; + protected override bool CanHealSingleSpell => base.CanHealSingleSpell && BlueId == BLUID.Healer; + + protected override IRotationConfigSet CreateConfiguration() + { + return base.CreateConfiguration() + .SetBool("MoonFluteBreak", false, "MoonFlute") + .SetBool("SingleAOE", true, "Single AOE") + .SetBool("GamblerKill", false, "Gambler Kill") + .SetBool("UseFinalSting", false, "Use FinalSting") + .SetFloat("FinalStingHP", 0, "FinalStingHP"); + } + + private bool MoonFluteBreak => Configs.GetBool("MoonFluteBreak"); + private bool UseFinalSting => Configs.GetBool("UseFinalSting"); + private float FinalStingHP => Configs.GetFloat("FinalStingHP"); + /// + /// 0-70练级,快速练级,滑舌拉怪 + /// + private bool QuickLevel => false; + /// + /// 赌几率秒杀 + /// + private bool GamblerKill => Configs.GetBool("GamblerKill"); + /// + /// 单体时是否释放高伤害AOE + /// + private bool SingleAOE => Configs.GetBool("SingleAOE"); + + protected override bool AttackAbility(byte abilitiesRemaining, out IAction act) + { + act = null; + return false; + } + + protected override bool EmergencyAbility(byte abilitiesRemaining, IAction nextGCD, out IAction act) + { + if (nextGCD.IsTheSameTo(false, Selfdestruct, FinalSting)) + { + if (Swiftcast.CanUse(out act)) return true; + } + return base.EmergencyAbility(abilitiesRemaining, nextGCD, out act); + } + + protected override bool MoveForwardGCD(out IAction act) + { + //正义飞踢 + if (JKick.CanUse(out act, mustUse: true)) return true; + return base.MoveForwardGCD(out act); + } + + protected override bool GeneralGCD(out IAction act) + { + act = null; + //狂战士副作用期间 + if (Player.HasStatus(true, StatusID.WaningNocturne)) return false; + //鬼宿脚 + if (PhantomFlurry.IsCoolingDown && !PhantomFlurry.ElapsedAfter(1) || Player.HasStatus(true, StatusID.PhantomFlurry)) + { + if (!Player.WillStatusEnd(0.1f, true, StatusID.PhantomFlurry) && Player.WillStatusEnd(1, true, StatusID.PhantomFlurry) && PhantomFlurry2.CanUse(out act, mustUse: true)) return true; + return false; + } + //穿甲散弹 + if (Player.HasStatus(true, StatusID.SurpanakhaFury)) + { + if (Surpanakha.CanUse(out act, mustUse: true, emptyOrSkipCombo: true)) return true; + } + + //终极针组合 + if (UseFinalSting && CanUseFinalSting(out act)) return true; + + //爆发 + if (MoonFluteBreak && DBlueBreak(out act)) return true; + + //高伤害 + if (PrimalSpell(out act)) return true; + //群体 + if (AreaGCD(out act)) return true; + //单体填充 + if (SingleGCD(out act)) return true; + + + act = null; + return false; + } + + protected override bool HealSingleGCD(out IAction act) + { + if (BlueId == BLUID.Healer) + { + //有某些非常危险的状态。 + if (SpecialType == SpecialCommandType.EsunaStanceNorth && WeakenPeople.Any() || DyingPeople.Any()) + { + if (Exuviation.CanUse(out act, mustUse: true)) return true; + } + if (AngelsSnack.CanUse(out act)) return true; + if (Stotram.CanUse(out act)) return true; + if (PomCure.CanUse(out act)) return true; + } + else + { + if (WhiteWind.CanUse(out act, mustUse: true)) return true; + } + + return base.HealSingleGCD(out act); + } + + /// + /// D青爆发 + /// + /// + /// + private bool DBlueBreak(out IAction act) + { + if (TripleTrident.OnSlot && TripleTrident.WillHaveOneChargeGCD(OnSlotCount(Whistle, Tingle), 0)) + { + //口笛 + if (Whistle.CanUse(out act)) return true; + //哔哩哔哩 + if (!Player.HasStatus(true, StatusID.Tingling) + && Tingle.CanUse(out act, mustUse: true)) return true; + if (Offguard.CanUse(out act)) return true; + //鱼叉 + if (TripleTrident.CanUse(out act, mustUse: true)) return true; + } + + if (AllOnSlot(Whistle, FinalSting, BasicInstinct) && UseFinalSting) + { + if (Whistle.CanUse(out act)) return true; + //破防 + if (Offguard.CanUse(out act)) return true; + //哔哩哔哩 + if (Tingle.CanUse(out act)) return true; + } + + //月笛 + if (CanUseMoonFlute(out act)) return true; + + if (!Player.HasStatus(true, StatusID.WaxingNocturne)) return false; + + //月下彼岸花 + if (Nightbloom.CanUse(out act, mustUse: true)) return true; + //地火喷发 + if (Eruption.CanUse(out act, mustUse: true)) return true; + //马特拉魔术 + if (MatraMagic.CanUse(out act, mustUse: true)) return true; + //正义飞踢 + if (JKick.CanUse(out act, mustUse: true)) return true; + //捕食 + if (Devour.CanUse(out act, mustUse: true)) return true; + //轰雷 + if (ShockStrike.CanUse(out act, mustUse: true)) return true; + //冰雪乱舞 + if (GlassDance.CanUse(out act, mustUse: true)) return true; + //魔法锤 + if (MagicHammer.CanUse(out act, mustUse: true)) return true; + //穿甲散弹 + if (Surpanakha.CurrentCharges >= 3 && Surpanakha.CanUse(out act, mustUse: true, emptyOrSkipCombo: true)) return true; + //鬼宿脚 + if (PhantomFlurry.CanUse(out act, mustUse: true)) return true; + + //冰雾 + if (WhiteDeath.CanUse(out act)) return true; + //如意大旋风 + if (InBurst && !MoonFluteBreak && BothEnds.CanUse(out act, mustUse: true)) return true; + //类星体 + if (Quasar.CanUse(out act, mustUse: true)) return true; + //飞翎雨 + if (FeatherRain.CanUse(out act, mustUse: true)) return true; + //山崩 + if (MountainBuster.CanUse(out act, mustUse: true)) return true; + //冰雪乱舞 + if (MountainBuster.CanUse(out act, mustUse: true)) return true; + + //音爆 + if (SonicBoom.CanUse(out act)) return true; + + return false; + } + + + /// + /// 月笛条件 + /// + /// + /// + private bool CanUseMoonFlute(out IAction act) + { + if (!MoonFlute.CanUse(out act) && !HasHostilesInRange) return false; + + if (Player.HasStatus(true, StatusID.WaxingNocturne)) return false; + + if (Player.HasStatus(true, StatusID.Harmonized)) return true; + + return false; + } + + /// + /// 终极针组合 + /// + /// + /// + private bool CanUseFinalSting(out IAction act) + { + act = null; + if (!UseFinalSting) return false; + if (!FinalSting.CanUse(out _)) return false; + + var useFinalSting = Player.HasStatus(true, StatusID.WaxingNocturne, StatusID.Harmonized); + + if (AllOnSlot(Whistle, MoonFlute, FinalSting) && !AllOnSlot(BasicInstinct)) + { + if ((float)Target.CurrentHp / Target.MaxHp > FinalStingHP) return false; + + if (Whistle.CanUse(out act)) return true; + if (MoonFlute.CanUse(out act)) return true; + if (useFinalSting && FinalSting.CanUse(out act)) return true; + } + + if (AllOnSlot(Whistle, MoonFlute, FinalSting, BasicInstinct)) + { + //破防 + if (Player.HasStatus(true, StatusID.WaxingNocturne) && Offguard.CanUse(out act)) return true; + + if ((float)Target.CurrentHp / Target.MaxHp > FinalStingHP) return false; + if (Whistle.CanUse(out act)) return true; + if (MoonFlute.CanUse(out act)) return true; + if (useFinalSting && FinalSting.CanUse(out act)) return true; + } + + return false; + } + + /// + /// 单体GCD填充 + /// + /// + /// + private bool SingleGCD(out IAction act) + { + act = null; + if (Player.HasStatus(true, StatusID.WaxingNocturne)) return false; + + //滑舌 +眩晕 0-70练级用 + if (QuickLevel && StickyTongue.CanUse(out act)) return true; + + //苦闷之歌 + if (AllOnSlot(Bristle, SongofTorment) && SongofTorment.CanUse(out _)) + { + //怒发冲冠 + if (Bristle.CanUse(out act)) return true; + if (SongofTorment.CanUse(out act)) return true; + } + if (SongofTorment.CanUse(out act)) return true; + + //复仇冲击 + if (RevengeBlast.CanUse(out act)) return true; + //赌徒行为 + if (GamblerKill) + { + //导弹 + if (Missile.CanUse(out act)) return true; + //螺旋尾 + if (TailScrew.CanUse(out act)) return true; + //死亡宣告 + if (Doom.CanUse(out act)) return true; + } + + //锋利菜刀 近战 眩晕增伤 + if (SharpenedKnife.CanUse(out act)) return true; + + //吸血 回蓝 + if (Player.CurrentMp < 1000 && BloodDrain.CanUse(out act)) return true; + //音爆 + if (SonicBoom.CanUse(out act)) return true; + if (DrillCannons.CanUse(out act, mustUse: true)) return true; + //永恒射线 无法 +眩晕1s + if (PerpetualRay.CanUse(out act)) return true; + //深渊贯穿 无物 +麻痹 + if (AbyssalTransfixion.CanUse(out act)) return true; + //逆流 雷法 +加重 + if (Reflux.CanUse(out act)) return true; + //水炮 + if (WaterCannon.CanUse(out act)) return true; + + //小侦测 + if (CondensedLibra.CanUse(out act)) return true; + + //滑舌 +眩晕 + if (StickyTongue.CanUse(out act)) return true; + + //投掷沙丁鱼(打断) + if (FlyingSardine.CanUse(out act)) return true; + + return false; + } + + /// + /// 范围GCD填充 + /// + /// + /// + private bool AreaGCD(out IAction act) + { + act = null; + if (Player.HasStatus(true, StatusID.WaxingNocturne)) return false; + + //赌徒行为 + if (GamblerKill) + { + //火箭炮 + if (Launcher.CanUse(out act, mustUse: true)) return true; + //5级即死 + if (Level5Death.CanUse(out act, mustUse: true)) return true; + } + + if (false) + { + if (AcornBomb.CanUse(out act, mustUse: true)) return true; + if (Faze.CanUse(out act, mustUse: true)) return true; + if (Snort.CanUse(out act, mustUse: true)) return true; + if (BadBreath.CanUse(out act, mustUse: true)) return true; + if (Chirp.CanUse(out act, mustUse: true)) return true; + if (Level5Petrify.CanUse(out act, mustUse: true)) return true; + } + + //陆行鸟陨石 + if (HasCompanion && ChocoMeteor.CanUse(out act, mustUse: true)) return true; + + if (HostileTargets.GetObjectInRadius(6).Count() < 3) + { + //水力吸引 + if (HydroPull.CanUse(out act)) return true; + } + + //寒冰咆哮 + if (TheRamVoice.CanUse(out act)) return true; + + //超振动 + if (!IsMoving && Target.HasStatus(false, StatusID.DeepFreeze) && TheRamVoice.CanUse(out act)) return true; + + //雷电咆哮 + if (TheDragonVoice.CanUse(out act)) return true; + + //冰焰 + if (Blaze.CanUse(out act)) return true; + if (FeculentFlood.CanUse(out act)) return true; + //火炎放射 + if (FlameThrower.CanUse(out act)) return true; + //水流吐息 + if (AquaBreath.CanUse(out act)) return true; + //高压电流 + if (HighVoltage.CanUse(out act)) return true; + //怒视 + if (Glower.CanUse(out act)) return true; + //平原震裂 + if (Plaincracker.CanUse(out act)) return true; + //诡异视线 + if (TheLook.CanUse(out act)) return true; + //喷墨 + if (InkJet.CanUse(out act)) return true; + if (FireAngon.CanUse(out act)) return true; + if (MindBlast.CanUse(out act)) return true; + if (AlpineDraft.CanUse(out act)) return true; + if (ProteanWave.CanUse(out act)) return true; + if (Northerlies.CanUse(out act)) return true; + if (Electrogenesis.CanUse(out act)) return true; + if (WhiteKnightsTour.CanUse(out act)) return true; + if (BlackKnightsTour.CanUse(out act)) return true; + if (Tatamigaeshi.CanUse(out act)) return true; + + if (MustardBomb.CanUse(out act)) return true; + if (AetherialSpark.CanUse(out act)) return true; + if (MaledictionofWater.CanUse(out act)) return true; + if (FlyingFrenzy.CanUse(out act)) return true; + if (DrillCannons.CanUse(out act)) return true; + if (Weight4tonze.CanUse(out act)) return true; + if (Needles1000.CanUse(out act)) return true; + if (Kaltstrahl.CanUse(out act)) return true; + if (PeripheralSynthesis.CanUse(out act)) return true; + if (FlameThrower.CanUse(out act)) return true; + if (FlameThrower.CanUse(out act)) return true; + if (SaintlyBeam.CanUse(out act)) return true; + + return false; + } + + /// + /// 有CD的技能 + /// + /// + /// + private bool PrimalSpell(out IAction act) + { + act = null; + if (Player.HasStatus(true, StatusID.WaxingNocturne)) return false; + + //冰雾 + if (WhiteDeath.CanUse(out act)) return true; + //玄天武水壁 + if (DivineCataract.CanUse(out act)) return true; + + //斗灵弹 + if (TheRoseofDestruction.CanUse(out act)) return true; + + //渔叉三段 + if (InBurst && !MoonFluteBreak && TripleTrident.CanUse(out act)) return true; + //马特拉魔术 + if (InBurst && !MoonFluteBreak && MatraMagic.CanUse(out act)) return true; + + //捕食 + if (Devour.CanUse(out act)) return true; + //魔法锤 + //if (MagicHammer.ShouldUse(out act)) return true; + + //月下彼岸花 + if (InBurst && !MoonFluteBreak && Nightbloom.CanUse(out act, mustUse: SingleAOE)) return true; + //如意大旋风 + if (InBurst && !MoonFluteBreak && BothEnds.CanUse(out act, mustUse: SingleAOE)) return true; + + //穿甲散弹 + if (InBurst && !MoonFluteBreak && Surpanakha.CurrentCharges >= 3 && Surpanakha.CanUse(out act, mustUse: SingleAOE, emptyOrSkipCombo: true)) return true; + + //类星体 + if (Quasar.CanUse(out act, mustUse: SingleAOE)) return true; + //正义飞踢 + if (!IsMoving && JKick.CanUse(out act, mustUse: SingleAOE)) return true; + + //地火喷发 + if (Eruption.CanUse(out act, mustUse: SingleAOE)) return true; + //飞翎雨 + if (FeatherRain.CanUse(out act, mustUse: SingleAOE)) return true; + + //轰雷 + if (ShockStrike.CanUse(out act, mustUse: SingleAOE)) return true; + //山崩 + if (MountainBuster.CanUse(out act, mustUse: SingleAOE)) return true; + + //冰雪乱舞 + if (MountainBuster.CanUse(out act, mustUse: SingleAOE)) return true; + + //if (MountainBuster.ShouldUse(out act, mustUse: SingleAOE)) return true; + + + return false; + } +} diff --git a/RotationSolver.Default/Magical/BLU_Simplify.cs b/RotationSolver.Default/Magical/BLU_Simplify.cs new file mode 100644 index 000000000..656c5e65e --- /dev/null +++ b/RotationSolver.Default/Magical/BLU_Simplify.cs @@ -0,0 +1,46 @@ +using RotationSolver.Actions; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.Basic; + +namespace RotationSolver.Default.Magical; + +internal class BLU_Simplify : BLU_Base +{ + public override string GameVersion => "6.3"; + + public override string RotationName => "Simplify"; + + public override string Description => "This is a simplfied version for me (ArchiTed) using, \nwhich doesn't contain all actions."; + + protected override bool AttackAbility(byte abilitiesRemaining, out IAction act) + { + act = null; + return false; + } + + protected override bool GeneralGCD(out IAction act) + { + if (ChocoMeteor.CanUse(out act)) return true; + if (DrillCannons.CanUse(out act)) return true; + + + if (TripleTrident.OnSlot && TripleTrident.RightType && TripleTrident.WillHaveOneChargeGCD(OnSlotCount(Whistle, Tingle), 0)) + { + if ((TripleTrident.CanUse(out _, mustUse: true) || !HasHostilesInRange) && Whistle.CanUse(out act)) return true; + + if (!Player.HasStatus(true, StatusID.Tingling) + && Tingle.CanUse(out act, mustUse: true)) return true; + if (Offguard.CanUse(out act)) return true; + + if (TripleTrident.CanUse(out act, mustUse: true)) return true; + } + if (ChocoMeteor.CanUse(out act, mustUse: DataCenter.HasCompanion)) return true; + + if (SonicBoom.CanUse(out act)) return true; + if (DrillCannons.CanUse(out act, mustUse: true)) return true; + + return false; + } +} diff --git a/RotationSolver.Default/Magical/RDM_Default.cs b/RotationSolver.Default/Magical/RDM_Default.cs new file mode 100644 index 000000000..49e9242e8 --- /dev/null +++ b/RotationSolver.Default/Magical/RDM_Default.cs @@ -0,0 +1,172 @@ +using RotationSolver.Actions; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Configuration.RotationConfig; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.Basic; + +namespace RotationSolver.Default.Magical; + +[RotationDesc(ActionID.Embolden)] +internal sealed class RDM_Default : RDM_Base +{ + public override string GameVersion => "6.31"; + + public override string RotationName => "Default"; + + public bool CanStartMeleeCombo + { + get + { + if (Player.HasStatus(true, StatusID.Manafication, StatusID.Embolden) || + BlackMana == 100 || WhiteMana == 100) return true; + + //��ħ��Ԫû�����������£�Ҫ���С��ħԪ���������� + if (BlackMana == WhiteMana) return false; + + else if (WhiteMana < BlackMana) + { + if (Player.HasStatus(true, StatusID.VerstoneReady)) return false; + } + else + { + if (Player.HasStatus(true, StatusID.VerfireReady)) return false; + } + + if (Player.HasStatus(true, Vercure.StatusProvide)) return false; + + //Waiting for embolden. + if (Embolden.EnoughLevel && Embolden.WillHaveOneChargeGCD(5)) return false; + + return true; + } + } + + static RDM_Default() + { + Acceleration.RotationCheck = b => InCombat; + } + + protected override IRotationConfigSet CreateConfiguration() + { + return base.CreateConfiguration() + .SetBool("UseVercure", true, "Use Vercure for Dualcast"); + } + + protected override IAction CountDownAction(float remainTime) + { + if (remainTime < Verthunder.CastTime + Service.Config.CountDownAhead + && Verthunder.CanUse(out var act)) return act; + + //Remove Swift + StatusHelper.StatusOff(StatusID.Dualcast); + StatusHelper.StatusOff(StatusID.Acceleration); + StatusHelper.StatusOff(StatusID.Swiftcast); + + return base.CountDownAction(remainTime); + } + + protected override bool GeneralGCD(out IAction act) + { + act = null; + if (ManaStacks == 3) return false; + + if (!Verthunder2.CanUse(out _)) + { + if (Verfire.CanUse(out act)) return true; + if (Verstone.CanUse(out act)) return true; + } + + if (Scatter.CanUse(out act)) return true; + if (WhiteMana < BlackMana) + { + if (Veraero2.CanUse(out act) && BlackMana - WhiteMana != 5) return true; + if (Veraero.CanUse(out act) && BlackMana - WhiteMana != 6) return true; + } + if (Verthunder2.CanUse(out act)) return true; + if (Verthunder.CanUse(out act)) return true; + + if (Jolt.CanUse(out act)) return true; + + if (Configs.GetBool("UseVercure") && Vercure.CanUse(out act)) return true; + + return false; + } + + + protected override bool EmergencyGCD(out IAction act) + { + if (ManaStacks == 3) + { + if (BlackMana > WhiteMana) + { + if (Verholy.CanUse(out act, mustUse: true)) return true; + } + if (Verflare.CanUse(out act, mustUse: true)) return true; + } + + if (Resolution.CanUse(out act, mustUse: true)) return true; + if (Scorch.CanUse(out act, mustUse: true)) return true; + + + if (IsLastGCD(true, Moulinet) && Moulinet.CanUse(out act, mustUse: true)) return true; + if (Zwerchhau.CanUse(out act)) return true; + if (Redoublement.CanUse(out act)) return true; + + if (!CanStartMeleeCombo) return false; + + if (Moulinet.CanUse(out act)) + { + if (BlackMana >= 60 && WhiteMana >= 60) return true; + } + else + { + if (BlackMana >= 50 && WhiteMana >= 50 && Riposte.CanUse(out act)) return true; + } + if (ManaStacks > 0 && Riposte.CanUse(out act)) return true; + + return false; + } + + protected override bool EmergencyAbility(byte abilityRemain, IAction nextGCD, out IAction act) + { + act = null; + if (CombatElapsedLess(4)) return false; + + if (InBurst && Embolden.CanUse(out act, mustUse: true)) return true; + + //Use Manafication after embolden. + if ((Player.HasStatus(true, StatusID.Embolden) || IsLastAbility(ActionID.Embolden)) + && Manafication.CanUse(out act)) return true; + + act = null; + return false; + } + + protected override bool AttackAbility(byte abilitiesRemaining, out IAction act) + { + //Swift + if (ManaStacks == 0 && (BlackMana < 50 || WhiteMana < 50) + && (CombatElapsedLess(4) || !Manafication.EnoughLevel || !Manafication.WillHaveOneChargeGCD(0, 1))) + { + if (!Player.HasStatus(true, StatusID.VerfireReady, StatusID.VerstoneReady)) + { + if (Swiftcast.CanUse(out act)) return true; + if (Acceleration.CanUse(out act, emptyOrSkipCombo: true)) return true; + } + } + + if (InBurst && UseBurstMedicine(out act)) return true; + + //Attack abilities. + if (ContreSixte.CanUse(out act, mustUse: true)) return true; + if (Fleche.CanUse(out act)) return true; + + if (Engagement.CanUse(out act, emptyOrSkipCombo: true)) return true; + if (CorpsAcorps.CanUse(out act, mustUse: true) && !IsMoving) return true; + + return false; + } +} + diff --git a/RotationSolver.Default/Magical/SMN_Default.cs b/RotationSolver.Default/Magical/SMN_Default.cs new file mode 100644 index 000000000..d94bd84fd --- /dev/null +++ b/RotationSolver.Default/Magical/SMN_Default.cs @@ -0,0 +1,175 @@ +using RotationSolver.Actions; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Configuration.RotationConfig; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.Basic; + +namespace RotationSolver.Default.Magical; + +[RotationDesc(ActionID.SearingLight)] +internal sealed class SMN_Default : SMN_Base +{ + public override string GameVersion => "6.28"; + + public override string RotationName => "Default"; + + protected override IRotationConfigSet CreateConfiguration() + { + return base.CreateConfiguration() + .SetCombo("addSwiftcast", 0, "Use Swiftcast", "No", "Wind", "Fire", "All") + .SetCombo("SummonOrder", 0, "Order", "Soil-Wind-Fire", "Soil-Fire-Wind", "Wind-Soil-Fire") + .SetBool("addCrimsonCyclone", true, "Auto CrimsonCyclon"); + } + + public SMN_Default() + { + RuinIV.RotationCheck = b => !Player.HasStatus(true, StatusID.Swiftcast) && !InBahamut && !InPhoenix; + SearingLight.RotationCheck = b => !Player.HasStatus(false, StatusID.SearingLight); + } + + protected override bool CanHealSingleSpell => false; + + [RotationDesc(ActionID.CrimsonCyclone)] + protected override bool MoveForwardGCD(out IAction act) + { + //火神突进 + if (CrimsonCyclone.CanUse(out act, mustUse: true)) return true; + return base.MoveForwardGCD(out act); + } + + protected override bool GeneralGCD(out IAction act) + { + //宝石兽召唤 + if (SummonCarbuncle.CanUse(out act)) return true; + + //风神读条 + if (Slipstream.CanUse(out act, mustUse: true)) return true; + //火神冲锋 + if (CrimsonStrike.CanUse(out act, mustUse: true)) return true; + + //AOE + if (PreciousBrilliance.CanUse(out act)) return true; + //单体 + if (Gemshine.CanUse(out act)) return true; + + if (!IsMoving && Configs.GetBool("addCrimsonCyclone") && CrimsonCyclone.CanUse(out act, mustUse: true)) return true; + + //龙神不死鸟 + if ((Player.HasStatus(false, StatusID.SearingLight) || SearingLight.IsCoolingDown) && SummonBahamut.CanUse(out act)) return true; + if (!SummonBahamut.EnoughLevel && HasHostilesInRange && Aethercharge.CanUse(out act)) return true; + + //毁4 + if (IsMoving && (Player.HasStatus(true, StatusID.GarudasFavor) || InIfrit) && RuinIV.CanUse(out act, mustUse: true)) return true; + + //召唤蛮神 + switch (Configs.GetCombo("SummonOrder")) + { + default: + //土 + if (SummonTopaz.CanUse(out act)) return true; + //风 + if (SummonEmerald.CanUse(out act)) return true; + //火 + if (SummonRuby.CanUse(out act)) return true; + break; + + case 1: + //土 + if (SummonTopaz.CanUse(out act)) return true; + //火 + if (SummonRuby.CanUse(out act)) return true; + //风 + if (SummonEmerald.CanUse(out act)) return true; + break; + + case 2: + //风 + if (SummonEmerald.CanUse(out act)) return true; + //土 + if (SummonTopaz.CanUse(out act)) return true; + //火 + if (SummonRuby.CanUse(out act)) return true; + break; + } + if (SummonTimerRemaining == 0 && AttunmentTimerRemaining == 0 && RuinIV.CanUse(out act, mustUse: true)) return true; + //迸裂三灾 + if (Outburst.CanUse(out act)) return true; + + //毁123 + if (Ruin.CanUse(out act)) return true; + return false; + } + + protected override bool AttackAbility(byte abilitiesRemaining, out IAction act) + { + if (InBurst) + { + //灼热之光 + if (SearingLight.CanUse(out act)) return true; + } + + //龙神不死鸟迸发 + if ((InBahamut && SummonBahamut.ElapsedAfterGCD(3) || InPhoenix || IsTargetBoss && IsTargetDying) && EnkindleBahamut.CanUse(out act, mustUse: true)) return true; + //死星核爆 + if ((SummonBahamut.ElapsedAfterGCD(3) || IsTargetBoss && IsTargetDying) && Deathflare.CanUse(out act, mustUse: true)) return true; + //苏生之炎 + if (Rekindle.CanUse(out act, mustUse: true)) return true; + //山崩 + if (MountainBuster.CanUse(out act, mustUse: true)) return true; + + //痛苦核爆 + if ((Player.HasStatus(false, StatusID.SearingLight) && InBahamut && (SummonBahamut.ElapsedAfterGCD(3) || !EnergyDrain.IsCoolingDown) || + !SearingLight.EnoughLevel || IsTargetBoss && IsTargetDying) && Painflare.CanUse(out act)) return true; + //溃烂爆发 + if ((Player.HasStatus(false, StatusID.SearingLight) && InBahamut && (SummonBahamut.ElapsedAfterGCD(3) || !EnergyDrain.IsCoolingDown) || + !SearingLight.EnoughLevel || IsTargetBoss && IsTargetDying) && Fester.CanUse(out act)) return true; + + //能量抽取 + if (EnergySiphon.CanUse(out act)) return true; + //能量吸收 + if (EnergyDrain.CanUse(out act)) return true; + + return false; + } + protected override bool EmergencyAbility(byte abilitiesRemaining, IAction nextGCD, out IAction act) + { + //即刻进循环 + switch (Configs.GetCombo("addSwiftcast")) + { + default: + break; + case 1: + if (nextGCD.IsTheSameTo(true, Slipstream) || Attunement == 0 && Player.HasStatus(true, StatusID.GarudasFavor)) + { + if (Swiftcast.CanUse(out act, mustUse: true)) return true; + } + break; + case 2: + if (InIfrit && (nextGCD.IsTheSameTo(true, Gemshine, PreciousBrilliance) || IsMoving)) + { + if (Swiftcast.CanUse(out act, mustUse: true)) return true; + } + break; + + case 3: + if (nextGCD.IsTheSameTo(true, Slipstream) || Attunement == 0 && Player.HasStatus(true, StatusID.GarudasFavor) || + InIfrit && (nextGCD.IsTheSameTo(true, Gemshine, PreciousBrilliance) || IsMoving)) + { + if (Swiftcast.CanUse(out act, mustUse: true)) return true; + } + break; + } + return base.EmergencyAbility(abilitiesRemaining, nextGCD, out act); + } + + protected override IAction CountDownAction(float remainTime) + { + if (remainTime <= 30 && SummonCarbuncle.CanUse(out _)) return SummonCarbuncle; + //1.5s预读毁3 + if (remainTime <= Ruin.CastTime + Service.Config.CountDownAhead + && Ruin.CanUse(out _)) return Ruin; + return base.CountDownAction(remainTime); + } +} diff --git a/RotationSolver.Default/Melee/DRG_Default.cs b/RotationSolver.Default/Melee/DRG_Default.cs new file mode 100644 index 000000000..0b3fcf4df --- /dev/null +++ b/RotationSolver.Default/Melee/DRG_Default.cs @@ -0,0 +1,149 @@ +using RotationSolver.Actions; +using RotationSolver.Attributes; +using RotationSolver.Commands; +using RotationSolver.Configuration.RotationConfig; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.Basic; +using RotationSolver.Rotations.CustomRotation; +using System.Collections.Generic; + +namespace RotationSolver.Default.Melee; + +internal sealed class DRG_Default : DRG_Base +{ + public override string GameVersion => "6.18"; + + public override string RotationName => "Default"; + + protected override IRotationConfigSet CreateConfiguration() + { + return base.CreateConfiguration().SetBool("DRG_ShouldDelay", true, "Delay the dragon?") + .SetBool("DRG_Opener", false, "Opener in lv.88") + .SetBool("DRG_SafeMove", true, "Moving save"); + } + + [RotationDesc(ActionID.SpineshatterDive, ActionID.DragonfireDive)] + protected override bool MoveForwardAbility(byte abilityRemain, out IAction act, bool recordTarget = true) + { + if (abilityRemain > 1) + { + if (SpineshatterDive.CanUse(out act, emptyOrSkipCombo: true, recordTarget: recordTarget)) return true; + if (DragonfireDive.CanUse(out act, mustUse: true, emptyOrSkipCombo: true, recordTarget: recordTarget)) return true; + } + + act = null; + return false; + } + protected override bool EmergencyAbility(byte abilityRemain, IAction nextGCD, out IAction act) + { + if (nextGCD.IsTheSameTo(true, FullThrust, CoerthanTorment) + || Player.HasStatus(true, StatusID.LanceCharge) && nextGCD.IsTheSameTo(false, FangandClaw)) + { + //���� + if (abilityRemain == 1 && LifeSurge.CanUse(out act, emptyOrSkipCombo: true)) return true; + } + + return base.EmergencyAbility(abilityRemain, nextGCD, out act); + } + + protected override bool AttackAbility(byte abilitiesRemaining, out IAction act) + { + if (InBurst) + { + //��ǹ + if (LanceCharge.CanUse(out act, mustUse: true)) + { + if (abilitiesRemaining == 1 && !Player.HasStatus(true, StatusID.PowerSurge)) return true; + if (Player.HasStatus(true, StatusID.PowerSurge)) return true; + } + + //�������� + if (DragonSight.CanUse(out act, mustUse: true)) return true; + + //ս������ + if (BattleLitany.CanUse(out act, mustUse: true)) return true; + } + + //����֮�� + if (Nastrond.CanUse(out act, mustUse: true)) return true; + + //׹�dz� + if (Stardiver.CanUse(out act, mustUse: true)) return true; + + //���� + if (HighJump.EnoughLevel) + { + if (HighJump.CanUse(out act)) return true; + } + else + { + if (Jump.CanUse(out act)) return true; + } + + //���Խ������Ѫ + if (Geirskogul.CanUse(out act, mustUse: true)) return true; + + //����� + if (SpineshatterDive.CanUse(out act, emptyOrSkipCombo: true)) + { + if (Player.HasStatus(true, StatusID.LanceCharge) && LanceCharge.ElapsedAfterGCD(3)) return true; + } + if (Player.HasStatus(true, StatusID.PowerSurge) && SpineshatterDive.CurrentCharges != 1 && SpineshatterDive.CanUse(out act)) return true; + + //����� + if (MirageDive.CanUse(out act)) return true; + + //���׳� + if (DragonfireDive.CanUse(out act, mustUse: true)) + { + if (Player.HasStatus(true, StatusID.LanceCharge) && LanceCharge.ElapsedAfterGCD(3)) return true; + } + + //�����㾦 + if (WyrmwindThrust.CanUse(out act, mustUse: true)) return true; + + return false; + } + + protected override bool GeneralGCD(out IAction act) + { + #region Ⱥ�� + if (CoerthanTorment.CanUse(out act)) return true; + if (SonicThrust.CanUse(out act)) return true; + if (DoomSpike.CanUse(out act)) return true; + + #endregion + + #region ���� + if (Configs.GetBool("ShouldDelay")) + { + if (WheelingThrust.CanUse(out act)) return true; + if (FangandClaw.CanUse(out act)) return true; + } + else + { + if (FangandClaw.CanUse(out act)) return true; + if (WheelingThrust.CanUse(out act)) return true; + } + + if (FullThrust.CanUse(out act)) return true; + if (ChaosThrust.CanUse(out act)) return true; + + //�����Ƿ���Ҫ��Buff + if (Player.WillStatusEndGCD(5, 0, true, StatusID.PowerSurge)) + { + if (Disembowel.CanUse(out act)) return true; + } + + if (VorpalThrust.CanUse(out act)) return true; + if (TrueThrust.CanUse(out act)) return true; + + if (SpecialType == SpecialCommandType.MoveForward && MoveForwardAbility(1, out act)) return true; + if (PiercingTalon.CanUse(out act)) return true; + + return false; + + #endregion + } +} diff --git a/RotationSolver.Default/Melee/MNK_Default.cs b/RotationSolver.Default/Melee/MNK_Default.cs new file mode 100644 index 000000000..82a18ecda --- /dev/null +++ b/RotationSolver.Default/Melee/MNK_Default.cs @@ -0,0 +1,195 @@ +using Dalamud.Game.ClientState.JobGauge.Enums; +using RotationSolver.Actions; +using RotationSolver.Attributes; +using RotationSolver.Commands; +using RotationSolver.Configuration.RotationConfig; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.Basic; +using System; +using System.Linq; + +namespace RotationSolver.Default.Melee; + +[RotationDesc(ActionID.RiddleofFire)] +internal sealed class MNK_Default : MNK_Base +{ + public override string GameVersion => "6.31"; + + public override string RotationName => "LunarSolarOpener"; + + protected override IRotationConfigSet CreateConfiguration() + { + return base.CreateConfiguration().SetBool("AutoFormShift", true, "Auto use FormShift"); + } + + protected override IAction CountDownAction(float remainTime) + { + if (remainTime < 0.2) + { + if (Thunderclap.CanUse(out var act, true, true)) return act; + if (Thunderclap.CanUse(out act, false, true)) return act; + } + if (remainTime < 15) + { + if (Chakra < 5 && Meditation.CanUse(out var act)) return act; + if (FormShift.CanUse(out act)) return act; + } + + return base.CountDownAction(remainTime); + } + + private static bool OpoOpoForm(out IAction act) + { + if (ArmoftheDestroyer.CanUse(out act)) return true; + if (DragonKick.CanUse(out act)) return true; + if (Bootshine.CanUse(out act)) return true; + return false; + } + + + private static bool RaptorForm(out IAction act) + { + if (FourpointFury.CanUse(out act)) return true; + if (Player.WillStatusEndGCD(3, 0, true, StatusID.DisciplinedFist) && TwinSnakes.CanUse(out act)) return true; + if (TrueStrike.CanUse(out act)) return true; + return false; + } + + private static bool CoerlForm(out IAction act) + { + if (Rockbreaker.CanUse(out act)) return true; + if (Demolish.CanUse(out act)) return true; + if (SnapPunch.CanUse(out act)) return true; + return false; + } + + protected override bool GeneralGCD(out IAction act) + { + if (PerfectBalanceActions(out act)) return true; + + if (Player.HasStatus(true, StatusID.CoerlForm)) + { + if (CoerlForm(out act)) return true; + } + else if (Player.HasStatus(true, StatusID.RaptorForm)) + { + if (RaptorForm(out act)) return true; + } + if (OpoOpoForm(out act)) return true; + + if (SpecialType == SpecialCommandType.MoveForward && MoveForwardAbility(1, out act)) return true; + if (Chakra < 5 && Meditation.CanUse(out act)) return true; + if (Configs.GetBool("AutoFormShift") && FormShift.CanUse(out act)) return true; + + return false; + } + + static bool PerfectBalanceActions(out IAction act) + { + if (!BeastChakras.Contains(BeastChakra.NONE)) + { + if (HasSolar && HasLunar) + { + if (PhantomRush.CanUse(out act, mustUse: true)) return true; + if (TornadoKick.CanUse(out act, mustUse: true)) return true; + } + if (BeastChakras.Contains(BeastChakra.RAPTOR)) + { + if (RisingPhoenix.CanUse(out act, mustUse: true)) return true; + if (FlintStrike.CanUse(out act, mustUse: true)) return true; + } + else + { + if (ElixirField.CanUse(out act, mustUse: true)) return true; + } + } + else if (Player.HasStatus(true, StatusID.PerfectBalance) && Level >= 60) + { + //Some time, no choice + if (HasSolar) + { + if (LunarNadi(out act)) return true; + } + else if (BeastChakras.Contains(BeastChakra.COEURL) || BeastChakras.Contains(BeastChakra.RAPTOR)) + { + if (SolarNadi(out act)) return true; + } + + //Add status when solar. + if (Player.WillStatusEndGCD(3, 0, true, StatusID.DisciplinedFist) + || Target.WillStatusEndGCD(3, 0, true, StatusID.Demolish)) + { + if (SolarNadi(out act)) return true; + } + if (LunarNadi(out act)) return true; + } + + act = null; + return false; + } + + static bool LunarNadi(out IAction act) + { + if (OpoOpoForm(out act)) return true; + return false; + } + + static bool SolarNadi(out IAction act) + { + //Emergency usage of status. + if (!BeastChakras.Contains(BeastChakra.RAPTOR) + && Player.WillStatusEndGCD(1, 0, true, StatusID.DisciplinedFist)) + { + if (RaptorForm(out act)) return true; + } + if (!BeastChakras.Contains(BeastChakra.COEURL) + && Target.WillStatusEndGCD(1, 0, true, StatusID.Demolish)) + { + if (CoerlForm(out act)) return true; + } + + if (!BeastChakras.Contains(BeastChakra.RAPTOR)) + { + if (RaptorForm(out act)) return true; + } + if (!BeastChakras.Contains(BeastChakra.OPOOPO)) + { + if (OpoOpoForm(out act)) return true; + } + if (!BeastChakras.Contains(BeastChakra.COEURL)) + { + if (CoerlForm(out act)) return true; + } + + return CoerlForm(out act); + } + + protected override bool AttackAbility(byte abilitiesRemaining, out IAction act) + { + act = null; + if (abilitiesRemaining == 1 && InCombat) + { + if (UseBurstMedicine(out act)) return true; + if (InBurst && !CombatElapsedLess(5) && RiddleofFire.CanUse(out act)) return true; + } + + if (CombatElapsedLess(8)) return false; + + if (Brotherhood.CanUse(out act)) return true; + + if (BeastChakras.Contains(BeastChakra.NONE) && Player.HasStatus(true, StatusID.RaptorForm) + && (!RiddleofFire.EnoughLevel || Player.HasStatus(false, StatusID.RiddleofFire))) + { + if (PerfectBalance.CanUse(out act, emptyOrSkipCombo: true)) return true; + } + + if (RiddleofWind.CanUse(out act)) return true; + + if (HowlingFist.CanUse(out act)) return true; + if (SteelPeak.CanUse(out act)) return true; + if (HowlingFist.CanUse(out act, mustUse: true)) return true; + + return false; + } +} diff --git a/RotationSolver.Default/Melee/NIN_Default.cs b/RotationSolver.Default/Melee/NIN_Default.cs new file mode 100644 index 000000000..88089fc6b --- /dev/null +++ b/RotationSolver.Default/Melee/NIN_Default.cs @@ -0,0 +1,325 @@ +using RotationSolver.Actions; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Commands; +using RotationSolver.Configuration.RotationConfig; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.Basic; + +namespace RotationSolver.Default.Melee; + +[RotationDesc(ActionID.Mug)] +internal sealed class NIN_Default : NIN_Base +{ + public override string GameVersion => "6.0"; + + public override string RotationName => "Default"; + + private static INinAction _ninactionAim = null; + + protected override IRotationConfigSet CreateConfiguration() + { + return base.CreateConfiguration() + .SetBool("UseHide", true, "Use hide") + .SetBool("AutoUnhide", true, "Auto Unhide."); + } + + private static void SetNinjustus(INinAction act) + { + if (_ninactionAim != null && IsLastAction(false, Ten, Jin, Chi, FumaShurikenTen, FumaShurikenJin)) return; + _ninactionAim = act; + } + + private bool ChoiceNinjutsus(out IAction act) + { + act = null; + if (AdjustId(2260) != 2260) return false; + + //在GCD快转完的时候再判断是否调整非空忍术 + if (_ninactionAim != null && WeaponRemain < 0.2) return false; + + //有生杀予夺 + if (Player.HasStatus(true, StatusID.Kassatsu)) + { + if (GokaMekkyaku.CanUse(out _)) + { + SetNinjustus(GokaMekkyaku); + return false; + } + if (HyoshoRanryu.CanUse(out _)) + { + SetNinjustus(HyoshoRanryu); + return false; + } + + if (Katon.CanUse(out _)) + { + SetNinjustus(Katon); + return false; + } + + if (Raiton.CanUse(out _)) + { + SetNinjustus(Raiton); + return false; + } + } + else + { + bool empty = Ten.CanUse(out _, mustUse: true); + bool haveDoton = Player.HasStatus(true, StatusID.Doton); + + //加状态 + if (Huraijin.CanUse(out act)) return true; + + if (InHuton && _ninactionAim?.ID == Huton.ID) + { + ClearNinjutsus(); + return false; + } + + if (empty && (!InCombat || !Huraijin.EnoughLevel) && Huton.CanUse(out _)) + { + SetNinjustus(Huton); + return false; + } + + if (GeneralNinjutsus(empty, haveDoton)) return false; + } + return false; + } + + private bool GeneralNinjutsus(bool empty, bool haveDoton) + { + //清空忍术 + if (empty) + { + if (Katon.CanUse(out _)) + { + if (!haveDoton && !IsMoving && TenChiJin.WillHaveOneChargeGCD(0, 1)) _ninactionAim = Doton; + else SetNinjustus(Katon); + return true; + } + //背刺 + if (InBurst && Suiton.CanUse(out _)) + { + SetNinjustus(Suiton); + } + } + //常规单体忍术 + if (Ten.CanUse(out _) && (!TenChiJin.EnoughLevel || TenChiJin.IsCoolingDown)) + { + if (Raiton.CanUse(out _)) + { + SetNinjustus(Raiton); + return true; + } + + if (!Chi.EnoughLevel && FumaShuriken.CanUse(out _)) + { + SetNinjustus(FumaShuriken); + return true; + } + } + return false; + } + + private static void ClearNinjutsus() + { + _ninactionAim = null; + } + + private bool DoNinjutsus(out IAction act) + { + act = null; + + //有天地人 + if (Player.HasStatus(true, StatusID.TenChiJin)) + { + uint tenId = AdjustId(Ten.ID); + uint chiId = AdjustId(Chi.ID); + uint jinId = AdjustId(Jin.ID); + + //第一个 + if (tenId == FumaShurikenTen.ID) + { + //AOE + if (Katon.CanUse(out _)) + { + if (FumaShurikenJin.CanUse(out act)) return true; + } + //Single + if (FumaShurikenTen.CanUse(out act)) return true; + } + + //第二击杀AOE + else if (tenId == KatonTen.ID) + { + if (KatonTen.CanUse(out act, mustUse: true)) return true; + } + //其他几击 + else if (chiId == RaitonChi.ID) + { + if (RaitonChi.CanUse(out act, mustUse: true)) return true; + } + else if (chiId == DotonChi.ID) + { + if (DotonChi.CanUse(out act, mustUse: true)) return true; + } + else if (jinId == SuitonJin.ID) + { + if (SuitonJin.CanUse(out act, mustUse: true)) return true; + } + } + + if (_ninactionAim == null) return false; + + uint id = AdjustId(2260); + + //没开始,释放第一个 + if (id == 2260) + { + //重置 + if (!Player.HasStatus(true, StatusID.Kassatsu, StatusID.TenChiJin) + && !Ten.CanUse(out _, mustUse: true)) + { + return false; + } + act = _ninactionAim.Ninjutsus[0]; + return true; + } + //失败了 + else if (id == RabbitMedium.ID) + { + ClearNinjutsus(); + act = null; + return false; + } + //结束了 + else if (id == _ninactionAim.ID) + { + if (_ninactionAim.CanUse(out act, mustUse: true)) return true; + if (_ninactionAim.ID == Doton.ID && !InCombat) + { + act = _ninactionAim; + return true; + } + } + //释放第二个 + else if (id == FumaShuriken.ID) + { + if (_ninactionAim.Ninjutsus.Length > 1) + { + act = _ninactionAim.Ninjutsus[1]; + return true; + } + } + //释放第三个 + else if (id == Katon.ID || id == Raiton.ID || id == Hyoton.ID) + { + if (_ninactionAim.Ninjutsus.Length > 2) + { + act = _ninactionAim.Ninjutsus[2]; + return true; + } + } + //ClearNinjutsus(); + return false; + } + + protected override bool GeneralGCD(out IAction act) + { + if (IsLastAction(false, DotonChi, SuitonJin, + RabbitMedium, FumaShuriken, Katon, Raiton, + Hyoton, Huton, Doton, Suiton, GokaMekkyaku, HyoshoRanryu)) + { + ClearNinjutsus(); + } + if (ChoiceNinjutsus(out act)) return true; + if (DoNinjutsus(out act)) return true; + + //用真北取消隐匿 + if (Configs.GetBool("AutoUnhide")) + { + StatusHelper.StatusOff(StatusID.Hidden); + } + //用隐匿恢复忍术数量 + if (!InCombat && _ninactionAim == null && Configs.GetBool("UseHide") + && Ten.IsCoolingDown && Hide.CanUse(out act)) return true; + + var replace = AdjustId(2260); + //无忍术或者忍术中途停了 + if (_ninactionAim == null || replace != 2260 && replace != _ninactionAim.ID) + { + //大招 + if (FleetingRaiju.CanUse(out act)) return true; + if (ForkedRaiju.CanUse(out act)) + { + if (ForkedRaiju.Target.DistanceToPlayer() < 2) + { + return true; + } + } + + if (PhantomKamaitachi.CanUse(out act)) return true; + + if (Huraijin.CanUse(out act)) return true; + + //AOE + if (HakkeMujinsatsu.CanUse(out act)) return true; + if (DeathBlossom.CanUse(out act)) return true; + + //Single + if (ArmorCrush.CanUse(out act)) return true; + if (AeolianEdge.CanUse(out act)) return true; + if (GustSlash.CanUse(out act)) return true; + if (SpinningEdge.CanUse(out act)) return true; + + //飞刀 + if (SpecialType == SpecialCommandType.MoveForward && MoveForwardAbility(1, out act)) return true; + if (ThrowingDagger.CanUse(out act)) return true; + } + + act = null; + return false; + } + + [RotationDesc(ActionID.ForkedRaiju)] + protected override bool MoveForwardGCD(out IAction act) + { + if (ForkedRaiju.CanUse(out act)) return true; + return base.MoveForwardGCD(out act); + } + + protected override bool AttackAbility(byte abilitiesRemaining, out IAction act) + { + act = null; + if (!InCombat || AdjustId(2260) != 2260) return false; + + //夺取 + if (InBurst && Mug.CanUse(out act)) return true; + + //解决Buff + if (TrickAttack.CanUse(out act)) return true; + if (Meisui.CanUse(out act)) return true; + + if (!IsMoving && TenChiJin.CanUse(out act)) return true; + if (Kassatsu.CanUse(out act)) return true; + if (UseBurstMedicine(out act)) return true; + + if (Bunshin.CanUse(out act)) return true; + if (HellfrogMedium.CanUse(out act)) return true; + if (Bhavacakra.CanUse(out act)) return true; + + if (!DreamWithinaDream.EnoughLevel) + { + if (Assassinate.CanUse(out act)) return true; + } + else + { + if (DreamWithinaDream.CanUse(out act)) return true; + } + return false; + } +} diff --git a/RotationSolver.Default/Melee/RPR_Default.cs b/RotationSolver.Default/Melee/RPR_Default.cs new file mode 100644 index 000000000..8355edf2b --- /dev/null +++ b/RotationSolver.Default/Melee/RPR_Default.cs @@ -0,0 +1,189 @@ +using RotationSolver.Actions; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Configuration.RotationConfig; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.Basic; +using RotationSolver.Rotations.CustomRotation; +using System.Collections.Generic; + +namespace RotationSolver.Default.Melee; + +internal sealed class RPR_Default : RPR_Base +{ + public override string GameVersion => "6.28"; + + public override string RotationName => "Default"; + + protected override IRotationConfigSet CreateConfiguration() + { + return base.CreateConfiguration().SetBool("EnshroudPooling", false, "Enshroud Pooling"); + } + public RPR_Default() + { + //保留红条不第一时间打出去,保证暴食不空转 同时保证不延后大丰收 + BloodStalk.RotationCheck = b => !Player.HasStatus(true, StatusID.BloodsownCircle) && !Player.HasStatus(true, StatusID.ImmortalSacrifice) && (Gluttony.EnoughLevel && !Gluttony.WillHaveOneChargeGCD(4) || !Gluttony.EnoughLevel || Soul == 100); + GrimSwathe.RotationCheck = BloodStalk.RotationCheck; + + //必须有dot + ArcaneCircle.RotationCheck = b => Target.HasStatus(true, StatusID.DeathsDesign); + + //必须进战 + HarvestMoon.RotationCheck = b => InCombat; + } + + + protected override IAction CountDownAction(float remainTime) + { + //倒数收获月 + if (remainTime <= 30 && Soulsow.CanUse(out _)) return Soulsow; + //提前2s勾刃 + if (remainTime <= Harpe.CastTime + Service.Config.CountDownAhead + && Harpe.CanUse(out _)) return Harpe; + return base.CountDownAction(remainTime); + } + + protected override bool GeneralGCD(out IAction act) + { + //非战斗收获月 + if (Soulsow.CanUse(out act)) return true; + + //上Debuff + if (WhorlofDeath.CanUse(out act)) return true; + if (ShadowofDeath.CanUse(out act)) return true; + + //补蓝 + if (SoulReaver) + { + if (Guillotine.CanUse(out act)) return true; + if (Player.HasStatus(true, StatusID.EnhancedGibbet)) + { + if (Gibbet.CanUse(out act)) return true; + } + else + { + if (Gallows.CanUse(out act)) return true; + } + } + + //夜游魂变身状态 + if (Enshrouded) + { + //补DoT + if (ShadowofDeath.CanUse(out act)) return true; + + if (LemureShroud > 1) + { + if (Configs.GetBool("EnshroudPooling") && PlentifulHarvest.EnoughLevel && ArcaneCircle.WillHaveOneCharge(9) && + (LemureShroud == 4 && Target.WillStatusEnd(30, true, StatusID.DeathsDesign) || LemureShroud == 3 && Target.WillStatusEnd(50, true, StatusID.DeathsDesign))) //双附体窗口期 + { + if (ShadowofDeath.CanUse(out act, mustUse: true)) return true; + } + + //夜游魂衣-虚无/交错收割 阴冷收割 + if (GrimReaping.CanUse(out act)) return true; + if (Player.HasStatus(true, StatusID.EnhancedCrossReaping) || !Player.HasStatus(true, StatusID.EnhancedVoidReaping)) + { + if (CrossReaping.CanUse(out act)) return true; + } + else + { + if (VoidReaping.CanUse(out act)) return true; + } + } + if (LemureShroud == 1) + { + if (Communio.EnoughLevel) + { + if (!IsMoving && Communio.CanUse(out act, mustUse: true)) + { + return true; + } + //跑机制来不及读条?补个buff混一下 + else + { + if (ShadowofDeath.CanUse(out act, mustUse: IsMoving)) return true; + } + } + else + { + //夜游魂衣-虚无/交错收割 阴冷收割 + if (GrimReaping.CanUse(out act)) return true; + if (Player.HasStatus(true, StatusID.EnhancedCrossReaping) || !Player.HasStatus(true, StatusID.EnhancedVoidReaping)) + { + if (CrossReaping.CanUse(out act)) return true; + } + else + { + if (VoidReaping.CanUse(out act)) return true; + } + } + } + } + + //大丰收 + if (PlentifulHarvest.CanUse(out act, mustUse: true)) return true; + + //灵魂钐割 + if (SoulScythe.CanUse(out act, emptyOrSkipCombo: true)) return true; + //灵魂切割 + if (SoulSlice.CanUse(out act, emptyOrSkipCombo: true)) return true; + + //群体二连 + if (NightmareScythe.CanUse(out act)) return true; + if (SpinningScythe.CanUse(out act)) return true; + + //单体三连 + if (InfernalSlice.CanUse(out act)) return true; + if (WaxingSlice.CanUse(out act)) return true; + if (Slice.CanUse(out act)) return true; + + //摸不到怪 先花掉收获月 + if (HarvestMoon.CanUse(out act, mustUse: true)) return true; + if (Harpe.CanUse(out act)) return true; + + return false; + } + + protected override bool AttackAbility(byte abilitiesRemaining, out IAction act) + { + if (InBurst) + { + //神秘环 + if (ArcaneCircle.CanUse(out act)) return true; + + if (IsTargetBoss && IsTargetDying || //资源倾泻 + !Configs.GetBool("EnshroudPooling") && Shroud >= 50 ||//未开启双附体 + Configs.GetBool("EnshroudPooling") && Shroud >= 50 && + (!PlentifulHarvest.EnoughLevel || //等级不足以双附体 + Player.HasStatus(true, StatusID.ArcaneCircle) || //在神秘环期间附体 + ArcaneCircle.WillHaveOneCharge(8) || //双附体起手 + !Player.HasStatus(true, StatusID.ArcaneCircle) && ArcaneCircle.WillHaveOneCharge(65) && !ArcaneCircle.WillHaveOneCharge(50) || //奇数分钟不用攒附体 + !Player.HasStatus(true, StatusID.ArcaneCircle) && Shroud >= 90)) //攒蓝条为双附体 + { + //夜游魂衣 + if (Enshroud.CanUse(out act)) return true; + } + } + if (Enshrouded) + { + //夜游魂衣-夜游魂切割 夜游魂钐割 + if (LemuresScythe.CanUse(out act, emptyOrSkipCombo: true)) return true; + if (LemuresSlice.CanUse(out act, emptyOrSkipCombo: true)) return true; + } + + //暴食 + //大丰收期间延后暴食 + if (PlentifulHarvest.EnoughLevel && !Player.HasStatus(true, StatusID.ImmortalSacrifice) && !Player.HasStatus(true, StatusID.BloodsownCircle) || !PlentifulHarvest.EnoughLevel) + { + if (Gluttony.CanUse(out act, mustUse: true)) return true; + } + + //AOE + if (GrimSwathe.CanUse(out act)) return true; + //单体 + if (BloodStalk.CanUse(out act)) return true; + return false; + } +} \ No newline at end of file diff --git a/RotationSolver.Default/Melee/SAM_Default.cs b/RotationSolver.Default/Melee/SAM_Default.cs new file mode 100644 index 000000000..6b1d0b2eb --- /dev/null +++ b/RotationSolver.Default/Melee/SAM_Default.cs @@ -0,0 +1,148 @@ +using RotationSolver.Actions; +using RotationSolver.Attributes; +using RotationSolver.Configuration.RotationConfig; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.Basic; +using RotationSolver.Rotations.CustomRotation; +using System.Collections.Generic; + +namespace RotationSolver.Default.Melee; + +internal sealed class SAM_Default : SAM_Base +{ + public override string GameVersion => "6.28"; + + public override string RotationName => "Default"; + + protected override IRotationConfigSet CreateConfiguration() + { + return base.CreateConfiguration() + .SetFloat("addKenki", 50, "use Kenki above.", min: 0, max: 85, speed: 5); + } + + /// + /// 明镜止水 + /// + private static bool haveMeikyoShisui => Player.HasStatus(true, StatusID.MeikyoShisui); + + public SAM_Default() + { + //明镜里ban了最基础技能 + Hakaze.RotationCheck = b => !haveMeikyoShisui; + Fuko.RotationCheck = b => !haveMeikyoShisui; + Fuga.RotationCheck = b => !haveMeikyoShisui; + Enpi.RotationCheck = b => !haveMeikyoShisui; + //保证有buff加成 + Higanbana.RotationCheck = b => HaveMoon && HaveFlower; + OgiNamikiri.RotationCheck = b => HaveMoon && HaveFlower; + HissatsuSenei.RotationCheck = b => HaveMoon && HaveFlower; + HissatsuGuren.RotationCheck = b => HaveMoon && HaveFlower; + } + + //public override SortedList DescriptionDict => new() + //{ + // {DescType.DefenseSingle, $"{ThirdEye}"}, + // {DescType.MoveAction, $"{HissatsuGyoten}"}, + //}; + + protected override bool GeneralGCD(out IAction act) + { + //奥义回返 + if (KaeshiNamikiri.CanUse(out act, mustUse: true)) return true; + + //燕回返 + if (KaeshiGoken.CanUse(out act, emptyOrSkipCombo: true, mustUse: true)) return true; + if (KaeshiSetsugekka.CanUse(out act, emptyOrSkipCombo: true, mustUse: true)) return true; + + //奥义斩浪 + if ((!IsTargetBoss || Target.HasStatus(true, StatusID.Higanbana)) && OgiNamikiri.CanUse(out act, mustUse: true)) return true; + + //处理居合术 + if (SenCount == 1 && IsTargetBoss && !IsTargetDying) + { + if (Higanbana.CanUse(out act)) return true; + } + if (SenCount == 2) + { + if (TenkaGoken.CanUse(out act, mustUse: !MidareSetsugekka.EnoughLevel)) return true; + } + if (SenCount == 3) + { + if (MidareSetsugekka.CanUse(out act)) return true; + } + + //连击2 + if ((!HaveMoon || MoonTime < FlowerTime || !Oka.EnoughLevel) && Mangetsu.CanUse(out act, emptyOrSkipCombo: haveMeikyoShisui && !HasGetsu)) return true; + if ((!HaveFlower || FlowerTime < MoonTime) && Oka.CanUse(out act, emptyOrSkipCombo: haveMeikyoShisui && !HasKa)) return true; + if (!HasSetsu && Yukikaze.CanUse(out act, emptyOrSkipCombo: haveMeikyoShisui && HasGetsu && HasKa && !HasSetsu)) return true; + + //连击3 + if (Gekko.CanUse(out act, emptyOrSkipCombo: haveMeikyoShisui && !HasGetsu)) return true; + if (Kasha.CanUse(out act, emptyOrSkipCombo: haveMeikyoShisui && !HasKa)) return true; + + //连击2 + if ((!HaveMoon || MoonTime < FlowerTime || !Shifu.EnoughLevel) && Jinpu.CanUse(out act)) return true; + if ((!HaveFlower || FlowerTime < MoonTime) && Shifu.CanUse(out act)) return true; + + //连击1 + if (Fuko.CanUse(out act)) return true; + if (!Fuko.EnoughLevel && Fuga.CanUse(out act)) return true; + if (Hakaze.CanUse(out act)) return true; + + //燕飞 + if (Enpi.CanUse(out act)) return true; + act = null; + return false; + } + + protected override bool AttackAbility(byte abilitiesRemaining, out IAction act) + { + //意气冲天 + if (Kenki <= 50 && Ikishoten.CanUse(out act)) return true; + + //叶隐 + if (Target.HasStatus(true, StatusID.Higanbana) && Target.WillStatusEnd(32, true, StatusID.Higanbana) && !Target.WillStatusEnd(28, true, StatusID.Higanbana) && SenCount == 1 && IsLastAction(true, Yukikaze) && !haveMeikyoShisui) + { + if (Hagakure.CanUse(out act)) return true; + } + + //闪影、红莲 + if (HissatsuGuren.CanUse(out act, mustUse: !HissatsuSenei.EnoughLevel)) return true; + if (HissatsuSenei.CanUse(out act)) return true; + + //照破、无明照破 + if (Shoha2.CanUse(out act)) return true; + if (Shoha.CanUse(out act)) return true; + + //震天、九天 + if (Kenki >= 50 && Ikishoten.WillHaveOneCharge(10) || Kenki >= Configs.GetFloat("addKenki") || IsTargetBoss && IsTargetDying) + { + if (HissatsuKyuten.CanUse(out act)) return true; + if (HissatsuShinten.CanUse(out act)) return true; + } + + act = null; + return false; + } + protected override bool EmergencyAbility(byte abilitiesRemaining, IAction nextGCD, out IAction act) + { + //明镜止水 + if (HasHostilesInRange && IsLastGCD(true, Yukikaze, Mangetsu, Oka) && + (!IsTargetBoss || Target.HasStatus(true, StatusID.Higanbana) && !Target.WillStatusEnd(40, true, StatusID.Higanbana) || !HaveMoon && !HaveFlower || IsTargetBoss && IsTargetDying)) + { + if (MeikyoShisui.CanUse(out act, emptyOrSkipCombo: true)) return true; + } + return base.EmergencyAbility(abilitiesRemaining, nextGCD, out act); + } + + + protected override IAction CountDownAction(float remainTime) + { + //开局使用明镜 + if (remainTime <= 5 && MeikyoShisui.CanUse(out _)) return MeikyoShisui; + //真北防止boss面向没到位 + if (remainTime <= 2 && TrueNorth.CanUse(out _)) return TrueNorth; + return base.CountDownAction(remainTime); + } +} \ No newline at end of file diff --git a/RotationSolver.Default/Ranged/BRD_Default.cs b/RotationSolver.Default/Ranged/BRD_Default.cs new file mode 100644 index 000000000..48a3a7b41 --- /dev/null +++ b/RotationSolver.Default/Ranged/BRD_Default.cs @@ -0,0 +1,213 @@ +using Dalamud.Game.ClientState.JobGauge.Enums; +using RotationSolver.Actions; +using RotationSolver.Attributes; +using RotationSolver.Configuration.RotationConfig; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.Basic; + +namespace RotationSolver.Default.Ranged; + +internal sealed class BRD_Default : BRD_Base +{ + public override string GameVersion => "6.28"; + + public override string RotationName => "Default"; + + protected override IRotationConfigSet CreateConfiguration() => base.CreateConfiguration() + .SetBool("BindWAND", false, "Use Raging Strikes on WAND") + .SetCombo("FirstSong", 0, "First Song", "WAND", "MAGE", "ARMY") + .SetFloat("WANDTime", 43, "WAND Time", min: 0, max: 45, speed: 1) + .SetFloat("MAGETime", 34, "MAGE Time", min: 0, max: 45, speed: 1) + .SetFloat("ARMYTime", 43, "ARMY Time", min: 0, max: 45, speed: 1); + + public override string Description => "Please make sure that the three song times add up to 120 seconds!"; + + private bool BindWAND => Configs.GetBool("BindWAND") && WanderersMinuet.EnoughLevel; + private int FirstSong => Configs.GetCombo("FirstSong"); + private float WANDRemainTime => 45 - Configs.GetFloat("WANDTime"); + private float MAGERemainTime => 45 - Configs.GetFloat("MAGETime"); + private float ARMYRemainTime => 45 - Configs.GetFloat("ARMYTime"); + + protected override bool GeneralGCD(out IAction act) + { + //伶牙俐齿 + if (IronJaws.CanUse(out act)) return true; + if (IronJaws.CanUse(out act, mustUse: true) && IronJaws.Target.WillStatusEnd(30, true, IronJaws.TargetStatus)) + { + if (Player.HasStatus(true, StatusID.RagingStrikes) && Player.WillStatusEndGCD(1, 0, true, StatusID.RagingStrikes)) return true; + } + + //放大招! + if (CanUseApexArrow(out act)) return true; + //爆破箭 + if (BlastArrow.CanUse(out act, mustUse: true)) + { + if (!Player.HasStatus(true, StatusID.RagingStrikes)) return true; + if (Player.HasStatus(true, StatusID.RagingStrikes) && Barrage.IsCoolingDown) return true; + } + + //群体GCD + if (Shadowbite.CanUse(out act)) return true; + if (QuickNock.CanUse(out act)) return true; + + //上毒 + if (Windbite.CanUse(out act)) return true; + if (VenomousBite.CanUse(out act)) return true; + + //直线射击 + if (StraitShoot.CanUse(out act)) return true; + + //强力射击 + if (HeavyShoot.CanUse(out act)) return true; + + return false; + } + + protected override bool EmergencyAbility(byte abilitiesRemaining, IAction nextGCD, out IAction act) + { + //如果接下来要上毒或者要直线射击,那算了。 + if (nextGCD.IsTheSameTo(true, StraitShoot, VenomousBite, Windbite, IronJaws)) + { + return base.EmergencyAbility(abilitiesRemaining, nextGCD, out act); + } + else if ((!RagingStrikes.EnoughLevel || Player.HasStatus(true, StatusID.RagingStrikes)) && (!BattleVoice.EnoughLevel || Player.HasStatus(true, StatusID.BattleVoice))) + { + if ((EmpyrealArrow.IsCoolingDown && !EmpyrealArrow.WillHaveOneChargeGCD(1) || !EmpyrealArrow.EnoughLevel) && Repertoire != 3) + { + //纷乱箭 + if (!Player.HasStatus(true, StatusID.StraightShotReady) && Barrage.CanUse(out act)) return true; + } + } + + return base.EmergencyAbility(abilitiesRemaining, nextGCD, out act); + } + + protected override bool AttackAbility(byte abilitiesRemaining, out IAction act) + { + act = null; + + if (Song == Song.NONE) + { + if (FirstSong == 0 && WanderersMinuet.CanUse(out act)) return true; + if (FirstSong == 1 && MagesBallad.CanUse(out act)) return true; + if (FirstSong == 2 && ArmysPaeon.CanUse(out act)) return true; + if (WanderersMinuet.CanUse(out act)) return true; + if (MagesBallad.CanUse(out act)) return true; + if (ArmysPaeon.CanUse(out act)) return true; + } + + if (InBurst && Song != Song.NONE && MagesBallad.EnoughLevel) + { + //猛者强击 + if (RagingStrikes.CanUse(out act)) + { + if (BindWAND && Song == Song.WANDERER && WanderersMinuet.EnoughLevel) return true; + if (!BindWAND) return true; + } + + //光明神的最终乐章 + if (RadiantFinale.CanUse(out act, mustUse: true)) + { + if (Player.HasStatus(true, StatusID.RagingStrikes) && RagingStrikes.ElapsedAfterGCD(1)) return true; + } + + //战斗之声 + if (BattleVoice.CanUse(out act, mustUse: true)) + { + if (IsLastAction(true, RadiantFinale)) return true; + + if (Player.HasStatus(true, StatusID.RagingStrikes) && RagingStrikes.ElapsedAfterGCD(1)) return true; + } + } + + if (RadiantFinale.EnoughLevel && RadiantFinale.IsCoolingDown && BattleVoice.EnoughLevel && !BattleVoice.IsCoolingDown) return false; + + //放浪神的小步舞曲 + if (WanderersMinuet.CanUse(out act)) + { + if (SongEndAfter(ARMYRemainTime) && (Song != Song.NONE || Player.HasStatus(true, StatusID.ArmyEthos)) && abilitiesRemaining == 1) return true; + } + + //九天连箭 + if (Song != Song.NONE && EmpyrealArrow.CanUse(out act)) return true; + + //完美音调 + if (PitchPerfect.CanUse(out act)) + { + if (SongEndAfter(3) && Repertoire > 0) return true; + + if (Repertoire == 3) return true; + + if (Repertoire == 2 && EmpyrealArrow.WillHaveOneChargeGCD(1) && abilitiesRemaining == 1) return true; + + if (Repertoire == 2 && EmpyrealArrow.WillHaveOneChargeGCD() && abilitiesRemaining == 2) return true; + } + + //贤者的叙事谣 + if (MagesBallad.CanUse(out act)) + { + if (Song == Song.WANDERER && SongEndAfter(WANDRemainTime) && Repertoire == 0) return true; + if (Song == Song.ARMY && SongEndAfterGCD(2) && WanderersMinuet.IsCoolingDown) return true; + } + + + //军神的赞美歌 + if (ArmysPaeon.CanUse(out act)) + { + if (WanderersMinuet.EnoughLevel && SongEndAfter(MAGERemainTime) && Song == Song.MAGE) return true; + if (WanderersMinuet.EnoughLevel && SongEndAfter(2) && MagesBallad.IsCoolingDown && Song == Song.WANDERER) return true; + if (!WanderersMinuet.EnoughLevel && SongEndAfter(2)) return true; + } + + //测风诱导箭 + if (Sidewinder.CanUse(out act)) + { + if (Player.HasStatus(true, StatusID.BattleVoice) && (Player.HasStatus(true, StatusID.RadiantFinale) || !RadiantFinale.EnoughLevel)) return true; + + if (!BattleVoice.WillHaveOneCharge(10) && !RadiantFinale.WillHaveOneCharge(10)) return true; + + if (RagingStrikes.IsCoolingDown && !Player.HasStatus(true, StatusID.RagingStrikes)) return true; + } + + //看看现在有没有开猛者强击和战斗之声 + bool empty = Player.HasStatus(true, StatusID.RagingStrikes) && (Player.HasStatus(true, StatusID.BattleVoice) || !BattleVoice.EnoughLevel) || Song == Song.MAGE; + + if (EmpyrealArrow.IsCoolingDown || !EmpyrealArrow.WillHaveOneChargeGCD() || Repertoire != 3 || !EmpyrealArrow.EnoughLevel) + { + //死亡剑雨 + if (RainofDeath.CanUse(out act, emptyOrSkipCombo: empty)) return true; + + //失血箭 + if (Bloodletter.CanUse(out act, emptyOrSkipCombo: empty)) return true; + } + + return false; + } + + private bool CanUseApexArrow(out IAction act) + { + //放大招! + if (!ApexArrow.CanUse(out act, mustUse: true)) return false; + + //aoe期间 + if (QuickNock.CanUse(out _) && SoulVoice == 100) return true; + + //快爆发了,攒着等爆发 + if (SoulVoice == 100 && BattleVoice.WillHaveOneCharge(25)) return false; + + //爆发快过了,如果手里还有绝峰箭,就把绝峰箭打出去 + if (SoulVoice >= 80 && Player.HasStatus(true, StatusID.RagingStrikes) && Player.WillStatusEnd(10, false, StatusID.RagingStrikes)) return true; + + //爆发期绝峰箭 + if (SoulVoice == 100 && Player.HasStatus(true, StatusID.RagingStrikes) && Player.HasStatus(true, StatusID.BattleVoice)) return true; + + //贤者歌期间 + if (Song == Song.MAGE && SoulVoice >= 80 && SongEndAfter(22) && SongEndAfter(18)) return true; + + //能量之声等于100 + if (!Player.HasStatus(true, StatusID.RagingStrikes) && SoulVoice == 100) return true; + + return false; + } +} diff --git a/RotationSolver.Default/Ranged/DNC_Default.cs b/RotationSolver.Default/Ranged/DNC_Default.cs new file mode 100644 index 000000000..0b098cf88 --- /dev/null +++ b/RotationSolver.Default/Ranged/DNC_Default.cs @@ -0,0 +1,198 @@ +using RotationSolver.Actions; +using RotationSolver.Attributes; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.Basic; + +namespace RotationSolver.Default.Ranged; + +internal sealed class DNC_Default : DNC_Base +{ + public override string GameVersion => "6.28"; + + public override string RotationName => "Default"; + + protected override IAction CountDownAction(float remainTime) + { + if (remainTime <= 15) + { + if (StandardStep.CanUse(out var act, mustUse: true)) return act; + if (ExcutionStepGCD(out act)) return act; + } + return base.CountDownAction(remainTime); + } + + protected override bool AttackAbility(byte abilitiesRemaining, out IAction act) + { + act = null; + //����״̬��ֹʹ�� + if (IsDancing) return false; + + //����֮̽�� + if (Devilment.CanUse(out act)) + { + if (InBurst && !TechnicalStep.EnoughLevel) return true; + + if (Player.HasStatus(true, StatusID.TechnicalFinish)) return true; + } + + //Ӧ������� + if (UseClosedPosition(out act)) return true; + + //�ٻ� + if (Flourish.CanUse(out act)) return true; + + //���衤�� + if (FanDance3.CanUse(out act, mustUse: true)) return true; + + if (Player.HasStatus(true, StatusID.Devilment) || Feathers > 3 || !TechnicalStep.EnoughLevel) + { + //���衤�� + if (FanDance2.CanUse(out act)) return true; + //���衤�� + if (FanDance.CanUse(out act)) return true; + } + + //���衤�� + if (FanDance4.CanUse(out act, mustUse: true)) + { + if (TechnicalStep.EnoughLevel && TechnicalStep.IsCoolingDown && TechnicalStep.WillHaveOneChargeGCD()) return false; + return true; + } + + return false; + } + + protected override bool GeneralGCD(out IAction act) + { + //����� + if (!InCombat && !Player.HasStatus(true, StatusID.ClosedPosition1) && ClosedPosition.CanUse(out act)) return true; + + //�����貽 + if (FinishStepGCD(out act)) return true; + + //ִ���貽 + if (ExcutionStepGCD(out act)) return true; + + //�����貽 + if (InBurst && InCombat && TechnicalStep.CanUse(out act, mustUse: true)) return true; + + //����GCD + if (AttackGCD(out act, Player.HasStatus(true, StatusID.Devilment))) return true; + + return false; + } + + /// + /// ����GCD + /// + /// + /// + /// + bool AttackGCD(out IAction act, bool breaking) + { + act = null; + //����״̬��ֹʹ�� + if (IsDancing) return false; + + //���� + if ((breaking || Esprit >= 85) && SaberDance.CanUse(out act, mustUse: true)) return true; + + //������ + if (Tillana.CanUse(out act, mustUse: true)) return true; + + //������ + if (StarfallDance.CanUse(out act, mustUse: true)) return true; + + //ʹ�ñ�׼�貽 + if (UseStandardStep(out act)) return true; + + //����AOE + if (Bloodshower.CanUse(out act)) return true; + if (Fountainfall.CanUse(out act)) return true; + //�������� + if (RisingWindmill.CanUse(out act)) return true; + if (ReverseCascade.CanUse(out act)) return true; + + //����AOE + if (Bladeshower.CanUse(out act)) return true; + if (Windmill.CanUse(out act)) return true; + //�������� + if (Fountain.CanUse(out act)) return true; + if (Cascade.CanUse(out act)) return true; + + return false; + } + + /// + /// ʹ�ñ�׼�貽 + /// + /// + /// + private bool UseStandardStep(out IAction act) + { + if (!StandardStep.CanUse(out act, mustUse: true)) return false; + if (Player.WillStatusEndGCD(2, 0, true, StatusID.StandardFinish)) return true; + + //�ȼ��������̫�಻����,��ֱ�����˻���ɶ�� + if (Level - Target.Level > 10) return false; + + //��Χû�е��˲����� + if (!HasHostilesInRange) return false; + + //�����貽״̬�Ϳ���ȴ��ʱ���ͷ� + if (TechnicalStep.EnoughLevel && (Player.HasStatus(true, StatusID.TechnicalFinish) || TechnicalStep.IsCoolingDown && TechnicalStep.WillHaveOneCharge(5))) return false; + + return true; + } + + /// + /// Ӧ������� + /// + /// + /// + private bool UseClosedPosition(out IAction act) + { + if (!ClosedPosition.CanUse(out act)) return false; + + //Ӧ������� + if (InCombat && Player.HasStatus(true, StatusID.ClosedPosition1)) + { + foreach (var friend in PartyMembers) + { + if (friend.HasStatus(true, StatusID.ClosedPosition2)) + { + if (ClosedPosition.Target != friend) return true; + break; + } + } + } + //else if (ClosedPosition.ShouldUse(out act)) return true; + + act = null; + return false; + } + + private static bool FinishStepGCD(out IAction act) + { + act = null; + if (!IsDancing) return false; + + //��׼�貽���� + if (Player.HasStatus(true, StatusID.StandardStep) && (Player.WillStatusEnd(1, true, StatusID.StandardStep) || CompletedSteps == 2 && Player.WillStatusEnd(1, true, StatusID.StandardFinish)) + || StandardFinish.CanUse(out _, mustUse: true)) + { + act = StandardStep; + return true; + } + + //�����貽���� + if (Player.HasStatus(true, StatusID.TechnicalStep) && Player.WillStatusEnd(1, true, StatusID.TechnicalStep) || TechnicalFinish.CanUse(out _, mustUse: true)) + { + act = TechnicalStep; + return true; + } + + return false; + } +} diff --git a/RotationSolver.Default/Ranged/MCH_Default.cs b/RotationSolver.Default/Ranged/MCH_Default.cs new file mode 100644 index 000000000..a054a7e43 --- /dev/null +++ b/RotationSolver.Default/Ranged/MCH_Default.cs @@ -0,0 +1,235 @@ +using RotationSolver.Actions; +using RotationSolver.Attributes; +using RotationSolver.Configuration.RotationConfig; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.Basic; + +namespace RotationSolver.Default.Ranged; + +internal sealed class MCH_Default : MCH_Base +{ + public override string GameVersion => "6.28"; + + public override string RotationName => "Default"; + + + /// + /// 4人本小怪快死了 + /// + private static bool isDyingNotBoss => !Target.IsBoss() && IsTargetDying && PartyMembers.Count() is > 1 and <= 4; + + protected override IRotationConfigSet CreateConfiguration() + { + return base.CreateConfiguration() + .SetBool("MCH_Opener", true, "Basic Opener") + .SetBool("MCH_Automaton", true, "Care for Automation") + .SetBool("MCH_Reassemble", true, "Ressamble for ChainSaw") + .SetBool("DelayHypercharge", false, "Use Hypercharge late"); + } + + protected override bool GeneralGCD(out IAction act) + { + //不在战斗中时重置起手 + if (!InCombat) + { + //开场前整备,空气锚和钻头必须冷却好 + if (AirAnchor.EnoughLevel && (!AirAnchor.IsCoolingDown || !Drill.IsCoolingDown) && Reassemble.CanUse(out act, emptyOrSkipCombo: true)) return true; + } + + //群体常规GCD + //AOE,毒菌冲击 + if (Bioblaster.CanUse(out act)) return true; + if (ChainSaw.CanUse(out act)) return true; + if (IsOverheated && AutoCrossbow.CanUse(out act)) return true; + if (SpreadShot.CanUse(out act)) return true; + + if (!IsOverheated || IsOverheated && OverheatedEndAfterGCD()) + { + //单体,四个牛逼的技能。先空气锚再钻头 + if (AirAnchor.CanUse(out act)) return true; + else if (!AirAnchor.EnoughLevel && HotShot.CanUse(out act)) return true; + if (Drill.CanUse(out act)) return true; + if (ChainSaw.CanUse(out act, mustUse: true)) + { + if (Player.HasStatus(true, StatusID.Reassemble)) return true; + if (!Configs.GetBool("MCH_Opener") || Wildfire.IsCoolingDown) return true; + if (AirAnchor.IsCoolingDown && AirAnchor.ElapsedAfterGCD(4) && Drill.IsCoolingDown && Drill.ElapsedAfterGCD(3)) return true; + if (AirAnchor.IsCoolingDown && AirAnchor.ElapsedAfterGCD(3) && Drill.IsCoolingDown && Drill.ElapsedAfterGCD(4)) return true; + } + } + + //过热状态 + if (IsOverheated && HeatBlast.CanUse(out act)) return true; + + //单体常规GCD + if (CleanShot.CanUse(out act)) return true; + if (SlugShot.CanUse(out act)) return true; + if (SplitShot.CanUse(out act)) return true; + + return false; + } + + protected override IAction CountDownAction(float remainTime) + { + //提前5秒整备 + if (remainTime <= 5 && Reassemble.CanUse(out _, emptyOrSkipCombo: true)) return Reassemble; + return base.CountDownAction(remainTime); + } + protected override bool EmergencyAbility(byte abilitiesRemaining, IAction nextGCD, out IAction act) + { + //等级小于钻头时,绑定狙击弹 + if (!Drill.EnoughLevel && nextGCD.IsTheSameTo(true, CleanShot)) + { + if (Reassemble.CanUse(out act, emptyOrSkipCombo: true)) return true; + } + //等级小于90时,整备不再留层数 + if ((!ChainSaw.EnoughLevel || !Configs.GetBool("MCH_Reassemble")) + && nextGCD.IsTheSameTo(false, AirAnchor, Drill)) + { + if (Reassemble.CanUse(out act, emptyOrSkipCombo: true)) return true; + } + //整备优先链锯 + if (Configs.GetBool("MCH_Reassemble") && nextGCD.IsTheSameTo(true, ChainSaw)) + { + if (Reassemble.CanUse(out act, emptyOrSkipCombo: true)) return true; + } + //如果接下来要搞三大金刚了,整备吧! + if (ChainSaw.EnoughLevel && nextGCD.IsTheSameTo(true, AirAnchor, Drill)) + { + if (Reassemble.CanUse(out act)) return true; + } + //起手在链锯前释放野火 + if (nextGCD.IsTheSameTo(true, ChainSaw) && !IsLastGCD(true, HeatBlast)) + { + if (InBurst && Configs.GetBool("MCH_Opener") && Wildfire.CanUse(out act)) return true; + } + return base.EmergencyAbility(abilitiesRemaining, nextGCD, out act); + } + + protected override bool AttackAbility(byte abilitiesRemaining, out IAction act) + { + //野火 + if (InBurst && CanUseWildfire(out act)) return true; + + //车式浮空炮塔 + if (CanUseRookAutoturret(out act)) return true; + + //起手虹吸弹、弹射 + if (Ricochet.CurrentCharges == Ricochet.MaxCharges && Ricochet.CanUse(out act, mustUse: true, emptyOrSkipCombo: true)) return true; + if (GaussRound.CurrentCharges == GaussRound.MaxCharges && GaussRound.CanUse(out act, mustUse: true, emptyOrSkipCombo: true)) return true; + + //枪管加热 + if (BarrelStabilizer.CanUse(out act)) return true; + + //超荷 + if (CanUseHypercharge(out act) && (Configs.GetBool("MCH_Opener") && abilitiesRemaining == 1 || !Configs.GetBool("MCH_Opener"))) return true; + + if (GaussRound.CurrentCharges <= Ricochet.CurrentCharges) + { + //弹射 + if (Ricochet.CanUse(out act, mustUse: true, emptyOrSkipCombo: true)) return true; + } + //虹吸弹 + if (GaussRound.CanUse(out act, mustUse: true, emptyOrSkipCombo: true)) return true; + + act = null!; + return false; + } + + /// + /// 判断能否使用野火 + /// + /// + /// + private bool CanUseWildfire(out IAction act) + { + if (!Wildfire.CanUse(out act)) return false; + + if (Heat < 50 && !IsOverheated) return false; + + //小怪和AOE期间不打野火 + if (SpreadShot.CanUse(out _) || PartyMembers.Count() is > 1 and <= 4 && !Target.IsBoss()) return false; + + //在过热时 + if (IsLastAction(true, Hypercharge)) return true; + + if (ChainSaw.EnoughLevel && !ChainSaw.IsCoolingDown) return false; + + if (Hypercharge.IsCoolingDown) return false; + + //当上一个技能是钻头,空气锚,热冲击时不释放野火 + if (IsLastGCD(true, Drill, HeatBlast, AirAnchor)) return false; + + return true; + } + + /// + /// 判断能否使用超荷 + /// + /// + /// + private bool CanUseHypercharge(out IAction act) + { + if (!Hypercharge.CanUse(out act) || Player.HasStatus(true, StatusID.Reassemble)) return false; + + //有野火buff必须释放超荷 + if (Player.HasStatus(true, StatusID.Wildfire)) return true; + + //4人本小怪快死了不释放 + //if (isDyingNotBoss) return false; + + //在三大金刚还剩8秒冷却好时不释放超荷 + if (Drill.EnoughLevel && Drill.WillHaveOneChargeGCD(3)) return false; + if (AirAnchor.EnoughLevel && AirAnchor.WillHaveOneCharge(3)) return false; + if (ChainSaw.EnoughLevel && (ChainSaw.IsCoolingDown && ChainSaw.WillHaveOneCharge(3) || !ChainSaw.IsCoolingDown) && Configs.GetBool("MCH_Opener")) return false; + + //小怪AOE和4人本超荷判断 + if (SpreadShot.CanUse(out _)) + { + if (!AutoCrossbow.EnoughLevel) return false; + return true; + } + + //等级低于野火 + if (!Wildfire.EnoughLevel) return true; + + //野火前攒热量 + if (!Wildfire.WillHaveOneChargeGCD(5) && Wildfire.WillHaveOneChargeGCD(18)) + { + //如果期间热量溢出超过5,就释放一次超荷 + if (IsLastGCD((ActionID)Drill.ID) && Heat >= 85) return true; + return false; + } + else return true; + } + + /// + /// 判断能否使用机器人 + /// + /// + /// + private bool CanUseRookAutoturret(out IAction act) + { + if (!RookAutoturret.CanUse(out act, mustUse: true)) return false; + + //4人本小怪快死了不释放 + if (isDyingNotBoss) return false; + + //如果上一个技能是野火不释放 + if (IsLastAction((ActionID)Wildfire.ID)) return false; + + //电量等于100,强制释放 + if (Battery == 100 && ChainSaw.EnoughLevel && !ChainSaw.WillHaveOneCharge(13)) return true; + + //小怪,AOE,不吃团辅判断 + if (!Configs.GetBool("MCH_Automaton") || !Target.IsBoss() && !IsMoving || Level < Wildfire.ID) return true; + if (SpreadShot.CanUse(out _) && !Target.IsBoss() && IsMoving) return false; + + //机器人吃团辅判断 + if (AirAnchor.IsCoolingDown && AirAnchor.WillHaveOneChargeGCD() && Battery > 80) return true; + if (ChainSaw.WillHaveOneCharge(4) || ChainSaw.IsCoolingDown && !ChainSaw.ElapsedAfterGCD(3) && Battery <= 60) return true; + + return false; + } +} diff --git a/RotationSolver.Default/RotationSolver.Default.csproj b/RotationSolver.Default/RotationSolver.Default.csproj index 7aad1fde1..019704d34 100644 --- a/RotationSolver.Default/RotationSolver.Default.csproj +++ b/RotationSolver.Default/RotationSolver.Default.csproj @@ -3,12 +3,12 @@ net7.0-windows enable - enable $(AppData)\XIVLauncher\addon\Hooks\dev\ + AnyCPU;ARM64 - + $(DalamudLibPath)Dalamud.dll diff --git a/RotationSolver.Default/Tank/DRK_Default.cs b/RotationSolver.Default/Tank/DRK_Default.cs new file mode 100644 index 000000000..4d4c823ce --- /dev/null +++ b/RotationSolver.Default/Tank/DRK_Default.cs @@ -0,0 +1,177 @@ +using RotationSolver.Actions; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Commands; +using RotationSolver.Configuration.RotationConfig; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.Basic; +namespace RotationSolver.Default.Tank; + + +[RotationDesc(ActionID.BloodWeapon, ActionID.Delirium)] +internal sealed class DRK_Default : DRK_Base +{ + public override string GameVersion => "6.31"; + + public override string RotationName => "Default"; + + protected override bool CanHealSingleAbility => false; + + private static bool InDeliruim => !Delirium.EnoughLevel || Delirium.IsCoolingDown && Delirium.ElapsedAfterGCD(1) && !Delirium.ElapsedAfterGCD(7); + + private static bool CombatLess => CombatElapsedLess(3); + + private bool CheckDrakSide + { + get + { + if (DarkSideEndAfterGCD(3)) return true; + + if (CombatLess) return false; + + if (Configs.GetBool("TheBlackestNight") && Player.CurrentMp < 6000) return false; + + if (InDeliruim || HasDarkArts) return true; + + return Player.CurrentMp >= 8500; + } + } + + private bool UseBlood + { + get + { + if (!Delirium.EnoughLevel) return true; + + if (Player.HasStatus(true, StatusID.Delirium) && Player.StatusStack(true, StatusID.BloodWeapon) < 2) return true; + + if (BloodWeapon.WillHaveOneChargeGCD(1) || Blood >= 90 && !Player.HasStatus(true, StatusID.Delirium)) return true; + + return false; + } + } + + protected override IRotationConfigSet CreateConfiguration() + => base.CreateConfiguration() + .SetBool("TheBlackestNight", true, "Keep 3000 MP"); + + protected override IAction CountDownAction(float remainTime) + { + //Provoke when has Shield. + if (HasTankStance && remainTime <= Service.Config.AbilitiesInterval && Provoke.CanUse(out var act)) return act; + if (remainTime <= 2 && UseBurstMedicine(out act)) return act; + if (remainTime <= 3 && TheBlackestNight.CanUse(out act)) return act; + if (remainTime <= 4 && BloodWeapon.CanUse(out act)) return act; + return base.CountDownAction(remainTime); + } + + [RotationDesc(ActionID.TheBlackestNight)] + protected override bool HealSingleAbility(byte abilitiesRemaining, out IAction act) + { + if (TheBlackestNight.CanUse(out act)) return true; + + return false; + } + + [RotationDesc(ActionID.DarkMissionary, ActionID.Reprisal)] + protected override bool DefenseAreaAbility(byte abilitiesRemaining, out IAction act) + { + if (DarkMissionary.CanUse(out act)) return true; + if (Reprisal.CanUse(out act, mustUse: true)) return true; + + return false; + } + + [RotationDesc(ActionID.TheBlackestNight, ActionID.Oblation, ActionID.ShadowWall, ActionID.Rampart, ActionID.DarkMind, ActionID.Reprisal)] + protected override bool DefenseSingleAbility(byte abilitiesRemaining, out IAction act) + { + if (TheBlackestNight.CanUse(out act)) return true; + + if (abilitiesRemaining == 2) + { + //10 + if (HostileTargets.Count() > 1 && Oblation.CanUse(out act)) return true; + + //30 + if (ShadowWall.CanUse(out act)) return true; + + //20 + if (Rampart.CanUse(out act)) return true; + if (DarkMind.CanUse(out act)) return true; + + //10 + if (Oblation.CanUse(out act, emptyOrSkipCombo: true)) return true; + } + + if (Reprisal.CanUse(out act)) return true; + + act = null; + return false; + } + + protected override bool GeneralGCD(out IAction act) + { + //Use Blood + if (UseBlood) + { + if (Quietus.CanUse(out act)) return true; + if (Bloodspiller.CanUse(out act)) return true; + } + + //AOE + if (StalwartSoul.CanUse(out act)) return true; + if (Unleash.CanUse(out act)) return true; + + //单体 + if (Souleater.CanUse(out act)) return true; + if (SyphonStrike.CanUse(out act)) return true; + if (HardSlash.CanUse(out act)) return true; + + if (SpecialType == SpecialCommandType.MoveForward && MoveForwardAbility(1, out act)) return true; + if (Unmend.CanUse(out act)) return true; + + return false; + } + + protected override bool AttackAbility(byte abilitiesRemaining, out IAction act) + { + if (CheckDrakSide) + { + if (FloodofDarkness.CanUse(out act)) return true; + if (EdgeofDarkness.CanUse(out act)) return true; + } + + if (InBurst) + { + if (BloodWeapon.CanUse(out act)) return true; + if (Delirium.CanUse(out act)) return true; + } + + if (CombatLess) + { + act = null; + return false; + } + + if (LivingShadow.CanUse(out act)) return true; + + if (!IsMoving && SaltedEarth.CanUse(out act, mustUse: true)) return true; + + if (InDeliruim) + { + if (Shadowbringer.CanUse(out act, mustUse: true)) return true; + + if (AbyssalDrain.CanUse(out act)) return true; + if (CarveandSpit.CanUse(out act)) return true; + + if (Shadowbringer.CanUse(out act, mustUse: true, emptyOrSkipCombo: true)) return true; + } + + if (SaltandDarkness.CanUse(out act)) return true; + + if (Plunge.CanUse(out act, mustUse: true) && !IsMoving) return true; + + return false; + } +} \ No newline at end of file diff --git a/RotationSolver.Default/Tank/GNB_Default.cs b/RotationSolver.Default/Tank/GNB_Default.cs new file mode 100644 index 000000000..f846702e1 --- /dev/null +++ b/RotationSolver.Default/Tank/GNB_Default.cs @@ -0,0 +1,279 @@ +using RotationSolver.Actions; +using RotationSolver.Attributes; +using RotationSolver.Commands; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.Basic; + +namespace RotationSolver.Default.Tank; + +internal sealed class GNB_Default : GNB_Base +{ + public override string GameVersion => "6.18"; + + public override string RotationName => "Default"; + + protected override bool CanHealSingleSpell => false; + + protected override bool CanHealAreaSpell => false; + + protected override bool GeneralGCD(out IAction act) + { + //���� + if (CanUseDoubleDown(out act)) return true; + + //����֮�� AOE + if (FatedCircle.CanUse(out act)) return true; + + //AOE + if (DemonSlaughter.CanUse(out act)) return true; + if (DemonSlice.CanUse(out act)) return true; + + //���� + if (CanUseGnashingFang(out act)) return true; + + //������ + if (CanUseSonicBreak(out act)) return true; + + //��������� + if (WickedTalon.CanUse(out act, emptyOrSkipCombo: true)) return true; + if (SavageClaw.CanUse(out act, emptyOrSkipCombo: true)) return true; + + //������ + if (CanUseBurstStrike(out act)) return true; + + if (SolidBarrel.CanUse(out act)) return true; + if (BrutalShell.CanUse(out act)) return true; + if (KeenEdge.CanUse(out act)) return true; + + if (SpecialType == SpecialCommandType.MoveForward && MoveForwardAbility(1, out act)) return true; + + if (LightningShot.CanUse(out act)) return true; + + return false; + } + + protected override bool AttackAbility(byte abilitiesRemaining, out IAction act) + { + //����,Ŀǰֻ��4GCD���ֵ��ж� + if (InBurst && abilitiesRemaining == 1 && CanUseNoMercy(out act)) return true; + + //Σ������ + if (DangerZone.CanUse(out act)) + { + if (!IsFullParty && !DangerZone.IsTargetBoss) return true; + + //�ȼ�С������, + if (!GnashingFang.EnoughLevel && (Player.HasStatus(true, StatusID.NoMercy) || !NoMercy.WillHaveOneCharge(15))) return true; + + //������,����֮�� + if (Player.HasStatus(true, StatusID.NoMercy) && GnashingFang.IsCoolingDown) return true; + + //�DZ����� + if (!Player.HasStatus(true, StatusID.NoMercy) && !GnashingFang.WillHaveOneCharge(20)) return true; + } + + //���γ岨 + if (CanUseBowShock(out act)) return true; + + //���� + if (JugularRip.CanUse(out act)) return true; + if (AbdomenTear.CanUse(out act)) return true; + if (EyeGouge.CanUse(out act)) return true; + if (Hypervelocity.CanUse(out act)) return true; + + //Ѫ�� + if (GnashingFang.IsCoolingDown && Bloodfest.CanUse(out act)) return true; + + //��㹥��,�ַ�ն + if (RoughDivide.CanUse(out act, mustUse: true) && !IsMoving) return true; + if (Player.HasStatus(true, StatusID.NoMercy) && RoughDivide.CanUse(out act, mustUse: true)) return true; + + act = null; + return false; + } + + [RotationDesc(ActionID.HeartofLight, ActionID.Reprisal)] + protected override bool DefenseAreaAbility(byte abilitiesRemaining, out IAction act) + { + if (HeartofLight.CanUse(out act, emptyOrSkipCombo: true)) return true; + if (Reprisal.CanUse(out act, mustUse: true)) return true; + return false; + } + + [RotationDesc(ActionID.HeartofStone, ActionID.Nebula, ActionID.Rampart, ActionID.Camouflage, ActionID.Reprisal)] + protected override bool DefenseSingleAbility(byte abilitiesRemaining, out IAction act) + { + if (abilitiesRemaining == 2) + { + //10 + if (HeartofStone.CanUse(out act)) return true; + + //30 + if (Nebula.CanUse(out act)) return true; + + //20 + if (Rampart.CanUse(out act)) return true; + + //10 + if (Camouflage.CanUse(out act)) return true; + } + + if (Reprisal.CanUse(out act)) return true; + + act = null; + return false; + } + + [RotationDesc(ActionID.Aurora)] + protected override bool HealSingleAbility(byte abilitiesRemaining, out IAction act) + { + if (Aurora.CanUse(out act, emptyOrSkipCombo: true) && abilitiesRemaining == 1) return true; + return false; + } + + private bool CanUseNoMercy(out IAction act) + { + if (!NoMercy.CanUse(out act)) return false; + + if (!IsFullParty && !IsTargetBoss && !IsMoving && DemonSlice.CanUse(out _)) return true; + + //�ȼ����ڱ��������ж� + if (!BurstStrike.EnoughLevel) return true; + + if (BurstStrike.EnoughLevel) + { + //4GCD�����ж� + if (IsLastGCD((ActionID)KeenEdge.ID) && Ammo == 1 && !GnashingFang.IsCoolingDown && !Bloodfest.IsCoolingDown) return true; + + //3�������� + else if (Ammo == (Level >= 88 ? 3 : 2)) return true; + + //2�������� + else if (Ammo == 2 && GnashingFang.IsCoolingDown) return true; + } + + act = null; + return false; + } + + private bool CanUseGnashingFang(out IAction act) + { + //�����ж� + if (GnashingFang.CanUse(out act)) + { + //��4�˱�����ʹ�� + if (DemonSlice.CanUse(out _)) return false; + + //������3������ + if (Ammo == (Level >= 88 ? 3 : 2) && (Player.HasStatus(true, StatusID.NoMercy) || !NoMercy.WillHaveOneCharge(55))) return true; + + //���������� + if (Ammo > 0 && !NoMercy.WillHaveOneCharge(17) && NoMercy.WillHaveOneCharge(35)) return true; + + //3���ҽ�������ӵ������,��ǰ������ǰ������ + if (Ammo == 3 && IsLastGCD((ActionID)BrutalShell.ID) && NoMercy.WillHaveOneCharge(3)) return true; + + //1����Ѫ������ȴ���� + if (Ammo == 1 && !NoMercy.WillHaveOneCharge(55) && Bloodfest.WillHaveOneCharge(5)) return true; + + //4GCD���������ж� + if (Ammo == 1 && !NoMercy.WillHaveOneCharge(55) && (!Bloodfest.IsCoolingDown && Bloodfest.EnoughLevel || !Bloodfest.EnoughLevel)) return true; + } + return false; + } + + /// + /// ������ + /// + /// + /// + private bool CanUseSonicBreak(out IAction act) + { + //�����ж� + if (SonicBreak.CanUse(out act)) + { + //��4�˱����в�ʹ�� + if (DemonSlice.CanUse(out _)) return false; + + //if (!IsFullParty && !SonicBreak.IsTargetBoss) return false; + + if (!GnashingFang.EnoughLevel && Player.HasStatus(true, StatusID.NoMercy)) return true; + + //����������ʹ�������� + if (GnashingFang.IsCoolingDown && Player.HasStatus(true, StatusID.NoMercy)) return true; + + //�����ж� + if (!DoubleDown.EnoughLevel && Player.HasStatus(true, StatusID.ReadyToRip) + && GnashingFang.IsCoolingDown) return true; + + } + return false; + } + + /// + /// ���� + /// + /// + /// + private bool CanUseDoubleDown(out IAction act) + { + //�����ж� + if (DoubleDown.CanUse(out act, mustUse: true)) + { + //��4�˱����� + if (DemonSlice.CanUse(out _) && Player.HasStatus(true, StatusID.NoMercy)) return true; + + //�������ƺ�ʹ�ñ��� + if (SonicBreak.IsCoolingDown && Player.HasStatus(true, StatusID.NoMercy)) return true; + + //2������������ж�,��ǰʹ�ñ��� + if (Player.HasStatus(true, StatusID.NoMercy) && !NoMercy.WillHaveOneCharge(55) && Bloodfest.WillHaveOneCharge(5)) return true; + + } + return false; + } + + /// + /// ������ + /// + /// + /// + private bool CanUseBurstStrike(out IAction act) + { + if (BurstStrike.CanUse(out act)) + { + //��4�˱�������AOEʱ��ʹ�� + if (DemonSlice.CanUse(out _)) return false; + + //�������ʣ0.5����ȴ��,���ͷű�����,��Ҫ��Ϊ���ٲ�ͬ���ܻ�ʹ�����Ӻ�̫�������ж�һ�� + if (SonicBreak.IsCoolingDown && SonicBreak.WillHaveOneCharge(0.5f) && GnashingFang.EnoughLevel) return false; + + //�����б������ж� + if (Player.HasStatus(true, StatusID.NoMercy) && + AmmoComboStep == 0 && + !GnashingFang.WillHaveOneCharge(1)) return true; + if (Level < 88 && Ammo == 2) return true; + //�������ֹ��� + if (IsLastGCD((ActionID)BrutalShell.ID) && + (Ammo == (Level >= 88 ? 3 : 2) || + Bloodfest.WillHaveOneCharge(6) && Ammo <= 2 && !NoMercy.WillHaveOneCharge(10) && Bloodfest.EnoughLevel)) return true; + + } + return false; + } + + private bool CanUseBowShock(out IAction act) + { + if (BowShock.CanUse(out act, mustUse: true)) + { + if (DemonSlice.CanUse(out _) && !IsFullParty) return true; + + if (!SonicBreak.EnoughLevel && Player.HasStatus(true, StatusID.NoMercy)) return true; + + //������,������������������ȴ�� + if (Player.HasStatus(true, StatusID.NoMercy) && SonicBreak.IsCoolingDown) return true; + } + return false; + } +} \ No newline at end of file diff --git a/RotationSolver.Default/PLD_Outer.cs b/RotationSolver.Default/Tank/PLD_Default.cs similarity index 89% rename from RotationSolver.Default/PLD_Outer.cs rename to RotationSolver.Default/Tank/PLD_Default.cs index af6a26a17..cbec22a79 100644 --- a/RotationSolver.Default/PLD_Outer.cs +++ b/RotationSolver.Default/Tank/PLD_Default.cs @@ -1,21 +1,20 @@ using RotationSolver.Actions; using RotationSolver.Attributes; +using RotationSolver.Basic; using RotationSolver.Configuration.RotationConfig; using RotationSolver.Data; using RotationSolver.Helpers; using RotationSolver.Rotations.Basic; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace RotationSolver.Default; +namespace RotationSolver.Default.Tank; -public class PLD_Outer : PLD_Base +[LinkDesc("https://xiv.sleepyshiba.com/pld/img/63-60stentative2.png")] +[RotationDesc("The whole rotation's burst\nis base on:")] +[RotationDesc(ActionID.FightorFlight)] +internal class PLD_Default : PLD_Base { public override string GameVersion => "6.31"; - public override string RotationName => "Outer One!"; + public override string RotationName => "Tentative v1.2"; public override string Description => "Please work well!"; @@ -27,7 +26,7 @@ protected override IRotationConfigSet CreateConfiguration() protected override IAction CountDownAction(float remainTime) { - if (remainTime < HolySpirit.CastTime + Service.Configuration.CountDownAhead + if (remainTime < HolySpirit.CastTime + Service.Config.CountDownAhead && HolySpirit.CanUse(out var act)) return act; if (remainTime < 15 && Configs.GetBool("UseDivineVeilPre") @@ -101,7 +100,7 @@ protected override bool GeneralGCD(out IAction act) } [RotationDesc(ActionID.Reprisal, ActionID.DivineVeil)] - protected override bool DefenceAreaAbility(byte abilitiesRemaining, out IAction act) + protected override bool DefenseAreaAbility(byte abilitiesRemaining, out IAction act) { if (Reprisal.CanUse(out act, mustUse: true)) return true; if (DivineVeil.CanUse(out act)) return true; @@ -116,7 +115,7 @@ protected override bool HealAreaAbility(byte abilitiesRemaining, out IAction act } [RotationDesc(ActionID.Sentinel, ActionID.Rampart, ActionID.Bulwark, ActionID.Sheltron, ActionID.Reprisal)] - protected override bool DefenceSingleAbility(byte abilitiesRemaining, out IAction act) + protected override bool DefenseSingleAbility(byte abilitiesRemaining, out IAction act) { if (abilitiesRemaining == 2) { @@ -138,5 +137,4 @@ protected override bool DefenceSingleAbility(byte abilitiesRemaining, out IActio return false; } - } diff --git a/RotationSolver.Default/Tank/WAR_Default.cs b/RotationSolver.Default/Tank/WAR_Default.cs new file mode 100644 index 000000000..50c49a939 --- /dev/null +++ b/RotationSolver.Default/Tank/WAR_Default.cs @@ -0,0 +1,111 @@ +using RotationSolver.Actions; +using RotationSolver.Attributes; +using RotationSolver.Commands; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Rotations.Basic; + +namespace RotationSolver.Default.Tank; + +internal sealed class WAR_Default : WAR_Base +{ + public override string GameVersion => "6.0"; + + public override string RotationName => "Default"; + + static WAR_Default() + { + InnerBeast.RotationCheck = b => !Player.WillStatusEndGCD(3, 0, true, StatusID.SurgingTempest); + } + + [RotationDesc(ActionID.ShakeItOff, ActionID.Reprisal)] + protected override bool DefenseAreaAbility(byte abilitiesRemaining, out IAction act) + { + if (ShakeItOff.CanUse(out act, mustUse: true)) return true; + if (Reprisal.CanUse(out act, mustUse: true)) return true; + return false; + } + + [RotationDesc(ActionID.PrimalRend)] + protected override bool MoveForwardGCD(out IAction act) + { + if (PrimalRend.CanUse(out act, mustUse: true)) return true; + return false; + } + + protected override bool GeneralGCD(out IAction act) + { + //��㹥�� + if (PrimalRend.CanUse(out act, mustUse: true) && !IsMoving) + { + if (PrimalRend.Target.DistanceToPlayer() < 1) return true; + } + + if (SteelCyclone.CanUse(out act)) return true; + if (InnerBeast.CanUse(out act)) return true; + + if (MythrilTempest.CanUse(out act)) return true; + if (Overpower.CanUse(out act)) return true; + + if (StormsEye.CanUse(out act)) return true; + if (StormsPath.CanUse(out act)) return true; + if (Maim.CanUse(out act)) return true; + if (HeavySwing.CanUse(out act)) return true; + + if (SpecialType == SpecialCommandType.MoveForward && MoveForwardAbility(1, out act)) return true; + + if (Tomahawk.CanUse(out act)) return true; + + return false; + } + + [RotationDesc(ActionID.RawIntuition, ActionID.Vengeance, ActionID.Rampart, ActionID.RawIntuition, ActionID.Reprisal)] + protected override bool DefenseSingleAbility(byte abilitiesRemaining, out IAction act) + { + if (abilitiesRemaining == 2) + { + if (HostileTargets.Count() > 1) + { + //10 + if (RawIntuition.CanUse(out act)) return true; + } + + //30 + if (Vengeance.CanUse(out act)) return true; + + //20 + if (Rampart.CanUse(out act)) return true; + + //10 + if (RawIntuition.CanUse(out act)) return true; + } + if (Reprisal.CanUse(out act)) return true; + + return false; + } + + protected override bool AttackAbility(byte abilitiesRemaining, out IAction act) + { + if (!Player.WillStatusEndGCD(3, 0, true, StatusID.SurgingTempest) || !MythrilTempest.EnoughLevel) + { + if (!InnerRelease.IsCoolingDown && Berserk.CanUse(out act)) return true; + } + + if (Player.GetHealthRatio() < 0.6f) + { + if (ThrillofBattle.CanUse(out act)) return true; + if (Equilibrium.CanUse(out act)) return true; + } + + if (!HasTankStance && NascentFlash.CanUse(out act)) return true; + + if (Infuriate.CanUse(out act, emptyOrSkipCombo: true)) return true; + + if (Orogeny.CanUse(out act)) return true; + if (Upheaval.CanUse(out act)) return true; + + if (Onslaught.CanUse(out act, mustUse: true) && !IsMoving) return true; + + return false; + } +} diff --git a/RotationSolver.Old/Actions/Actions.cd b/RotationSolver.Old/Actions/Actions.cd new file mode 100644 index 000000000..90bf61d81 --- /dev/null +++ b/RotationSolver.Old/Actions/Actions.cd @@ -0,0 +1,41 @@ + + + + + + CAAowECLaCBgzaxEBIMMEzQBMEDEBYxRhEBUhgADgMA= + Actions\BaseAction\BaseAction_ActionInfo.cs + + + + + + + AAIAAAAEAAAAgAhEAAAAAQQAABAEAAAAAAAAAoAAAAA= + Actions\BaseItem.cs + + + + + + + AAAAAAAAAAAAgABAAAAAAQAAAAAEAAAAAAAAAgAAAAA= + Actions\IAction.cs + + + + + + CAAogAAAAAAABYQEBIAIADAAAACABIAAAAAEAAAAgMA= + Actions\IBaseAction.cs + + + + + + AAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAA= + Actions\IBaseItem.cs + + + + \ No newline at end of file diff --git a/RotationSolver/Actions/BaseAction/BaseAction_ActionInfo.cs b/RotationSolver.Old/Actions/BaseAction/BaseAction_ActionInfo.cs similarity index 100% rename from RotationSolver/Actions/BaseAction/BaseAction_ActionInfo.cs rename to RotationSolver.Old/Actions/BaseAction/BaseAction_ActionInfo.cs diff --git a/RotationSolver/Actions/BaseAction/BaseAction_BasicInfo.cs b/RotationSolver.Old/Actions/BaseAction/BaseAction_BasicInfo.cs similarity index 100% rename from RotationSolver/Actions/BaseAction/BaseAction_BasicInfo.cs rename to RotationSolver.Old/Actions/BaseAction/BaseAction_BasicInfo.cs diff --git a/RotationSolver/Actions/BaseAction/BaseAction_Cooldown.cs b/RotationSolver.Old/Actions/BaseAction/BaseAction_Cooldown.cs similarity index 100% rename from RotationSolver/Actions/BaseAction/BaseAction_Cooldown.cs rename to RotationSolver.Old/Actions/BaseAction/BaseAction_Cooldown.cs diff --git a/RotationSolver/Actions/BaseAction/BaseAction_Display.cs b/RotationSolver.Old/Actions/BaseAction/BaseAction_Display.cs similarity index 100% rename from RotationSolver/Actions/BaseAction/BaseAction_Display.cs rename to RotationSolver.Old/Actions/BaseAction/BaseAction_Display.cs diff --git a/RotationSolver/Actions/BaseAction/BaseAction_Target.cs b/RotationSolver.Old/Actions/BaseAction/BaseAction_Target.cs similarity index 100% rename from RotationSolver/Actions/BaseAction/BaseAction_Target.cs rename to RotationSolver.Old/Actions/BaseAction/BaseAction_Target.cs diff --git a/RotationSolver/Actions/BaseItem.cs b/RotationSolver.Old/Actions/BaseItem.cs similarity index 100% rename from RotationSolver/Actions/BaseItem.cs rename to RotationSolver.Old/Actions/BaseItem.cs diff --git a/RotationSolver/Actions/IAction.cs b/RotationSolver.Old/Actions/IAction.cs similarity index 100% rename from RotationSolver/Actions/IAction.cs rename to RotationSolver.Old/Actions/IAction.cs diff --git a/RotationSolver/Actions/IBaseAction.cs b/RotationSolver.Old/Actions/IBaseAction.cs similarity index 100% rename from RotationSolver/Actions/IBaseAction.cs rename to RotationSolver.Old/Actions/IBaseAction.cs diff --git a/RotationSolver.Old/Actions/IBaseItem.cs b/RotationSolver.Old/Actions/IBaseItem.cs new file mode 100644 index 000000000..9339852ea --- /dev/null +++ b/RotationSolver.Old/Actions/IBaseItem.cs @@ -0,0 +1,7 @@ +namespace RotationSolver.Actions +{ + public interface IBaseItem : IAction + { + bool CanUse(out IAction item); + } +} diff --git a/RotationSolver/Actions/MedicineItem.cs b/RotationSolver.Old/Actions/MedicineItem.cs similarity index 100% rename from RotationSolver/Actions/MedicineItem.cs rename to RotationSolver.Old/Actions/MedicineItem.cs diff --git a/RotationSolver/Attributes/DefaultRotationAttribute.cs b/RotationSolver.Old/Attributes/DefaultRotationAttribute.cs similarity index 100% rename from RotationSolver/Attributes/DefaultRotationAttribute.cs rename to RotationSolver.Old/Attributes/DefaultRotationAttribute.cs diff --git a/RotationSolver.Old/Attributes/LinkDescAttribute.cs b/RotationSolver.Old/Attributes/LinkDescAttribute.cs new file mode 100644 index 000000000..2d99352ea --- /dev/null +++ b/RotationSolver.Old/Attributes/LinkDescAttribute.cs @@ -0,0 +1,16 @@ +using ImGuiScene; +using System; + +namespace RotationSolver.Attributes; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +public class LinkDescAttribute : Attribute +{ + //public TextureWrap Texture { get; } + public string Path { get; } + public LinkDescAttribute(string path) + { + Path = path; + //Texture = Service.DataManager.GetImGuiTexture(path); + } +} diff --git a/RotationSolver/Attributes/RotationDescAttribute.cs b/RotationSolver.Old/Attributes/RotationDescAttribute.cs similarity index 100% rename from RotationSolver/Attributes/RotationDescAttribute.cs rename to RotationSolver.Old/Attributes/RotationDescAttribute.cs diff --git a/RotationSolver/Commands/RSCommandType.cs b/RotationSolver.Old/Commands/RSCommandType.cs similarity index 100% rename from RotationSolver/Commands/RSCommandType.cs rename to RotationSolver.Old/Commands/RSCommandType.cs diff --git a/RotationSolver.Old/Commands/RSCommands_Actions.cs b/RotationSolver.Old/Commands/RSCommands_Actions.cs new file mode 100644 index 000000000..c6051340b --- /dev/null +++ b/RotationSolver.Old/Commands/RSCommands_Actions.cs @@ -0,0 +1,106 @@ +using Dalamud.Game.ClientState.Conditions; +using Dalamud.Game.ClientState.Objects.SubKinds; +using Dalamud.Game.Gui; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.SigReplacers; +using RotationSolver.Updaters; +using System; +using System.Runtime.InteropServices; + +namespace RotationSolver.Commands +{ + internal static partial class RSCommands + { + private static DateTime _fastClickStopwatch = DateTime.Now; + + internal static unsafe void DoAnAction(bool isGCD) + { + if (StateType == StateCommandType.Cancel) return; + + var localPlayer = Service.ClientState.LocalPlayer; + if (localPlayer == null) return; + + //0.2s内,不能重复按按钮。 + if (DateTime.Now - _fastClickStopwatch < new TimeSpan(0, 0, 0, 0, 200)) return; + _fastClickStopwatch = DateTime.Now; + + //Do Action + var nextAction = ActionUpdater.NextAction; +#if DEBUG + //if (nextAction is BaseAction acti) + // Service.ChatGui.Print($"Will Do {acti} {ActionUpdater.WeaponElapsed}"); +#endif + if (nextAction == null) return; + if (!isGCD && nextAction is BaseAction act1 && act1.IsRealGCD) return; + + if (nextAction.Use()) + { + if (Service.Configuration.KeyBoardNoise) PreviewUpdater.PulseAtionBar(nextAction.AdjustedID); + if (nextAction is BaseAction act) + { +#if DEBUG + //Service.ChatGui.Print($"{act}, {act.Target.Name}, {ActionUpdater.AbilityRemainCount}, {ActionUpdater.WeaponElapsed}"); +#endif + //Change Target + if ((Service.TargetManager.Target?.IsNPCEnemy() ?? true) + && (act.Target?.IsNPCEnemy() ?? false)) + { + Service.TargetManager.SetTarget(act.Target); + } + } + } + return; + } + + internal static void ResetSpecial() => DoSpecialCommandType(SpecialCommandType.EndSpecial, false); + internal static void CancelState() + { + if (StateType != StateCommandType.Cancel) DoStateCommandType(StateCommandType.Cancel); + } + + internal static void UpdateRotationState() + { + if (Service.ClientState.LocalPlayer.CurrentHp == 0 + || Service.Conditions[ConditionFlag.LoggingOut]) + { + CancelState(); + } + else if (Service.Configuration.AutoOffBetweenArea && ( + Service.Conditions[ConditionFlag.BetweenAreas] + || Service.Conditions[ConditionFlag.BetweenAreas51]) + || Service.Conditions[ConditionFlag.OccupiedInCutSceneEvent]) + { + CancelState(); + } + //Auto start at count Down. + else if (Service.Configuration.StartOnCountdown && CountDown.CountDownTime > 0) + { + if (StateType == StateCommandType.Cancel) + { + DoStateCommandType(StateCommandType.Smart); + } + } + } + /// + /// Submit text/command to outgoing chat. + /// Can be used to enter chat commands. + /// + /// Text to submit. + public unsafe static void SubmitToChat(string text) + { + IntPtr uiModule = Service.GameGui.GetUIModule(); + + using (ChatPayload payload = new ChatPayload(text)) + { + IntPtr mem1 = Marshal.AllocHGlobal(400); + Marshal.StructureToPtr(payload, mem1, false); + + Service.Address.GetChatBox(uiModule, mem1, IntPtr.Zero, 0); + + Marshal.FreeHGlobal(mem1); + } + } + } +} diff --git a/RotationSolver.Old/Commands/RSCommands_BasicInfo.cs b/RotationSolver.Old/Commands/RSCommands_BasicInfo.cs new file mode 100644 index 000000000..73d1b5b0e --- /dev/null +++ b/RotationSolver.Old/Commands/RSCommands_BasicInfo.cs @@ -0,0 +1,77 @@ +using Dalamud.Game.Command; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using RotationSolver.Data; +using RotationSolver.Localization; +using System; +using System.Linq; + +namespace RotationSolver.Commands +{ + internal static partial class RSCommands + { + internal const string _command = "/rotation"; + + internal static TargetingType TargetingType + { + get + { + if (Service.Configuration.TargetingTypes.Count == 0) + { + Service.Configuration.TargetingTypes.Add(TargetingType.Big); + Service.Configuration.TargetingTypes.Add(TargetingType.Small); + Service.Configuration.Save(); + } + + return Service.Configuration.TargetingTypes[Service.Configuration.TargetingIndex %= Service.Configuration.TargetingTypes.Count]; + } + } + + internal static void Enable() + => Service.CommandManager.AddHandler(_command, new CommandInfo(OnCommand) + { + HelpMessage = LocalizationManager.RightLang.Commands_Rotation, + ShowInHelp = true, + }); + + internal static void Disable() => Service.CommandManager.RemoveHandler(_command); + + private static void OnCommand(string command, string arguments) + { + DoOneCommand(arguments); + } + + private static void DoOneCommand(string str) + { + if (TryGetOneEnum(str, out var stateType)) + { + DoStateCommandType(stateType); + } + else if (TryGetOneEnum(str, out var specialType)) + { + DoSpecialCommandType(specialType); + } + else if (TryGetOneEnum(str, out var otherType)) + { + DoOtherCommand(otherType, str.Substring(otherType.ToString().Length).Trim()); + } + else + { + RotationSolverPlugin.OpenConfigWindow(); + } + } + + private static bool TryGetOneEnum(string str, out T type) where T : struct, Enum + { + type = default; + try + { + type = Enum.GetValues().First(c => str.StartsWith(c.ToString(), StringComparison.OrdinalIgnoreCase)); + return true; + } + catch + { + return false; + } + } + } +} diff --git a/RotationSolver/Commands/RSCommands_Display.cs b/RotationSolver.Old/Commands/RSCommands_Display.cs similarity index 100% rename from RotationSolver/Commands/RSCommands_Display.cs rename to RotationSolver.Old/Commands/RSCommands_Display.cs diff --git a/RotationSolver.Old/Commands/RSCommands_OtherCommand.cs b/RotationSolver.Old/Commands/RSCommands_OtherCommand.cs new file mode 100644 index 000000000..d20f21056 --- /dev/null +++ b/RotationSolver.Old/Commands/RSCommands_OtherCommand.cs @@ -0,0 +1,150 @@ +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Helpers; +using RotationSolver.Localization; +using RotationSolver.Rotations.CustomRotation; +using RotationSolver.SigReplacers; +using RotationSolver.Updaters; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace RotationSolver.Commands +{ + internal static partial class RSCommands + { + internal record NextAct(IBaseAction act, DateTime deadTime); + + private static List NextActs = new List(); + internal static IBaseAction NextAction + { + get + { + if (TimeLineUpdater.TimeLineAction != null) return TimeLineUpdater.TimeLineAction; + + var next = NextActs.FirstOrDefault(); + + while (next != null && NextActs.Count > 0 && (next.deadTime < DateTime.Now || IActionHelper.IsLastAction(true, next.act))) + { + NextActs.RemoveAt(0); + next = NextActs.FirstOrDefault(); + } + return next?.act; + } + } + + private static void DoOtherCommand(OtherCommandType otherType, string str) + { + switch (otherType) + { + case OtherCommandType.Rotations: + var customCombo = RotationUpdater.RightNowRotation; + if (customCombo == null) return; + + DoRotationCommand(customCombo, str); + break; + + case OtherCommandType.DoActions: + DoActionCommand(str); + break; + + case OtherCommandType.ToggleActions: + ToggleActionCommand(str); + break; + + case OtherCommandType.Settings: + DoSettingCommand(str); + break; + } + } + + private static void DoSettingCommand(string str) + { + if (str.Contains(nameof(Service.Configuration.AutoBurst))) + { + Service.Configuration.AutoBurst = !Service.Configuration.AutoBurst; + + //Say out. + Service.ChatGui.Print(string.Format(LocalizationManager.RightLang.Commands_ChangeAutoBurst, + Service.Configuration.AutoBurst)); + } + } + + private static void ToggleActionCommand(string str) + { + foreach (var act in RotationUpdater.RightRotationBaseActions) + { + if (str == act.Name) + { + act.IsEnabled = !act.IsEnabled; + + //Service.ToastGui.ShowQuest(string.Format(LocalizationManager.RightLang.Commands_InsertAction, time), + // new Dalamud.Game.Gui.Toast.QuestToastOptions() + // { + // IconId = act.IconID, + // }); + + return; + } + } + } + + private static void DoActionCommand(string str) + { + //Todo! + var strs = str.Split('-'); + + if (strs != null && strs.Length == 2 && double.TryParse(strs[1], out var time)) + { + var actName = strs[0]; + foreach (var iAct in RotationUpdater.RightRotationBaseActions) + { + if (iAct is not BaseAction act) continue; + if (!act.IsTimeline) continue; + + if (actName == act.Name) + { + var index = NextActs.FindIndex(i => i.act.ID == act.ID); + var newItem = new NextAct(act, DateTime.Now.AddSeconds(time)); + if (index < 0) + { + NextActs.Add(newItem); + } + else + { + NextActs[index] = newItem; + } + NextActs = NextActs.OrderBy(i => i.deadTime).ToList(); + + Service.ToastGui.ShowQuest(string.Format(LocalizationManager.RightLang.Commands_InsertAction, time), + new Dalamud.Game.Gui.Toast.QuestToastOptions() + { + IconId = act.IconID, + }); + + return; + } + } + } + + Service.ChatGui.Print(LocalizationManager.RightLang.Commands_InsertActionFailure); + } + + + private static void DoRotationCommand(ICustomRotation customCombo, string str) + { + var configs = customCombo.Configs; + foreach (var config in configs) + { + if (config.DoCommand(configs, str)) + { + Service.ChatGui.Print(string.Format(LocalizationManager.RightLang.Commands_ChangeRotationConfig, + config.DisplayName, configs.GetDisplayString(config.Name))); + + return; + } + } + Service.ChatGui.Print(LocalizationManager.RightLang.Commands_CannotFindRotationConfig); + } + } +} diff --git a/RotationSolver.Old/Commands/RSCommands_StateSpecialCommand.cs b/RotationSolver.Old/Commands/RSCommands_StateSpecialCommand.cs new file mode 100644 index 000000000..0c646b0b9 --- /dev/null +++ b/RotationSolver.Old/Commands/RSCommands_StateSpecialCommand.cs @@ -0,0 +1,83 @@ +using Lumina.Excel.GeneratedSheets; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Localization; +using RotationSolver.SigReplacers; +using System; + +namespace RotationSolver.Commands +{ + internal static partial class RSCommands + { + private static DateTime _specialStateStartTime = DateTime.MinValue; + private static double SpecialTimeLeft => Service.Configuration.SpecialDuration - (DateTime.Now - _specialStateStartTime).TotalSeconds; + + private static SpecialCommandType _specialType = SpecialCommandType.EndSpecial; + internal static SpecialCommandType SpecialType => + SpecialTimeLeft < 0 ? SpecialCommandType.EndSpecial : _specialType; + + internal static StateCommandType StateType { get; private set; } = StateCommandType.Cancel; + + + private static string _stateString = "Off", _specialString = string.Empty; + internal static string EntryString => + _stateString + (SpecialTimeLeft < 0 ? string.Empty : $" - {_specialString}: {SpecialTimeLeft:F2}s"); + + private static void UpdateToast() + { + if (!Service.Configuration.ShowInfoOnToast) return; + + Service.ToastGui.ShowQuest(" " + EntryString, new Dalamud.Game.Gui.Toast.QuestToastOptions() + { + IconId = 101, + }); + } + + private static unsafe void DoStateCommandType(StateCommandType stateType) => DoOneCommandType(stateType, EnumTranslations.ToSayout, role => + { + if (StateType == StateCommandType.Smart + && stateType == StateCommandType.Smart) + { + Service.Configuration.TargetingIndex += 1; + Service.Configuration.TargetingIndex %= Service.Configuration.TargetingTypes.Count; + } + + StateType = stateType; + + UpdateStateNamePlate(); + + _stateString = stateType.ToStateString(role); + UpdateToast(); + }); + + public static unsafe void UpdateStateNamePlate() + { + if (Service.ClientState.LocalPlayer == null) return; + + Service.ClientState.LocalPlayer.GetAddress()->NamePlateIconId = + StateType == StateCommandType.Cancel ? 0u : (uint)Service.Configuration.NamePlateIconId; + } + + private static void DoSpecialCommandType(SpecialCommandType specialType, bool sayout = true) => DoOneCommandType(specialType, sayout ? EnumTranslations.ToSayout : (s, r) => string.Empty, role => + { + _specialType = specialType; + _specialString = specialType.ToSpecialString(role); + + _specialStateStartTime = specialType == SpecialCommandType.EndSpecial ? DateTime.MinValue : DateTime.Now; + if (sayout) UpdateToast(); + }); + + private static void DoOneCommandType(T type, Func sayout, Action doingSomething) + where T : struct, Enum + { + //Get jobrole. + var role = Service.DataManager.GetExcelSheet().GetRow( + Service.ClientState.LocalPlayer.ClassJob.Id).GetJobRole(); + + doingSomething(role); + + //Saying out. + if (Service.Configuration.SayOutStateChanged) Watcher.Speak(sayout(type, role)); + } + } +} diff --git a/RotationSolver/Configuration/ActionEventInfo.cs b/RotationSolver.Old/Configuration/ActionEventInfo.cs similarity index 100% rename from RotationSolver/Configuration/ActionEventInfo.cs rename to RotationSolver.Old/Configuration/ActionEventInfo.cs diff --git a/RotationSolver/Configuration/MacroInfo.cs b/RotationSolver.Old/Configuration/MacroInfo.cs similarity index 100% rename from RotationSolver/Configuration/MacroInfo.cs rename to RotationSolver.Old/Configuration/MacroInfo.cs diff --git a/RotationSolver/Configuration/PluginConfiguration.cs b/RotationSolver.Old/Configuration/PluginConfiguration.cs similarity index 100% rename from RotationSolver/Configuration/PluginConfiguration.cs rename to RotationSolver.Old/Configuration/PluginConfiguration.cs diff --git a/RotationSolver/Configuration/RotationConfig/IRotationConfig.cs b/RotationSolver.Old/Configuration/RotationConfig/IRotationConfig.cs similarity index 100% rename from RotationSolver/Configuration/RotationConfig/IRotationConfig.cs rename to RotationSolver.Old/Configuration/RotationConfig/IRotationConfig.cs diff --git a/RotationSolver/Configuration/RotationConfig/IRotationConfigSet.cs b/RotationSolver.Old/Configuration/RotationConfig/IRotationConfigSet.cs similarity index 100% rename from RotationSolver/Configuration/RotationConfig/IRotationConfigSet.cs rename to RotationSolver.Old/Configuration/RotationConfig/IRotationConfigSet.cs diff --git a/RotationSolver.Old/Configuration/RotationConfig/RotationConfig.cd b/RotationSolver.Old/Configuration/RotationConfig/RotationConfig.cd new file mode 100644 index 000000000..f2010082a --- /dev/null +++ b/RotationSolver.Old/Configuration/RotationConfig/RotationConfig.cd @@ -0,0 +1,70 @@ + + + + + + AAAAEAAAIAAAAgAAAAAAABYAAABAAAAAAAAAAAAAAAA= + Configuration\RotationConfig\RotationConfigBase.cs + + + + + + + AAAAAAAAAAAAAgAAAAAAABAAAAAAAAAAAAAAAAAAAAA= + Configuration\RotationConfig\RotationConfigBoolean.cs + + + + + + AAAAAAAAAAAACgAAAAAAABIAAAAAAAAAAAAAAAAAAAA= + Configuration\RotationConfig\RotationConfigCombo.cs + + + + + + AAAAAAAAAAAAAAAAgAAAAAAAAAAAAIAAAAAAAAAAAAA= + Configuration\RotationConfig\RotationConfigComparer.cs + + + + + + + AAAAAAAAABAAAgAAEAAAAAABAAAAAAAAAAAAAAAAAAA= + Configuration\RotationConfig\RotationConfigFloat.cs + + + + + + CACgAAgAACAAAhAIAEAABAAAAAAQAgQkAAAAAAAAAAA= + Configuration\RotationConfig\RotationConfigSet.cs + + + + + + + AAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + Configuration\RotationConfig\RotationConfigString.cs + + + + + + AACgAAgAACAAAgAAAEAABAAAAAAAAgAgAAAAAAAAAAA= + Configuration\RotationConfig\IRotationConfigSet.cs + + + + + + AAAAEAAAIAAAAgAAAAAAABYAAAAAAAAAAAAAAAAAAAA= + Configuration\RotationConfig\IRotationConfig.cs + + + + \ No newline at end of file diff --git a/RotationSolver/Configuration/RotationConfig/RotationConfigBase.cs b/RotationSolver.Old/Configuration/RotationConfig/RotationConfigBase.cs similarity index 100% rename from RotationSolver/Configuration/RotationConfig/RotationConfigBase.cs rename to RotationSolver.Old/Configuration/RotationConfig/RotationConfigBase.cs diff --git a/RotationSolver/Configuration/RotationConfig/RotationConfigBoolean.cs b/RotationSolver.Old/Configuration/RotationConfig/RotationConfigBoolean.cs similarity index 100% rename from RotationSolver/Configuration/RotationConfig/RotationConfigBoolean.cs rename to RotationSolver.Old/Configuration/RotationConfig/RotationConfigBoolean.cs diff --git a/RotationSolver/Configuration/RotationConfig/RotationConfigCombo.cs b/RotationSolver.Old/Configuration/RotationConfig/RotationConfigCombo.cs similarity index 100% rename from RotationSolver/Configuration/RotationConfig/RotationConfigCombo.cs rename to RotationSolver.Old/Configuration/RotationConfig/RotationConfigCombo.cs diff --git a/RotationSolver.Old/Configuration/RotationConfig/RotationConfigComparer.cs b/RotationSolver.Old/Configuration/RotationConfig/RotationConfigComparer.cs new file mode 100644 index 000000000..25e90056d --- /dev/null +++ b/RotationSolver.Old/Configuration/RotationConfig/RotationConfigComparer.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace RotationSolver.Configuration.RotationConfig +{ + internal class RotationConfigComparer : IEqualityComparer + { + public bool Equals(IRotationConfig x, IRotationConfig y) => x.Name.Equals(y.Name); + + public int GetHashCode([DisallowNull] IRotationConfig obj) => obj.Name.GetHashCode(); + } +} diff --git a/RotationSolver/Configuration/RotationConfig/RotationConfigFloat.cs b/RotationSolver.Old/Configuration/RotationConfig/RotationConfigFloat.cs similarity index 100% rename from RotationSolver/Configuration/RotationConfig/RotationConfigFloat.cs rename to RotationSolver.Old/Configuration/RotationConfig/RotationConfigFloat.cs diff --git a/RotationSolver/Configuration/RotationConfig/RotationConfigSet.cs b/RotationSolver.Old/Configuration/RotationConfig/RotationConfigSet.cs similarity index 100% rename from RotationSolver/Configuration/RotationConfig/RotationConfigSet.cs rename to RotationSolver.Old/Configuration/RotationConfig/RotationConfigSet.cs diff --git a/RotationSolver/Configuration/RotationConfig/RotationConfigString.cs b/RotationSolver.Old/Configuration/RotationConfig/RotationConfigString.cs similarity index 100% rename from RotationSolver/Configuration/RotationConfig/RotationConfigString.cs rename to RotationSolver.Old/Configuration/RotationConfig/RotationConfigString.cs diff --git a/RotationSolver/Data/ActionCate.cs b/RotationSolver.Old/Data/ActionCate.cs similarity index 100% rename from RotationSolver/Data/ActionCate.cs rename to RotationSolver.Old/Data/ActionCate.cs diff --git a/RotationSolver.Old/Data/ActionID.cs b/RotationSolver.Old/Data/ActionID.cs new file mode 100644 index 000000000..7629083f5 --- /dev/null +++ b/RotationSolver.Old/Data/ActionID.cs @@ -0,0 +1,3418 @@ +namespace RotationSolver.Data; + +public enum ActionID : uint +{ + /// + /// 无技能 + /// + None = 0, + + #region Astrologian + + /// + /// 生辰 + /// + Ascend = 3603, + + /// + /// 凶星 + /// + Malefic = 3596, + + /// + /// 烧灼 + /// + Combust = 3599, + + /// + /// 重力 + /// + Gravity = 3615, + + /// + /// 吉星 + /// + Benefic = 3594, + + /// + /// 福星 + /// + Benefic2 = 3610, + + /// + /// 吉星相位 + /// + AspectedBenefic = 3595, + + /// + /// 先天禀赋 + /// + EssentialDignity = 3614, + + /// + /// 星位合图 + /// + Synastry = 3612, + + /// + /// 天星交错 + /// + CelestialIntersection = 16556, + + /// + /// 擢升 + /// + Exaltation = 25873, + + /// + /// 阳星 + /// + Helios = 3600, + + /// + /// 阳星相位 + /// + AspectedHelios = 3601, + + /// + /// 天星冲日 + /// + CelestialOpposition = 16553, + + /// + /// 地星 + /// + EarthlyStar = 7439, + + /// + /// 命运之轮 + /// + CollectiveUnconscious = 3613, + + /// + /// 天宫图 + /// + Horoscope = 16557, + + /// + /// 光速 + /// + Lightspeed = 3606, + + /// + /// 中间学派 + /// + NeutralSect = 16559, + + /// + /// 大宇宙 + /// + Macrocosmos = 25874, + + /// + /// 星力 + /// + Astrodyne = 25870, + + /// + /// 占卜 + /// + Divination = 16552, + + /// + /// 抽卡 + /// + Draw = 3590, + + /// + /// 重抽 + /// + Redraw = 3593, + + /// + /// 小奥秘卡 + /// + MinorArcana = 7443, + + /// + /// 出王冠卡 + /// + CrownPlay = 25869, + + /// + /// 太阳神之衡 + /// + Balance = 4401, + + /// + /// 放浪神之箭 + /// + Arrow = 4402, + + /// + /// 战争神之枪 + /// + Spear = 4403, + + /// + /// 世界树之干 + /// + Bole = 4404, + + /// + /// 河流神之瓶 + /// + Ewer = 4405, + + /// + /// 建筑神之塔 + /// + Spire = 4406, + #endregion + + #region BlackMage + /// + /// 闪雷 + /// + Thunder = 144, + + /// + /// 震雷 + /// + Thunder2 = 7447, + + /// + /// 暴雷 + /// + Thunder3 = 153, + + Thunder4 = 7420, + + /// + /// 星灵移位 + /// + Transpose = 149, + + /// + /// 灵极魂 + /// + UmbralSoul = 16506, + + /// + /// 魔罩 + /// + Manaward = 157, + + /// + /// 魔泉 + /// + Manafont = 158, + + /// + /// 激情咏唱 + /// + Sharpcast = 3574, + + /// + /// 三连咏唱 + /// + Triplecast = 7421, + + /// + /// 黑魔纹 + /// + Leylines = 3573, + + /// + /// 魔纹步 + /// + BetweenTheLines = 7419, + + /// + /// 以太步 + /// + AetherialManipulation = 155, + + /// + /// 详述 + /// + Amplifier = 25796, + + /// + /// 核爆 + /// + Flare = 162, + + /// + /// 绝望 + /// + Despair = 16505, + + /// + /// 秽浊 + /// + Foul = 7422, + + /// + /// 异言 + /// + Xenoglossy = 16507, + + /// + /// 崩溃 + /// + Scathe = 156, + + /// + /// 悖论 + /// + Paradox = 25797, + + /// + /// 火炎 + /// + Fire = 141, + + /// + /// 烈炎 + /// + Fire2 = 147, + + /// + /// 爆炎 + /// + Fire3 = 152, + + /// + /// 炽炎 + /// + Fire4 = 3577, + + /// + /// 冰结 + /// + Blizzard = 142, + + /// + /// 冰冻 + /// + Blizzard2 = 25793, + + /// + /// 冰封 + /// + Blizzard3 = 154, + + /// + /// 冰澈 + /// + Blizzard4 = 3576, + + /// + /// 冻结 + /// + Freeze = 159, + #endregion + + #region BlueMage + /// + /// 水炮 + /// + WaterCannon = 11385, + + /// + /// 火炎放射 + /// + FlameThrower = 11402, + + /// + /// 水流吐息 + /// + AquaBreath = 11390, + + /// + /// 狂乱 + /// + FlyingFrenzy = 11389, + + /// + /// 钻头炮 + /// + DrillCannons = 11398, + + /// + /// 高压电流 + /// + HighVoltage = 11387, + + /// + /// 若隐若现 + /// + Loom = 11401, + + /// + /// 终极针 + /// + FinalSting = 11407, + + /// + /// 苦闷之歌 + /// + SongofTorment = 11386, + + /// + /// 怒视 + /// + Glower = 11404, + + /// + /// 平原震裂 + /// + Plaincracker = 11391, + + /// + /// 怒发冲冠 + /// + Bristle = 11393, + + /// + /// 白风 + /// + WhiteWind = 11406, + + /// + /// 5级石化 + /// + Level5Petrify = 11414, + + /// + /// 锋利菜刀 + /// + SharpenedKnife = 11400, + + /// + /// 冰棘屏障 + /// + IceSpikes = 11418, + + /// + /// 吸血 + /// + BloodDrain = 11395, + + /// + /// 橡果炸弹 + /// + AcornBomb = 11392, + + /// + /// 投弹 + /// + BombToss = 11396, + + /// + /// 破防 + /// + Offguard = 11411, + + /// + /// 自爆 + /// + Selfdestruct = 11408, + + /// + /// 拍掌 + /// + Faze = 11403, + + /// + /// 投掷沙丁鱼 + /// + FlyingSardine = 11423, + + /// + /// 鼻息 + /// + Snort = 11383, + + /// + /// 4星吨 + /// + Weight4tonze = 11384, + + /// + /// 诡异视线 + /// + TheLook = 11399, + + /// + /// 臭气 + /// + BadBreath = 11388, + + /// + /// 超硬化 + /// + Diamondback = 11424, + + /// + /// 强力守护 + /// + MightyGuard = 11417, + + /// + /// 滑舌 + /// + StickyTongue = 11412, + + /// + /// 油性分泌物 + /// + ToadOil = 11410, + + /// + /// 寒冰咆哮 + /// + TheRamVoice = 11419, + + /// + /// 雷电咆哮 + /// + TheDragonVoice = 11420, + + /// + /// 导弹 + /// + Missile = 11405, + + /// + /// 千针刺 + /// + Needles1000 = 11397, + + /// + /// 喷墨 + /// + InkJet = 11422, + + /// + /// 火投枪 + /// + FireAngon = 11425, + + /// + /// 月之笛 + /// + MoonFlute = 11415, + + /// + /// 螺旋尾 + /// + TailScrew = 11413, + + /// + /// 精神冲击 + /// + MindBlast = 11394, + + /// + /// 死亡宣告 + /// + Doom = 11416, + + /// + /// 惊奇光 + /// + PeculiarLight = 11421, + + /// + /// 飞翎雨 + /// + FeatherRain = 11426, + + /// + /// 地火喷发 + /// + Eruption = 11427, + + /// + /// 山崩 + /// + MountainBusterBLU = 11428, + + /// + /// 轰雷 + /// + ShockStrike = 11429, + + /// + /// 冰雪乱舞 + /// + GlassDance = 11430, + + /// + /// 水神的面纱 + /// + VeiloftheWhorl = 11431, + + /// + /// 高山气流 + /// + AlpineDraft = 18295, + + /// + /// 万变水波 + /// + ProteanWave = 18296, + + /// + /// 狂风暴雪 + /// + Northerlies = 18297, + + /// + /// 生物电 + /// + Electrogenesis = 18298, + + /// + /// 寒光 + /// + Kaltstrahl = 18299, + + /// + /// 深渊贯穿 + /// + AbyssalTransfixion = 18300, + + /// + /// 唧唧咋咋 + /// + Chirp = 18301, + + /// + /// 怪音波 + /// + EerieSoundwave = 18302, + + /// + /// 绒绒治疗 + /// + PomCure = 18303, + + /// + /// 哥布防御 + /// + Gobskin = 18304, + + /// + /// 魔法锤 + /// + MagicHammer = 18305, + + /// + /// 防御指示 + /// + Avail = 18306, + + /// + /// 蛙腿 + /// + FrogLegs = 18307, + + /// + /// 音爆 + /// + SonicBoom = 18308, + + /// + /// 口笛 + /// + Whistle = 18309, + + /// + /// 白骑士之旅 + /// + WhiteKnightsTour = 18310, + + /// + /// 黑骑士之旅 + /// + BlackKnightsTour = 18311, + + /// + /// 5级即死 + /// + Level5Death = 18312, + + /// + /// 火箭炮 + /// + Launcher = 18313, + + /// + /// 永恒射线 + /// + PerpetualRay = 18314, + + /// + /// 仙人盾 + /// + Cactguard = 18315, + + /// + /// 复仇冲击 + /// + RevengeBlast = 18316, + + /// + /// 天使低语 + /// + AngelWhisper = 18317, + + /// + /// 蜕皮 + /// + Exuviation = 18318, + + /// + /// 逆流 + /// + Reflux = 18319, + + /// + /// 捕食 + /// + Devour = 18320, + + /// + /// 小侦测 + /// + CondensedLibra = 18321, + + /// + /// 以太复制 + /// + AethericMimicry = 18322, + + /// + /// 穿甲散弹 + /// + Surpanakha = 18323, + + /// + /// 类星体 + /// + Quasar = 18324, + + /// + /// 正义飞踢 + /// + JKick = 18325, + + /// + /// 渔叉三段 + /// + TripleTrident = 23264, + + /// + /// 哔哩哔哩 + /// + Tingle = 23265, + + /// + /// 掀地板之术 + /// + Tatamigaeshi = 23266, + + /// + /// 彻骨雾寒 + /// + ColdFog = 23267, + + /// + /// 赞歌 + /// + Stotram = 23269, + + /// + /// 圣光射线 + /// + SaintlyBeam = 23270, + + /// + /// 污泥泼洒 + /// + FeculentFlood = 23271, + + /// + /// 天使的点心 + /// + AngelsSnack = 23272, + + /// + /// 玄结界 + /// + ChelonianGate = 23273, + + /// + /// 斗灵弹 + /// + TheRoseofDestruction = 23275, + + /// + /// 斗争本能 + /// + BasicInstinct = 23276, + + /// + /// 超振动 + /// + Ultravibration = 23277, + + /// + /// 冰焰 + /// + Blaze = 23278, + + /// + /// 芥末爆弹 + /// + MustardBomb = 23279, + + /// + /// 龙之力 + /// + DragonForce = 23280, + + /// + /// 以太火花 + /// + AetherialSpark = 23281, + + /// + /// 水力吸引 + /// + HydroPull = 23282, + + /// + /// 水脉诅咒 + /// + MaledictionofWater = 23283, + + /// + /// 陆行鸟陨石 + /// + ChocoMeteor = 23284, + + /// + /// 马特拉魔术 + /// + MatraMagic = 23285, + + /// + /// 生成外设 + /// + PeripheralSynthesis = 23286, + + /// + /// 如意大旋风 + /// + BothEnds = 23287, + + /// + /// 鬼宿脚 + /// + PhantomFlurry = 23288, + + /// + /// 月下彼岸花 + /// + Nightbloom = 23290, + + /// + /// 冰雾 + /// + WhiteDeath = 23268, + + /// + /// 玄天武水壁 + /// + DivineCataract = 23274, + + /// + /// 鬼宿脚(需要buff版本) + /// + PhantomFlurry2 = 23289, + #endregion + + #region Bard + /// + /// 强力射击 + /// + HeavyShoot = 97, + + /// + /// 直线射击 + /// + StraitShoot = 98, + + /// + /// 毒咬箭 + /// + VenomousBite = 100, + + /// + /// 风蚀箭 + /// + Windbite = 113, + + /// + /// 伶牙俐齿 + /// + IronJaws = 3560, + + /// + /// 贤者的叙事谣 + /// + MagesBallad = 114, + + /// + /// 军神的赞美歌 + /// + ArmysPaeon = 116, + + /// + /// 放浪神的小步舞曲 + /// + WanderersMinuet = 3559, + + /// + /// 战斗之声 + /// + BattleVoice = 118, + + /// + /// 猛者强击 + /// + RagingStrikes = 101, + + /// + /// 光明神的最终乐章 + /// + RadiantFinale = 25785, + + /// + /// 纷乱箭 + /// + Barrage = 107, + + /// + /// 九天连箭 + /// + EmpyrealArrow = 3558, + + /// + /// 完美音调 + /// + PitchPerfect = 7404, + + /// + /// 失血箭 + /// + Bloodletter = 110, + + /// + /// 死亡箭雨 + /// + RainofDeath = 117, + + /// + /// 连珠箭 + /// + QuickNock = 106, + + /// + /// 影噬箭 + /// + Shadowbite = 16494, + + /// + /// 光阴神的礼赞凯歌 + /// + WardensPaean = 3561, + + /// + /// 大地神的抒情恋歌 + /// + NaturesMinne = 7408, + + /// + /// 侧风诱导箭 + /// + Sidewinder = 3562, + + /// + /// 绝峰箭 + /// + ApexArrow = 16496, + + /// + /// 爆破箭 + /// + BlastArrow = 25784, + + /// + /// 行吟 + /// + Troubadour = 7405, + #endregion + + #region Dancer + /// + /// 瀑泻 + /// + Cascade = 15989, + + /// + /// 喷泉 + /// + Fountain = 15990, + + /// + /// 逆瀑泻 + /// + ReverseCascade = 15991, + + /// + /// 坠喷泉 + /// + Fountainfall = 15992, + + /// + /// 扇舞·序 + /// + FanDance = 16007, + + /// + /// 风车 + /// + Windmill = 15993, + + /// + /// 落刃雨 + /// + Bladeshower = 15994, + + /// + /// 升风车 + /// + RisingWindmill = 15995, + + /// + /// 落血雨 + /// + Bloodshower = 15996, + + /// + /// 扇舞·破 + /// + FanDance2 = 16008, + + /// + /// 扇舞·急 + /// + FanDance3 = 16009, + + /// + /// 扇舞·终 + /// + FanDance4 = 25791, + + /// + /// 剑舞 + /// + SaberDance = 16005, + + /// + /// 流星舞 + /// + StarfallDance = 25792, + + /// + /// 前冲步 + /// + EnAvant = 16010, + + /// + /// 蔷薇曲脚步 + /// + Emboite = 15999, + + /// + /// 小鸟交叠跳 + /// + Entrechat = 16000, + + /// + /// 绿叶小踢腿 + /// + Jete = 16001, + + /// + /// 金冠趾尖转 + /// + Pirouette = 16002, + + /// + /// 标准舞步 + /// + StandardStep = 15997, + + /// + /// 技巧舞步 + /// + TechnicalStep = 15998, + + /// + /// 标准舞步结束 + /// + StandardFinish = 16003, + + /// + /// 技巧舞步结束 + /// + TechnicalFinish = 16004, + + /// + /// 防守之桑巴 + /// + ShieldSamba = 16012, + + /// + /// 治疗之华尔兹 + /// + CuringWaltz = 16015, + + /// + /// 闭式舞姿 + /// + ClosedPosition = 16006, + + /// + /// 进攻之探戈 + /// + Devilment = 16011, + + /// + /// 百花争艳 + /// + Flourish = 16013, + + /// + /// 即兴表演 + /// + Improvisation = 16014, + + /// + /// 即兴表演结束 + /// + Improvised = 25789, + + /// + /// 提拉纳 + /// + Tillana = 25790, + #endregion + + #region Dragoon + /// + /// 精准刺 + /// + TrueThrust = 75, + + /// + /// 贯通刺 + /// + VorpalThrust = 78, + + /// + /// 龙眼雷电 + /// + RaidenThrust = 16479, + + /// + /// 直刺 + /// + FullThrust = 84, + + /// + /// 苍穹刺 + /// + HeavensThrust = 25771, + + /// + /// 开膛枪 + /// + Disembowel = 87, + + /// + /// 樱花怒放 + /// + ChaosThrust = 88, + + /// + /// 樱花缭乱 + /// + ChaoticSpring = 25772, + + /// + /// 龙尾大回旋 + /// + WheelingThrust = 3556, + + /// + /// 龙牙龙爪 + /// + FangandClaw = 3554, + + /// + /// 贯穿尖 + /// + PiercingTalon = 90, + + /// + /// 死天枪 + /// + DoomSpike = 86, + + /// + /// 音速刺 + /// + SonicThrust = 7397, + + /// + /// 龙眼苍穹 + /// + DraconianFury = 25770, + + /// + /// 山境酷刑 + /// + CoerthanTorment = 16477, + + /// + /// 破碎冲 + /// + SpineshatterDive = 95, + + /// + /// 龙炎冲 + /// + DragonfireDive = 96, + + /// + /// 跳跃 + /// + Jump = 92, + + /// + /// 高跳 + /// + HighJump = 16478, + + /// + /// 幻象冲 + /// + MirageDive = 7399, + + /// + /// 武神枪 + /// + Geirskogul = 3555, + + /// + /// 死者之岸 + /// + Nastrond = 7400, + + /// + /// 坠星冲 + /// + Stardiver = 16480, + + /// + /// 天龙点睛 + /// + WyrmwindThrust = 25773, + #endregion + + #region DRK + /// + /// 重斩 + /// + HardSlash = 3617, + + /// + /// 吸收斩 + /// + SyphonStrike = 3623, + + /// + /// 释放 + /// + Unleash = 3621, + + /// + /// 深恶痛绝 + /// + Grit = 3629, + + /// + /// 伤残 + /// + Unmend = 3624, + + /// + /// 噬魂斩 + /// + Souleater = 3632, + + /// + /// 暗黑波动 + /// + FloodofDarkness = 16466, + + /// + /// 暗黑锋 + /// + EdgeofDarkness = 16467, + + /// + /// 嗜血 + /// + BloodWeapon = 3625, + + /// + /// 暗影墙 + /// + ShadowWall = 3636, + + /// + /// 弃明投暗 + /// + DarkMind = 3634, + + /// + /// 行尸走肉 + /// + LivingDead = 3638, + + /// + /// 腐秽大地 + /// + SaltedEarth = 3639, + + /// + /// 跳斩 + /// + Plunge = 3640, + + /// + /// 吸血深渊 + /// + AbyssalDrain = 3641, + + /// + /// 精雕怒斩 + /// + CarveandSpit = 3643, + + /// + /// 血溅 + /// + Bloodspiller = 7392, + + /// + /// 寂灭 + /// + Quietus = 7391, + + /// + /// 血乱 + /// + Delirium = 7390, + + /// + /// 至黑之夜 + /// + TheBlackestNight = 7393, + + /// + /// 刚魂 + /// + StalwartSoul = 16468, + + /// + /// 暗黑布道 + /// + DarkMissionary = 16471, + + /// + /// 掠影示现 + /// + LivingShadow = 16472, + + /// + /// 献奉 + /// + Oblation = 25754, + + /// + /// 龙剑 + /// + LifeSurge = 83, + + /// + /// 猛枪 + /// + LanceCharge = 85, + + /// + /// 巨龙视线 + /// + DragonSight = 7398, + + /// + /// 战斗连祷 + /// + BattleLitany = 3557, + + /// + /// 暗影使者 + /// + Shadowbringer = 25757, + + /// + /// 腐秽黑暗 + /// + SaltandDarkness = 25755, + #endregion + + #region GNB + /// + /// 王室亲卫 + /// + RoyalGuard = 16142, + + /// + /// 利刃斩 + /// + KeenEdge = 16137, + + /// + /// 无情 + /// + NoMercy = 16138, + + /// + /// 残暴弹 + /// + BrutalShell = 16139, + + /// + /// 伪装 + /// + Camouflage = 16140, + + /// + /// 恶魔切 + /// + DemonSlice = 16141, + + /// + /// 闪雷弹 + /// + LightningShot = 16143, + + /// + /// 危险领域 + /// + DangerZone = 16144, + + /// + /// 迅连斩 + /// + SolidBarrel = 16145, + + /// + /// 爆发击 + /// + BurstStrike = 16162, + + /// + /// 星云 + /// + Nebula = 16148, + + /// + /// 恶魔杀 + /// + DemonSlaughter = 16149, + + /// + /// 极光 + /// + Aurora = 16151, + + /// + /// 超火流星 + /// + Superbolide = 16152, + + /// + /// 音速破 + /// + SonicBreak = 16153, + + /// + /// 粗分斩 + /// + RoughDivide = 16154, + + /// + /// 烈牙 + /// + GnashingFang = 16146, + + /// + /// 弓形冲波 + /// + BowShock = 16159, + + /// + /// 光之心 + /// + HeartofLight = 16160, + + /// + /// 石之心 + /// + HeartofStone = 16161, + + /// + /// 命运之环 + /// + FatedCircle = 16163, + + /// + /// 血壤 + /// + Bloodfest = 16164, + + /// + /// 倍攻 + /// + DoubleDown = 25760, + + /// + /// 猛兽爪 + /// + SavageClaw = 16147, + + /// + /// 凶禽爪 + /// + WickedTalon = 16150, + + /// + /// 续剑 + /// + Continuation = 16155, + + /// + /// 撕喉 + /// + JugularRip = 16156, + + /// + /// 裂膛 + /// + AbdomenTear = 16157, + + /// + /// 穿目 + /// + EyeGouge = 16158, + + /// + /// 超高速 + /// + Hypervelocity = 25759, + #endregion + + #region MCH + /// + /// 分裂弹 + /// + SplitShot = 2866, + + /// + /// 热分裂弹 + /// + HeatedSplitShot = 7411, + + /// + /// 独头弹 + /// + SlugShot = 2868, + + /// + /// 热独头弹 + /// + HeatedSlugShot = 7412, + + /// + /// 狙击弹 + /// + CleanShot = 2873, + + /// + /// 热冲击 + /// + HeatBlast = 7410, + + /// + /// 散射 + /// + SpreadShot = 2870, + + /// + /// 自动弩 + /// + AutoCrossbow = 16497, + + /// + /// 热弹 + /// + HotShot = 2872, + + /// + /// 空气锚 + /// + AirAnchor = 16500, + + /// + /// 钻头 + /// + Drill = 16498, + + /// + /// 回转飞锯 + /// + ChainSaw = 25788, + + /// + /// 毒菌冲击 + /// + Bioblaster = 16499, + + /// + /// 整备 + /// + Reassemble = 2876, + + /// + /// 超荷 + /// + Hypercharge = 17209, + + /// + /// 野火 + /// + Wildfire = 2878, + + /// + /// 虹吸弹 + /// + GaussRound = 2874, + + /// + /// 弹射 + /// + Ricochet = 2890, + + /// + /// 枪管加热 + /// + BarrelStabilizer = 7414, + + /// + /// 车式浮空炮塔 + /// + RookAutoturret = 2864, + + /// + /// 策动 + /// + Tactician = 16889, + + Dismantle = 2887, + #endregion + + #region MNK + /// + /// 双龙脚 + /// + DragonKick = 74, + + /// + /// 连击 + /// + Bootshine = 53, + + /// + /// 破坏神冲 aoe + /// + ArmoftheDestroyer = 62, + + /// + /// 破坏神脚 aoe + /// + ShadowoftheDestroyer = 25767, + + /// + /// 双掌打 伤害提高 + /// + TwinSnakes = 61, + + /// + /// 正拳 + /// + TrueStrike = 54, + + /// + /// 四面脚 aoe + /// + FourpointFury = 16473, + + /// + /// 破碎拳 + /// + Demolish = 66, + + /// + /// 崩拳 + /// + SnapPunch = 56, + + /// + /// 地烈劲 aoe + /// + Rockbreaker = 70, + + /// + /// 斗气 + /// + Meditation = 3546, + + /// + /// 铁山靠 + /// + SteelPeak = 25761, + + /// + /// 空鸣拳 + /// + HowlingFist = 25763, + + /// + /// 义结金兰 + /// + Brotherhood = 7396, + + /// + /// 红莲极意 提高dps + /// + RiddleofFire = 7395, + + /// + /// 突进技能 + /// + Thunderclap = 25762, + + /// + /// 真言 + /// + Mantra = 65, + + /// + /// 震脚 + /// + PerfectBalance = 69, + + /// + /// 苍气炮 阴 + /// + ElixirField = 3545, + + /// + /// 爆裂脚 阳 + /// + FlintStrike = 25882, + + /// + /// 翻天脚 兔 + /// + CelestialRevolution = 25765, + + /// + /// 凤凰舞 + /// + RisingPhoenix = 25768, + + /// + /// 斗魂旋风脚 阴阳 + /// + TornadoKick = 3543, + + /// + /// 梦幻斗舞 + /// + PhantomRush = 25769, + + /// + /// 演武 + /// + FormShift = 4262, + + /// + /// 金刚极意 盾 + /// + RiddleofEarth = 7394, + + /// + /// 疾风极意 + /// + RiddleofWind = 25766, + #endregion + + #region NIN + /// + /// 隐遁 + /// + Hide = 2245, + + /// + /// 双刃旋 + /// + SpinningEdge = 2240, + + /// + /// 残影 + /// + ShadeShift = 2241, + + /// + /// 绝风 + /// + GustSlash = 2242, + + /// + /// 飞刀 + /// + ThrowingDagger = 2247, + + /// + /// 夺取 + /// + Mug = 2248, + + /// + /// 攻其不备 + /// + TrickAttack = 2258, + + /// + /// 旋风刃 + /// + AeolianEdge = 2255, + + /// + /// 血雨飞花 + /// + DeathBlossom = 2254, + + /// + /// 天之印 + /// + Ten = 2259, + + /// + /// 地之印 + /// + Chi = 2261, + + /// + /// 人之印 + /// + Jin = 2263, + + /// + /// 天地人 + /// + TenChiJin = 7403, + + /// + /// 缩地 + /// + Shukuchi = 2262, + + /// + /// 断绝 + /// + Assassinate = 2246, + + /// + /// 命水 + /// + Meisui = 16489, + + /// + /// 生杀予夺 + /// + Kassatsu = 2264, + + /// + /// 八卦无刃杀 + /// + HakkeMujinsatsu = 16488, + + /// + /// 强甲破点突 + /// + ArmorCrush = 3563, + + /// + /// 通灵之术·大虾蟆 + /// + HellfrogMedium = 7401, + + /// + /// 六道轮回 + /// + Bhavacakra = 7402, + + /// + /// 分身之术 + /// + Bunshin = 16493, + + /// + /// 残影镰鼬 + /// + PhantomKamaitachi = 25774, + + /// + /// 月影雷兽牙 + /// + FleetingRaiju = 25778, + + /// + /// 月影雷兽爪 + /// + ForkedRaiju = 25777, + + /// + /// 风来刃 + /// + Huraijin = 25876, + + /// + /// 梦幻三段 + /// + DreamWithinaDream = 3566, + + /// + /// 风魔手里剑天 + /// + FumaShurikenTen = 18873, + + /// + /// 风魔手里剑人 + /// + FumaShurikenJin = 18875, + + /// + /// 火遁之术天 + /// + KatonTen = 18876, + + /// + /// 雷遁之术地 + /// + RaitonChi = 18877, + + /// + /// 土遁之术地 + /// + DotonChi = 18880, + + /// + /// 水遁之术人 + /// + SuitonJin = 18881, + + /// + /// 通灵之术 + /// + RabbitMedium = 2272, + + /// + /// 风魔手里剑 + /// + FumaShuriken = 2265, + + /// + /// 火遁之术 + /// + Katon = 2266, + + /// + /// 雷遁之术 + /// + Raiton = 2267, + + /// + /// 冰遁之术 + /// + Hyoton = 2268, + + /// + /// 风遁之术 + /// + Huton = 2269, + + /// + /// 土遁之术 + /// + Doton = 2270, + + /// + /// 水遁之术 + /// + Suiton = 2271, + + /// + /// 劫火灭却之术 + /// + GokaMekkyaku = 16491, + + /// + /// 冰晶乱流之术 + /// + HyoshoRanryu = 16492, + #endregion + + #region PLD + /// + /// 钢铁信念 + /// + IronWill = 28, + + Bulwark = 22, + + /// + /// 先锋剑 + /// + FastBlade = 9, + + /// + /// 暴乱剑 + /// + RiotBlade = 15, + + /// + /// 沥血剑 + /// + GoringBlade = 3538, + + /// + /// 战女神之怒 + /// + RageofHalone = 21, + + /// + /// 王权剑 + /// + RoyalAuthority = 3539, + + /// + /// 投盾 + /// + ShieldLob = 24, + + /// + /// 战逃反应 + /// + FightorFlight = 20, + + /// + /// 全蚀斩 + /// + TotalEclipse = 7381, + + /// + /// 日珥斩 + /// + Prominence = 16457, + + /// + /// 预警 + /// + Sentinel = 17, + + /// + /// 厄运流转 + /// + CircleofScorn = 23, + + /// + /// 深奥之灵 + /// + SpiritsWithin = 29, + + /// + /// 神圣领域 + /// + HallowedGround = 30, + + /// + /// 圣光幕帘 + /// + DivineVeil = 3540, + + /// + /// 深仁厚泽 + /// + Clemency = 3541, + + /// + /// 干预 + /// + Intervention = 7382, + + /// + /// 调停 + /// + Intervene = 16461, + + /// + /// 赎罪剑 + /// + Atonement = 16460, + + /// + /// 偿赎剑 + /// + Expiacion = 25747, + + /// + /// 英勇之剑 + /// + BladeofValor = 25750, + + /// + /// 真理之剑 + /// + BladeofTruth = 25749, + + /// + /// 信念之剑 + /// + BladeofFaith = 25748, + + /// + /// 安魂祈祷 + /// + Requiescat = 7383, + + /// + /// 悔罪 + /// + Confiteor = 16459, + + /// + /// 圣环 + /// + HolyCircle = 16458, + + /// + /// 圣灵 + /// + HolySpirit = 7384, + + /// + /// 武装戍卫 + /// + PassageofArms = 7385, + + /// + /// 保护 + /// + Cover = 27, + + /// + /// 盾阵 + /// + Sheltron = 3542, + #endregion + + #region RDM + /// + /// 赤复活 + /// + Verraise = 7523, + + /// + /// 震荡 + /// + Jolt = 7503, + + /// + /// 回刺 + /// + Riposte = 7504, + + /// + /// 赤闪雷 + /// + Verthunder = 7505, + + /// + /// 短兵相接 + /// + CorpsAcorps = 7506, + + /// + /// 赤疾风 + /// + Veraero = 7507, + + /// + /// 散碎 + /// + Scatter = 7509, + + /// + /// 赤震雷 + /// + Verthunder2 = 16524, + + /// + /// 赤烈风 + /// + Veraero2 = 16525, + + /// + /// 赤火炎 + /// + Verfire = 7510, + + /// + /// 赤飞石 + /// + Verstone = 7511, + + /// + /// 交击斩 + /// + Zwerchhau = 7512, + + /// + /// 交剑 + /// + Engagement = 16527, + + /// + /// 飞剑 + /// + Fleche = 7517, + + /// + /// 连攻 + /// + Redoublement = 7516, + + /// + /// 促进 + /// + Acceleration = 7518, + + /// + /// 划圆斩 + /// + Moulinet = 7513, + + /// + /// 赤治疗 + /// + Vercure = 7514, + + /// + /// 六分反击 + /// + ContreSixte = 7519, + + /// + /// 鼓励 + /// + Embolden = 7520, + + /// + /// 续斩 + /// + Reprise = 16529, + + /// + /// 抗死 + /// + MagickBarrier = 25857, + + /// + /// 赤核爆 + /// + Verflare = 7525, + + /// + /// 赤神圣 + /// + Verholy = 7526, + + /// + /// 焦热 + /// + Scorch = 16530, + + /// + /// 决断 + /// + Resolution = 25858, + + /// + /// 魔元化 + /// + Manafication = 7521, + #endregion + + #region RPR + /// + /// 切割 + /// + Slice = 24373, + + /// + /// 增盈切割 + /// + WaxingSlice = 24374, + + /// + /// 地狱切割 + /// + InfernalSlice = 24375, + + /// + /// 死亡之影 + /// + ShadowofDeath = 24378, + + /// + /// 灵魂切割 + /// + SoulSlice = 24380, + + /// + /// 旋转钐割 + /// + SpinningScythe = 24376, + + /// + /// 噩梦钐割 + /// + NightmareScythe = 24377, + + /// + /// 死亡之涡 + /// + WhorlofDeath = 24379, + + /// + /// 灵魂钐割 + /// + SoulScythe = 24381, + + /// + /// 绞决 + /// + Gibbet = 24382, + + /// + /// 缢杀 + /// + Gallows = 24383, + + /// + /// 断首 + /// + Guillotine = 24384, + + /// + /// 隐匿挥割 + /// + BloodStalk = 24389, + + /// + /// 束缚挥割 + /// + GrimSwathe = 24392, + + /// + /// 暴食 + /// + Gluttony = 24393, + + /// + /// 神秘环 + /// + ArcaneCircle = 24405, + + /// + /// 大丰收 + /// + PlentifulHarvest = 24385, + + /// + /// 夜游魂衣 + /// + Enshroud = 24394, + + /// + /// 团契 + /// + Communio = 24398, + + /// + /// 夜游魂切割 + /// + LemuresSlice = 24399, + + /// + /// 夜游魂钐割 + /// + LemuresScythe = 24400, + + /// + /// 地狱入境 + /// + HellsIngress = 24401, + + /// + /// 地狱出境 + /// + HellsEgress = 24402, + + /// + /// 虚无收割 + /// + VoidReaping = 24395, + + /// + /// 交错收割 + /// + CrossReaping = 24396, + + /// + /// 阴冷收割 + /// + GrimReaping = 24397, + + /// + /// 勾刃 + /// + Harpe = 24386, + + /// + /// 播魂种 + /// + Soulsow = 24387, + + /// + /// 收获月 + /// + HarvestMoon = 24388, + + /// + /// 神秘纹 加盾 + /// + ArcaneCrest = 24404, + #endregion + + #region SAM + /// + /// 刃风 + /// + Hakaze = 7477, + + /// + /// 阵风 + /// + Jinpu = 7478, + + /// + /// 心眼 + /// + ThirdEye = 7498, + + /// + /// 燕飞 + /// + Enpi = 7486, + + /// + /// 士风 + /// + Shifu = 7479, + + /// + /// 风雅 + /// + Fuga = 7483, + + /// + /// 风光 + /// + Fuko = 25780, + + /// + /// 月光 + /// + Gekko = 7481, + + /// + /// 叶隐 + /// + Hagakure = 7495, + + /// + /// 彼岸花 + /// + Higanbana = 7489, + + /// + /// 天下五剑 + /// + TenkaGoken = 7488, + + /// + /// 纷乱雪月花 + /// + MidareSetsugekka = 7487, + + /// + /// 满月 + /// + Mangetsu = 7484, + + /// + /// 花车 + /// + Kasha = 7482, + + /// + /// 樱花 + /// + Oka = 7485, + + /// + /// 明镜止水 + /// + MeikyoShisui = 7499, + + /// + /// 雪风 + /// + Yukikaze = 7480, + + /// + /// 必杀剑·晓天 + /// + HissatsuGyoten = 7492, + + /// + /// 必杀剑·夜天 + /// + HissatsuYaten = 7493, + + /// + /// 必杀剑·震天 + /// + HissatsuShinten = 7490, + + /// + /// 必杀剑·九天 + /// + HissatsuKyuten = 7491, + + /// + /// 意气冲天 + /// + Ikishoten = 16482, + + /// + /// 必杀剑·红莲 + /// + HissatsuGuren = 7496, + + /// + /// 必杀剑·闪影 + /// + HissatsuSenei = 16481, + + /// + /// 燕回返 + /// + TsubameGaeshi = 16483, + + /// + /// 回返五剑 + /// + KaeshiGoken = 16485, + + /// + /// 回返雪月花 + /// + KaeshiSetsugekka = 16486, + + /// + /// 照破 + /// + Shoha = 16487, + + /// + /// 无明照破 + /// + Shoha2 = 25779, + + /// + /// 奥义斩浪 + /// + OgiNamikiri = 25781, + + /// + /// 回返斩浪 + /// + KaeshiNamikiri = 25782, + #endregion + + #region SCH + /// + /// 医术 + /// + Physick = 190, + + /// + /// 鼓舞激励之策 + /// + Adloquium = 185, + + /// + /// 复生 + /// + Resurrection = 173, + + /// + /// 士气高扬之策 + /// + Succor = 186, + + /// + /// 生命活性法 + /// + Lustrate = 189, + + /// + /// 野战治疗阵 + /// + SacredSoil = 188, + + /// + /// 不屈不挠之策 + /// + Indomitability = 3583, + + /// + /// 深谋远虑之策 + /// + Excogitation = 7434, + + /// + /// 慰藉 + /// + Consolation = 16546, + + /// + /// 生命回生法 + /// + Protraction = 25867, + + /// + /// 毒菌 + /// + Bio = 17864, + + /// + /// 毁灭 + /// + Ruin = 17869, + + /// + /// 毁坏 + /// + Ruin2 = 17870, + + /// + /// 能量吸收 + /// + EnergyDrain = 167, + + /// + /// 破阵法 + /// + ArtofWar = 16539, + + /// + /// 炽天召唤 + /// + SummonSeraph = 16545, + + /// + /// 朝日召唤 + /// + SummonEos = 17215, + + /// + /// 仙光的低语/天使的低语 + /// + WhisperingDawn = 16537, + + /// + /// 异想的幻光/炽天的幻光 + /// + FeyIllumination = 16538, + + /// + /// 转化 + /// + Dissipation = 3587, + + /// + /// 以太契约-异想的融光 + /// + Aetherpact = 7437, + + /// + /// 异想的祥光 + /// + FeyBlessing = 16543, + + /// + /// 以太超流 + /// + Aetherflow = 166, + + /// + /// 秘策 + /// + Recitation = 16542, + + /// + /// 连环计 + /// + ChainStratagem = 7436, + + /// + /// 展开战术 + /// + DeploymentTactics = 3585, + + /// + /// 应急战术 + /// + EmergencyTactics = 3586, + + /// + /// 疾风怒涛之计 + /// + Expedient = 25868, + #endregion + + #region SGE + /// + /// 复苏 + /// + Egeiro = 24287, + + /// + /// 注药 + /// + Dosis = 24283, + + /// + /// 均衡注药 + /// + EukrasianDosis = 24283, + + /// + /// 发炎 + /// + Phlegma = 24289, + + /// + /// 发炎2 + /// + Phlegma2 = 24307, + + /// + /// 发炎3 + /// + Phlegma3 = 24313, + + /// + /// 诊断 + /// + Diagnosis = 24284, + + /// + /// 心关 + /// + Kardia = 24285, + + /// + /// 预后 + /// + Prognosis = 24286, + + /// + /// 自生 + /// + Physis = 24288, + + /// + /// 自生2 + /// + Physis2 = 24302, + + /// + /// 均衡 + /// + Eukrasia = 24290, + + /// + /// 拯救 + /// + Soteria = 24294, + + /// + /// 神翼 + /// + Icarus = 24295, + + /// + /// 灵橡清汁 + /// + Druochole = 24296, + + /// + /// 失衡 + /// + Dyskrasia = 24297, + + /// + /// 坚角清汁 + /// + Kerachole = 24298, + + /// + /// 寄生清汁 + /// + Ixochole = 24299, + + /// + /// 活化 + /// + Zoe = 24300, + + /// + /// 白牛清汁 + /// + Taurochole = 24303, + + /// + /// 箭毒 + /// + Toxikon = 24304, + + /// + /// 输血 + /// + Haima = 24305, + + /// + /// 均衡诊断 + /// + EukrasianDiagnosis = 24291, + + /// + /// 均衡预后 + /// + EukrasianPrognosis = 24292, + + /// + /// 根素 + /// + Rhizomata = 24309, + + /// + /// 整体论 + /// + Holos = 24310, + + /// + /// 泛输血 + /// + Panhaima = 24311, + + /// + /// 混合 + /// + Krasis = 24317, + + /// + /// 魂灵风息 + /// + Pneuma = 24318, + + /// + /// 消化 + /// + Pepsis = 24301, + #endregion + + #region SMN + /// + /// 宝石耀 + /// + Gemshine = 25883, + + /// + /// 宝石辉 + /// + PreciousBrilliance = 25884, + + /// + /// 毁灭 + /// + RuinSMN = 163, + + /// + /// 迸裂 + /// + Outburst = 16511, + + /// + /// 宝石兽召唤 + /// + SummonCarbuncle = 25798, + + /// + /// 灼热之光 团辅 + /// + SearingLight = 25801, + + /// + /// 守护之光 + /// + RadiantAegis = 25799, + + /// + /// 医术 + /// + PhysickSMN = 16230, + + /// + /// 以太蓄能 + /// + Aethercharge = 25800, + + /// + /// 龙神召唤 + /// + SummonBahamut = 7427, + + /// + /// 红宝石召唤 + /// + SummonRuby = 25802, + + /// + /// 黄宝石召唤 + /// + SummonTopaz = 25803, + + /// + /// 绿宝石召唤 + /// + SummonEmerald = 25804, + + /// + /// 复生 + /// + ResurrectionSMN = 173, + + /// + /// 能量吸收 + /// + EnergyDrainSMN = 16508, + + /// + /// 能量抽取 + /// + EnergySiphon = 16510, + + /// + /// 灵泉之炎 + /// + FountainofFire = 16514, + + /// + /// 溃烂爆发 + /// + Fester = 181, + + /// + /// 痛苦核爆 + /// + Painflare = 3578, + + /// + /// 毁绝 + /// + RuinIV = 7426, + + /// + /// 星级脉冲 + /// + AstralImpulse = 25820, + + /// + /// 星极超流 + /// + AstralFlow = 25822, + + /// + /// 龙神迸发 + /// + EnkindleBahamut = 7429, + + /// + /// 龙神附体 + /// + DreadwyrmTrance = 3581, + + /// + /// 死星核爆 + /// + Deathflare = 3582, + + /// + /// 苏生之炎 + /// + Rekindle = 25830, + + /// + /// 深红旋风 + /// + CrimsonCyclone = 25835, + + /// + /// 深红强袭 + /// + CrimsonStrike = 25885, + + /// + /// 山崩 + /// + MountainBuster = 25836, + + /// + /// 螺旋气流 + /// + Slipstream = 25837, + #endregion + + #region WAR + /// + /// 守护 + /// + Defiance = 48, + + /// + /// 重劈 + /// + HeavySwing = 31, + + /// + /// 凶残裂 + /// + Maim = 37, + + /// + /// 暴风斩 + /// + StormsPath = 42, + + /// + /// 暴风碎 红斧 + /// + StormsEye = 45, + + /// + /// 飞斧 + /// + Tomahawk = 46, + + /// + /// 猛攻 + /// + Onslaught = 7386, + + /// + /// 动乱 + /// + Upheaval = 7387, + + /// + /// 超压斧 + /// + Overpower = 41, + + /// + /// 秘银暴风 + /// + MythrilTempest = 16462, + + /// + /// 群山隆起 + /// + Orogeny = 25752, + + /// + /// 原初之魂 + /// + InnerBeast = 49, + + /// + /// 原初的解放 + /// + InnerRelease = 7389, + + /// + /// 钢铁旋风 + /// + SteelCyclone = 51, + + /// + /// 战嚎 + /// + Infuriate = 52, + + /// + /// 狂暴 + /// + Berserk = 38, + + /// + /// 战栗 + /// + ThrillofBattle = 40, + + /// + /// 泰然自若 + /// + Equilibrium = 3552, + + /// + /// 原初的勇猛 + /// + NascentFlash = 16464, + + /// + /// 原初的血气 + /// + Bloodwhetting = 25751, + + /// + /// 复仇 + /// + Vengeance = 44, + + /// + /// 原初的直觉 + /// + RawIntuition = 3551, + + /// + /// 摆脱 + /// + ShakeItOff = 7388, + + /// + /// 死斗 + /// + Holmgang = 43, + + /// + /// 蛮荒崩裂 + /// + PrimalRend = 25753, + #endregion + + #region WHM + /// + /// 治疗 + /// + Cure = 120, + + /// + /// 医治 + /// + Medica = 124, + + /// + /// 复活 + /// + Raise1 = 125, + + /// + /// 救疗 + /// + Cure2 = 135, + + /// + /// 医济 + /// + Medica2 = 133, + + /// + /// 再生 + /// + Regen = 137, + + /// + /// 愈疗 + /// + Cure3 = 131, + + /// + /// 天赐祝福 + /// + Benediction = 140, + + /// + /// 庇护所 + /// + Asylum = 3569, + + /// + /// 安慰之心 + /// + AfflatusSolace = 16531, + + /// + /// 神名 + /// + Tetragrammaton = 3570, + + /// + /// 神祝祷 + /// + DivineBenison = 7432, + + /// + /// 狂喜之心 + /// + AfflatusRapture = 16534, + + /// + /// 水流幕 + /// + Aquaveil = 25861, + + /// + /// 礼仪之铃 + /// + LiturgyoftheBell = 25862, + + /// + /// 飞石 + /// + Stone = 119, + + /// + /// 疾风 + /// + Aero = 121, + + /// + /// 神圣 + /// + Holy = 139, + + /// + /// 法令 + /// + Assize = 3571, + + /// + /// 苦难之心 + /// + AfflatusMisery = 16535, + + /// + /// 神速咏唱 + /// + PresenseOfMind = 136, + + /// + /// 无中生有 + /// + ThinAir = 7430, + + /// + /// 全大赦 + /// + PlenaryIndulgence = 7433, + + /// + /// 节制 + /// + Temperance = 16536, + #endregion + + #region General + /// + /// 昏乱 + /// + Addle = 7560, + + /// + /// 即刻咏唱 + /// + Swiftcast = 7561, + + /// + /// 康复 + /// + Esuna = 7568, + + /// + /// 营救 + /// + Rescue = 7571, + + /// + /// 沉静 + /// + Repose = 16560, + + /// + /// 醒梦 + /// + LucidDreaming = 7562, + + /// + /// 内丹 + /// + SecondWind = 7541, + + /// + /// 亲疏自行 + /// + ArmsLength = 7548, + + /// + /// 铁壁 + /// + Rampart = 7531, + + /// + /// 挑衅 + /// + Provoke = 7533, + + /// + /// 雪仇 + /// + Reprisal = 7535, + + /// + /// 退避 + /// + Shirk = 7537, + + /// + /// 浴血 + /// + Bloodbath = 7542, + + /// + /// 牵制 + /// + Feint = 7549, + + /// + /// 插言 + /// + Interject = 7538, + + /// + /// 下踢 + /// + LowBlow = 7540, + + /// + /// 7863 + /// + LegSweep = 7863, + + /// + /// 伤头 + /// + HeadGraze = 7551, + + /// + /// 沉稳咏唱 + /// + Surecast = 7559, + + /// + /// 真北 + /// + TrueNorth = 7546, + + /// + /// 速行 + /// + Peloton = 7557, + #endregion +} diff --git a/RotationSolver.Old/Data/ChatPayload.cs b/RotationSolver.Old/Data/ChatPayload.cs new file mode 100644 index 000000000..83ba0b3cf --- /dev/null +++ b/RotationSolver.Old/Data/ChatPayload.cs @@ -0,0 +1,39 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace RotationSolver.Data; + +[StructLayout(LayoutKind.Explicit)] +public readonly struct ChatPayload : IDisposable +{ + [FieldOffset(0)] + private readonly IntPtr textPointer; + + [FieldOffset(16)] + private readonly ulong textLength; + + [FieldOffset(8)] + private readonly ulong unk1; + + [FieldOffset(24)] + private readonly ulong unk2; + + internal ChatPayload(string text) + { + byte[] stringBytes = Encoding.UTF8.GetBytes(text); + textPointer = Marshal.AllocHGlobal(stringBytes.Length + 30); + Marshal.Copy(stringBytes, 0, textPointer, stringBytes.Length); + Marshal.WriteByte(textPointer + stringBytes.Length, 0); + + textLength = (ulong)(stringBytes.Length + 1); + + unk1 = 64; + unk2 = 0; + } + + public void Dispose() + { + Marshal.FreeHGlobal(textPointer); + } +} diff --git a/RotationSolver.Old/Data/ClassJobIDs.cs b/RotationSolver.Old/Data/ClassJobIDs.cs new file mode 100644 index 000000000..51c3516d1 --- /dev/null +++ b/RotationSolver.Old/Data/ClassJobIDs.cs @@ -0,0 +1,194 @@ +namespace RotationSolver.Data; + +public enum ClassJobID : uint +{ + /// + /// 冒险者 + /// + Adventurer = 0, + + /// + /// 剑术师 + /// + Gladiator = 1, + + /// + /// 格斗家 + /// + Pugilist = 2, + + /// + /// 斧术师 + /// + Marauder = 3, + + /// + /// 枪术师 + /// + Lancer = 4, + + /// + /// 弓箭手 + /// + Archer = 5, + + /// + /// 幻术师 + /// + Conjurer = 6, + + /// + /// 咒术师 + /// + Thaumaturge = 7, + + /// + /// 刻木匠 + /// + Carpenter = 8, + + /// + /// 锻铁匠 + /// + Blacksmith = 9, + + /// + /// 铸甲匠 + /// + Armorer = 10, + + /// + /// 雕金匠 + /// + Goldsmith = 11, + + /// + /// 制革匠 + /// + Leatherworker = 12, + + /// + /// 裁衣匠 + /// + Weaver = 13, + + /// + /// 炼金术士 + /// + Alchemist = 14, + + /// + /// 烹调师 + /// + Culinarian = 15, + + /// + /// 骑士 + /// + Paladin = 19, + + /// + /// 武僧 + /// + Monk = 20, + + /// + /// 战士 + /// + Warrior = 21, + + /// + /// 龙骑士 + /// + Dragoon = 22, + + /// + /// 吟游诗人 + /// + Bard = 23, + + /// + /// 白魔法师 + /// + WhiteMage = 24, + + /// + /// 黑魔法师 + /// + BlackMage = 25, + + /// + /// 秘术师 + /// + Arcanist = 26, + + /// + /// 召唤师 + /// + Summoner = 27, + + /// + /// 学者 + /// + Scholar = 28, + + /// + /// 双剑师 + /// + Rogue = 29, + + /// + /// 忍者 + /// + Ninja = 30, + + /// + /// 机工士 + /// + Machinist = 31, + + /// + /// 黑暗骑士 + /// + DarkKnight = 32, + + /// + /// 占星术士 + /// + Astrologian = 33, + + /// + /// 武士 + /// + Samurai = 34, + + /// + /// 赤魔法师 + /// + RedMage = 35, + + /// + /// 青魔法师 + /// + BlueMage = 36, + + /// + /// 绝枪战士 + /// + Gunbreaker = 37, + + /// + /// 舞者 + /// + Dancer = 38, + + /// + /// 钐镰客 + /// + Reaper = 39, + + /// + /// 贤者 + /// + Sage = 40, +} diff --git a/RotationSolver/Data/DescType.cs b/RotationSolver.Old/Data/DescType.cs similarity index 100% rename from RotationSolver/Data/DescType.cs rename to RotationSolver.Old/Data/DescType.cs diff --git a/RotationSolver.Old/Data/EnemyPositional.cs b/RotationSolver.Old/Data/EnemyPositional.cs new file mode 100644 index 000000000..404c698bd --- /dev/null +++ b/RotationSolver.Old/Data/EnemyPositional.cs @@ -0,0 +1,9 @@ +namespace RotationSolver.Data; + +public enum EnemyPositional : byte +{ + None, + Rear, + Flank, + Front, +} diff --git a/RotationSolver/Data/IconSet.cs b/RotationSolver.Old/Data/IconSet.cs similarity index 100% rename from RotationSolver/Data/IconSet.cs rename to RotationSolver.Old/Data/IconSet.cs diff --git a/RotationSolver/Data/JobRole.cs b/RotationSolver.Old/Data/JobRole.cs similarity index 100% rename from RotationSolver/Data/JobRole.cs rename to RotationSolver.Old/Data/JobRole.cs diff --git a/RotationSolver/Data/MacroItem.cs b/RotationSolver.Old/Data/MacroItem.cs similarity index 100% rename from RotationSolver/Data/MacroItem.cs rename to RotationSolver.Old/Data/MacroItem.cs diff --git a/RotationSolver/Data/ObjectListDelay.cs b/RotationSolver.Old/Data/ObjectListDelay.cs similarity index 100% rename from RotationSolver/Data/ObjectListDelay.cs rename to RotationSolver.Old/Data/ObjectListDelay.cs diff --git a/RotationSolver/Data/RandomDelay.cs b/RotationSolver.Old/Data/RandomDelay.cs similarity index 100% rename from RotationSolver/Data/RandomDelay.cs rename to RotationSolver.Old/Data/RandomDelay.cs diff --git a/RotationSolver.Old/Data/StatusID.cs b/RotationSolver.Old/Data/StatusID.cs new file mode 100644 index 000000000..0d96c8662 --- /dev/null +++ b/RotationSolver.Old/Data/StatusID.cs @@ -0,0 +1,1122 @@ +namespace RotationSolver.Data; + +public enum StatusID : ushort +{ + /// + /// 空值 + /// + None = 0, + + /// + /// 雷1 + /// + Thunder = 161, + + /// + /// 雷2 + /// + Thunder2 = 162, + + /// + /// 雷3 + /// + Thunder3 = 163, + + /// + /// 雷4 + /// + Thunder4 = 1210, + + /// + /// 昏乱 + /// + Addle = 1203, + + + /// + /// 昏乱 + /// + Feint = 1195, + + #region WHM + /// + /// 医济 + /// + Medica2 = 150, + + /// + /// 医济 + /// + TrueMedica2 = 2792, + + /// + /// 再生 + /// + Regen1 = 158, + + /// + /// 再生 + /// + Regen2 = 897, + + /// + /// 再生 + /// + Regen3 = 1330, + + /// + /// 神速咏唱 + /// + PresenceOfMind = 157, + + /// + /// 无中生有 + /// + ThinAir = 1217, + + /// + /// 神祝祷 + /// + DivineBenison = 1218, + + /// + /// 疾风 + /// + Aero = 143, + + /// + /// 烈风 + /// + Aero2 = 144, + + /// + /// 天辉 + /// + Dia = 1871, + #endregion + + /// + /// 即刻咏唱 + /// + Swiftcast = 167, + + /// + /// 连续咏唱 + /// + Dualcast = 1249, + + /// + /// 三连咏唱 + /// + Triplecast = 1211, + + /// + /// 激情咏唱 + /// + Sharpcast = 867, + + /// + /// 雷云 + /// + Thundercloud = 164, + + /// + /// 核爆效果提高 + /// + EnhancedFlare = 2960, + + /// + /// 黑魔纹 + /// + LeyLines = 737, + + /// + /// 火苗 + /// + Firestarter = 165, + + /// + /// 复活 + /// + Raise = 148, + + /// + /// 止步 + /// + Bind1 = 13, + Bind2 = 1345, + + /// + /// 赤火炎预备 + /// + VerfireReady = 1234, + + /// + /// 赤飞石预备 + /// + VerstoneReady = 1235, + + /// + /// 促进 + /// + Acceleration = 1238, + + /// + /// 死亡宣告,可以被解除的! + /// + Doom = 910, + + /// + /// 神圣领域 + /// + HallowedGround = 82, + + /// + /// 死斗 + /// + Holmgang = 409, + + /// + /// 行尸走肉 + /// + WillDead = 810, + + /// + /// 死而不僵 + /// + WalkingDead = 811, + + /// + /// 超火流行 + /// + Superbolide = 1836, + + /// + /// 直线射击预备 + /// + StraightShotReady = 122, + + /// + /// 毒咬箭Dot + /// + VenomousBite = 124, + + /// + /// 风蚀箭Dot + /// + Windbite = 129, + + /// + /// 毒咬箭 Plus Dot + /// + CausticBite = 1200, + + /// + /// 风蚀箭 Plus Dot + /// + Stormbite = 1201, + + /// + /// 影噬箭准备 + /// + ShadowbiteReady = 3002, + + /// + /// 爆破箭准备 + /// + BlastArrowReady = 2692, + + /// + /// 猛者强击 + /// + RagingStrikes = 125, + + /// + /// 战斗之声 + /// + BattleVoice = 141, + + /// + /// 光明神的最终乐章 + /// + RadiantFinale = 2722, + + /// + /// 军神的契约 + /// + ArmyEthos = 1933, + + /// + /// 铁壁 + /// + Rampart = 1191, + + /// + /// 复仇 + /// + Vengeance = 89, + + /// + /// 守护 盾姿 + /// + Defiance = 91, + + /// + /// 原初的直觉 + /// + RawIntuition = 735, + + /// + /// 原初的血气 + /// + Bloodwhetting = 2678, + + /// + /// 原初的解放 + /// + InnerRelease = 1177, + + /// + /// 原初的混沌 + /// + NascentChaos = 1897, + + /// + /// 战场风暴 + /// + SurgingTempest = 2677, + + /// + /// 蛮荒崩裂预备 + /// + PrimalRendReady = 2624, + + /// + /// 烧灼Dot + /// + Combust = 838, + Combust2 = 843, + Combust3 = 1881, + Combust4 = 2041, + + /// + /// 吉星相位 + /// + AspectedBenefic = 835, + + /// + /// 阳星相位 + /// + AspectedHelios = 836, + + /// + /// 天星交错 + /// + Intersection = 1889, + + /// + /// 天宫图 + /// + Horoscope = 1890, + + /// + /// 阳星天宫图 + /// + HoroscopeHelios = 1891, + + /// + /// 光速 + /// + LightSpeed = 841, + + /// + /// 可以重抽 + /// + ClarifyingDraw = 2713, + + /// + /// 六个Buff + /// + TheBalance = 1882, + TheBole = 1883, + TheArrow = 1884, + TheSpear = 1885, + TheEwer = 1886, + TheSpire = 1887, + + /// + /// 地星主宰 + /// + EarthlyDominance = 1224, + + /// + /// 巨星主宰 + /// + GiantDominance = 1248, + + /// + /// 行吟 + /// + Troubadour = 1934, + + /// + /// 策动 + /// + Tactician1 = 1951, + + /// + /// 策动 + /// + Tactician2 = 2177, + + /// + /// 防守之桑巴 + /// + ShieldSamba = 1826, + + /// + /// 死亡烙印 + /// + DeathsDesign = 2586, + + //妖异之镰 + SoulReaver = 2587, + + /// + /// 绞决效果提高 + /// + EnhancedGibbet = 2588, + EnhancedGallows = 2589, + EnhancedVoidReaping = 2590, + EnhancedCrossReaping = 2591, + + /// + /// 夜游魂 + /// + Enshrouded = 2593, + + /// + /// 祭祀环 + /// + CircleofSacrifice = 2972, + + /// + /// 死亡祭品 + /// + ImmortalSacrifice = 2592, + + /// + /// 死亡祭祀 + /// + BloodsownCircle = 2972, + + /// + /// 神秘环 + /// + ArcaneCircle = 2599, + + /// + /// 播魂种 + /// + Soulsow = 2594, + + /// + /// 回退准备 + /// + Threshold = 2595, + + /// + /// 勾刃效果提高 + /// + EnhancedHarpe = 2845, + + /// + /// 龙牙龙爪预备状态 + /// + SharperFangandClaw = 802, + + /// + /// 龙尾大回旋预备状态 + /// + EnhancedWheelingThrust = 803, + + /// + /// 龙枪状态 + /// + PowerSurge = 2720, + + /// + /// 龙剑状态 + /// + LifeSurge = 2175, + + /// + /// 猛枪 + /// + LanceCharge = 1864, + + /// + /// 幻象冲预备状态 + /// + DiveReady = 1243, + + /// + /// 巨龙右眼 + /// + RightEye = 1910, + + #region MNK + /// + /// 魔猿形 + /// + OpoOpoForm = 107, + + /// + /// 盗龙形 + /// + RaptorForm = 108, + + /// + /// 猛豹形 + /// + CoerlForm = 109, + + /// + /// 连击效果提高 + /// + LeadenFist = 1861, + + /// + /// 功力 + /// + DisciplinedFist = 3001, + + /// + /// 破碎拳 + /// + Demolish = 246, + + /// + /// 震脚 + /// + PerfectBalance = 110, + + /// + /// 无相身形 + /// + FormlessFist = 2513, + + /// + /// 红莲极意 + /// + RiddleofFire = 1181, + + /// + /// 义结金兰:攻击 + /// + Brotherhood = 1185, + #endregion + + /// + /// 对称投掷 + /// + SilkenSymmetry = 2693, + SilkenSymmetry2 = 3017, + + /// + /// 非对称投掷 + /// + SilkenFlow = 2694, + SilkenFlow2 = 3018, + + /// + /// 扇舞·急 + /// + ThreefoldFanDance = 1820, + + /// + /// 扇舞·终 + /// + FourfoldFanDance = 2699, + + /// + /// 流星舞预备 + /// + FlourishingStarfall = 2700, + + /// + /// 标准舞步 + /// + StandardStep = 1818, + + /// + /// 标准舞步结束 + /// + StandardFinish = 1821, + + /// + /// 技巧舞步结束 + /// + TechnicalFinish = 1822, + + /// + /// 技巧舞步 + /// + TechnicalStep = 1819, + + /// + /// 闭式舞姿 + /// + ClosedPosition1 = 1823, + + /// + /// 闭式舞姿 + /// + ClosedPosition2 = 2026, + + /// + /// 进攻之探戈 + /// + Devilment = 1825, + + /// + /// 提拉纳预备 + /// + FlourishingFinish = 2698, + + /// + /// 虚弱 + /// + Weakness = 43, + + /// + /// 濒死 + /// + BrinkofDeath = 44, + + /// + /// 心关 + /// + Kardia = 2604, + + /// + /// 关心 + /// + Kardion = 2605, + + /// + /// 均衡注药1 + /// + EukrasianDosis = 2614, + + /// + /// 均衡注药2 + /// + EukrasianDosis2 = 2615, + + /// + /// 均衡注药3 + /// + EukrasianDosis3 = 2616, + + /// + /// 均衡诊断 + /// + EukrasianDiagnosis = 2607, + + /// + /// 均衡诊断 + /// + EukrasianPrognosis = 2609, + + /// + /// 坚角清汁 + /// + Kerachole = 2618, + + /// + /// 钢铁信念 盾姿 + /// + IronWill = 79, + + /// + /// 预警 + /// + Sentinel = 74, + + /// + /// 沥血剑 + /// + GoringBlade = 725, + + /// + /// 英勇之剑 + /// + BladeofValor = 2721, + + DivineMight = 2673, + + /// + /// 赎罪剑 + /// + SwordOath = 1902, + + /// + /// 安魂祈祷 + /// + Requiescat = 1368, + + /// + /// 战逃反应 + /// + FightOrFlight = 76, + + /// + /// 深恶痛绝 + /// + Grit = 743, + + /// + /// 王室亲卫 + /// + RoyalGuard = 1833, + + /// + /// 毁绝预备 + /// + FurtherRuin = 2701, + + /// + /// 灼热之光 + /// + SearingLight = 2703, + + /// + /// 深红旋风预备 + /// + IfritsFavor = 2724, + + /// + /// 螺旋气流预备 + /// + GarudasFavor = 2725, + + /// + /// 山崩预备 + /// + TitansFavor = 2853, + + #region SCH + /// + /// 鼓舞 + /// + Galvanize = 297, + + /// + /// 转化 + /// + Dissipation = 791, + + /// + /// 秘策 + /// + Recitation = 1896, + + /// + /// 毒菌1 + /// + Bio = 179, + + /// + /// 毒菌2 + /// + Bio2 = 189, + + /// + /// 毒菌3 + /// + Biolysis = 1895, + + /// + /// 连环计 + /// + ChainStratagem = 1221, + #endregion + + /// + /// 暗影墙 + /// + ShadowWall = 747, + + /// + /// 弃明投暗 + /// + DarkMind = 746, + + /// + /// 腐秽大地 + /// + SaltedEarth = 749, + + /// + /// 血乱 + /// + Delirium = 1972, + + /// + /// 嗜血 + /// + BloodWeapon = 742, + + /// + /// 残影镰鼬预备 + /// + PhantomKamaitachiReady = 2723, + + /// + /// 月影雷兽预备 + /// + RaijuReady = 2690, + + /// + /// 忍术 + /// + Ninjutsu = 496, + + /// + /// 生杀予夺 + /// + Kassatsu = 497, + + /// + /// 土遁之术 + /// + Doton = 501, + + /// + /// 水遁 + /// + Suiton = 507, + + /// + /// 隐遁 + /// + Hidden = 614, + + /// + /// 天地人 + /// + TenChiJin = 1186, + + /// + /// 极光 + /// + Aurora = 1835, + + /// + /// 伪装 + /// + Camouflage = 1832, + + /// + /// 星云 + /// + Nebula = 1834, + + /// + /// 石之心 + /// + HeartofStone = 1840, + + /// + /// 无情 + /// + NoMercy = 1831, + + /// + /// 撕喉预备 + /// + ReadyToRip = 1842, + + /// + /// 裂膛预备 + /// + ReadyToTear = 1843, + + /// + /// 穿目预备 + /// + ReadyToGouge = 1844, + + /// + /// 超高速预备 + /// + ReadyToBlast = 2686, + + #region SAM + /// + /// 彼岸花 + /// + Higanbana = 1228, + + /// + /// 明镜止水 + /// + MeikyoShisui = 1233, + + /// + /// 燕飞效果提高 + /// + Enhanced_Enpi = 1236, + + /// + /// 风月 + /// + Fugetsu = 1298, + + /// + /// 风花 + /// + Fuka = 1299, + + /// + /// 奥义斩浪预备 + /// + OgiNamikiriReady = 2959, + #endregion + + /// + /// 不能使用能力技 + /// + Amnesia = 5, + + //不能干活 + Stun = 2, + Stun2 = 1343, + Sleep = 3, + Sleep2 = 926, + Sleep3 = 1348, + Pacification = 6, + Pacification2 = 620, + Silence = 7, + Silence2 = 1347, + + /// + /// m慢 + /// + Slow = 9, + Slow2 = 10, + Slow3 = 193, + Slow4 = 561, + Slow5 = 1346, + + + /// + /// 打不中! + /// + Blind = 15, + Blind2 = 564, + Blind3 = 1345, + + /// + /// 麻痹 + /// + Paralysis = 17, + Paralysis2 = 482, + + /// + /// 噩梦 + /// + Nightmare = 423, + + /// + /// 耐心 + /// + Patience = 850, + + /// + /// 战斗连祷 + /// + BattleLitany = 786, + + /// + /// 真理之剑预备状态 + /// + ReadyForBladeofFaith = 3019, + + /// + /// 盾阵 + /// + Sheltron = 728, + + /// + /// 圣盾阵 + /// + HolySheltron = 2674, + + /// + /// 野火 + /// + Wildfire = 1946, + + /// + /// 整备 + /// + Reassemble = 851, + + /// + /// 真北 + /// + TrueNorth = 1250, + + /// + /// 金刚极意 + /// + RiddleofEarth = 1179, + + /// + /// 醒梦 + /// + LucidDreaming = 1204, + + /// + /// 速行 + /// + Peloton = 1199, + + /// + + /// 即兴表演 + /// + Improvisation = 1827, + + /// + /// 享受即兴表演 + /// + _Improvisation = 2695, + + + /// + /// 即兴表演结束 + /// + Improvised_Finish = 2697, + + /// + /// 舞动的热情 + /// + Rising_Rhythm = 2696, + + + /// + /// 魔元化 + /// + Manafication = 1971, + + + /// + /// 鼓励 + /// + Embolden = 2282, + + /// 以太复制:防护 + /// + AethericMimicryTank = 2124, + + /// + /// 以太复制:进攻 + /// + AethericMimicryDPS = 2125, + + /// + /// 以太复制:治疗 + /// + AethericMimicryHealer = 2126, + + /// + /// 狂战士化 + /// + WaxingNocturne = 1718, + + /// + /// 狂战士化的副作用 + /// + WaningNocturne = 1727, + + /// + /// 意志薄弱 + /// + BrushwithDeath = 2127, + + /// + /// 蓄力 + /// + Boost = 1716, + + /// + /// 攻击准备 + /// + Harmonized = 2118, + + /// + /// 斗争本能 + /// + BasicInstinct = 2498, + + /// + /// 哔哩哔哩 + /// + Tingling = 2492, + + /// + /// 鬼宿脚 + /// + PhantomFlurry = 2502, + + /// + /// 穿甲散弹强化 + /// + SurpanakhaFury = 2130, + + /// + /// 出血 + /// + Bleeding = 1714, + + /// + /// 冻结 + /// + DeepFreeze = 1731, + + /// + /// 冰雾 + /// + TouchofFrost = 2994, + + /// + /// 玄天武水壁 + /// + AuspiciousTrance = 2497, + + /// + /// 致死腐烂毒素 + /// + Necrosis = 2965, + + MightyGuard = 1719, + + IceSpikes = 1307, + + RespellingSpray = 556, + Magitek = 2166, + + StoneSkin = 151, + IceSpikesInvincible = 198, + + CircleOfPower = 738, + + Aetherpact = 1223, + + ConfiteorReady = 3019, + + Bulwark = 77, +} diff --git a/RotationSolver/Data/TargetHostileType.cs b/RotationSolver.Old/Data/TargetHostileType.cs similarity index 100% rename from RotationSolver/Data/TargetHostileType.cs rename to RotationSolver.Old/Data/TargetHostileType.cs diff --git a/RotationSolver.Old/Data/TargetType.cs b/RotationSolver.Old/Data/TargetType.cs new file mode 100644 index 000000000..a6936ae19 --- /dev/null +++ b/RotationSolver.Old/Data/TargetType.cs @@ -0,0 +1,11 @@ +namespace RotationSolver.Data; + +public enum TargetingType +{ + Big, + Small, + HighHP, + LowHP, + HighMaxHP, + LowMaxHP, +} diff --git a/RotationSolver/Helpers/ActionHelper.cs b/RotationSolver.Old/Helpers/ActionHelper.cs similarity index 100% rename from RotationSolver/Helpers/ActionHelper.cs rename to RotationSolver.Old/Helpers/ActionHelper.cs diff --git a/RotationSolver/Helpers/ConditionHelper.cs b/RotationSolver.Old/Helpers/ConditionHelper.cs similarity index 100% rename from RotationSolver/Helpers/ConditionHelper.cs rename to RotationSolver.Old/Helpers/ConditionHelper.cs diff --git a/RotationSolver/Helpers/ConfigurationHelper.cs b/RotationSolver.Old/Helpers/ConfigurationHelper.cs similarity index 100% rename from RotationSolver/Helpers/ConfigurationHelper.cs rename to RotationSolver.Old/Helpers/ConfigurationHelper.cs diff --git a/RotationSolver/Helpers/CooldownHelper.cs b/RotationSolver.Old/Helpers/CooldownHelper.cs similarity index 100% rename from RotationSolver/Helpers/CooldownHelper.cs rename to RotationSolver.Old/Helpers/CooldownHelper.cs diff --git a/RotationSolver/Helpers/IActionHelper.cs b/RotationSolver.Old/Helpers/IActionHelper.cs similarity index 100% rename from RotationSolver/Helpers/IActionHelper.cs rename to RotationSolver.Old/Helpers/IActionHelper.cs diff --git a/RotationSolver/Helpers/ImguiHelper.cs b/RotationSolver.Old/Helpers/ImGuiHelper.cs similarity index 100% rename from RotationSolver/Helpers/ImguiHelper.cs rename to RotationSolver.Old/Helpers/ImGuiHelper.cs diff --git a/RotationSolver/Helpers/MarkingHelper.cs b/RotationSolver.Old/Helpers/MarkingHelper.cs similarity index 100% rename from RotationSolver/Helpers/MarkingHelper.cs rename to RotationSolver.Old/Helpers/MarkingHelper.cs diff --git a/RotationSolver/Helpers/ObjectHelper.cs b/RotationSolver.Old/Helpers/ObjectHelper.cs similarity index 100% rename from RotationSolver/Helpers/ObjectHelper.cs rename to RotationSolver.Old/Helpers/ObjectHelper.cs diff --git a/RotationSolver/Helpers/ReflectionHelper.cs b/RotationSolver.Old/Helpers/ReflectionHelper.cs similarity index 100% rename from RotationSolver/Helpers/ReflectionHelper.cs rename to RotationSolver.Old/Helpers/ReflectionHelper.cs diff --git a/RotationSolver/Helpers/StatusHelper.cs b/RotationSolver.Old/Helpers/StatusHelper.cs similarity index 100% rename from RotationSolver/Helpers/StatusHelper.cs rename to RotationSolver.Old/Helpers/StatusHelper.cs diff --git a/RotationSolver/Helpers/TargetFilter.cs b/RotationSolver.Old/Helpers/TargetFilter.cs similarity index 100% rename from RotationSolver/Helpers/TargetFilter.cs rename to RotationSolver.Old/Helpers/TargetFilter.cs diff --git a/RotationSolver.Old/ITexture.cs b/RotationSolver.Old/ITexture.cs new file mode 100644 index 000000000..95a25db7c --- /dev/null +++ b/RotationSolver.Old/ITexture.cs @@ -0,0 +1,11 @@ +namespace RotationSolver; + +public interface ITexture +{ + uint IconID { get; } + string Name { get; } + + string Description { get; } + + bool IsEnabled { get; set; } +} diff --git a/RotationSolver/Interfaces.cd b/RotationSolver.Old/Interfaces.cd similarity index 100% rename from RotationSolver/Interfaces.cd rename to RotationSolver.Old/Interfaces.cd diff --git a/RotationSolver.Old/Localization/EnumTranslations.cs b/RotationSolver.Old/Localization/EnumTranslations.cs new file mode 100644 index 000000000..973999a06 --- /dev/null +++ b/RotationSolver.Old/Localization/EnumTranslations.cs @@ -0,0 +1,180 @@ +using Dalamud.Game.ClientState.Keys; +using RotationSolver.Commands; +using RotationSolver.Data; +using RotationSolver.Rotations.Basic; +using RotationSolver.Rotations.CustomRotation; +using RotationSolver.Timeline; + +namespace RotationSolver.Localization; + +internal static class EnumTranslations +{ + internal static string ToName(this TargetConditionType type) => type switch + { + TargetConditionType.HaveStatus => LocalizationManager.RightLang.TargetConditionType_HaveStatus, + TargetConditionType.IsDying => LocalizationManager.RightLang.TargetConditionType_IsDying, + TargetConditionType.IsBoss => LocalizationManager.RightLang.TargetConditionType_IsBoss, + TargetConditionType.Distance => LocalizationManager.RightLang.TargetConditionType_Distance, + TargetConditionType.StatusEnd => LocalizationManager.RightLang.TargetConditionType_StatusEnd, + TargetConditionType.StatusEndGCD => LocalizationManager.RightLang.TargetConditionType_StatusEndGCD, + TargetConditionType.CastingAction => LocalizationManager.RightLang.TargetConditionType_CastingAction, + _ => string.Empty, + }; + + internal static string ToName(this ComboConditionType type) => type switch + { + ComboConditionType.Bool => LocalizationManager.RightLang.ComboConditionType_Bool, + ComboConditionType.Byte => LocalizationManager.RightLang.ComboConditionType_Byte, + ComboConditionType.Time => LocalizationManager.RightLang.ComboConditionType_Time, + ComboConditionType.TimeGCD => LocalizationManager.RightLang.ComboConditionType_GCD, + ComboConditionType.Last => LocalizationManager.RightLang.ComboConditionType_Last, + _ => string.Empty, + }; + + internal static string ToName(this ActionConditonType type) => type switch + { + ActionConditonType.Elapsed => LocalizationManager.RightLang.ActionConditionType_Elapsed, + ActionConditonType.ElapsedGCD => LocalizationManager.RightLang.ActionConditionType_ElapsedGCD, + ActionConditonType.Remain => LocalizationManager.RightLang.ActionConditionType_Remain, + ActionConditonType.RemainGCD => LocalizationManager.RightLang.ActionConditionType_RemainGCD, + ActionConditonType.ShouldUse => LocalizationManager.RightLang.ActionConditionType_ShouldUse, + ActionConditonType.EnoughLevel => LocalizationManager.RightLang.ActionConditionType_EnoughLevel, + ActionConditonType.IsCoolDown => LocalizationManager.RightLang.ActionConditionType_IsCoolDown, + ActionConditonType.CurrentCharges => LocalizationManager.RightLang.ActionConditionType_CurrentCharges, + ActionConditonType.MaxCharges => LocalizationManager.RightLang.ActionConditionType_MaxCharges, + _ => string.Empty, + }; + + public static string ToName(this VirtualKey k) => k switch + { + VirtualKey.SHIFT => "SHIFT", + VirtualKey.CONTROL => "CTRL", + VirtualKey.MENU => "ALT", + _ => k.ToString(), + }; + + public static string ToName(this EnemyPositional value) => value switch + { + EnemyPositional.None => LocalizationManager.RightLang.EnemyLocation_None, + EnemyPositional.Rear => LocalizationManager.RightLang.EnemyLocation_Rear, + EnemyPositional.Flank => LocalizationManager.RightLang.EnemyLocation_Flank, + EnemyPositional.Front => LocalizationManager.RightLang.EnemyLocation_Front, + _ => string.Empty, + }; + + public static string ToName(this DescType type) => type switch + { + DescType.BurstActions => LocalizationManager.RightLang.DescType_BurstActions, + + DescType.MoveForwardGCD => LocalizationManager.RightLang.DescType_MoveForwardGCD, + DescType.HealSingleGCD => LocalizationManager.RightLang.DescType_HealSingleGCD, + DescType.HealAreaGCD => LocalizationManager.RightLang.DescType_HealAreaGCD, + DescType.DefenseSingleGCD => LocalizationManager.RightLang.DescType_DefenseSingleGCD, + DescType.DefenseAreaGCD => LocalizationManager.RightLang.DescType_DefenseAreaGCD, + + DescType.MoveForwardAbility => LocalizationManager.RightLang.DescType_MoveForwardAbility, + DescType.MoveBackAbility => LocalizationManager.RightLang.DescType_MoveBackAbility, + DescType.HealSingleAbility => LocalizationManager.RightLang.DescType_HealSingleAbility, + DescType.HealAreaAbility => LocalizationManager.RightLang.DescType_HealAreaAbility, + DescType.DefenceSingleAbility => LocalizationManager.RightLang.DescType_DefenseSingleAbility, + DescType.DefenceAreaAbility => LocalizationManager.RightLang.DescType_DefenseAreaAbility, + + _ => string.Empty, + }; + + public static string ToName(this JobRole role) => role switch + { + JobRole.None => LocalizationManager.RightLang.JobRole_None, + JobRole.Tank => LocalizationManager.RightLang.JobRole_Tank, + JobRole.Melee => LocalizationManager.RightLang.JobRole_Melee, + JobRole.Ranged => LocalizationManager.RightLang.JobRole_Ranged, + JobRole.Healer => LocalizationManager.RightLang.JobRole_Healer, + JobRole.RangedPhysical => LocalizationManager.RightLang.JobRole_RangedPhysical, + JobRole.RangedMagicial => LocalizationManager.RightLang.JobRole_RangedMagicial, + JobRole.DiscipleoftheLand => LocalizationManager.RightLang.JobRole_DiscipleoftheLand, + JobRole.DiscipleoftheHand => LocalizationManager.RightLang.JobRole_DiscipleoftheHand, + _ => string.Empty, + }; + + public static string ToName(this TargetingType role) => role switch + { + TargetingType.Big => LocalizationManager.RightLang.TargetingType_Big, + TargetingType.Small => LocalizationManager.RightLang.TargetingType_Small, + TargetingType.HighHP => LocalizationManager.RightLang.TargetingType_HighHP, + TargetingType.LowHP => LocalizationManager.RightLang.TargetingType_LowHP, + TargetingType.HighMaxHP => LocalizationManager.RightLang.TargetingType_HighMaxHP, + TargetingType.LowMaxHP => LocalizationManager.RightLang.TargetingType_LowMaxHP, + _ => string.Empty, + }; + + internal static string ToSayout(this SpecialCommandType type, JobRole role) => type switch + { + SpecialCommandType.EndSpecial => type.ToSpecialString(role), + _ => LocalizationManager.RightLang.SpecialCommandType_Start + type.ToSpecialString(role), + }; + + internal static string ToSayout(this StateCommandType type, JobRole role) => type switch + { + StateCommandType.Cancel => LocalizationManager.RightLang.SpecialCommandType_Cancel, + _ => type.ToStateString(role), + }; + + internal static string ToSpecialString(this SpecialCommandType type, JobRole role) => type switch + { + SpecialCommandType.HealArea => LocalizationManager.RightLang.SpecialCommandType_HealArea, + SpecialCommandType.HealSingle => LocalizationManager.RightLang.SpecialCommandType_HealSingle, + SpecialCommandType.DefenseArea => LocalizationManager.RightLang.SpecialCommandType_DefenseArea, + SpecialCommandType.DefenseSingle => LocalizationManager.RightLang.SpecialCommandType_DefenseSingle, + SpecialCommandType.EsunaStanceNorth => role switch + { + JobRole.Tank => LocalizationManager.RightLang.SpecialCommandType_TankStance, + JobRole.Healer => CustomRotation.Esuna.Name, + JobRole.Melee => CustomRotation.TrueNorth.Name, + _ => nameof(SpecialCommandType.EsunaStanceNorth), + }, + SpecialCommandType.RaiseShirk => role switch + { + JobRole.Tank => CustomRotation.Shirk.Name, + JobRole.Healer => WHM_Base.Raise1.Name, + _ => nameof(SpecialCommandType.RaiseShirk), + }, + SpecialCommandType.MoveForward => LocalizationManager.RightLang.SpecialCommandType_MoveForward, + SpecialCommandType.MoveBack => LocalizationManager.RightLang.SpecialCommandType_MoveBack, + SpecialCommandType.AntiKnockback => LocalizationManager.RightLang.SpecialCommandType_AntiKnockback, + SpecialCommandType.Burst => LocalizationManager.RightLang.SpecialCommandType_Burst, + SpecialCommandType.EndSpecial => LocalizationManager.RightLang.SpecialCommandType_EndSpecial, + _ => string.Empty, + }; + + internal static string ToStateString(this StateCommandType type, JobRole role) => type switch + { + StateCommandType.Smart => LocalizationManager.RightLang.SpecialCommandType_Smart + RSCommands.TargetingType.ToName(), + StateCommandType.Manual => LocalizationManager.RightLang.SpecialCommandType_Manual, + StateCommandType.Cancel => LocalizationManager.RightLang.SpecialCommandType_Off, + _ => string.Empty, + }; + + internal static string ToHelp(this SpecialCommandType type) => type switch + { + SpecialCommandType.HealArea => LocalizationManager.RightLang.Configwindow_HelpItem_HealArea, + SpecialCommandType.HealSingle => LocalizationManager.RightLang.Configwindow_HelpItem_HealSingle, + SpecialCommandType.DefenseArea => LocalizationManager.RightLang.Configwindow_HelpItem_DefenseArea, + SpecialCommandType.DefenseSingle => LocalizationManager.RightLang.Configwindow_HelpItem_DefenseSingle, + SpecialCommandType.EsunaStanceNorth => LocalizationManager.RightLang.Configwindow_HelpItem_Esuna, + SpecialCommandType.RaiseShirk => LocalizationManager.RightLang.Configwindow_HelpItem_RaiseShirk, + SpecialCommandType.MoveForward => LocalizationManager.RightLang.Configwindow_HelpItem_MoveForward, + SpecialCommandType.MoveBack => LocalizationManager.RightLang.Configwindow_HelpItem_MoveBack, + SpecialCommandType.AntiKnockback => LocalizationManager.RightLang.Configwindow_HelpItem_AntiKnockback, + SpecialCommandType.Burst => LocalizationManager.RightLang.Configwindow_HelpItem_Burst, + SpecialCommandType.EndSpecial => LocalizationManager.RightLang.Configwindow_HelpItem_EndSpecial, + _ => string.Empty, + }; + + internal static string ToHelp(this StateCommandType type) => type switch + { + StateCommandType.Smart => LocalizationManager.RightLang.Configwindow_HelpItem_AttackSmart, + StateCommandType.Manual => LocalizationManager.RightLang.Configwindow_HelpItem_AttackManual, + StateCommandType.Cancel => LocalizationManager.RightLang.Configwindow_HelpItem_AttackCancel, + _ => string.Empty, + }; +} diff --git a/RotationSolver.Old/Localization/Localization.json b/RotationSolver.Old/Localization/Localization.json new file mode 100644 index 000000000..16884b1b9 --- /dev/null +++ b/RotationSolver.Old/Localization/Localization.json @@ -0,0 +1,367 @@ +{ + "Commands_Rotation": "Open config window.", + "Commands_ChangeAutoBurst": "Modify automatic burst to {0}", + "Commands_ChangeRotationConfig": "Modify {0} to {1}", + "Commands_CannotFindRotationConfig": "Failed to find the config in this rotation, please check it.", + "Commands_InsertAction": "Will use it within {0}s", + "Commands_InsertActionFailure": "Can not find the action, please check the action name.", + "Commands_SayHelloToAuthor": "This \"{0}\" is probably one of the authors of the \"Rotation Solver\", so say hello to him!", + "ConfigWindow_Header": "Rotation Solver Settings v", + "ConfigWindow_RotationItem": "Rotation", + "ConfigWindow_ParamItem": "Param", + "ConfigWindow_EventItem": "Event", + "ConfigWindow_ActionItem": "Action", + "ConfigWindow_HelpItem": "Help", + "ConfigWindow_ActionItem_Description": "Modify the usage for each action.", + "ConfigWindow_HelpItem_Description": "In this window, you can see all Rotation Solver built-in commands for combat. ", + "Configwindow_HelpItem_AttackSmart": "Start attacking in smart mode(auto-targeting) when out of combat, otherwise switch the target according to the conditions.", + "Configwindow_HelpItem_AttackManual": "Start attacking in manual mode. You need to choose the target manually.", + "Configwindow_HelpItem_AttackCancel": "Stop attacking. Remember to turn it off when not in use!", + "Configwindow_HelpItem_HealArea": "Open a window to use AoE heal.", + "Configwindow_HelpItem_HealSingle": "Open a window to use single heal.", + "Configwindow_HelpItem_DefenseArea": "Open a window to use AoE defense.", + "Configwindow_HelpItem_DefenseSingle": "Open a window to use single defense.", + "Configwindow_HelpItem_Esuna": "Open a window to use Esuna,tank stance actions or True North.", + "Configwindow_HelpItem_RaiseShirk": "Open a window to use Raise or Shirk.", + "Configwindow_HelpItem_AntiKnockback": "Open a window to use knockback-penalty actions.", + "Configwindow_HelpItem_Burst": "Open a window to burst.", + "Configwindow_HelpItem_MoveForward": "Open a window to move forward.", + "Configwindow_HelpItem_MoveBack": "Open a window to move back.", + "Configwindow_HelpItem_EndSpecial": "Close special window.", + "Configwindow_Helper_SwitchRotation": "Click to switch authors", + "Configwindow_Helper_GameVersion": "Game Version", + "Configwindow_Helper_OpenSource": "Open the source code URL", + "Configwindow_Helper_RunCommand": "Click to execute the command", + "Configwindow_Helper_CopyCommand": "Right-click to copy command", + "Configwindow_Helper_InsertCommand": "Insert \"{0}\" first in 5s", + "Configwindow_Rotation_Description": "You can enable the function for each job you want and configure the setting about how to use actions.", + "Configwindow_Rotation_KeyName": "The key name is", + "Configwindow_Events_AddEvent": "AddEvents", + "Configwindow_Events_Description": "In this window, you can set what macro will be trigger after using an action.", + "Configwindow_Events_ActionName": "Action Name", + "Configwindow_Events_MacroIndex": "Macro No.", + "Configwindow_Events_ShareMacro": "Is Shared", + "Configwindow_Events_RemoveEvent": "Delete Event", + "Configwindow_Events_DutyStart": "Duty Start: ", + "Configwindow_Events_DutyEnd": "Duty End: ", + "Configwindow_Params_Description": "In this window, you can set the parameters about the using way of actions.", + "Configwindow_Param_NeverReplaceIcon": "Never Replace Icons", + "Configwindow_Param_NeverReplaceIconDesc": "Icon replacement: Repose is automatically displayed as the next action to be used", + "Configwindow_Param_UseOverlayWindow": "Display Top Overlay", + "Configwindow_Param_UseOverlayWindowDesc": "This top window is used to display some extra information on your game window, such as target's positional, target and sub-target, etc.", + "Configwindow_Param_Basic": "Basic", + "Configwindow_Param_ActionAhead": "Set the time advance of using actions", + "Configwindow_Param_AbilitiesInterval": "Set the interval between 0GCD using", + "Configwindow_Param_CountDownAhead": "Set the time advance of using casting actions on counting down.", + "Configwindow_Param_SpecialDuration": "Set the duration of special windows set by commands", + "Configwindow_Param_AddDotGCDCount": "Set GCD advance of DOT refresh", + "Configwindow_Param_AutoOffBetweenArea": "Turn off when player is between area.", + "Configwindow_Param_UseWorkTask": "Use work task for acceleration.", + "Configwindow_Param_Delay": "Delay", + "Configwindow_Param_WeaponDelay": "Set the range of random delay for GCD in second.", + "Configwindow_Param_DeathDelay": "Set the range of random delay for raising deaths in second.", + "Configwindow_Param_HostileDelay": "Set the range of random delay for finding hostile targets in second.", + "Configwindow_Param_InterruptDelay": "Set the range of random delay for interrupting hostile targets in second.", + "Configwindow_Param_WeakenDelay": "Set the range of random delay for esuna weakens in second.", + "Configwindow_Param_HealDelay": "Set the range of random delay for healing people in second.", + "Configwindow_Param_NotInCombatDelay": "Set the range of random delay for Not In Combat in second.", + "Configwindow_Param_StopCastingDelay": "Set the range of random delay for stoping casting when target is no need to cast in second.", + "Configwindow_Param_Display": "Display", + "Configwindow_Param_Advanced": "Advanced", + "Configwindow_Param_PoslockCasting": "Lock the movement when casting.", + "Configwindow_Param_UseStopCasting": "Use stopping casting when target is dead.", + "Configwindow_Param_ShowActionFlag": "Show action flag.", + "Configwindow_Param_ShowHealthRatio": "Show the health ratio for the check of Boss, Dying, Dot.", + "Configwindow_Param_HealthRatioBoss": "If target's max health ratio is higher than this, regard it as Boss.", + "Configwindow_Param_HealthRatioDying": "If target's current health ratio is lower than this, regard it is dying.", + "Configwindow_Param_HealthRatioDot": "If target's current health ratio is higher than this, regard it can be dot.", + "Configwindow_Param_PoslockModifier": "Set the modifier key to unlock the movement temporary", + "Configwindow_Param_PoslockDescription": "LT is for gamepad player", + "Configwindow_Param_CastingDisplay": "Enhance castbar with casting status", + "Configwindow_Param_TeachingMode": "Teaching mode", + "Configwindow_Param_TeachingModeColor": "Prompt box color of teaching mode", + "Configwindow_Param_MovingTargetColor": "Prompt box color of moving target", + "Configwindow_Param_TargetColor": "Target color", + "Configwindow_Params_SubTargetColor": "Sub-target color", + "Configwindow_Param_KeyBoardNoise": "Simulate the effect of pressing", + "Configwindow_Params_VoiceVolume": "Voice volume", + "Configwindow_Param_FlytextPositional": "Hint positional anticipation by flytext", + "Configwindow_Param_SayPositional": "Hint positional anticipation by shouting", + "Configwindow_Param_PositionalFeedback": "Positional error feedback", + "Configwindow_Param_DrawPositional": "Draw Positional on the screen", + "Configwindow_Param_DrawMeleeRange": "Draw the range of melee on the screen", + "Configwindow_Param_ShowMoveTarget": "Show the pointing target of the move skill", + "Configwindow_Param_ShowTarget": "Show Target", + "Configwindow_Param_PositionalFeedbackDesc": "Attention: Positional anticipation is experimental, just for reference only.", + "Configwindow_Param_PositionaErrorText": "Positional error prompt", + "Configwindow_Params_LocationWrongTextDesc": "How do you want to be scolded if you have a positional error ?!", + "Configwindow_Param_SayOutStateChanged": "Saying the state changes out", + "Configwindow_Param_ShowInfoOnDtr": "Display plugin state on dtrbar", + "Configwindow_Param_ShowWorkTaskFPS": "Display Task FPS on dtrbar", + "Configwindow_Param_ShowInfoOnToast": "Display plugin state on toast", + "Configwindow_Param_NamePlateIconId": "Player's name plate icon id when state is on. Recommand 61435, 61437", + "Configwindow_Param_Action": "Action", + "Configwindow_Param_UseAOEAction": "Use AOE actions", + "Configwindow_Param_UseAOEWhenManual": "Use AOE actions in manual mode", + "Configwindow_Param_AutoBurst": "Automatic burst", + "Configwindow_Param_UseAbility": "Auto-use abilities", + "Configwindow_Param_NoNewHostiles": "Don't attack new mobs by aoe", + "Configwindow_Params_NoNewHostilesDesc": "Nerver use any AOE action when this action may attack the mobs that not is a hostile target.", + "Configwindow_Param_UseDefenceAbility": "Use defence abilities", + "Configwindow_Param_UseDefenceAbilityDesc": "It is recommended to check this option if you are playing Raids./nPlan the heal and defense by yourself.???", + "Configwindow_Param_AutoShield": "Auto tank stance", + "Configwindow_Param_AutoProvokeForTank": "Auto Provoke (Tank)", + "Configwindow_Param_AutoProvokeForTankDesc": "When a hostile is hitting the non-Tank member of party, it will automatically use the Provoke.", + "Configwindow_Param_AutoUseTrueNorth": "Auto TrueNorth (Melee)", + "Configwindow_Param_RaisePlayerBySwift": "Raise player by swift", + "Configwindow_Param_UseGroundBeneficialAbility": "Use beneficaial ground-targeted actions", + "Configwindow_Param_RaisePlayerByCasting": "Raise player by casting when swift is in cooldown", + "Configwindow_Param_UseHealWhenNotAHealer": "Use heal when not-healer", + "Configwindow_Param_LessMPNoRaise": "Nerver raise player if MP is less than the set value", + "Configwindow_Param_UseItem": "Use items", + "Configwindow_Param_UseItemDesc": "Use poison, WIP", + "Configwindow_Param_Conditon": "Condition", + "Configwindow_Param_StartOnCountdown": "Turn on auto-rotation on countdown", + "Configwindow_Param_EsunaAll": "Esuna All Statuses.", + "Configwindow_Param_InterruptibleMoreCheck": "Interrupt the action with action type check.", + "Configwindow_Param_HealOutOfCombat": "Heal party members outside of combat.", + "Configwindow_Param_HealthDifference": "Set the HP standard deviation threshold for using AOE heal (ability & spell)", + "Configwindow_Param_HealthAreaAbility": "Set the HP threshold for using AOE healing ability", + "Configwindow_Param_HealthAreaSpell": "Set the HP threshold for using AOE healing spell", + "Configwindow_Param_HealingOfTimeSubtractArea": "Set the HP threshold reduce with hot effect(AOE)", + "Configwindow_Param_HealthSingleAbility": "Set the HP threshold for using single healing ability", + "Configwindow_Param_HealthSingleSpell": "Set the HP threshold for using single healing spell", + "Configwindow_Param_HealingOfTimeSubtractSingle": "Set the HP threshold reduce with hot effect(single)", + "Configwindow_Param_HealthForDyingTank": "Set the HP threshold for tank to use invincibility", + "Configwindow_Param_MeleeRangeOffset": "Melee Range action using offset", + "Configwindow_Param_Target": "Target", + "Configwindow_Param_RightNowTargetToHostileType": "Hostile target filtering condition", + "Configwindow_Param_TargetToHostileType1": "All targets can attack", + "Configwindow_Param_TargetToHostileType2": "Targets have a target or all targets can attack", + "Configwindow_Param_TargetToHostileType3": "Targets have a target", + "Configwindow_Param_NoticeUnexpectedCombat": "NOTICE: You are not turn the auto off between area on. It may start a combat unexpectedly.", + "Configwindow_Param_AddEnemyListToHostile": "Add Enemies list to the hostile target.", + "Configwindow_Param_ChooseAttackMark": "Priority attack targets with attack markers", + "Configwindow_Param_CanAttackMarkAOE": "Forced use of AOE", + "Configwindow_Param_AttackMarkAOEDesc": "Attention: Checking this option , AA will attack as many hostile targets as possible, while ignoring whether the attack will cover the marked target.", + "Configwindow_Param_FilterStopMark": "Never attack targets with stop markers", + "Configwindow_Param_ObjectMinRadius": "Set the minimum target circle threshold possessed by the attack target", + "Configwindow_Param_MoveTargetAngle": "The size of the sector angle that can be selected as the moveable target", + "Configwindow_Param_MoveTargetAngleDesc": "If the selection mode is based on character facing, i.e., targets within the character's viewpoint are movable targets. \nIf the selection mode is screen-centered, i.e., targets within a sector drawn upward from the character's point are movable targets.", + "Configwindow_Param_ChangeTargetForFate": "Select only Fate targets in Fate", + "Configwindow_Param_OnlyAttackInView": "Only attack the target in view.", + "Configwindow_Param_MoveTowardsScreen": "Using movement actions towards the object in the center of the screen", + "Configwindow_Param_MoveTowardsScreenDesc": "Using movement actions towards the object in the center of the screen, otherwise toward the facing object.", + "Configwindow_Param_RaiseAll": "Raise all (include passerby)", + "Configwindow_Param_TargetFriendly": "Target all for friendly actions(include passerby)", + "Configwindow_Param_RaiseBrinkofDeath": "Raise player even has Brink of Death", + "Configwindow_Param_MoveAreaActionFarthest": "Moving Area Ability to farthest", + "Configwindow_Param_MoveAreaActionFarthestDesc": "Move to the furthest position from character's face direction.", + "Configwindow_Param_Hostile": "Hostile", + "Configwindow_Param_HostileDesc": "You can set the logic of hostile target selection to allow flexibility in switching the logic of selecting hostile in battle.", + "Configwindow_Param_AddHostileCondition": "Add selection condition", + "Configwindow_Param_HostileCondition": "Hostile target selection condition", + "Configwindow_Param_ConditionUp": "Up", + "Configwindow_Param_ConditionDown": "Down", + "Configwindow_Param_ConditionDelete": "Delete", + "Timeline_DragdropDescription": "Drag&drop to move,Ctrl+Alt+RightClick to delete.", + "Timeline_SearchBar": "Search Bar", + "Timeline_MustUse": "MustUse", + "Timeline_MustUseDesc": "Skip AOE and Buff.", + "Timeline_Empty": "UseUp", + "Timeline_EmptyDesc": "UseUp or Skip Combo", + "Timeline_TimelineDescription": "Add some condition to automatic use this action.", + "Timeline_Can": "Can", + "Timeline_Cannot": "Cannot", + "Timeline_Is": "Is", + "Timeline_Isnot": "Isnot", + "Timeline_Have": "Have", + "Timeline_Havenot": "Havenot", + "Timeline_Ability": "Ability", + "Timeline_Charges": "Charges", + "Timeline_ConditionSet": "ConditionSet", + "Timeline_ActionCondition": "ActionCondition", + "Timeline_TargetCondition": "TargetCondition", + "Timeline_RotationCondition": "RotationCondition", + "Timeline_ActionTarget": "{0}'s target", + "Timeline_Target": "Target", + "Timeline_Player": "Player", + "Timeline_StatusSelf": "StatusSelf", + "Timeline_StatusSelfDesc": "StatusSelf", + "Action_Friendly": "Support", + "Action_Attack": "Attack", + "ComboConditionType_Bool": "Boolean", + "ComboConditionType_Byte": "Byte", + "ComboConditionType_Time": "Time", + "ComboConditionType_GCD": "GCD", + "ComboConditionType_Last": "Last", + "TargetingType_Big": "Big", + "TargetingType_Small": "Small", + "TargetingType_HighHP": "High HP", + "TargetingType_LowHP": "Low HP", + "TargetingType_HighMaxHP": "High Max HP", + "TargetingType_LowMaxHP": "Low Max HP", + "SpecialCommandType_Start": "Start ", + "SpecialCommandType_HealArea": "Heal Area", + "SpecialCommandType_HealSingle": "Heal Single", + "SpecialCommandType_DefenseArea": "Defense Area", + "SpecialCommandType_DefenseSingle": "Defense Single", + "SpecialCommandType_TankStance": "Tank Stance", + "SpecialCommandType_MoveForward": "Move Forward", + "SpecialCommandType_MoveBack": "Move Back", + "SpecialCommandType_AntiKnockback": "Anti-Knockback", + "SpecialCommandType_Burst": "Burst", + "SpecialCommandType_EndSpecial": "End Special", + "SpecialCommandType_Smart": "Smart ", + "SpecialCommandType_Manual": "Manual", + "SpecialCommandType_Cancel": "Cancel", + "SpecialCommandType_Off": "Off", + "ActionConditionType_Elapsed": "Elapsed", + "ActionConditionType_ElapsedGCD": "ElapsedGCD ", + "ActionConditionType_Remain": "RemainTime", + "ActionConditionType_RemainGCD": "RemainGCD", + "ActionConditionType_ShouldUse": "ShouldUse", + "ActionConditionType_EnoughLevel": "EnoughLevel", + "ActionConditionType_IsCoolDown": "IsCoolDown", + "ActionConditionType_CurrentCharges": "CurrentCharges", + "ActionConditionType_MaxCharges": "MaxCharges", + "TargetConditionType_HaveStatus": "Have Status", + "TargetConditionType_IsDying": "Is Dying", + "TargetConditionType_IsBoss": "Is Boss", + "TargetConditionType_Distance": "Distance", + "TargetConditionType_StatusEnd": "Status End", + "TargetConditionType_StatusEndGCD": "Status End GCD", + "TargetConditionType_CastingAction": "Casting Action", + "DescType_BurstActions": "Burst Actions", + "DescType_MoveForwardGCD": "Move Forward GCD", + "DescType_HealAreaGCD": "Area Healing GCD", + "DescType_HealSingleGCD": "Single Healing GCD", + "DescType_DefenseAreaGCD": "Area Defense GCD", + "DescType_DefenseSingleGCD": "Single Defense GCD", + "DescType_HealAreaAbility": "Area Healing Ability", + "DescType_HealSingleAbility": "Single Healing Ability", + "DescType_DefenseAreaAbility": "Area Defense Ability", + "DescType_DefenseSingleAbility": "Single Defense Ability", + "DescType_MoveForwardAbility": "Move Forward Ability", + "DescType_MoveBackAbility": "Move Back Ability", + "JobRole_None": "Gathering&Production", + "JobRole_Tank": "Tank", + "JobRole_Melee": "Melee", + "JobRole_Ranged": "Ranged", + "JobRole_Healer": "Healer", + "JobRole_RangedPhysical": "Ranged", + "JobRole_RangedMagicial": "Magicial", + "JobRole_DiscipleoftheLand": "Disciple of the Land", + "JobRole_DiscipleoftheHand": "Disciple of the Hand", + "EnemyLocation_None": "None", + "EnemyLocation_Rear": "Rear", + "EnemyLocation_Flank": "Flank", + "EnemyLocation_Front": "Front", + "MemberInfoName": { + "IsMoving": "IsMoving", + "HaveHostilesInRange": "Have Hostiles InRange", + "IsFullParty": "Is Full Party", + "SettingBreak": "Breaking", + "Level": "Level", + "InCombat": "In Combat", + "IsLastGCD": "Just used GCD", + "IsLastAbility": "Just used Ability", + "IsLastAction": "Just used Action", + "IsTargetDying": "Target is dying", + "IsTargetBoss": "Target is Boss", + "HaveSwift": "Have Swift", + "HaveShield": "Have defensive stance", + "PlayCard": "Play", + "UmbralIceStacks": "Umbral Ice Stacks", + "AstralFireStacks": "Astral Fire Stacks", + "PolyglotStacks": "Polyglot Stacks", + "UmbralHearts": "Umbral Heart Stacks", + "IsParadoxActive": "Is Paradox Active ?", + "InUmbralIce": "In Umbral Ice", + "InAstralFire": "In Astral Fire", + "IsEnochianActive": "Is Enochian Active?", + "EnchinaEndAfter": "Enchina End After (s)", + "EnchinaEndAfterGCD": "Enchina End After (GCDs)", + "ElementTimeEndAfter": "Element Time End After (s)", + "ElementTimeEndAfterGCD": "Element Time End After (GCDs)", + "HasFire": "Has Firestarter", + "HasThunder": "Has Thunder", + "IsPolyglotStacksMaxed": "Whether Polyglot already has the maximum number of charge stacks at the current level", + "SoulVoice": "Soul Voice", + "SongEndAfter": "Song End After (s)", + "SongEndAfterGCD": "Song End After (GCDs)", + "Repertoire": "Song Gauge Stacks", + "IsDancing": "Is Dancing", + "Esprit": "Esprit", + "Feathers": "Feathers", + "CompletedSteps": "CompletedSteps", + "FinishStepGCD": "FinishStepGCD", + "ExcutionStepGCD": "Excution Step GCD", + "Blood": "Blood", + "HasDarkArts": "Has Dark Arts", + "DarkSideEndAfter": "DarkSideEndAfter", + "DarkSideEndAfterGCD": "DarkSideEndAfterGCD", + "Ammo": "Ammo", + "AmmoComboStep": "Ammo Combo Step", + "IsOverheated": "Is Over heated", + "Heat": "Heat", + "Battery": "Battery", + "OverheatedEndAfter": "Over heated End After (s)", + "OverheatedEndAfterGCD": "Over heated End After(GCDs)", + "Chakra": "Chakra" + }, + "MemberInfoDesc": { + "IsMoving": "Player Is Moving", + "HaveHostilesInRange": "Have Hostiles In Range(Melee <3m,Ranged<25m)", + "IsFullParty": "Is Full Party", + "SettingBreak": "In break", + "Level": "Player level", + "InCombat": "In Combat", + "IsLastGCD": "Just used GCD", + "IsLastAbility": "Just used ability", + "IsLastAction": "Just used Action", + "IsTargetDying": "Target is Dying", + "IsTargetBoss": "Target is Boss", + "HaveSwift": "Have Swift", + "HaveShield": "Have defensive stance", + "PlayCard": "Play", + "UmbralIceStacks": "Umbral Ice Stacks", + "AstralFireStacks": "Astral Fire Stacks", + "PolyglotStacks": "Polyglot Stacks", + "UmbralHearts": "Umbral Heart Stacks", + "IsParadoxActive": "Is Paradox Active?", + "InUmbralIce": "In Umbral Ice", + "InAstralFire": "In Astral Fire", + "IsEnochianActive": "Is Enochian Active?", + "EnchinaEndAfter": "Enchina End After (s)", + "EnchinaEndAfterGCD": "Enchina End After (GCDs)", + "ElementTimeEndAfter": "Element remaining time", + "ElementTimeEndAfterGCD": "Element remaining time", + "HasFire": "Has Firestarter", + "HasThunder": "Has Thunder", + "IsPolyglotStacksMaxed": "Whether Polyglot already has the maximum number of charge stacks at the current level", + "SoulVoice": "SoulVoice", + "SongEndAfter": "Song End After (s)", + "SongEndAfterGCD": "Song End After (GCDs)", + "Repertoire": "Song Gauge Stacks", + "IsDancing": "Is Dancing", + "Esprit": "Esprit", + "Feathers": "Feathers", + "CompletedSteps": "Completed Steps", + "FinishStepGCD": "Finish Step GCD", + "ExcutionStepGCD": "Excution Step GCD", + "Blood": "Blood", + "HasDarkArts": "Has Dark Arts", + "DarkSideEndAfter": "DarkSide End After (s)", + "DarkSideEndAfterGCD": "DarkSide End After (GCDs)", + "Ammo": "Ammo", + "AmmoComboStep": "Ammo Combo Step", + "IsOverheated": "Is Over heated", + "Heat": "Heat", + "Battery": "Battery", + "OverheatedEndAfter": "OverheatedEndAfter", + "OverheatedEndAfterGCD": "OverheatedEndAfterGCD", + "Chakra": "Chakra" + }, + "HighEndWarning": "You'd better not use Rotation Solver in {0}!" +} \ No newline at end of file diff --git a/RotationSolver.Old/Localization/LocalizationManager.cs b/RotationSolver.Old/Localization/LocalizationManager.cs new file mode 100644 index 000000000..9e541c2ff --- /dev/null +++ b/RotationSolver.Old/Localization/LocalizationManager.cs @@ -0,0 +1,79 @@ +using Dalamud.Logging; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; + +namespace RotationSolver.Localization; + +internal class LocalizationManager : IDisposable +{ + public static Strings RightLang { get; private set; } = new Strings(); + + private readonly Dictionary _translations = new Dictionary(); + public LocalizationManager() + { + var assembly = Assembly.GetCallingAssembly(); + + foreach (var lang in Dalamud.Localization.ApplicableLangCodes) + { + ReadFile(lang, assembly); + } + + SetLanguage(Service.Interface.UiLanguage); + Service.Interface.LanguageChanged += OnLanguageChange; + } + + private void ReadFile(string lang, Assembly assembly) + { + Stream manifestResourceStream = assembly.GetManifestResourceStream("RotationSolver.Localization." + lang + ".json"); + if (manifestResourceStream == null) return; + using StreamReader streamReader = new StreamReader(manifestResourceStream); + _translations[lang] = JsonConvert.DeserializeObject(streamReader.ReadToEnd()); + } + + private void SetLanguage(string lang) + { + if (_translations.TryGetValue(lang, out var value)) + { + RightLang = value; + } + else + { + RightLang = new Strings(); + } + + RotationSolverPlugin.ChangeUITranslation(); + } + +#if DEBUG + public void ExportLocalization() + { + var directory = @"D:\OneDrive - stu.zafu.edu.cn\PartTime\FFXIV\RotationSolver\RotationSolver\Localization"; + if (!Directory.Exists(directory)) return; + + //Default values. + var path = Path.Combine(directory, "Localization.json"); + File.WriteAllText(path, JsonConvert.SerializeObject(new Strings(), Formatting.Indented)); + } +#endif + + public void Dispose() + { + Service.Interface.LanguageChanged -= OnLanguageChange; + } + + private void OnLanguageChange(string languageCode) + { + try + { + PluginLog.Information($"Loading Localization for {languageCode}"); + SetLanguage(languageCode); + } + catch (Exception ex) + { + PluginLog.Error(ex, "Unable to Load Localization"); + } + } +} diff --git a/RotationSolver.Old/Localization/Strings_Major.cs b/RotationSolver.Old/Localization/Strings_Major.cs new file mode 100644 index 000000000..ed0c4192d --- /dev/null +++ b/RotationSolver.Old/Localization/Strings_Major.cs @@ -0,0 +1,526 @@ +using System.Collections.Generic; + +namespace RotationSolver.Localization; + +internal partial class Strings +{ + #region Commands + public string Commands_Rotation { get; set; } = "Open config window."; + public string Commands_ChangeAutoBurst { get; set; } = "Modify automatic burst to {0}"; + public string Commands_ChangeRotationConfig { get; set; } = "Modify {0} to {1}"; + public string Commands_CannotFindRotationConfig { get; set; } = "Failed to find the config in this rotation, please check it."; + + public string Commands_InsertAction { get; set; } = "Will use it within {0}s"; + + public string Commands_InsertActionFailure { get; set; } = "Can not find the action, please check the action name."; + public string Commands_SayHelloToAuthor { get; set; } = "This \"{0}\" is probably one of the authors of the \"Rotation Solver\", so say hello to him!"; + + #endregion + + #region ConfigWindow + public string ConfigWindow_Header { get; set; } = "Rotation Solver Settings v"; + public string ConfigWindow_RotationItem { get; set; } = "Rotation"; + public string ConfigWindow_ParamItem { get; set; } = "Param"; + public string ConfigWindow_EventItem { get; set; } = "Event"; + public string ConfigWindow_ActionItem { get; set; } = "Action"; + public string ConfigWindow_HelpItem { get; set; } = "Help"; + + public string ConfigWindow_ActionItem_Description { get; set; } + = "Modify the usage for each action."; + + public string ConfigWindow_HelpItem_Description { get; set; } + = "In this window, you can see all Rotation Solver built-in commands for combat. "; + + public string Configwindow_HelpItem_AttackSmart { get; set; } + = "Start attacking in smart mode(auto-targeting) when out of combat, otherwise switch the target according to the conditions."; + + public string Configwindow_HelpItem_AttackManual { get; set; } + = "Start attacking in manual mode. You need to choose the target manually."; + + public string Configwindow_HelpItem_AttackCancel { get; set; } + = "Stop attacking. Remember to turn it off when not in use!"; + + public string Configwindow_HelpItem_HealArea { get; set; } + = "Open a window to use AoE heal."; + + public string Configwindow_HelpItem_HealSingle { get; set; } + = "Open a window to use single heal."; + + public string Configwindow_HelpItem_DefenseArea { get; set; } + = "Open a window to use AoE defense."; + + public string Configwindow_HelpItem_DefenseSingle { get; set; } + = "Open a window to use single defense."; + + public string Configwindow_HelpItem_Esuna { get; set; } + = "Open a window to use Esuna,tank stance actions or True North."; + + public string Configwindow_HelpItem_RaiseShirk { get; set; } + = "Open a window to use Raise or Shirk."; + + public string Configwindow_HelpItem_AntiKnockback { get; set; } + = "Open a window to use knockback-penalty actions."; + + public string Configwindow_HelpItem_Burst { get; set; } + = "Open a window to burst."; + + public string Configwindow_HelpItem_MoveForward { get; set; } + = "Open a window to move forward."; + + public string Configwindow_HelpItem_MoveBack { get; set; } + = "Open a window to move back."; + + public string Configwindow_HelpItem_EndSpecial { get; set; } + = "Close special window."; + public string Configwindow_Helper_SwitchRotation { get; set; } = "Click to switch authors"; + public string Configwindow_Helper_GameVersion { get; set; } = "Game Version"; + public string Configwindow_Helper_OpenSource { get; set; } = "Open the source code URL"; + public string Configwindow_Helper_RunCommand { get; set; } = "Click to execute the command"; + public string Configwindow_Helper_CopyCommand { get; set; } = "Right-click to copy command"; + public string Configwindow_Helper_InsertCommand { get; set; } = "Insert \"{0}\" first in 5s"; + public string Configwindow_Rotation_Description { get; set; } = "You can enable the function for each job you want and configure the setting about how to use actions."; + public string Configwindow_Rotation_KeyName { get; set; } = "The key name is"; + public string Configwindow_Events_AddEvent { get; set; } = "AddEvents"; + public string Configwindow_Events_Description { get; set; } = "In this window, you can set what macro will be trigger after using an action."; + public string Configwindow_Events_ActionName { get; set; } = "Action Name"; + public string Configwindow_Events_MacroIndex { get; set; } = "Macro No."; + public string Configwindow_Events_ShareMacro { get; set; } = "Is Shared"; + public string Configwindow_Events_RemoveEvent { get; set; } = "Delete Event"; + public string Configwindow_Events_DutyStart { get; set; } = "Duty Start: "; + public string Configwindow_Events_DutyEnd{ get; set; } = "Duty End: "; + public string Configwindow_Params_Description { get; set; } = "In this window, you can set the parameters about the using way of actions."; + public string Configwindow_Param_NeverReplaceIcon { get; set; } = "Never Replace Icons"; + public string Configwindow_Param_NeverReplaceIconDesc { get; set; } = "Icon replacement: Repose is automatically displayed as the next action to be used"; + public string Configwindow_Param_UseOverlayWindow { get; set; } = "Display Top Overlay"; + public string Configwindow_Param_UseOverlayWindowDesc { get; set; } = "This top window is used to display some extra information on your game window, such as target's positional, target and sub-target, etc."; + public string Configwindow_Param_Basic { get; set; } = "Basic"; + + public string Configwindow_Param_ActionAhead { get; set; } = "Set the time advance of using actions"; + public string Configwindow_Param_AbilitiesInterval { get; set; } = "Set the interval between 0GCD using"; + public string Configwindow_Param_CountDownAhead { get; set; } = "Set the time advance of using casting actions on counting down."; + public string Configwindow_Param_SpecialDuration { get; set; } = "Set the duration of special windows set by commands"; + public string Configwindow_Param_AddDotGCDCount { get; set; } = "Set GCD advance of DOT refresh"; + public string Configwindow_Param_AutoOffBetweenArea { get; set; } = "Turn off when player is between area."; + + public string Configwindow_Param_UseWorkTask { get; set; } = "Use work task for acceleration."; + public string Configwindow_Param_Delay { get; set; } = "Delay"; + + public string Configwindow_Param_WeaponDelay { get; set; } = "Set the range of random delay for GCD in second."; + public string Configwindow_Param_DeathDelay { get; set; } = "Set the range of random delay for raising deaths in second."; + public string Configwindow_Param_HostileDelay { get; set; } = "Set the range of random delay for finding hostile targets in second."; + public string Configwindow_Param_InterruptDelay { get; set; } = "Set the range of random delay for interrupting hostile targets in second."; + public string Configwindow_Param_WeakenDelay { get; set; } = "Set the range of random delay for esuna weakens in second."; + + public string Configwindow_Param_HealDelay { get; set; } = "Set the range of random delay for healing people in second."; + + public string Configwindow_Param_NotInCombatDelay { get; set; } = "Set the range of random delay for Not In Combat in second."; + public string Configwindow_Param_StopCastingDelay { get; set; } = "Set the range of random delay for stoping casting when target is no need to cast in second."; + public string Configwindow_Param_Display { get; set; } = "Display"; + public string Configwindow_Param_Advanced { get; set; } = "Advanced"; + public string Configwindow_Param_PoslockCasting { get; set; } = "Lock the movement when casting."; + public string Configwindow_Param_UseStopCasting { get; set; } = "Use stopping casting when target is dead."; + public string Configwindow_Param_ShowActionFlag { get; set; } = "Show action flag."; + public string Configwindow_Param_ShowHealthRatio { get; set; } = "Show the health ratio for the check of Boss, Dying, Dot."; + + public string Configwindow_Param_HealthRatioBoss { get; set; } = "If target's max health ratio is higher than this, regard it as Boss."; + + public string Configwindow_Param_HealthRatioDying { get; set; } = "If target's current health ratio is lower than this, regard it is dying."; + public string Configwindow_Param_HealthRatioDot { get; set; } = "If target's current health ratio is higher than this, regard it can be dot."; + public string Configwindow_Param_PoslockModifier { get; set; } = "Set the modifier key to unlock the movement temporary"; + public string Configwindow_Param_PoslockDescription { get; set; } = "LT is for gamepad player"; + public string Configwindow_Param_CastingDisplay { get; set; } = "Enhance castbar with casting status"; + public string Configwindow_Param_TeachingMode { get; set; } = "Teaching mode"; + public string Configwindow_Param_TeachingModeColor { get; set; } = "Prompt box color of teaching mode"; + public string Configwindow_Param_MovingTargetColor { get; set; } = "Prompt box color of moving target"; + public string Configwindow_Param_TargetColor { get; set; } = "Target color"; + public string Configwindow_Params_SubTargetColor { get; set; } = "Sub-target color"; + public string Configwindow_Param_KeyBoardNoise { get; set; } = "Simulate the effect of pressing"; + public string Configwindow_Params_VoiceVolume { get; set; } = "Voice volume"; + public string Configwindow_Param_FlytextPositional { get; set; } = "Hint positional anticipation by flytext"; + public string Configwindow_Param_SayPositional { get; set; } = "Hint positional anticipation by shouting"; + public string Configwindow_Param_PositionalFeedback { get; set; } = "Positional error feedback"; + public string Configwindow_Param_DrawPositional { get; set; } = "Draw Positional on the screen"; + public string Configwindow_Param_DrawMeleeRange { get; set; } = "Draw the range of melee on the screen"; + public string Configwindow_Param_ShowMoveTarget { get; set; } = "Show the pointing target of the move skill"; + public string Configwindow_Param_ShowTarget { get; set; } = "Show Target"; + public string Configwindow_Param_PositionalFeedbackDesc { get; set; } = "Attention: Positional anticipation is experimental, just for reference only."; + public string Configwindow_Param_PositionaErrorText { get; set; } = "Positional error prompt"; + public string Configwindow_Params_LocationWrongTextDesc { get; set; } = "How do you want to be scolded if you have a positional error ?!"; + public string Configwindow_Param_SayOutStateChanged { get; set; } = "Saying the state changes out"; + public string Configwindow_Param_ShowInfoOnDtr { get; set; } = "Display plugin state on dtrbar"; + + public string Configwindow_Param_ShowWorkTaskFPS { get; set; } = "Display Task FPS on dtrbar"; + + public string Configwindow_Param_ShowInfoOnToast { get; set; } = "Display plugin state on toast"; + public string Configwindow_Param_NamePlateIconId { get; set; } = "Player's name plate icon id when state is on. Recommand 61435, 61437"; + public string Configwindow_Param_Action { get; set; } = "Action"; + public string Configwindow_Param_UseAOEAction { get; set; } = "Use AOE actions"; + + public string Configwindow_Param_UseAOEWhenManual { get; set; } = "Use AOE actions in manual mode"; + public string Configwindow_Param_AutoBurst { get; set; } = "Automatic burst"; + public string Configwindow_Param_UseAbility { get; set; } = "Auto-use abilities"; + public string Configwindow_Param_NoNewHostiles { get; set; } = "Don't attack new mobs by aoe"; + public string Configwindow_Params_NoNewHostilesDesc { get; set; } = "Nerver use any AOE action when this action may attack the mobs that not is a hostile target."; + public string Configwindow_Param_UseDefenceAbility { get; set; } = "Use defence abilities"; + public string Configwindow_Param_UseDefenceAbilityDesc { get; set; } = "It is recommended to check this option if you are playing Raids./nPlan the heal and defense by yourself.???"; + public string Configwindow_Param_AutoShield { get; set; } = "Auto tank stance"; + public string Configwindow_Param_AutoProvokeForTank { get; set; } = "Auto Provoke (Tank)"; + public string Configwindow_Param_AutoProvokeForTankDesc { get; set; } = "When a hostile is hitting the non-Tank member of party, it will automatically use the Provoke."; + public string Configwindow_Param_AutoUseTrueNorth { get; set; } = "Auto TrueNorth (Melee)"; + public string Configwindow_Param_RaisePlayerBySwift { get; set; } = "Raise player by swift"; + public string Configwindow_Param_UseGroundBeneficialAbility { get; set; } = "Use beneficaial ground-targeted actions"; + public string Configwindow_Param_RaisePlayerByCasting { get; set; } = "Raise player by casting when swift is in cooldown"; + public string Configwindow_Param_UseHealWhenNotAHealer { get; set; } = "Use heal when not-healer"; + public string Configwindow_Param_LessMPNoRaise { get; set; } = "Nerver raise player if MP is less than the set value"; + public string Configwindow_Param_UseItem { get; set; } = "Use items"; + public string Configwindow_Param_UseItemDesc { get; set; } = "Use poison, WIP"; + public string Configwindow_Param_Conditon { get; set; } = "Condition"; + public string Configwindow_Param_StartOnCountdown { get; set; } = "Turn on auto-rotation on countdown"; + public string Configwindow_Param_EsunaAll { get; set; } = "Esuna All Statuses."; + public string Configwindow_Param_InterruptibleMoreCheck { get; set; } = "Interrupt the action with action type check."; + + public string Configwindow_Param_HealOutOfCombat { get; set; } = "Heal party members outside of combat."; + + public string Configwindow_Param_HealthDifference { get; set; } = "Set the HP standard deviation threshold for using AOE heal (ability & spell)"; + public string Configwindow_Param_HealthAreaAbility { get; set; } = "Set the HP threshold for using AOE healing ability"; + public string Configwindow_Param_HealthAreaSpell { get; set; } = "Set the HP threshold for using AOE healing spell"; + public string Configwindow_Param_HealingOfTimeSubtractArea { get; set; } = "Set the HP threshold reduce with hot effect(AOE)"; + public string Configwindow_Param_HealthSingleAbility { get; set; } = "Set the HP threshold for using single healing ability"; + public string Configwindow_Param_HealthSingleSpell { get; set; } = "Set the HP threshold for using single healing spell"; + + + public string Configwindow_Param_HealingOfTimeSubtractSingle { get; set; } = "Set the HP threshold reduce with hot effect(single)"; + public string Configwindow_Param_HealthForDyingTank { get; set; } = "Set the HP threshold for tank to use invincibility"; + + public string Configwindow_Param_MeleeRangeOffset { get; set; } = "Melee Range action using offset"; + public string Configwindow_Param_Target { get; set; } = "Target"; + public string Configwindow_Param_RightNowTargetToHostileType { get; set; } = "Hostile target filtering condition"; + public string Configwindow_Param_TargetToHostileType1 { get; set; } = "All targets can attack"; + public string Configwindow_Param_TargetToHostileType2 { get; set; } = "Targets have a target or all targets can attack"; + public string Configwindow_Param_TargetToHostileType3 { get; set; } = "Targets have a target"; + public string Configwindow_Param_NoticeUnexpectedCombat { get; set; } = "NOTICE: You are not turn the auto off between area on. It may start a combat unexpectedly."; + public string Configwindow_Param_AddEnemyListToHostile { get; set; } = "Add Enemies list to the hostile target."; + public string Configwindow_Param_ChooseAttackMark { get; set; } = "Priority attack targets with attack markers"; + public string Configwindow_Param_CanAttackMarkAOE { get; set; } = "Forced use of AOE"; + public string Configwindow_Param_AttackMarkAOEDesc { get; set; } = "Attention: Checking this option , AA will attack as many hostile targets as possible, while ignoring whether the attack will cover the marked target."; + public string Configwindow_Param_FilterStopMark { get; set; } = "Never attack targets with stop markers"; + public string Configwindow_Param_ObjectMinRadius { get; set; } = "Set the minimum target circle threshold possessed by the attack target"; + public string Configwindow_Param_MoveTargetAngle { get; set; } = "The size of the sector angle that can be selected as the moveable target"; + public string Configwindow_Param_MoveTargetAngleDesc { get; set; } = "If the selection mode is based on character facing, i.e., targets within the character's viewpoint are movable targets. \nIf the selection mode is screen-centered, i.e., targets within a sector drawn upward from the character's point are movable targets."; + public string Configwindow_Param_ChangeTargetForFate { get; set; } = "Select only Fate targets in Fate"; + public string Configwindow_Param_OnlyAttackInView { get; set; } = "Only attack the target in view."; + + public string Configwindow_Param_MoveTowardsScreen { get; set; } = "Using movement actions towards the object in the center of the screen"; + public string Configwindow_Param_MoveTowardsScreenDesc { get; set; } = "Using movement actions towards the object in the center of the screen, otherwise toward the facing object."; + public string Configwindow_Param_RaiseAll { get; set; } = "Raise all (include passerby)"; + public string Configwindow_Param_TargetFriendly { get; set; } = "Target all for friendly actions(include passerby)"; + public string Configwindow_Param_RaiseBrinkofDeath { get; set; } = "Raise player even has Brink of Death"; + public string Configwindow_Param_MoveAreaActionFarthest { get; set; } = "Moving Area Ability to farthest"; + public string Configwindow_Param_MoveAreaActionFarthestDesc { get; set; } = "Move to the furthest position from character's face direction."; + public string Configwindow_Param_Hostile { get; set; } = "Hostile"; + public string Configwindow_Param_HostileDesc { get; set; } = "You can set the logic of hostile target selection to allow flexibility in switching the logic of selecting hostile in battle."; + public string Configwindow_Param_AddHostileCondition { get; set; } = "Add selection condition"; + public string Configwindow_Param_HostileCondition { get; set; } = "Hostile target selection condition"; + public string Configwindow_Param_ConditionUp { get; set; } = "Up"; + public string Configwindow_Param_ConditionDown { get; set; } = "Down"; + public string Configwindow_Param_ConditionDelete { get; set; } = "Delete"; + #endregion + + #region ScriptWindow + public string Timeline_DragdropDescription { get; set; } = "Drag&drop to move,Ctrl+Alt+RightClick to delete."; + public string Timeline_SearchBar { get; set; } = "Search Bar"; + public string Timeline_MustUse { get; set; } = "MustUse"; + public string Timeline_MustUseDesc { get; set; } = "Skip AOE and Buff."; + public string Timeline_Empty { get; set; } = "UseUp"; + public string Timeline_EmptyDesc { get; set; } = "UseUp or Skip Combo"; + public string Timeline_TimelineDescription { get; set; } = "Add some condition to automatic use this action."; + public string Timeline_Can { get; set; } = "Can"; + public string Timeline_Cannot { get; set; } = "Cannot"; + public string Timeline_Is { get; set; } = "Is"; + public string Timeline_Isnot { get; set; } = "Isnot"; + public string Timeline_Have { get; set; } = "Have"; + public string Timeline_Havenot { get; set; } = "Havenot"; + public string Timeline_Ability { get; set; } = "Ability"; + public string Timeline_Charges { get; set; } = "Charges"; + public string Timeline_ConditionSet { get; set; } = "ConditionSet"; + public string Timeline_ActionCondition { get; set; } = "ActionCondition"; + public string Timeline_TargetCondition { get; set; } = "TargetCondition"; + public string Timeline_RotationCondition { get; set; } = "RotationCondition"; + public string Timeline_ActionTarget { get; set; } = "{0}'s target"; + public string Timeline_Target { get; set; } = "Target"; + public string Timeline_Player { get; set; } = "Player"; + public string Timeline_StatusSelf { get; set; } = "StatusSelf"; + public string Timeline_StatusSelfDesc { get; set; } = "StatusSelf"; + #endregion + + #region Actions + public string Action_Friendly { get; set; } = "Support"; + public string Action_Attack { get; set; } = "Attack"; + #endregion + + #region ComboConditionType + public string ComboConditionType_Bool { get; set; } = "Boolean"; + public string ComboConditionType_Byte { get; set; } = "Byte"; + public string ComboConditionType_Time { get; set; } = "Time"; + public string ComboConditionType_GCD { get; set; } = "GCD"; + public string ComboConditionType_Last { get; set; } = "Last"; + #endregion + + #region TargetingType + public string TargetingType_Big { get; set; } = "Big"; + public string TargetingType_Small { get; set; } = "Small"; + public string TargetingType_HighHP { get; set; } = "High HP"; + public string TargetingType_LowHP { get; set; } = "Low HP"; + public string TargetingType_HighMaxHP { get; set; } = "High Max HP"; + public string TargetingType_LowMaxHP { get; set; } = "Low Max HP"; + #endregion + + #region SpecialCommandTypeSayout + public string SpecialCommandType_Start { get; set; } = "Start "; + + public string SpecialCommandType_HealArea { get; set; } = "Heal Area"; + public string SpecialCommandType_HealSingle { get; set; } = "Heal Single"; + public string SpecialCommandType_DefenseArea { get; set; } = "Defense Area"; + public string SpecialCommandType_DefenseSingle { get; set; } = "Defense Single"; + public string SpecialCommandType_TankStance { get; set; } = "Tank Stance"; + public string SpecialCommandType_MoveForward { get; set; } = "Move Forward"; + public string SpecialCommandType_MoveBack { get; set; } = "Move Back"; + public string SpecialCommandType_AntiKnockback { get; set; } = "Anti-Knockback"; + public string SpecialCommandType_Burst { get; set; } = "Burst"; + public string SpecialCommandType_EndSpecial { get; set; } = "End Special"; + public string SpecialCommandType_Smart { get; set; } = "Smart "; + public string SpecialCommandType_Manual { get; set; } = "Manual"; + public string SpecialCommandType_Cancel { get; set; } = "Cancel"; + public string SpecialCommandType_Off { get; set; } = "Off"; + #endregion + + #region ActionConditionType + public string ActionConditionType_Elapsed { get; set; } = "Elapsed"; + public string ActionConditionType_ElapsedGCD { get; set; } = "ElapsedGCD "; + public string ActionConditionType_Remain { get; set; } = "RemainTime"; + public string ActionConditionType_RemainGCD { get; set; } = "RemainGCD"; + public string ActionConditionType_ShouldUse { get; set; } = "ShouldUse"; + public string ActionConditionType_EnoughLevel { get; set; } = "EnoughLevel"; + public string ActionConditionType_IsCoolDown { get; set; } = "IsCoolDown"; + public string ActionConditionType_CurrentCharges { get; set; } = "CurrentCharges"; + public string ActionConditionType_MaxCharges { get; set; } = "MaxCharges"; + #endregion + + #region TargetConditionType + public string TargetConditionType_HaveStatus { get; set; } = "Have Status"; + public string TargetConditionType_IsDying { get; set; } = "Is Dying"; + public string TargetConditionType_IsBoss { get; set; } = "Is Boss"; + public string TargetConditionType_Distance { get; set; } = "Distance"; + public string TargetConditionType_StatusEnd { get; set; } = "Status End"; + public string TargetConditionType_StatusEndGCD { get; set; } = "Status End GCD"; + public string TargetConditionType_CastingAction { get; set; } = "Casting Action"; + + #endregion + + #region DescType + public string DescType_BurstActions { get; set; } = "Burst Actions"; + public string DescType_MoveForwardGCD { get; set; } = "Move Forward GCD"; + public string DescType_HealAreaGCD { get; set; } = "Area Healing GCD"; + public string DescType_HealSingleGCD { get; set; } = "Single Healing GCD"; + public string DescType_DefenseAreaGCD { get; set; } = "Area Defense GCD"; + public string DescType_DefenseSingleGCD { get; set; } = "Single Defense GCD"; + + public string DescType_HealAreaAbility { get; set; } = "Area Healing Ability"; + public string DescType_HealSingleAbility { get; set; } = "Single Healing Ability"; + public string DescType_DefenseAreaAbility { get; set; } = "Area Defense Ability"; + public string DescType_DefenseSingleAbility { get; set; } = "Single Defense Ability"; + public string DescType_MoveForwardAbility { get; set; } = "Move Forward Ability"; + public string DescType_MoveBackAbility { get; set; } = "Move Back Ability"; + + #endregion + + #region JobRole + public string JobRole_None { get; set; } = "Gathering&Production"; + public string JobRole_Tank { get; set; } = "Tank"; + public string JobRole_Melee { get; set; } = "Melee"; + public string JobRole_Ranged { get; set; } = "Ranged"; + public string JobRole_Healer { get; set; } = "Healer"; + public string JobRole_RangedPhysical { get; set; } = "Ranged"; + public string JobRole_RangedMagicial { get; set; } = "Magicial"; + public string JobRole_DiscipleoftheLand { get; set; } = "Disciple of the Land"; + public string JobRole_DiscipleoftheHand { get; set; } = "Disciple of the Hand"; + + #endregion + + #region EnemyLocation + public string EnemyLocation_None { get; set; } = "None"; + public string EnemyLocation_Rear { get; set; } = "Rear"; + public string EnemyLocation_Flank { get; set; } = "Flank"; + public string EnemyLocation_Front { get; set; } = "Front"; + + #endregion + public Dictionary MemberInfoName { get; set; } = new Dictionary() + { + #region Rotation + { "IsMoving", "IsMoving"}, + { "HaveHostilesInRange", "Have Hostiles InRange"}, + { "IsFullParty", "Is Full Party"}, + { "SettingBreak", "Breaking"}, + { "Level", "Level"}, + { "InCombat", "In Combat"}, + { "IsLastGCD", "Just used GCD"}, + { "IsLastAbility", "Just used Ability"}, + { "IsLastAction", "Just used Action"}, + { "IsTargetDying", "Target is dying"}, + { "IsTargetBoss", "Target is Boss"}, + { "HaveSwift", "Have Swift"}, + { "HaveShield", "Have defensive stance"}, + #endregion + + #region AST + { "PlayCard", "Play"}, + #endregion + + #region BLM + { "UmbralIceStacks", "Umbral Ice Stacks"}, + { "AstralFireStacks", "Astral Fire Stacks"}, + { "PolyglotStacks", "Polyglot Stacks"}, + { "UmbralHearts", "Umbral Heart Stacks"}, + { "IsParadoxActive", "Is Paradox Active ?"}, + { "InUmbralIce", "In Umbral Ice"}, + { "InAstralFire", "In Astral Fire"}, + { "IsEnochianActive", "Is Enochian Active?"}, + { "EnchinaEndAfter", "Enchina End After (s)"}, + { "EnchinaEndAfterGCD", "Enchina End After (GCDs)"}, + { "ElementTimeEndAfter", "Element Time End After (s)"}, + { "ElementTimeEndAfterGCD", "Element Time End After (GCDs)"}, + { "HasFire", "Has Firestarter"}, + { "HasThunder", "Has Thunder"}, + { "IsPolyglotStacksMaxed", "Whether Polyglot already has the maximum number of charge stacks at the current level"}, //这玩意儿太长了! + #endregion + + #region BRD + { "SoulVoice", "Soul Voice"}, + { "SongEndAfter", "Song End After (s)"}, + { "SongEndAfterGCD", "Song End After (GCDs)"}, + { "Repertoire", "Song Gauge Stacks"}, + #endregion + + #region DNC + { "IsDancing", "Is Dancing"}, + { "Esprit", "Esprit"}, + { "Feathers", "Feathers"}, + { "CompletedSteps", "CompletedSteps"}, + { "FinishStepGCD", "FinishStepGCD"}, + { "ExcutionStepGCD", "Excution Step GCD"}, + #endregion + + #region DRG + #endregion + + #region DRK + { "Blood", "Blood"}, + { "HasDarkArts", "Has Dark Arts"}, + { "DarkSideEndAfter", "DarkSideEndAfter"}, + { "DarkSideEndAfterGCD", "DarkSideEndAfterGCD"}, + #endregion + + #region GNB + { "Ammo", "Ammo"}, + { "AmmoComboStep", "Ammo Combo Step"}, + #endregion + + #region MCH + { "IsOverheated", "Is Over heated"}, + { "Heat", "Heat"}, + { "Battery", "Battery"}, + { "OverheatedEndAfter", "Over heated End After (s)"}, + { "OverheatedEndAfterGCD", "Over heated End After(GCDs)"}, + #endregion + + #region MNK + { "Chakra", "Chakra"}, + #endregion + }; + + public Dictionary MemberInfoDesc { get; set; } = new Dictionary() + { + #region Rotation + { "IsMoving", "Player Is Moving"}, + { "HaveHostilesInRange", "Have Hostiles In Range(Melee <3m,Ranged<25m)"}, + { "IsFullParty", "Is Full Party"}, + { "SettingBreak", "In break"}, + { "Level", "Player level"}, + { "InCombat", "In Combat"}, + { "IsLastGCD", "Just used GCD"}, + { "IsLastAbility", "Just used ability"}, + { "IsLastAction", "Just used Action"}, + { "IsTargetDying", "Target is Dying"}, + { "IsTargetBoss", "Target is Boss"}, + { "HaveSwift", "Have Swift"}, + { "HaveShield", "Have defensive stance"}, + #endregion + + #region AST + { "PlayCard", "Play"}, + #endregion + + #region BLM + { "UmbralIceStacks", "Umbral Ice Stacks"}, + { "AstralFireStacks", "Astral Fire Stacks"}, + { "PolyglotStacks", "Polyglot Stacks"}, + { "UmbralHearts", "Umbral Heart Stacks"}, + { "IsParadoxActive", "Is Paradox Active?"}, + { "InUmbralIce", "In Umbral Ice"}, + { "InAstralFire", "In Astral Fire"}, + { "IsEnochianActive", "Is Enochian Active?"}, + { "EnchinaEndAfter", "Enchina End After (s)"}, + { "EnchinaEndAfterGCD", "Enchina End After (GCDs)"}, + { "ElementTimeEndAfter", "Element remaining time"}, + { "ElementTimeEndAfterGCD", "Element remaining time"}, + { "HasFire", "Has Firestarter"}, + { "HasThunder", "Has Thunder"}, + { "IsPolyglotStacksMaxed", "Whether Polyglot already has the maximum number of charge stacks at the current level"}, + #endregion + + #region BRD + { "SoulVoice", "SoulVoice"}, + { "SongEndAfter", "Song End After (s)"}, + { "SongEndAfterGCD", "Song End After (GCDs)"}, + { "Repertoire", "Song Gauge Stacks"}, + #endregion + + #region DNC + { "IsDancing", "Is Dancing"}, + { "Esprit", "Esprit"}, + { "Feathers", "Feathers"}, + { "CompletedSteps", "Completed Steps"}, + { "FinishStepGCD", "Finish Step GCD"}, + { "ExcutionStepGCD", "Excution Step GCD"}, + #endregion + + #region DRG + #endregion + + #region DRK + { "Blood", "Blood"}, + { "HasDarkArts", "Has Dark Arts"}, + { "DarkSideEndAfter", "DarkSide End After (s)"}, + { "DarkSideEndAfterGCD", "DarkSide End After (GCDs)"}, + #endregion + + #region GNB + { "Ammo", "Ammo"}, + { "AmmoComboStep", "Ammo Combo Step"}, + #endregion + + #region MCH + { "IsOverheated", "Is Over heated"}, + { "Heat", "Heat"}, + { "Battery", "Battery"}, + { "OverheatedEndAfter", "OverheatedEndAfter"}, + { "OverheatedEndAfterGCD", "OverheatedEndAfterGCD"}, + #endregion + + #region MNK + { "Chakra", "Chakra"}, + #endregion + }; + + public string HighEndWarning { get; set; } = "You'd better not use Rotation Solver in {0}!"; +} diff --git a/RotationSolver.Old/Localization/Strings_Rotation.cs b/RotationSolver.Old/Localization/Strings_Rotation.cs new file mode 100644 index 000000000..827e0b623 --- /dev/null +++ b/RotationSolver.Old/Localization/Strings_Rotation.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RotationSolver.Localization; + +internal partial class Strings +{ + //Add your Rotation Translations here as Properties. +} diff --git a/RotationSolver.Old/Localization/de.json b/RotationSolver.Old/Localization/de.json new file mode 100644 index 000000000..8d1071717 --- /dev/null +++ b/RotationSolver.Old/Localization/de.json @@ -0,0 +1,364 @@ +{ + "Commands_Rotation": "Open config window.", + "Commands_ChangeAutoBurst": "Modify automatic burst to {0}", + "Commands_ChangeRotationConfig": "Modify {0} to {1}", + "Commands_CannotFindRotationConfig": "Failed to find the config in this rotation, please check it.", + "Commands_InsertAction": "Will use it within {0}s", + "Commands_InsertActionFailure": "Can not find the action, please check the action name.", + "Commands_SayHelloToAuthor": "This \"{0}\" is probably one of the authors of the \"Rotation Solver\", so say hello to him!", + "ConfigWindow_Header": "Rotation Solver Settings v", + "ConfigWindow_RotationItem": "Rotation", + "ConfigWindow_ParamItem": "Param", + "ConfigWindow_EventItem": "Event", + "ConfigWindow_ActionItem": "Action", + "ConfigWindow_HelpItem": "Help", + "ConfigWindow_ActionItem_Description": "Modify the usage for each action.", + "ConfigWindow_HelpItem_Description": "In this window, you can see all Rotation Solver built-in commands for combat. ", + "Configwindow_HelpItem_AttackSmart": "Start attacking in smart mode(auto-targeting) when out of combat, otherwise switch the target according to the conditions.", + "Configwindow_HelpItem_AttackManual": "Start attacking in manual mode. You need to choose the target manually.", + "Configwindow_HelpItem_AttackCancel": "Stop attacking. Remember to turn it off when not in use!", + "Configwindow_HelpItem_HealArea": "Open a window to use AoE heal.", + "Configwindow_HelpItem_HealSingle": "Open a window to use single heal.", + "Configwindow_HelpItem_DefenseArea": "Open a window to use AoE defense.", + "Configwindow_HelpItem_DefenseSingle": "Open a window to use single defense.", + "Configwindow_HelpItem_Esuna": "Open a window to use Esuna,tank stance actions or True North.", + "Configwindow_HelpItem_RaiseShirk": "Open a window to use Raise or Shirk.", + "Configwindow_HelpItem_AntiKnockback": "Open a window to use knockback-penalty actions.", + "Configwindow_HelpItem_Burst": "Open a window to burst.", + "Configwindow_HelpItem_MoveForward": "Open a window to move forward.", + "Configwindow_HelpItem_MoveBack": "Open a window to move back.", + "Configwindow_HelpItem_EndSpecial": "Close special window.", + "Configwindow_Helper_SwitchRotation": "Click to switch authors", + "Configwindow_Helper_GameVersion": "Game Version", + "Configwindow_Helper_OpenSource": "Open the source code URL", + "Configwindow_Helper_RunCommand": "Click to execute the command", + "Configwindow_Helper_CopyCommand": "Right-click to copy command", + "Configwindow_Helper_InsertCommand": "Insert \"{0}\" first in 5s", + "Configwindow_Rotation_Description": "You can enable the function for each job you want and configure the setting about how to use actions.", + "Configwindow_Rotation_KeyName": "The key name is", + "Configwindow_Events_AddEvent": "AddEvents", + "Configwindow_Events_Description": "In this window, you can set what macro will be trigger after using an action.", + "Configwindow_Events_ActionName": "Action Name", + "Configwindow_Events_MacroIndex": "Macro No.", + "Configwindow_Events_ShareMacro": "Is Shared", + "Configwindow_Events_RemoveEvent": "Delete Event", + "Configwindow_Events_DutyStart": "Duty Start: ", + "Configwindow_Events_DutyEnd": "Duty End: ", + "Configwindow_Params_Description": "In this window, you can set the parameters about the using way of actions.", + "Configwindow_Param_NeverReplaceIcon": "Never Replace Icons", + "Configwindow_Param_NeverReplaceIconDesc": "Icon replacement: Repose is automatically displayed as the next action to be used", + "Configwindow_Param_UseOverlayWindow": "Display Top Overlay", + "Configwindow_Param_UseOverlayWindowDesc": "This top window is used to display some extra information on your game window, such as target's positional, target and sub-target, etc.", + "Configwindow_Param_Basic": "Basic", + "Configwindow_Param_ActionAhead": "Set the time advance of using actions", + "Configwindow_Param_AbilitiesInterval": "Set the interval between 0GCD using", + "Configwindow_Param_CountDownAhead": "Set the time advance of using casting actions on counting down.", + "Configwindow_Param_SpecialDuration": "Set the duration of special windows set by commands", + "Configwindow_Param_AddDotGCDCount": "Set GCD advance of DOT refresh", + "Configwindow_Param_AutoOffBetweenArea": "Turn off when player is between area.", + "Configwindow_Param_UseWorkTask": "Use work task for acceleration.", + "Configwindow_Param_Delay": "Delay", + "Configwindow_Param_WeaponDelay": "Set the range of random delay for GCD in second.", + "Configwindow_Param_DeathDelay": "Set the range of random delay for raising deaths in second.", + "Configwindow_Param_HostileDelay": "Set the range of random delay for finding hostile targets in second.", + "Configwindow_Param_InterruptDelay": "Set the range of random delay for interrupting hostile targets in second.", + "Configwindow_Param_WeakenDelay": "Set the range of random delay for esuna weakens in second.", + "Configwindow_Param_HealDelay": "Set the range of random delay for healing people in second.", + "Configwindow_Param_NotInCombatDelay": "Set the range of random delay for Not In Combat in second.", + "Configwindow_Param_StopCastingDelay": "Set the range of random delay for stoping casting when target is no need to cast in second.", + "Configwindow_Param_Display": "Display", + "Configwindow_Param_Advanced": "Advanced", + "Configwindow_Param_PoslockCasting": "Lock the movement when casting.", + "Configwindow_Param_UseStopCasting": "Use stopping casting when target is dead.", + "Configwindow_Param_ShowActionFlag": "Show action flag.", + "Configwindow_Param_ShowHealthRatio": "Show the health ratio for the check of Boss, Dying, Dot.", + "Configwindow_Param_HealthRatioBoss": "If target's max health ratio is higher than this, regard it as Boss.", + "Configwindow_Param_HealthRatioDying": "If target's current health ratio is lower than this, regard it is dying.", + "Configwindow_Param_HealthRatioDot": "If target's current health ratio is higher than this, regard it can be dot.", + "Configwindow_Param_PoslockModifier": "Set the modifier key to unlock the movement temporary", + "Configwindow_Param_PoslockDescription": "LT is for gamepad player", + "Configwindow_Param_CastingDisplay": "Enhance castbar with casting status", + "Configwindow_Param_TeachingMode": "Teaching mode", + "Configwindow_Param_TeachingModeColor": "Prompt box color of teaching mode", + "Configwindow_Param_MovingTargetColor": "Prompt box color of moving target", + "Configwindow_Param_TargetColor": "Target color", + "Configwindow_Params_SubTargetColor": "Sub-target color", + "Configwindow_Param_KeyBoardNoise": "Simulate the effect of pressing", + "Configwindow_Params_VoiceVolume": "Voice volume", + "Configwindow_Param_FlytextPositional": "Hint positional anticipation by flytext", + "Configwindow_Param_SayPositional": "Hint positional anticipation by shouting", + "Configwindow_Param_PositionalFeedback": "Positional error feedback", + "Configwindow_Param_ShowMoveTarget": "Show the pointing target of the move skill", + "Configwindow_Param_ShowTarget": "Show Target", + "Configwindow_Param_PositionalFeedbackDesc": "Attention: Positional anticipation is experimental, just for reference only.", + "Configwindow_Param_PositionaErrorText": "Positional error prompt", + "Configwindow_Params_LocationWrongTextDesc": "How do you want to be scolded if you have a positional error ?!", + "Configwindow_Param_SayOutStateChanged": "Saying the state changes out", + "Configwindow_Param_ShowInfoOnDtr": "Display plugin state on dtrbar", + "Configwindow_Param_ShowWorkTaskFPS": "Display Task FPS on dtrbar", + "Configwindow_Param_ShowInfoOnToast": "Display plugin state on toast", + "Configwindow_Param_NamePlateIconId": "Player's name plate icon id when state is on. Recommand 61435, 61437", + "Configwindow_Param_Action": "Action", + "Configwindow_Param_UseAOEAction": "Use AOE actions", + "Configwindow_Param_UseAOEWhenManual": "Use AOE actions in manual mode", + "Configwindow_Param_AutoBurst": "Automatic burst", + "Configwindow_Param_UseAbility": "Auto-use abilities", + "Configwindow_Param_NoNewHostiles": "Don't attack new mobs by aoe", + "Configwindow_Params_NoNewHostilesDesc": "Nerver use any AOE action when this action may attack the mobs that not is a hostile target.", + "Configwindow_Param_UseDefenceAbility": "Use defence abilities", + "Configwindow_Param_UseDefenceAbilityDesc": "It is recommended to check this option if you are playing Raids./nPlan the heal and defense by yourself.???", + "Configwindow_Param_AutoShield": "Auto tank stance", + "Configwindow_Param_AutoProvokeForTank": "Auto Provoke (Tank)", + "Configwindow_Param_AutoProvokeForTankDesc": "When a hostile is hitting the non-Tank member of party, it will automatically use the Provoke.", + "Configwindow_Param_AutoUseTrueNorth": "Auto TrueNorth (Melee)", + "Configwindow_Param_RaisePlayerBySwift": "Raise player by swift", + "Configwindow_Param_UseGroundBeneficialAbility": "Use beneficaial ground-targeted actions", + "Configwindow_Param_RaisePlayerByCasting": "Raise player by casting when swift is in cooldown", + "Configwindow_Param_UseHealWhenNotAHealer": "Use heal when not-healer", + "Configwindow_Param_LessMPNoRaise": "Nerver raise player if MP is less than the set value", + "Configwindow_Param_UseItem": "Use items", + "Configwindow_Param_UseItemDesc": "Use poison, WIP", + "Configwindow_Param_Conditon": "Condition", + "Configwindow_Param_StartOnCountdown": "Turn on auto-rotation on countdown", + "Configwindow_Param_EsunaAll": "Esuna All Statuses.", + "Configwindow_Param_InterruptibleMoreCheck": "Interrupt the action with action type check.", + "Configwindow_Param_HealOutOfCombat": "Heal party members outside of combat.", + "Configwindow_Param_HealthDifference": "Set the HP standard deviation threshold for using AOE heal (ability & spell)", + "Configwindow_Param_HealthAreaAbility": "Set the HP threshold for using AOE healing ability", + "Configwindow_Param_HealthAreaSpell": "Set the HP threshold for using AOE healing spell", + "Configwindow_Param_HealingOfTimeSubtractArea": "Set the HP threshold reduce with hot effect(AOE)", + "Configwindow_Param_HealthSingleAbility": "Set the HP threshold for using single healing ability", + "Configwindow_Param_HealthSingleSpell": "Set the HP threshold for using single healing spell", + "Configwindow_Param_HealingOfTimeSubtractSingle": "Set the HP threshold reduce with hot effect(single)", + "Configwindow_Param_HealthForDyingTank": "Set the HP threshold for tank to use invincibility", + "Configwindow_Param_Target": "Target", + "Configwindow_Param_RightNowTargetToHostileType": "Hostile target filtering condition", + "Configwindow_Param_TargetToHostileType1": "All targets can attack", + "Configwindow_Param_TargetToHostileType2": "Targets have a target or all targets can attack", + "Configwindow_Param_TargetToHostileType3": "Targets have a target", + "Configwindow_Param_NoticeUnexpectedCombat": "NOTICE: You are not turn the auto off between area on. It may start a combat unexpectedly.", + "Configwindow_Param_AddEnemyListToHostile": "Add Enemies list to the hostile target.", + "Configwindow_Param_ChooseAttackMark": "Priority attack targets with attack markers", + "Configwindow_Param_CanAttackMarkAOE": "Forced use of AOE", + "Configwindow_Param_AttackMarkAOEDesc": "Attention: Checking this option , AA will attack as many hostile targets as possible, while ignoring whether the attack will cover the marked target.", + "Configwindow_Param_FilterStopMark": "Never attack targets with stop markers", + "Configwindow_Param_ObjectMinRadius": "Set the minimum target circle threshold possessed by the attack target", + "Configwindow_Param_MoveTargetAngle": "The size of the sector angle that can be selected as the moveable target", + "Configwindow_Param_MoveTargetAngleDesc": "If the selection mode is based on character facing, i.e., targets within the character's viewpoint are movable targets. \nIf the selection mode is screen-centered, i.e., targets within a sector drawn upward from the character's point are movable targets.", + "Configwindow_Param_ChangeTargetForFate": "Select only Fate targets in Fate", + "Configwindow_Param_OnlyAttackInView": "Only attack the target in view.", + "Configwindow_Param_MoveTowardsScreen": "Using movement actions towards the object in the center of the screen", + "Configwindow_Param_MoveTowardsScreenDesc": "Using movement actions towards the object in the center of the screen, otherwise toward the facing object.", + "Configwindow_Param_RaiseAll": "Raise all (include passerby)", + "Configwindow_Param_TargetFriendly": "Target all for friendly actions(include passerby)", + "Configwindow_Param_RaiseBrinkofDeath": "Raise player even has Brink of Death", + "Configwindow_Param_MoveAreaActionFarthest": "Moving Area Ability to farthest", + "Configwindow_Param_MoveAreaActionFarthestDesc": "Move to the furthest position from character's face direction.", + "Configwindow_Param_Hostile": "Hostile", + "Configwindow_Param_HostileDesc": "You can set the logic of hostile target selection to allow flexibility in switching the logic of selecting hostile in battle.", + "Configwindow_Param_AddHostileCondition": "Add selection condition", + "Configwindow_Param_HostileCondition": "Hostile target selection condition", + "Configwindow_Param_ConditionUp": "Up", + "Configwindow_Param_ConditionDown": "Down", + "Configwindow_Param_ConditionDelete": "Delete", + "Timeline_DragdropDescription": "Drag&drop to move,Ctrl+Alt+RightClick to delete.", + "Timeline_SearchBar": "Search Bar", + "Timeline_MustUse": "MustUse", + "Timeline_MustUseDesc": "Skip AOE and Buff.", + "Timeline_Empty": "UseUp", + "Timeline_EmptyDesc": "UseUp or Skip Combo", + "Timeline_TimelineDescription": "Add some condition to automatic use this action.", + "Timeline_Can": "Can", + "Timeline_Cannot": "Cannot", + "Timeline_Is": "Is", + "Timeline_Isnot": "Isnot", + "Timeline_Have": "Have", + "Timeline_Havenot": "Havenot", + "Timeline_Ability": "Ability", + "Timeline_Charges": "Charges", + "Timeline_ConditionSet": "ConditionSet", + "Timeline_ActionCondition": "ActionCondition", + "Timeline_TargetCondition": "TargetCondition", + "Timeline_RotationCondition": "RotationCondition", + "Timeline_ActionTarget": "{0}'s target", + "Timeline_Target": "Target", + "Timeline_Player": "Player", + "Timeline_StatusSelf": "StatusSelf", + "Timeline_StatusSelfDesc": "StatusSelf", + "Action_Friendly": "Support", + "Action_Attack": "Attack", + "ComboConditionType_Bool": "Boolean", + "ComboConditionType_Byte": "Byte", + "ComboConditionType_Time": "Time", + "ComboConditionType_GCD": "GCD", + "ComboConditionType_Last": "Last", + "TargetingType_Big": "Big", + "TargetingType_Small": "Small", + "TargetingType_HighHP": "High HP", + "TargetingType_LowHP": "Low HP", + "TargetingType_HighMaxHP": "High Max HP", + "TargetingType_LowMaxHP": "Low Max HP", + "SpecialCommandType_Start": "Start ", + "SpecialCommandType_HealArea": "Heal Area", + "SpecialCommandType_HealSingle": "Heal Single", + "SpecialCommandType_DefenseArea": "Defense Area", + "SpecialCommandType_DefenseSingle": "Defense Single", + "SpecialCommandType_TankStance": "Tank Stance", + "SpecialCommandType_MoveForward": "Move Forward", + "SpecialCommandType_MoveBack": "Move Back", + "SpecialCommandType_AntiKnockback": "Anti-Knockback", + "SpecialCommandType_Burst": "Burst", + "SpecialCommandType_EndSpecial": "End Special", + "SpecialCommandType_Smart": "Smart ", + "SpecialCommandType_Manual": "Manual", + "SpecialCommandType_Cancel": "Cancel", + "SpecialCommandType_Off": "Off", + "ActionConditionType_Elapsed": "Elapsed", + "ActionConditionType_ElapsedGCD": "ElapsedGCD ", + "ActionConditionType_Remain": "RemainTime", + "ActionConditionType_RemainGCD": "RemainGCD", + "ActionConditionType_ShouldUse": "ShouldUse", + "ActionConditionType_EnoughLevel": "EnoughLevel", + "ActionConditionType_IsCoolDown": "IsCoolDown", + "ActionConditionType_CurrentCharges": "CurrentCharges", + "ActionConditionType_MaxCharges": "MaxCharges", + "TargetConditionType_HaveStatus": "Have Status", + "TargetConditionType_IsDying": "Is Dying", + "TargetConditionType_IsBoss": "Is Boss", + "TargetConditionType_Distance": "Distance", + "TargetConditionType_StatusEnd": "Status End", + "TargetConditionType_StatusEndGCD": "Status End GCD", + "TargetConditionType_CastingAction": "Casting Action", + "DescType_BurstActions": "Burst Actions", + "DescType_MoveForwardGCD": "Move Forward GCD", + "DescType_HealAreaGCD": "Area Healing GCD", + "DescType_HealSingleGCD": "Single Healing GCD", + "DescType_DefenseAreaGCD": "Area Defense GCD", + "DescType_DefenseSingleGCD": "Single Defense GCD", + "DescType_HealAreaAbility": "Area Healing Ability", + "DescType_HealSingleAbility": "Single Healing Ability", + "DescType_DefenseAreaAbility": "Area Defense Ability", + "DescType_DefenseSingleAbility": "Single Defense Ability", + "DescType_MoveForwardAbility": "Move Forward Ability", + "DescType_MoveBackAbility": "Move Back Ability", + "JobRole_None": "Gathering&Production", + "JobRole_Tank": "Tank", + "JobRole_Melee": "Melee", + "JobRole_Ranged": "Ranged", + "JobRole_Healer": "Healer", + "JobRole_RangedPhysical": "Ranged", + "JobRole_RangedMagicial": "Magicial", + "JobRole_DiscipleoftheLand": "Disciple of the Land", + "JobRole_DiscipleoftheHand": "Disciple of the Hand", + "EnemyLocation_None": "None", + "EnemyLocation_Rear": "Rear", + "EnemyLocation_Flank": "Flank", + "EnemyLocation_Front": "Front", + "MemberInfoName": { + "IsMoving": "IsMoving", + "HaveHostilesInRange": "Have Hostiles InRange", + "IsFullParty": "Is Full Party", + "SettingBreak": "Breaking", + "Level": "Level", + "InCombat": "In Combat", + "IsLastGCD": "Just used GCD", + "IsLastAbility": "Just used Ability", + "IsLastAction": "Just used Action", + "IsTargetDying": "Target is dying", + "IsTargetBoss": "Target is Boss", + "HaveSwift": "Have Swift", + "HaveShield": "Have defensive stance", + "PlayCard": "Play", + "UmbralIceStacks": "Umbral Ice Stacks", + "AstralFireStacks": "Astral Fire Stacks", + "PolyglotStacks": "Polyglot Stacks", + "UmbralHearts": "Umbral Heart Stacks", + "IsParadoxActive": "Is Paradox Active ?", + "InUmbralIce": "In Umbral Ice", + "InAstralFire": "In Astral Fire", + "IsEnochianActive": "Is Enochian Active?", + "EnchinaEndAfter": "Enchina End After (s)", + "EnchinaEndAfterGCD": "Enchina End After (GCDs)", + "ElementTimeEndAfter": "Element Time End After (s)", + "ElementTimeEndAfterGCD": "Element Time End After (GCDs)", + "HasFire": "Has Firestarter", + "HasThunder": "Has Thunder", + "IsPolyglotStacksMaxed": "Whether Polyglot already has the maximum number of charge stacks at the current level", + "SoulVoice": "Soul Voice", + "SongEndAfter": "Song End After (s)", + "SongEndAfterGCD": "Song End After (GCDs)", + "Repertoire": "Song Gauge Stacks", + "IsDancing": "Is Dancing", + "Esprit": "Esprit", + "Feathers": "Feathers", + "CompletedSteps": "CompletedSteps", + "FinishStepGCD": "FinishStepGCD", + "ExcutionStepGCD": "Excution Step GCD", + "Blood": "Blood", + "HasDarkArts": "Has Dark Arts", + "DarkSideEndAfter": "DarkSideEndAfter", + "DarkSideEndAfterGCD": "DarkSideEndAfterGCD", + "Ammo": "Ammo", + "AmmoComboStep": "Ammo Combo Step", + "IsOverheated": "Is Over heated", + "Heat": "Heat", + "Battery": "Battery", + "OverheatedEndAfter": "Over heated End After (s)", + "OverheatedEndAfterGCD": "Over heated End After(GCDs)", + "Chakra": "Chakra" + }, + "MemberInfoDesc": { + "IsMoving": "Player Is Moving", + "HaveHostilesInRange": "Have Hostiles In Range(Melee <3m,Ranged<25m)", + "IsFullParty": "Is Full Party", + "SettingBreak": "In break", + "Level": "Player level", + "InCombat": "In Combat", + "IsLastGCD": "Just used GCD", + "IsLastAbility": "Just used ability", + "IsLastAction": "Just used Action", + "IsTargetDying": "Target is Dying", + "IsTargetBoss": "Target is Boss", + "HaveSwift": "Have Swift", + "HaveShield": "Have defensive stance", + "PlayCard": "Play", + "UmbralIceStacks": "Umbral Ice Stacks", + "AstralFireStacks": "Astral Fire Stacks", + "PolyglotStacks": "Polyglot Stacks", + "UmbralHearts": "Umbral Heart Stacks", + "IsParadoxActive": "Is Paradox Active?", + "InUmbralIce": "In Umbral Ice", + "InAstralFire": "In Astral Fire", + "IsEnochianActive": "Is Enochian Active?", + "EnchinaEndAfter": "Enchina End After (s)", + "EnchinaEndAfterGCD": "Enchina End After (GCDs)", + "ElementTimeEndAfter": "Element remaining time", + "ElementTimeEndAfterGCD": "Element remaining time", + "HasFire": "Has Firestarter", + "HasThunder": "Has Thunder", + "IsPolyglotStacksMaxed": "Whether Polyglot already has the maximum number of charge stacks at the current level", + "SoulVoice": "SoulVoice", + "SongEndAfter": "Song End After (s)", + "SongEndAfterGCD": "Song End After (GCDs)", + "Repertoire": "Song Gauge Stacks", + "IsDancing": "Is Dancing", + "Esprit": "Esprit", + "Feathers": "Feathers", + "CompletedSteps": "Completed Steps", + "FinishStepGCD": "Finish Step GCD", + "ExcutionStepGCD": "Excution Step GCD", + "Blood": "Blood", + "HasDarkArts": "Has Dark Arts", + "DarkSideEndAfter": "DarkSide End After (s)", + "DarkSideEndAfterGCD": "DarkSide End After (GCDs)", + "Ammo": "Ammo", + "AmmoComboStep": "Ammo Combo Step", + "IsOverheated": "Is Over heated", + "Heat": "Heat", + "Battery": "Battery", + "OverheatedEndAfter": "OverheatedEndAfter", + "OverheatedEndAfterGCD": "OverheatedEndAfterGCD", + "Chakra": "Chakra" + }, + "HighEndWarning": "You'd better not use Rotation Solver in {0}!" +} \ No newline at end of file diff --git a/RotationSolver.Old/Localization/es.json b/RotationSolver.Old/Localization/es.json new file mode 100644 index 000000000..8d1071717 --- /dev/null +++ b/RotationSolver.Old/Localization/es.json @@ -0,0 +1,364 @@ +{ + "Commands_Rotation": "Open config window.", + "Commands_ChangeAutoBurst": "Modify automatic burst to {0}", + "Commands_ChangeRotationConfig": "Modify {0} to {1}", + "Commands_CannotFindRotationConfig": "Failed to find the config in this rotation, please check it.", + "Commands_InsertAction": "Will use it within {0}s", + "Commands_InsertActionFailure": "Can not find the action, please check the action name.", + "Commands_SayHelloToAuthor": "This \"{0}\" is probably one of the authors of the \"Rotation Solver\", so say hello to him!", + "ConfigWindow_Header": "Rotation Solver Settings v", + "ConfigWindow_RotationItem": "Rotation", + "ConfigWindow_ParamItem": "Param", + "ConfigWindow_EventItem": "Event", + "ConfigWindow_ActionItem": "Action", + "ConfigWindow_HelpItem": "Help", + "ConfigWindow_ActionItem_Description": "Modify the usage for each action.", + "ConfigWindow_HelpItem_Description": "In this window, you can see all Rotation Solver built-in commands for combat. ", + "Configwindow_HelpItem_AttackSmart": "Start attacking in smart mode(auto-targeting) when out of combat, otherwise switch the target according to the conditions.", + "Configwindow_HelpItem_AttackManual": "Start attacking in manual mode. You need to choose the target manually.", + "Configwindow_HelpItem_AttackCancel": "Stop attacking. Remember to turn it off when not in use!", + "Configwindow_HelpItem_HealArea": "Open a window to use AoE heal.", + "Configwindow_HelpItem_HealSingle": "Open a window to use single heal.", + "Configwindow_HelpItem_DefenseArea": "Open a window to use AoE defense.", + "Configwindow_HelpItem_DefenseSingle": "Open a window to use single defense.", + "Configwindow_HelpItem_Esuna": "Open a window to use Esuna,tank stance actions or True North.", + "Configwindow_HelpItem_RaiseShirk": "Open a window to use Raise or Shirk.", + "Configwindow_HelpItem_AntiKnockback": "Open a window to use knockback-penalty actions.", + "Configwindow_HelpItem_Burst": "Open a window to burst.", + "Configwindow_HelpItem_MoveForward": "Open a window to move forward.", + "Configwindow_HelpItem_MoveBack": "Open a window to move back.", + "Configwindow_HelpItem_EndSpecial": "Close special window.", + "Configwindow_Helper_SwitchRotation": "Click to switch authors", + "Configwindow_Helper_GameVersion": "Game Version", + "Configwindow_Helper_OpenSource": "Open the source code URL", + "Configwindow_Helper_RunCommand": "Click to execute the command", + "Configwindow_Helper_CopyCommand": "Right-click to copy command", + "Configwindow_Helper_InsertCommand": "Insert \"{0}\" first in 5s", + "Configwindow_Rotation_Description": "You can enable the function for each job you want and configure the setting about how to use actions.", + "Configwindow_Rotation_KeyName": "The key name is", + "Configwindow_Events_AddEvent": "AddEvents", + "Configwindow_Events_Description": "In this window, you can set what macro will be trigger after using an action.", + "Configwindow_Events_ActionName": "Action Name", + "Configwindow_Events_MacroIndex": "Macro No.", + "Configwindow_Events_ShareMacro": "Is Shared", + "Configwindow_Events_RemoveEvent": "Delete Event", + "Configwindow_Events_DutyStart": "Duty Start: ", + "Configwindow_Events_DutyEnd": "Duty End: ", + "Configwindow_Params_Description": "In this window, you can set the parameters about the using way of actions.", + "Configwindow_Param_NeverReplaceIcon": "Never Replace Icons", + "Configwindow_Param_NeverReplaceIconDesc": "Icon replacement: Repose is automatically displayed as the next action to be used", + "Configwindow_Param_UseOverlayWindow": "Display Top Overlay", + "Configwindow_Param_UseOverlayWindowDesc": "This top window is used to display some extra information on your game window, such as target's positional, target and sub-target, etc.", + "Configwindow_Param_Basic": "Basic", + "Configwindow_Param_ActionAhead": "Set the time advance of using actions", + "Configwindow_Param_AbilitiesInterval": "Set the interval between 0GCD using", + "Configwindow_Param_CountDownAhead": "Set the time advance of using casting actions on counting down.", + "Configwindow_Param_SpecialDuration": "Set the duration of special windows set by commands", + "Configwindow_Param_AddDotGCDCount": "Set GCD advance of DOT refresh", + "Configwindow_Param_AutoOffBetweenArea": "Turn off when player is between area.", + "Configwindow_Param_UseWorkTask": "Use work task for acceleration.", + "Configwindow_Param_Delay": "Delay", + "Configwindow_Param_WeaponDelay": "Set the range of random delay for GCD in second.", + "Configwindow_Param_DeathDelay": "Set the range of random delay for raising deaths in second.", + "Configwindow_Param_HostileDelay": "Set the range of random delay for finding hostile targets in second.", + "Configwindow_Param_InterruptDelay": "Set the range of random delay for interrupting hostile targets in second.", + "Configwindow_Param_WeakenDelay": "Set the range of random delay for esuna weakens in second.", + "Configwindow_Param_HealDelay": "Set the range of random delay for healing people in second.", + "Configwindow_Param_NotInCombatDelay": "Set the range of random delay for Not In Combat in second.", + "Configwindow_Param_StopCastingDelay": "Set the range of random delay for stoping casting when target is no need to cast in second.", + "Configwindow_Param_Display": "Display", + "Configwindow_Param_Advanced": "Advanced", + "Configwindow_Param_PoslockCasting": "Lock the movement when casting.", + "Configwindow_Param_UseStopCasting": "Use stopping casting when target is dead.", + "Configwindow_Param_ShowActionFlag": "Show action flag.", + "Configwindow_Param_ShowHealthRatio": "Show the health ratio for the check of Boss, Dying, Dot.", + "Configwindow_Param_HealthRatioBoss": "If target's max health ratio is higher than this, regard it as Boss.", + "Configwindow_Param_HealthRatioDying": "If target's current health ratio is lower than this, regard it is dying.", + "Configwindow_Param_HealthRatioDot": "If target's current health ratio is higher than this, regard it can be dot.", + "Configwindow_Param_PoslockModifier": "Set the modifier key to unlock the movement temporary", + "Configwindow_Param_PoslockDescription": "LT is for gamepad player", + "Configwindow_Param_CastingDisplay": "Enhance castbar with casting status", + "Configwindow_Param_TeachingMode": "Teaching mode", + "Configwindow_Param_TeachingModeColor": "Prompt box color of teaching mode", + "Configwindow_Param_MovingTargetColor": "Prompt box color of moving target", + "Configwindow_Param_TargetColor": "Target color", + "Configwindow_Params_SubTargetColor": "Sub-target color", + "Configwindow_Param_KeyBoardNoise": "Simulate the effect of pressing", + "Configwindow_Params_VoiceVolume": "Voice volume", + "Configwindow_Param_FlytextPositional": "Hint positional anticipation by flytext", + "Configwindow_Param_SayPositional": "Hint positional anticipation by shouting", + "Configwindow_Param_PositionalFeedback": "Positional error feedback", + "Configwindow_Param_ShowMoveTarget": "Show the pointing target of the move skill", + "Configwindow_Param_ShowTarget": "Show Target", + "Configwindow_Param_PositionalFeedbackDesc": "Attention: Positional anticipation is experimental, just for reference only.", + "Configwindow_Param_PositionaErrorText": "Positional error prompt", + "Configwindow_Params_LocationWrongTextDesc": "How do you want to be scolded if you have a positional error ?!", + "Configwindow_Param_SayOutStateChanged": "Saying the state changes out", + "Configwindow_Param_ShowInfoOnDtr": "Display plugin state on dtrbar", + "Configwindow_Param_ShowWorkTaskFPS": "Display Task FPS on dtrbar", + "Configwindow_Param_ShowInfoOnToast": "Display plugin state on toast", + "Configwindow_Param_NamePlateIconId": "Player's name plate icon id when state is on. Recommand 61435, 61437", + "Configwindow_Param_Action": "Action", + "Configwindow_Param_UseAOEAction": "Use AOE actions", + "Configwindow_Param_UseAOEWhenManual": "Use AOE actions in manual mode", + "Configwindow_Param_AutoBurst": "Automatic burst", + "Configwindow_Param_UseAbility": "Auto-use abilities", + "Configwindow_Param_NoNewHostiles": "Don't attack new mobs by aoe", + "Configwindow_Params_NoNewHostilesDesc": "Nerver use any AOE action when this action may attack the mobs that not is a hostile target.", + "Configwindow_Param_UseDefenceAbility": "Use defence abilities", + "Configwindow_Param_UseDefenceAbilityDesc": "It is recommended to check this option if you are playing Raids./nPlan the heal and defense by yourself.???", + "Configwindow_Param_AutoShield": "Auto tank stance", + "Configwindow_Param_AutoProvokeForTank": "Auto Provoke (Tank)", + "Configwindow_Param_AutoProvokeForTankDesc": "When a hostile is hitting the non-Tank member of party, it will automatically use the Provoke.", + "Configwindow_Param_AutoUseTrueNorth": "Auto TrueNorth (Melee)", + "Configwindow_Param_RaisePlayerBySwift": "Raise player by swift", + "Configwindow_Param_UseGroundBeneficialAbility": "Use beneficaial ground-targeted actions", + "Configwindow_Param_RaisePlayerByCasting": "Raise player by casting when swift is in cooldown", + "Configwindow_Param_UseHealWhenNotAHealer": "Use heal when not-healer", + "Configwindow_Param_LessMPNoRaise": "Nerver raise player if MP is less than the set value", + "Configwindow_Param_UseItem": "Use items", + "Configwindow_Param_UseItemDesc": "Use poison, WIP", + "Configwindow_Param_Conditon": "Condition", + "Configwindow_Param_StartOnCountdown": "Turn on auto-rotation on countdown", + "Configwindow_Param_EsunaAll": "Esuna All Statuses.", + "Configwindow_Param_InterruptibleMoreCheck": "Interrupt the action with action type check.", + "Configwindow_Param_HealOutOfCombat": "Heal party members outside of combat.", + "Configwindow_Param_HealthDifference": "Set the HP standard deviation threshold for using AOE heal (ability & spell)", + "Configwindow_Param_HealthAreaAbility": "Set the HP threshold for using AOE healing ability", + "Configwindow_Param_HealthAreaSpell": "Set the HP threshold for using AOE healing spell", + "Configwindow_Param_HealingOfTimeSubtractArea": "Set the HP threshold reduce with hot effect(AOE)", + "Configwindow_Param_HealthSingleAbility": "Set the HP threshold for using single healing ability", + "Configwindow_Param_HealthSingleSpell": "Set the HP threshold for using single healing spell", + "Configwindow_Param_HealingOfTimeSubtractSingle": "Set the HP threshold reduce with hot effect(single)", + "Configwindow_Param_HealthForDyingTank": "Set the HP threshold for tank to use invincibility", + "Configwindow_Param_Target": "Target", + "Configwindow_Param_RightNowTargetToHostileType": "Hostile target filtering condition", + "Configwindow_Param_TargetToHostileType1": "All targets can attack", + "Configwindow_Param_TargetToHostileType2": "Targets have a target or all targets can attack", + "Configwindow_Param_TargetToHostileType3": "Targets have a target", + "Configwindow_Param_NoticeUnexpectedCombat": "NOTICE: You are not turn the auto off between area on. It may start a combat unexpectedly.", + "Configwindow_Param_AddEnemyListToHostile": "Add Enemies list to the hostile target.", + "Configwindow_Param_ChooseAttackMark": "Priority attack targets with attack markers", + "Configwindow_Param_CanAttackMarkAOE": "Forced use of AOE", + "Configwindow_Param_AttackMarkAOEDesc": "Attention: Checking this option , AA will attack as many hostile targets as possible, while ignoring whether the attack will cover the marked target.", + "Configwindow_Param_FilterStopMark": "Never attack targets with stop markers", + "Configwindow_Param_ObjectMinRadius": "Set the minimum target circle threshold possessed by the attack target", + "Configwindow_Param_MoveTargetAngle": "The size of the sector angle that can be selected as the moveable target", + "Configwindow_Param_MoveTargetAngleDesc": "If the selection mode is based on character facing, i.e., targets within the character's viewpoint are movable targets. \nIf the selection mode is screen-centered, i.e., targets within a sector drawn upward from the character's point are movable targets.", + "Configwindow_Param_ChangeTargetForFate": "Select only Fate targets in Fate", + "Configwindow_Param_OnlyAttackInView": "Only attack the target in view.", + "Configwindow_Param_MoveTowardsScreen": "Using movement actions towards the object in the center of the screen", + "Configwindow_Param_MoveTowardsScreenDesc": "Using movement actions towards the object in the center of the screen, otherwise toward the facing object.", + "Configwindow_Param_RaiseAll": "Raise all (include passerby)", + "Configwindow_Param_TargetFriendly": "Target all for friendly actions(include passerby)", + "Configwindow_Param_RaiseBrinkofDeath": "Raise player even has Brink of Death", + "Configwindow_Param_MoveAreaActionFarthest": "Moving Area Ability to farthest", + "Configwindow_Param_MoveAreaActionFarthestDesc": "Move to the furthest position from character's face direction.", + "Configwindow_Param_Hostile": "Hostile", + "Configwindow_Param_HostileDesc": "You can set the logic of hostile target selection to allow flexibility in switching the logic of selecting hostile in battle.", + "Configwindow_Param_AddHostileCondition": "Add selection condition", + "Configwindow_Param_HostileCondition": "Hostile target selection condition", + "Configwindow_Param_ConditionUp": "Up", + "Configwindow_Param_ConditionDown": "Down", + "Configwindow_Param_ConditionDelete": "Delete", + "Timeline_DragdropDescription": "Drag&drop to move,Ctrl+Alt+RightClick to delete.", + "Timeline_SearchBar": "Search Bar", + "Timeline_MustUse": "MustUse", + "Timeline_MustUseDesc": "Skip AOE and Buff.", + "Timeline_Empty": "UseUp", + "Timeline_EmptyDesc": "UseUp or Skip Combo", + "Timeline_TimelineDescription": "Add some condition to automatic use this action.", + "Timeline_Can": "Can", + "Timeline_Cannot": "Cannot", + "Timeline_Is": "Is", + "Timeline_Isnot": "Isnot", + "Timeline_Have": "Have", + "Timeline_Havenot": "Havenot", + "Timeline_Ability": "Ability", + "Timeline_Charges": "Charges", + "Timeline_ConditionSet": "ConditionSet", + "Timeline_ActionCondition": "ActionCondition", + "Timeline_TargetCondition": "TargetCondition", + "Timeline_RotationCondition": "RotationCondition", + "Timeline_ActionTarget": "{0}'s target", + "Timeline_Target": "Target", + "Timeline_Player": "Player", + "Timeline_StatusSelf": "StatusSelf", + "Timeline_StatusSelfDesc": "StatusSelf", + "Action_Friendly": "Support", + "Action_Attack": "Attack", + "ComboConditionType_Bool": "Boolean", + "ComboConditionType_Byte": "Byte", + "ComboConditionType_Time": "Time", + "ComboConditionType_GCD": "GCD", + "ComboConditionType_Last": "Last", + "TargetingType_Big": "Big", + "TargetingType_Small": "Small", + "TargetingType_HighHP": "High HP", + "TargetingType_LowHP": "Low HP", + "TargetingType_HighMaxHP": "High Max HP", + "TargetingType_LowMaxHP": "Low Max HP", + "SpecialCommandType_Start": "Start ", + "SpecialCommandType_HealArea": "Heal Area", + "SpecialCommandType_HealSingle": "Heal Single", + "SpecialCommandType_DefenseArea": "Defense Area", + "SpecialCommandType_DefenseSingle": "Defense Single", + "SpecialCommandType_TankStance": "Tank Stance", + "SpecialCommandType_MoveForward": "Move Forward", + "SpecialCommandType_MoveBack": "Move Back", + "SpecialCommandType_AntiKnockback": "Anti-Knockback", + "SpecialCommandType_Burst": "Burst", + "SpecialCommandType_EndSpecial": "End Special", + "SpecialCommandType_Smart": "Smart ", + "SpecialCommandType_Manual": "Manual", + "SpecialCommandType_Cancel": "Cancel", + "SpecialCommandType_Off": "Off", + "ActionConditionType_Elapsed": "Elapsed", + "ActionConditionType_ElapsedGCD": "ElapsedGCD ", + "ActionConditionType_Remain": "RemainTime", + "ActionConditionType_RemainGCD": "RemainGCD", + "ActionConditionType_ShouldUse": "ShouldUse", + "ActionConditionType_EnoughLevel": "EnoughLevel", + "ActionConditionType_IsCoolDown": "IsCoolDown", + "ActionConditionType_CurrentCharges": "CurrentCharges", + "ActionConditionType_MaxCharges": "MaxCharges", + "TargetConditionType_HaveStatus": "Have Status", + "TargetConditionType_IsDying": "Is Dying", + "TargetConditionType_IsBoss": "Is Boss", + "TargetConditionType_Distance": "Distance", + "TargetConditionType_StatusEnd": "Status End", + "TargetConditionType_StatusEndGCD": "Status End GCD", + "TargetConditionType_CastingAction": "Casting Action", + "DescType_BurstActions": "Burst Actions", + "DescType_MoveForwardGCD": "Move Forward GCD", + "DescType_HealAreaGCD": "Area Healing GCD", + "DescType_HealSingleGCD": "Single Healing GCD", + "DescType_DefenseAreaGCD": "Area Defense GCD", + "DescType_DefenseSingleGCD": "Single Defense GCD", + "DescType_HealAreaAbility": "Area Healing Ability", + "DescType_HealSingleAbility": "Single Healing Ability", + "DescType_DefenseAreaAbility": "Area Defense Ability", + "DescType_DefenseSingleAbility": "Single Defense Ability", + "DescType_MoveForwardAbility": "Move Forward Ability", + "DescType_MoveBackAbility": "Move Back Ability", + "JobRole_None": "Gathering&Production", + "JobRole_Tank": "Tank", + "JobRole_Melee": "Melee", + "JobRole_Ranged": "Ranged", + "JobRole_Healer": "Healer", + "JobRole_RangedPhysical": "Ranged", + "JobRole_RangedMagicial": "Magicial", + "JobRole_DiscipleoftheLand": "Disciple of the Land", + "JobRole_DiscipleoftheHand": "Disciple of the Hand", + "EnemyLocation_None": "None", + "EnemyLocation_Rear": "Rear", + "EnemyLocation_Flank": "Flank", + "EnemyLocation_Front": "Front", + "MemberInfoName": { + "IsMoving": "IsMoving", + "HaveHostilesInRange": "Have Hostiles InRange", + "IsFullParty": "Is Full Party", + "SettingBreak": "Breaking", + "Level": "Level", + "InCombat": "In Combat", + "IsLastGCD": "Just used GCD", + "IsLastAbility": "Just used Ability", + "IsLastAction": "Just used Action", + "IsTargetDying": "Target is dying", + "IsTargetBoss": "Target is Boss", + "HaveSwift": "Have Swift", + "HaveShield": "Have defensive stance", + "PlayCard": "Play", + "UmbralIceStacks": "Umbral Ice Stacks", + "AstralFireStacks": "Astral Fire Stacks", + "PolyglotStacks": "Polyglot Stacks", + "UmbralHearts": "Umbral Heart Stacks", + "IsParadoxActive": "Is Paradox Active ?", + "InUmbralIce": "In Umbral Ice", + "InAstralFire": "In Astral Fire", + "IsEnochianActive": "Is Enochian Active?", + "EnchinaEndAfter": "Enchina End After (s)", + "EnchinaEndAfterGCD": "Enchina End After (GCDs)", + "ElementTimeEndAfter": "Element Time End After (s)", + "ElementTimeEndAfterGCD": "Element Time End After (GCDs)", + "HasFire": "Has Firestarter", + "HasThunder": "Has Thunder", + "IsPolyglotStacksMaxed": "Whether Polyglot already has the maximum number of charge stacks at the current level", + "SoulVoice": "Soul Voice", + "SongEndAfter": "Song End After (s)", + "SongEndAfterGCD": "Song End After (GCDs)", + "Repertoire": "Song Gauge Stacks", + "IsDancing": "Is Dancing", + "Esprit": "Esprit", + "Feathers": "Feathers", + "CompletedSteps": "CompletedSteps", + "FinishStepGCD": "FinishStepGCD", + "ExcutionStepGCD": "Excution Step GCD", + "Blood": "Blood", + "HasDarkArts": "Has Dark Arts", + "DarkSideEndAfter": "DarkSideEndAfter", + "DarkSideEndAfterGCD": "DarkSideEndAfterGCD", + "Ammo": "Ammo", + "AmmoComboStep": "Ammo Combo Step", + "IsOverheated": "Is Over heated", + "Heat": "Heat", + "Battery": "Battery", + "OverheatedEndAfter": "Over heated End After (s)", + "OverheatedEndAfterGCD": "Over heated End After(GCDs)", + "Chakra": "Chakra" + }, + "MemberInfoDesc": { + "IsMoving": "Player Is Moving", + "HaveHostilesInRange": "Have Hostiles In Range(Melee <3m,Ranged<25m)", + "IsFullParty": "Is Full Party", + "SettingBreak": "In break", + "Level": "Player level", + "InCombat": "In Combat", + "IsLastGCD": "Just used GCD", + "IsLastAbility": "Just used ability", + "IsLastAction": "Just used Action", + "IsTargetDying": "Target is Dying", + "IsTargetBoss": "Target is Boss", + "HaveSwift": "Have Swift", + "HaveShield": "Have defensive stance", + "PlayCard": "Play", + "UmbralIceStacks": "Umbral Ice Stacks", + "AstralFireStacks": "Astral Fire Stacks", + "PolyglotStacks": "Polyglot Stacks", + "UmbralHearts": "Umbral Heart Stacks", + "IsParadoxActive": "Is Paradox Active?", + "InUmbralIce": "In Umbral Ice", + "InAstralFire": "In Astral Fire", + "IsEnochianActive": "Is Enochian Active?", + "EnchinaEndAfter": "Enchina End After (s)", + "EnchinaEndAfterGCD": "Enchina End After (GCDs)", + "ElementTimeEndAfter": "Element remaining time", + "ElementTimeEndAfterGCD": "Element remaining time", + "HasFire": "Has Firestarter", + "HasThunder": "Has Thunder", + "IsPolyglotStacksMaxed": "Whether Polyglot already has the maximum number of charge stacks at the current level", + "SoulVoice": "SoulVoice", + "SongEndAfter": "Song End After (s)", + "SongEndAfterGCD": "Song End After (GCDs)", + "Repertoire": "Song Gauge Stacks", + "IsDancing": "Is Dancing", + "Esprit": "Esprit", + "Feathers": "Feathers", + "CompletedSteps": "Completed Steps", + "FinishStepGCD": "Finish Step GCD", + "ExcutionStepGCD": "Excution Step GCD", + "Blood": "Blood", + "HasDarkArts": "Has Dark Arts", + "DarkSideEndAfter": "DarkSide End After (s)", + "DarkSideEndAfterGCD": "DarkSide End After (GCDs)", + "Ammo": "Ammo", + "AmmoComboStep": "Ammo Combo Step", + "IsOverheated": "Is Over heated", + "Heat": "Heat", + "Battery": "Battery", + "OverheatedEndAfter": "OverheatedEndAfter", + "OverheatedEndAfterGCD": "OverheatedEndAfterGCD", + "Chakra": "Chakra" + }, + "HighEndWarning": "You'd better not use Rotation Solver in {0}!" +} \ No newline at end of file diff --git a/RotationSolver.Old/Localization/fr.json b/RotationSolver.Old/Localization/fr.json new file mode 100644 index 000000000..8d1071717 --- /dev/null +++ b/RotationSolver.Old/Localization/fr.json @@ -0,0 +1,364 @@ +{ + "Commands_Rotation": "Open config window.", + "Commands_ChangeAutoBurst": "Modify automatic burst to {0}", + "Commands_ChangeRotationConfig": "Modify {0} to {1}", + "Commands_CannotFindRotationConfig": "Failed to find the config in this rotation, please check it.", + "Commands_InsertAction": "Will use it within {0}s", + "Commands_InsertActionFailure": "Can not find the action, please check the action name.", + "Commands_SayHelloToAuthor": "This \"{0}\" is probably one of the authors of the \"Rotation Solver\", so say hello to him!", + "ConfigWindow_Header": "Rotation Solver Settings v", + "ConfigWindow_RotationItem": "Rotation", + "ConfigWindow_ParamItem": "Param", + "ConfigWindow_EventItem": "Event", + "ConfigWindow_ActionItem": "Action", + "ConfigWindow_HelpItem": "Help", + "ConfigWindow_ActionItem_Description": "Modify the usage for each action.", + "ConfigWindow_HelpItem_Description": "In this window, you can see all Rotation Solver built-in commands for combat. ", + "Configwindow_HelpItem_AttackSmart": "Start attacking in smart mode(auto-targeting) when out of combat, otherwise switch the target according to the conditions.", + "Configwindow_HelpItem_AttackManual": "Start attacking in manual mode. You need to choose the target manually.", + "Configwindow_HelpItem_AttackCancel": "Stop attacking. Remember to turn it off when not in use!", + "Configwindow_HelpItem_HealArea": "Open a window to use AoE heal.", + "Configwindow_HelpItem_HealSingle": "Open a window to use single heal.", + "Configwindow_HelpItem_DefenseArea": "Open a window to use AoE defense.", + "Configwindow_HelpItem_DefenseSingle": "Open a window to use single defense.", + "Configwindow_HelpItem_Esuna": "Open a window to use Esuna,tank stance actions or True North.", + "Configwindow_HelpItem_RaiseShirk": "Open a window to use Raise or Shirk.", + "Configwindow_HelpItem_AntiKnockback": "Open a window to use knockback-penalty actions.", + "Configwindow_HelpItem_Burst": "Open a window to burst.", + "Configwindow_HelpItem_MoveForward": "Open a window to move forward.", + "Configwindow_HelpItem_MoveBack": "Open a window to move back.", + "Configwindow_HelpItem_EndSpecial": "Close special window.", + "Configwindow_Helper_SwitchRotation": "Click to switch authors", + "Configwindow_Helper_GameVersion": "Game Version", + "Configwindow_Helper_OpenSource": "Open the source code URL", + "Configwindow_Helper_RunCommand": "Click to execute the command", + "Configwindow_Helper_CopyCommand": "Right-click to copy command", + "Configwindow_Helper_InsertCommand": "Insert \"{0}\" first in 5s", + "Configwindow_Rotation_Description": "You can enable the function for each job you want and configure the setting about how to use actions.", + "Configwindow_Rotation_KeyName": "The key name is", + "Configwindow_Events_AddEvent": "AddEvents", + "Configwindow_Events_Description": "In this window, you can set what macro will be trigger after using an action.", + "Configwindow_Events_ActionName": "Action Name", + "Configwindow_Events_MacroIndex": "Macro No.", + "Configwindow_Events_ShareMacro": "Is Shared", + "Configwindow_Events_RemoveEvent": "Delete Event", + "Configwindow_Events_DutyStart": "Duty Start: ", + "Configwindow_Events_DutyEnd": "Duty End: ", + "Configwindow_Params_Description": "In this window, you can set the parameters about the using way of actions.", + "Configwindow_Param_NeverReplaceIcon": "Never Replace Icons", + "Configwindow_Param_NeverReplaceIconDesc": "Icon replacement: Repose is automatically displayed as the next action to be used", + "Configwindow_Param_UseOverlayWindow": "Display Top Overlay", + "Configwindow_Param_UseOverlayWindowDesc": "This top window is used to display some extra information on your game window, such as target's positional, target and sub-target, etc.", + "Configwindow_Param_Basic": "Basic", + "Configwindow_Param_ActionAhead": "Set the time advance of using actions", + "Configwindow_Param_AbilitiesInterval": "Set the interval between 0GCD using", + "Configwindow_Param_CountDownAhead": "Set the time advance of using casting actions on counting down.", + "Configwindow_Param_SpecialDuration": "Set the duration of special windows set by commands", + "Configwindow_Param_AddDotGCDCount": "Set GCD advance of DOT refresh", + "Configwindow_Param_AutoOffBetweenArea": "Turn off when player is between area.", + "Configwindow_Param_UseWorkTask": "Use work task for acceleration.", + "Configwindow_Param_Delay": "Delay", + "Configwindow_Param_WeaponDelay": "Set the range of random delay for GCD in second.", + "Configwindow_Param_DeathDelay": "Set the range of random delay for raising deaths in second.", + "Configwindow_Param_HostileDelay": "Set the range of random delay for finding hostile targets in second.", + "Configwindow_Param_InterruptDelay": "Set the range of random delay for interrupting hostile targets in second.", + "Configwindow_Param_WeakenDelay": "Set the range of random delay for esuna weakens in second.", + "Configwindow_Param_HealDelay": "Set the range of random delay for healing people in second.", + "Configwindow_Param_NotInCombatDelay": "Set the range of random delay for Not In Combat in second.", + "Configwindow_Param_StopCastingDelay": "Set the range of random delay for stoping casting when target is no need to cast in second.", + "Configwindow_Param_Display": "Display", + "Configwindow_Param_Advanced": "Advanced", + "Configwindow_Param_PoslockCasting": "Lock the movement when casting.", + "Configwindow_Param_UseStopCasting": "Use stopping casting when target is dead.", + "Configwindow_Param_ShowActionFlag": "Show action flag.", + "Configwindow_Param_ShowHealthRatio": "Show the health ratio for the check of Boss, Dying, Dot.", + "Configwindow_Param_HealthRatioBoss": "If target's max health ratio is higher than this, regard it as Boss.", + "Configwindow_Param_HealthRatioDying": "If target's current health ratio is lower than this, regard it is dying.", + "Configwindow_Param_HealthRatioDot": "If target's current health ratio is higher than this, regard it can be dot.", + "Configwindow_Param_PoslockModifier": "Set the modifier key to unlock the movement temporary", + "Configwindow_Param_PoslockDescription": "LT is for gamepad player", + "Configwindow_Param_CastingDisplay": "Enhance castbar with casting status", + "Configwindow_Param_TeachingMode": "Teaching mode", + "Configwindow_Param_TeachingModeColor": "Prompt box color of teaching mode", + "Configwindow_Param_MovingTargetColor": "Prompt box color of moving target", + "Configwindow_Param_TargetColor": "Target color", + "Configwindow_Params_SubTargetColor": "Sub-target color", + "Configwindow_Param_KeyBoardNoise": "Simulate the effect of pressing", + "Configwindow_Params_VoiceVolume": "Voice volume", + "Configwindow_Param_FlytextPositional": "Hint positional anticipation by flytext", + "Configwindow_Param_SayPositional": "Hint positional anticipation by shouting", + "Configwindow_Param_PositionalFeedback": "Positional error feedback", + "Configwindow_Param_ShowMoveTarget": "Show the pointing target of the move skill", + "Configwindow_Param_ShowTarget": "Show Target", + "Configwindow_Param_PositionalFeedbackDesc": "Attention: Positional anticipation is experimental, just for reference only.", + "Configwindow_Param_PositionaErrorText": "Positional error prompt", + "Configwindow_Params_LocationWrongTextDesc": "How do you want to be scolded if you have a positional error ?!", + "Configwindow_Param_SayOutStateChanged": "Saying the state changes out", + "Configwindow_Param_ShowInfoOnDtr": "Display plugin state on dtrbar", + "Configwindow_Param_ShowWorkTaskFPS": "Display Task FPS on dtrbar", + "Configwindow_Param_ShowInfoOnToast": "Display plugin state on toast", + "Configwindow_Param_NamePlateIconId": "Player's name plate icon id when state is on. Recommand 61435, 61437", + "Configwindow_Param_Action": "Action", + "Configwindow_Param_UseAOEAction": "Use AOE actions", + "Configwindow_Param_UseAOEWhenManual": "Use AOE actions in manual mode", + "Configwindow_Param_AutoBurst": "Automatic burst", + "Configwindow_Param_UseAbility": "Auto-use abilities", + "Configwindow_Param_NoNewHostiles": "Don't attack new mobs by aoe", + "Configwindow_Params_NoNewHostilesDesc": "Nerver use any AOE action when this action may attack the mobs that not is a hostile target.", + "Configwindow_Param_UseDefenceAbility": "Use defence abilities", + "Configwindow_Param_UseDefenceAbilityDesc": "It is recommended to check this option if you are playing Raids./nPlan the heal and defense by yourself.???", + "Configwindow_Param_AutoShield": "Auto tank stance", + "Configwindow_Param_AutoProvokeForTank": "Auto Provoke (Tank)", + "Configwindow_Param_AutoProvokeForTankDesc": "When a hostile is hitting the non-Tank member of party, it will automatically use the Provoke.", + "Configwindow_Param_AutoUseTrueNorth": "Auto TrueNorth (Melee)", + "Configwindow_Param_RaisePlayerBySwift": "Raise player by swift", + "Configwindow_Param_UseGroundBeneficialAbility": "Use beneficaial ground-targeted actions", + "Configwindow_Param_RaisePlayerByCasting": "Raise player by casting when swift is in cooldown", + "Configwindow_Param_UseHealWhenNotAHealer": "Use heal when not-healer", + "Configwindow_Param_LessMPNoRaise": "Nerver raise player if MP is less than the set value", + "Configwindow_Param_UseItem": "Use items", + "Configwindow_Param_UseItemDesc": "Use poison, WIP", + "Configwindow_Param_Conditon": "Condition", + "Configwindow_Param_StartOnCountdown": "Turn on auto-rotation on countdown", + "Configwindow_Param_EsunaAll": "Esuna All Statuses.", + "Configwindow_Param_InterruptibleMoreCheck": "Interrupt the action with action type check.", + "Configwindow_Param_HealOutOfCombat": "Heal party members outside of combat.", + "Configwindow_Param_HealthDifference": "Set the HP standard deviation threshold for using AOE heal (ability & spell)", + "Configwindow_Param_HealthAreaAbility": "Set the HP threshold for using AOE healing ability", + "Configwindow_Param_HealthAreaSpell": "Set the HP threshold for using AOE healing spell", + "Configwindow_Param_HealingOfTimeSubtractArea": "Set the HP threshold reduce with hot effect(AOE)", + "Configwindow_Param_HealthSingleAbility": "Set the HP threshold for using single healing ability", + "Configwindow_Param_HealthSingleSpell": "Set the HP threshold for using single healing spell", + "Configwindow_Param_HealingOfTimeSubtractSingle": "Set the HP threshold reduce with hot effect(single)", + "Configwindow_Param_HealthForDyingTank": "Set the HP threshold for tank to use invincibility", + "Configwindow_Param_Target": "Target", + "Configwindow_Param_RightNowTargetToHostileType": "Hostile target filtering condition", + "Configwindow_Param_TargetToHostileType1": "All targets can attack", + "Configwindow_Param_TargetToHostileType2": "Targets have a target or all targets can attack", + "Configwindow_Param_TargetToHostileType3": "Targets have a target", + "Configwindow_Param_NoticeUnexpectedCombat": "NOTICE: You are not turn the auto off between area on. It may start a combat unexpectedly.", + "Configwindow_Param_AddEnemyListToHostile": "Add Enemies list to the hostile target.", + "Configwindow_Param_ChooseAttackMark": "Priority attack targets with attack markers", + "Configwindow_Param_CanAttackMarkAOE": "Forced use of AOE", + "Configwindow_Param_AttackMarkAOEDesc": "Attention: Checking this option , AA will attack as many hostile targets as possible, while ignoring whether the attack will cover the marked target.", + "Configwindow_Param_FilterStopMark": "Never attack targets with stop markers", + "Configwindow_Param_ObjectMinRadius": "Set the minimum target circle threshold possessed by the attack target", + "Configwindow_Param_MoveTargetAngle": "The size of the sector angle that can be selected as the moveable target", + "Configwindow_Param_MoveTargetAngleDesc": "If the selection mode is based on character facing, i.e., targets within the character's viewpoint are movable targets. \nIf the selection mode is screen-centered, i.e., targets within a sector drawn upward from the character's point are movable targets.", + "Configwindow_Param_ChangeTargetForFate": "Select only Fate targets in Fate", + "Configwindow_Param_OnlyAttackInView": "Only attack the target in view.", + "Configwindow_Param_MoveTowardsScreen": "Using movement actions towards the object in the center of the screen", + "Configwindow_Param_MoveTowardsScreenDesc": "Using movement actions towards the object in the center of the screen, otherwise toward the facing object.", + "Configwindow_Param_RaiseAll": "Raise all (include passerby)", + "Configwindow_Param_TargetFriendly": "Target all for friendly actions(include passerby)", + "Configwindow_Param_RaiseBrinkofDeath": "Raise player even has Brink of Death", + "Configwindow_Param_MoveAreaActionFarthest": "Moving Area Ability to farthest", + "Configwindow_Param_MoveAreaActionFarthestDesc": "Move to the furthest position from character's face direction.", + "Configwindow_Param_Hostile": "Hostile", + "Configwindow_Param_HostileDesc": "You can set the logic of hostile target selection to allow flexibility in switching the logic of selecting hostile in battle.", + "Configwindow_Param_AddHostileCondition": "Add selection condition", + "Configwindow_Param_HostileCondition": "Hostile target selection condition", + "Configwindow_Param_ConditionUp": "Up", + "Configwindow_Param_ConditionDown": "Down", + "Configwindow_Param_ConditionDelete": "Delete", + "Timeline_DragdropDescription": "Drag&drop to move,Ctrl+Alt+RightClick to delete.", + "Timeline_SearchBar": "Search Bar", + "Timeline_MustUse": "MustUse", + "Timeline_MustUseDesc": "Skip AOE and Buff.", + "Timeline_Empty": "UseUp", + "Timeline_EmptyDesc": "UseUp or Skip Combo", + "Timeline_TimelineDescription": "Add some condition to automatic use this action.", + "Timeline_Can": "Can", + "Timeline_Cannot": "Cannot", + "Timeline_Is": "Is", + "Timeline_Isnot": "Isnot", + "Timeline_Have": "Have", + "Timeline_Havenot": "Havenot", + "Timeline_Ability": "Ability", + "Timeline_Charges": "Charges", + "Timeline_ConditionSet": "ConditionSet", + "Timeline_ActionCondition": "ActionCondition", + "Timeline_TargetCondition": "TargetCondition", + "Timeline_RotationCondition": "RotationCondition", + "Timeline_ActionTarget": "{0}'s target", + "Timeline_Target": "Target", + "Timeline_Player": "Player", + "Timeline_StatusSelf": "StatusSelf", + "Timeline_StatusSelfDesc": "StatusSelf", + "Action_Friendly": "Support", + "Action_Attack": "Attack", + "ComboConditionType_Bool": "Boolean", + "ComboConditionType_Byte": "Byte", + "ComboConditionType_Time": "Time", + "ComboConditionType_GCD": "GCD", + "ComboConditionType_Last": "Last", + "TargetingType_Big": "Big", + "TargetingType_Small": "Small", + "TargetingType_HighHP": "High HP", + "TargetingType_LowHP": "Low HP", + "TargetingType_HighMaxHP": "High Max HP", + "TargetingType_LowMaxHP": "Low Max HP", + "SpecialCommandType_Start": "Start ", + "SpecialCommandType_HealArea": "Heal Area", + "SpecialCommandType_HealSingle": "Heal Single", + "SpecialCommandType_DefenseArea": "Defense Area", + "SpecialCommandType_DefenseSingle": "Defense Single", + "SpecialCommandType_TankStance": "Tank Stance", + "SpecialCommandType_MoveForward": "Move Forward", + "SpecialCommandType_MoveBack": "Move Back", + "SpecialCommandType_AntiKnockback": "Anti-Knockback", + "SpecialCommandType_Burst": "Burst", + "SpecialCommandType_EndSpecial": "End Special", + "SpecialCommandType_Smart": "Smart ", + "SpecialCommandType_Manual": "Manual", + "SpecialCommandType_Cancel": "Cancel", + "SpecialCommandType_Off": "Off", + "ActionConditionType_Elapsed": "Elapsed", + "ActionConditionType_ElapsedGCD": "ElapsedGCD ", + "ActionConditionType_Remain": "RemainTime", + "ActionConditionType_RemainGCD": "RemainGCD", + "ActionConditionType_ShouldUse": "ShouldUse", + "ActionConditionType_EnoughLevel": "EnoughLevel", + "ActionConditionType_IsCoolDown": "IsCoolDown", + "ActionConditionType_CurrentCharges": "CurrentCharges", + "ActionConditionType_MaxCharges": "MaxCharges", + "TargetConditionType_HaveStatus": "Have Status", + "TargetConditionType_IsDying": "Is Dying", + "TargetConditionType_IsBoss": "Is Boss", + "TargetConditionType_Distance": "Distance", + "TargetConditionType_StatusEnd": "Status End", + "TargetConditionType_StatusEndGCD": "Status End GCD", + "TargetConditionType_CastingAction": "Casting Action", + "DescType_BurstActions": "Burst Actions", + "DescType_MoveForwardGCD": "Move Forward GCD", + "DescType_HealAreaGCD": "Area Healing GCD", + "DescType_HealSingleGCD": "Single Healing GCD", + "DescType_DefenseAreaGCD": "Area Defense GCD", + "DescType_DefenseSingleGCD": "Single Defense GCD", + "DescType_HealAreaAbility": "Area Healing Ability", + "DescType_HealSingleAbility": "Single Healing Ability", + "DescType_DefenseAreaAbility": "Area Defense Ability", + "DescType_DefenseSingleAbility": "Single Defense Ability", + "DescType_MoveForwardAbility": "Move Forward Ability", + "DescType_MoveBackAbility": "Move Back Ability", + "JobRole_None": "Gathering&Production", + "JobRole_Tank": "Tank", + "JobRole_Melee": "Melee", + "JobRole_Ranged": "Ranged", + "JobRole_Healer": "Healer", + "JobRole_RangedPhysical": "Ranged", + "JobRole_RangedMagicial": "Magicial", + "JobRole_DiscipleoftheLand": "Disciple of the Land", + "JobRole_DiscipleoftheHand": "Disciple of the Hand", + "EnemyLocation_None": "None", + "EnemyLocation_Rear": "Rear", + "EnemyLocation_Flank": "Flank", + "EnemyLocation_Front": "Front", + "MemberInfoName": { + "IsMoving": "IsMoving", + "HaveHostilesInRange": "Have Hostiles InRange", + "IsFullParty": "Is Full Party", + "SettingBreak": "Breaking", + "Level": "Level", + "InCombat": "In Combat", + "IsLastGCD": "Just used GCD", + "IsLastAbility": "Just used Ability", + "IsLastAction": "Just used Action", + "IsTargetDying": "Target is dying", + "IsTargetBoss": "Target is Boss", + "HaveSwift": "Have Swift", + "HaveShield": "Have defensive stance", + "PlayCard": "Play", + "UmbralIceStacks": "Umbral Ice Stacks", + "AstralFireStacks": "Astral Fire Stacks", + "PolyglotStacks": "Polyglot Stacks", + "UmbralHearts": "Umbral Heart Stacks", + "IsParadoxActive": "Is Paradox Active ?", + "InUmbralIce": "In Umbral Ice", + "InAstralFire": "In Astral Fire", + "IsEnochianActive": "Is Enochian Active?", + "EnchinaEndAfter": "Enchina End After (s)", + "EnchinaEndAfterGCD": "Enchina End After (GCDs)", + "ElementTimeEndAfter": "Element Time End After (s)", + "ElementTimeEndAfterGCD": "Element Time End After (GCDs)", + "HasFire": "Has Firestarter", + "HasThunder": "Has Thunder", + "IsPolyglotStacksMaxed": "Whether Polyglot already has the maximum number of charge stacks at the current level", + "SoulVoice": "Soul Voice", + "SongEndAfter": "Song End After (s)", + "SongEndAfterGCD": "Song End After (GCDs)", + "Repertoire": "Song Gauge Stacks", + "IsDancing": "Is Dancing", + "Esprit": "Esprit", + "Feathers": "Feathers", + "CompletedSteps": "CompletedSteps", + "FinishStepGCD": "FinishStepGCD", + "ExcutionStepGCD": "Excution Step GCD", + "Blood": "Blood", + "HasDarkArts": "Has Dark Arts", + "DarkSideEndAfter": "DarkSideEndAfter", + "DarkSideEndAfterGCD": "DarkSideEndAfterGCD", + "Ammo": "Ammo", + "AmmoComboStep": "Ammo Combo Step", + "IsOverheated": "Is Over heated", + "Heat": "Heat", + "Battery": "Battery", + "OverheatedEndAfter": "Over heated End After (s)", + "OverheatedEndAfterGCD": "Over heated End After(GCDs)", + "Chakra": "Chakra" + }, + "MemberInfoDesc": { + "IsMoving": "Player Is Moving", + "HaveHostilesInRange": "Have Hostiles In Range(Melee <3m,Ranged<25m)", + "IsFullParty": "Is Full Party", + "SettingBreak": "In break", + "Level": "Player level", + "InCombat": "In Combat", + "IsLastGCD": "Just used GCD", + "IsLastAbility": "Just used ability", + "IsLastAction": "Just used Action", + "IsTargetDying": "Target is Dying", + "IsTargetBoss": "Target is Boss", + "HaveSwift": "Have Swift", + "HaveShield": "Have defensive stance", + "PlayCard": "Play", + "UmbralIceStacks": "Umbral Ice Stacks", + "AstralFireStacks": "Astral Fire Stacks", + "PolyglotStacks": "Polyglot Stacks", + "UmbralHearts": "Umbral Heart Stacks", + "IsParadoxActive": "Is Paradox Active?", + "InUmbralIce": "In Umbral Ice", + "InAstralFire": "In Astral Fire", + "IsEnochianActive": "Is Enochian Active?", + "EnchinaEndAfter": "Enchina End After (s)", + "EnchinaEndAfterGCD": "Enchina End After (GCDs)", + "ElementTimeEndAfter": "Element remaining time", + "ElementTimeEndAfterGCD": "Element remaining time", + "HasFire": "Has Firestarter", + "HasThunder": "Has Thunder", + "IsPolyglotStacksMaxed": "Whether Polyglot already has the maximum number of charge stacks at the current level", + "SoulVoice": "SoulVoice", + "SongEndAfter": "Song End After (s)", + "SongEndAfterGCD": "Song End After (GCDs)", + "Repertoire": "Song Gauge Stacks", + "IsDancing": "Is Dancing", + "Esprit": "Esprit", + "Feathers": "Feathers", + "CompletedSteps": "Completed Steps", + "FinishStepGCD": "Finish Step GCD", + "ExcutionStepGCD": "Excution Step GCD", + "Blood": "Blood", + "HasDarkArts": "Has Dark Arts", + "DarkSideEndAfter": "DarkSide End After (s)", + "DarkSideEndAfterGCD": "DarkSide End After (GCDs)", + "Ammo": "Ammo", + "AmmoComboStep": "Ammo Combo Step", + "IsOverheated": "Is Over heated", + "Heat": "Heat", + "Battery": "Battery", + "OverheatedEndAfter": "OverheatedEndAfter", + "OverheatedEndAfterGCD": "OverheatedEndAfterGCD", + "Chakra": "Chakra" + }, + "HighEndWarning": "You'd better not use Rotation Solver in {0}!" +} \ No newline at end of file diff --git a/RotationSolver.Old/Localization/ja.json b/RotationSolver.Old/Localization/ja.json new file mode 100644 index 000000000..8d1071717 --- /dev/null +++ b/RotationSolver.Old/Localization/ja.json @@ -0,0 +1,364 @@ +{ + "Commands_Rotation": "Open config window.", + "Commands_ChangeAutoBurst": "Modify automatic burst to {0}", + "Commands_ChangeRotationConfig": "Modify {0} to {1}", + "Commands_CannotFindRotationConfig": "Failed to find the config in this rotation, please check it.", + "Commands_InsertAction": "Will use it within {0}s", + "Commands_InsertActionFailure": "Can not find the action, please check the action name.", + "Commands_SayHelloToAuthor": "This \"{0}\" is probably one of the authors of the \"Rotation Solver\", so say hello to him!", + "ConfigWindow_Header": "Rotation Solver Settings v", + "ConfigWindow_RotationItem": "Rotation", + "ConfigWindow_ParamItem": "Param", + "ConfigWindow_EventItem": "Event", + "ConfigWindow_ActionItem": "Action", + "ConfigWindow_HelpItem": "Help", + "ConfigWindow_ActionItem_Description": "Modify the usage for each action.", + "ConfigWindow_HelpItem_Description": "In this window, you can see all Rotation Solver built-in commands for combat. ", + "Configwindow_HelpItem_AttackSmart": "Start attacking in smart mode(auto-targeting) when out of combat, otherwise switch the target according to the conditions.", + "Configwindow_HelpItem_AttackManual": "Start attacking in manual mode. You need to choose the target manually.", + "Configwindow_HelpItem_AttackCancel": "Stop attacking. Remember to turn it off when not in use!", + "Configwindow_HelpItem_HealArea": "Open a window to use AoE heal.", + "Configwindow_HelpItem_HealSingle": "Open a window to use single heal.", + "Configwindow_HelpItem_DefenseArea": "Open a window to use AoE defense.", + "Configwindow_HelpItem_DefenseSingle": "Open a window to use single defense.", + "Configwindow_HelpItem_Esuna": "Open a window to use Esuna,tank stance actions or True North.", + "Configwindow_HelpItem_RaiseShirk": "Open a window to use Raise or Shirk.", + "Configwindow_HelpItem_AntiKnockback": "Open a window to use knockback-penalty actions.", + "Configwindow_HelpItem_Burst": "Open a window to burst.", + "Configwindow_HelpItem_MoveForward": "Open a window to move forward.", + "Configwindow_HelpItem_MoveBack": "Open a window to move back.", + "Configwindow_HelpItem_EndSpecial": "Close special window.", + "Configwindow_Helper_SwitchRotation": "Click to switch authors", + "Configwindow_Helper_GameVersion": "Game Version", + "Configwindow_Helper_OpenSource": "Open the source code URL", + "Configwindow_Helper_RunCommand": "Click to execute the command", + "Configwindow_Helper_CopyCommand": "Right-click to copy command", + "Configwindow_Helper_InsertCommand": "Insert \"{0}\" first in 5s", + "Configwindow_Rotation_Description": "You can enable the function for each job you want and configure the setting about how to use actions.", + "Configwindow_Rotation_KeyName": "The key name is", + "Configwindow_Events_AddEvent": "AddEvents", + "Configwindow_Events_Description": "In this window, you can set what macro will be trigger after using an action.", + "Configwindow_Events_ActionName": "Action Name", + "Configwindow_Events_MacroIndex": "Macro No.", + "Configwindow_Events_ShareMacro": "Is Shared", + "Configwindow_Events_RemoveEvent": "Delete Event", + "Configwindow_Events_DutyStart": "Duty Start: ", + "Configwindow_Events_DutyEnd": "Duty End: ", + "Configwindow_Params_Description": "In this window, you can set the parameters about the using way of actions.", + "Configwindow_Param_NeverReplaceIcon": "Never Replace Icons", + "Configwindow_Param_NeverReplaceIconDesc": "Icon replacement: Repose is automatically displayed as the next action to be used", + "Configwindow_Param_UseOverlayWindow": "Display Top Overlay", + "Configwindow_Param_UseOverlayWindowDesc": "This top window is used to display some extra information on your game window, such as target's positional, target and sub-target, etc.", + "Configwindow_Param_Basic": "Basic", + "Configwindow_Param_ActionAhead": "Set the time advance of using actions", + "Configwindow_Param_AbilitiesInterval": "Set the interval between 0GCD using", + "Configwindow_Param_CountDownAhead": "Set the time advance of using casting actions on counting down.", + "Configwindow_Param_SpecialDuration": "Set the duration of special windows set by commands", + "Configwindow_Param_AddDotGCDCount": "Set GCD advance of DOT refresh", + "Configwindow_Param_AutoOffBetweenArea": "Turn off when player is between area.", + "Configwindow_Param_UseWorkTask": "Use work task for acceleration.", + "Configwindow_Param_Delay": "Delay", + "Configwindow_Param_WeaponDelay": "Set the range of random delay for GCD in second.", + "Configwindow_Param_DeathDelay": "Set the range of random delay for raising deaths in second.", + "Configwindow_Param_HostileDelay": "Set the range of random delay for finding hostile targets in second.", + "Configwindow_Param_InterruptDelay": "Set the range of random delay for interrupting hostile targets in second.", + "Configwindow_Param_WeakenDelay": "Set the range of random delay for esuna weakens in second.", + "Configwindow_Param_HealDelay": "Set the range of random delay for healing people in second.", + "Configwindow_Param_NotInCombatDelay": "Set the range of random delay for Not In Combat in second.", + "Configwindow_Param_StopCastingDelay": "Set the range of random delay for stoping casting when target is no need to cast in second.", + "Configwindow_Param_Display": "Display", + "Configwindow_Param_Advanced": "Advanced", + "Configwindow_Param_PoslockCasting": "Lock the movement when casting.", + "Configwindow_Param_UseStopCasting": "Use stopping casting when target is dead.", + "Configwindow_Param_ShowActionFlag": "Show action flag.", + "Configwindow_Param_ShowHealthRatio": "Show the health ratio for the check of Boss, Dying, Dot.", + "Configwindow_Param_HealthRatioBoss": "If target's max health ratio is higher than this, regard it as Boss.", + "Configwindow_Param_HealthRatioDying": "If target's current health ratio is lower than this, regard it is dying.", + "Configwindow_Param_HealthRatioDot": "If target's current health ratio is higher than this, regard it can be dot.", + "Configwindow_Param_PoslockModifier": "Set the modifier key to unlock the movement temporary", + "Configwindow_Param_PoslockDescription": "LT is for gamepad player", + "Configwindow_Param_CastingDisplay": "Enhance castbar with casting status", + "Configwindow_Param_TeachingMode": "Teaching mode", + "Configwindow_Param_TeachingModeColor": "Prompt box color of teaching mode", + "Configwindow_Param_MovingTargetColor": "Prompt box color of moving target", + "Configwindow_Param_TargetColor": "Target color", + "Configwindow_Params_SubTargetColor": "Sub-target color", + "Configwindow_Param_KeyBoardNoise": "Simulate the effect of pressing", + "Configwindow_Params_VoiceVolume": "Voice volume", + "Configwindow_Param_FlytextPositional": "Hint positional anticipation by flytext", + "Configwindow_Param_SayPositional": "Hint positional anticipation by shouting", + "Configwindow_Param_PositionalFeedback": "Positional error feedback", + "Configwindow_Param_ShowMoveTarget": "Show the pointing target of the move skill", + "Configwindow_Param_ShowTarget": "Show Target", + "Configwindow_Param_PositionalFeedbackDesc": "Attention: Positional anticipation is experimental, just for reference only.", + "Configwindow_Param_PositionaErrorText": "Positional error prompt", + "Configwindow_Params_LocationWrongTextDesc": "How do you want to be scolded if you have a positional error ?!", + "Configwindow_Param_SayOutStateChanged": "Saying the state changes out", + "Configwindow_Param_ShowInfoOnDtr": "Display plugin state on dtrbar", + "Configwindow_Param_ShowWorkTaskFPS": "Display Task FPS on dtrbar", + "Configwindow_Param_ShowInfoOnToast": "Display plugin state on toast", + "Configwindow_Param_NamePlateIconId": "Player's name plate icon id when state is on. Recommand 61435, 61437", + "Configwindow_Param_Action": "Action", + "Configwindow_Param_UseAOEAction": "Use AOE actions", + "Configwindow_Param_UseAOEWhenManual": "Use AOE actions in manual mode", + "Configwindow_Param_AutoBurst": "Automatic burst", + "Configwindow_Param_UseAbility": "Auto-use abilities", + "Configwindow_Param_NoNewHostiles": "Don't attack new mobs by aoe", + "Configwindow_Params_NoNewHostilesDesc": "Nerver use any AOE action when this action may attack the mobs that not is a hostile target.", + "Configwindow_Param_UseDefenceAbility": "Use defence abilities", + "Configwindow_Param_UseDefenceAbilityDesc": "It is recommended to check this option if you are playing Raids./nPlan the heal and defense by yourself.???", + "Configwindow_Param_AutoShield": "Auto tank stance", + "Configwindow_Param_AutoProvokeForTank": "Auto Provoke (Tank)", + "Configwindow_Param_AutoProvokeForTankDesc": "When a hostile is hitting the non-Tank member of party, it will automatically use the Provoke.", + "Configwindow_Param_AutoUseTrueNorth": "Auto TrueNorth (Melee)", + "Configwindow_Param_RaisePlayerBySwift": "Raise player by swift", + "Configwindow_Param_UseGroundBeneficialAbility": "Use beneficaial ground-targeted actions", + "Configwindow_Param_RaisePlayerByCasting": "Raise player by casting when swift is in cooldown", + "Configwindow_Param_UseHealWhenNotAHealer": "Use heal when not-healer", + "Configwindow_Param_LessMPNoRaise": "Nerver raise player if MP is less than the set value", + "Configwindow_Param_UseItem": "Use items", + "Configwindow_Param_UseItemDesc": "Use poison, WIP", + "Configwindow_Param_Conditon": "Condition", + "Configwindow_Param_StartOnCountdown": "Turn on auto-rotation on countdown", + "Configwindow_Param_EsunaAll": "Esuna All Statuses.", + "Configwindow_Param_InterruptibleMoreCheck": "Interrupt the action with action type check.", + "Configwindow_Param_HealOutOfCombat": "Heal party members outside of combat.", + "Configwindow_Param_HealthDifference": "Set the HP standard deviation threshold for using AOE heal (ability & spell)", + "Configwindow_Param_HealthAreaAbility": "Set the HP threshold for using AOE healing ability", + "Configwindow_Param_HealthAreaSpell": "Set the HP threshold for using AOE healing spell", + "Configwindow_Param_HealingOfTimeSubtractArea": "Set the HP threshold reduce with hot effect(AOE)", + "Configwindow_Param_HealthSingleAbility": "Set the HP threshold for using single healing ability", + "Configwindow_Param_HealthSingleSpell": "Set the HP threshold for using single healing spell", + "Configwindow_Param_HealingOfTimeSubtractSingle": "Set the HP threshold reduce with hot effect(single)", + "Configwindow_Param_HealthForDyingTank": "Set the HP threshold for tank to use invincibility", + "Configwindow_Param_Target": "Target", + "Configwindow_Param_RightNowTargetToHostileType": "Hostile target filtering condition", + "Configwindow_Param_TargetToHostileType1": "All targets can attack", + "Configwindow_Param_TargetToHostileType2": "Targets have a target or all targets can attack", + "Configwindow_Param_TargetToHostileType3": "Targets have a target", + "Configwindow_Param_NoticeUnexpectedCombat": "NOTICE: You are not turn the auto off between area on. It may start a combat unexpectedly.", + "Configwindow_Param_AddEnemyListToHostile": "Add Enemies list to the hostile target.", + "Configwindow_Param_ChooseAttackMark": "Priority attack targets with attack markers", + "Configwindow_Param_CanAttackMarkAOE": "Forced use of AOE", + "Configwindow_Param_AttackMarkAOEDesc": "Attention: Checking this option , AA will attack as many hostile targets as possible, while ignoring whether the attack will cover the marked target.", + "Configwindow_Param_FilterStopMark": "Never attack targets with stop markers", + "Configwindow_Param_ObjectMinRadius": "Set the minimum target circle threshold possessed by the attack target", + "Configwindow_Param_MoveTargetAngle": "The size of the sector angle that can be selected as the moveable target", + "Configwindow_Param_MoveTargetAngleDesc": "If the selection mode is based on character facing, i.e., targets within the character's viewpoint are movable targets. \nIf the selection mode is screen-centered, i.e., targets within a sector drawn upward from the character's point are movable targets.", + "Configwindow_Param_ChangeTargetForFate": "Select only Fate targets in Fate", + "Configwindow_Param_OnlyAttackInView": "Only attack the target in view.", + "Configwindow_Param_MoveTowardsScreen": "Using movement actions towards the object in the center of the screen", + "Configwindow_Param_MoveTowardsScreenDesc": "Using movement actions towards the object in the center of the screen, otherwise toward the facing object.", + "Configwindow_Param_RaiseAll": "Raise all (include passerby)", + "Configwindow_Param_TargetFriendly": "Target all for friendly actions(include passerby)", + "Configwindow_Param_RaiseBrinkofDeath": "Raise player even has Brink of Death", + "Configwindow_Param_MoveAreaActionFarthest": "Moving Area Ability to farthest", + "Configwindow_Param_MoveAreaActionFarthestDesc": "Move to the furthest position from character's face direction.", + "Configwindow_Param_Hostile": "Hostile", + "Configwindow_Param_HostileDesc": "You can set the logic of hostile target selection to allow flexibility in switching the logic of selecting hostile in battle.", + "Configwindow_Param_AddHostileCondition": "Add selection condition", + "Configwindow_Param_HostileCondition": "Hostile target selection condition", + "Configwindow_Param_ConditionUp": "Up", + "Configwindow_Param_ConditionDown": "Down", + "Configwindow_Param_ConditionDelete": "Delete", + "Timeline_DragdropDescription": "Drag&drop to move,Ctrl+Alt+RightClick to delete.", + "Timeline_SearchBar": "Search Bar", + "Timeline_MustUse": "MustUse", + "Timeline_MustUseDesc": "Skip AOE and Buff.", + "Timeline_Empty": "UseUp", + "Timeline_EmptyDesc": "UseUp or Skip Combo", + "Timeline_TimelineDescription": "Add some condition to automatic use this action.", + "Timeline_Can": "Can", + "Timeline_Cannot": "Cannot", + "Timeline_Is": "Is", + "Timeline_Isnot": "Isnot", + "Timeline_Have": "Have", + "Timeline_Havenot": "Havenot", + "Timeline_Ability": "Ability", + "Timeline_Charges": "Charges", + "Timeline_ConditionSet": "ConditionSet", + "Timeline_ActionCondition": "ActionCondition", + "Timeline_TargetCondition": "TargetCondition", + "Timeline_RotationCondition": "RotationCondition", + "Timeline_ActionTarget": "{0}'s target", + "Timeline_Target": "Target", + "Timeline_Player": "Player", + "Timeline_StatusSelf": "StatusSelf", + "Timeline_StatusSelfDesc": "StatusSelf", + "Action_Friendly": "Support", + "Action_Attack": "Attack", + "ComboConditionType_Bool": "Boolean", + "ComboConditionType_Byte": "Byte", + "ComboConditionType_Time": "Time", + "ComboConditionType_GCD": "GCD", + "ComboConditionType_Last": "Last", + "TargetingType_Big": "Big", + "TargetingType_Small": "Small", + "TargetingType_HighHP": "High HP", + "TargetingType_LowHP": "Low HP", + "TargetingType_HighMaxHP": "High Max HP", + "TargetingType_LowMaxHP": "Low Max HP", + "SpecialCommandType_Start": "Start ", + "SpecialCommandType_HealArea": "Heal Area", + "SpecialCommandType_HealSingle": "Heal Single", + "SpecialCommandType_DefenseArea": "Defense Area", + "SpecialCommandType_DefenseSingle": "Defense Single", + "SpecialCommandType_TankStance": "Tank Stance", + "SpecialCommandType_MoveForward": "Move Forward", + "SpecialCommandType_MoveBack": "Move Back", + "SpecialCommandType_AntiKnockback": "Anti-Knockback", + "SpecialCommandType_Burst": "Burst", + "SpecialCommandType_EndSpecial": "End Special", + "SpecialCommandType_Smart": "Smart ", + "SpecialCommandType_Manual": "Manual", + "SpecialCommandType_Cancel": "Cancel", + "SpecialCommandType_Off": "Off", + "ActionConditionType_Elapsed": "Elapsed", + "ActionConditionType_ElapsedGCD": "ElapsedGCD ", + "ActionConditionType_Remain": "RemainTime", + "ActionConditionType_RemainGCD": "RemainGCD", + "ActionConditionType_ShouldUse": "ShouldUse", + "ActionConditionType_EnoughLevel": "EnoughLevel", + "ActionConditionType_IsCoolDown": "IsCoolDown", + "ActionConditionType_CurrentCharges": "CurrentCharges", + "ActionConditionType_MaxCharges": "MaxCharges", + "TargetConditionType_HaveStatus": "Have Status", + "TargetConditionType_IsDying": "Is Dying", + "TargetConditionType_IsBoss": "Is Boss", + "TargetConditionType_Distance": "Distance", + "TargetConditionType_StatusEnd": "Status End", + "TargetConditionType_StatusEndGCD": "Status End GCD", + "TargetConditionType_CastingAction": "Casting Action", + "DescType_BurstActions": "Burst Actions", + "DescType_MoveForwardGCD": "Move Forward GCD", + "DescType_HealAreaGCD": "Area Healing GCD", + "DescType_HealSingleGCD": "Single Healing GCD", + "DescType_DefenseAreaGCD": "Area Defense GCD", + "DescType_DefenseSingleGCD": "Single Defense GCD", + "DescType_HealAreaAbility": "Area Healing Ability", + "DescType_HealSingleAbility": "Single Healing Ability", + "DescType_DefenseAreaAbility": "Area Defense Ability", + "DescType_DefenseSingleAbility": "Single Defense Ability", + "DescType_MoveForwardAbility": "Move Forward Ability", + "DescType_MoveBackAbility": "Move Back Ability", + "JobRole_None": "Gathering&Production", + "JobRole_Tank": "Tank", + "JobRole_Melee": "Melee", + "JobRole_Ranged": "Ranged", + "JobRole_Healer": "Healer", + "JobRole_RangedPhysical": "Ranged", + "JobRole_RangedMagicial": "Magicial", + "JobRole_DiscipleoftheLand": "Disciple of the Land", + "JobRole_DiscipleoftheHand": "Disciple of the Hand", + "EnemyLocation_None": "None", + "EnemyLocation_Rear": "Rear", + "EnemyLocation_Flank": "Flank", + "EnemyLocation_Front": "Front", + "MemberInfoName": { + "IsMoving": "IsMoving", + "HaveHostilesInRange": "Have Hostiles InRange", + "IsFullParty": "Is Full Party", + "SettingBreak": "Breaking", + "Level": "Level", + "InCombat": "In Combat", + "IsLastGCD": "Just used GCD", + "IsLastAbility": "Just used Ability", + "IsLastAction": "Just used Action", + "IsTargetDying": "Target is dying", + "IsTargetBoss": "Target is Boss", + "HaveSwift": "Have Swift", + "HaveShield": "Have defensive stance", + "PlayCard": "Play", + "UmbralIceStacks": "Umbral Ice Stacks", + "AstralFireStacks": "Astral Fire Stacks", + "PolyglotStacks": "Polyglot Stacks", + "UmbralHearts": "Umbral Heart Stacks", + "IsParadoxActive": "Is Paradox Active ?", + "InUmbralIce": "In Umbral Ice", + "InAstralFire": "In Astral Fire", + "IsEnochianActive": "Is Enochian Active?", + "EnchinaEndAfter": "Enchina End After (s)", + "EnchinaEndAfterGCD": "Enchina End After (GCDs)", + "ElementTimeEndAfter": "Element Time End After (s)", + "ElementTimeEndAfterGCD": "Element Time End After (GCDs)", + "HasFire": "Has Firestarter", + "HasThunder": "Has Thunder", + "IsPolyglotStacksMaxed": "Whether Polyglot already has the maximum number of charge stacks at the current level", + "SoulVoice": "Soul Voice", + "SongEndAfter": "Song End After (s)", + "SongEndAfterGCD": "Song End After (GCDs)", + "Repertoire": "Song Gauge Stacks", + "IsDancing": "Is Dancing", + "Esprit": "Esprit", + "Feathers": "Feathers", + "CompletedSteps": "CompletedSteps", + "FinishStepGCD": "FinishStepGCD", + "ExcutionStepGCD": "Excution Step GCD", + "Blood": "Blood", + "HasDarkArts": "Has Dark Arts", + "DarkSideEndAfter": "DarkSideEndAfter", + "DarkSideEndAfterGCD": "DarkSideEndAfterGCD", + "Ammo": "Ammo", + "AmmoComboStep": "Ammo Combo Step", + "IsOverheated": "Is Over heated", + "Heat": "Heat", + "Battery": "Battery", + "OverheatedEndAfter": "Over heated End After (s)", + "OverheatedEndAfterGCD": "Over heated End After(GCDs)", + "Chakra": "Chakra" + }, + "MemberInfoDesc": { + "IsMoving": "Player Is Moving", + "HaveHostilesInRange": "Have Hostiles In Range(Melee <3m,Ranged<25m)", + "IsFullParty": "Is Full Party", + "SettingBreak": "In break", + "Level": "Player level", + "InCombat": "In Combat", + "IsLastGCD": "Just used GCD", + "IsLastAbility": "Just used ability", + "IsLastAction": "Just used Action", + "IsTargetDying": "Target is Dying", + "IsTargetBoss": "Target is Boss", + "HaveSwift": "Have Swift", + "HaveShield": "Have defensive stance", + "PlayCard": "Play", + "UmbralIceStacks": "Umbral Ice Stacks", + "AstralFireStacks": "Astral Fire Stacks", + "PolyglotStacks": "Polyglot Stacks", + "UmbralHearts": "Umbral Heart Stacks", + "IsParadoxActive": "Is Paradox Active?", + "InUmbralIce": "In Umbral Ice", + "InAstralFire": "In Astral Fire", + "IsEnochianActive": "Is Enochian Active?", + "EnchinaEndAfter": "Enchina End After (s)", + "EnchinaEndAfterGCD": "Enchina End After (GCDs)", + "ElementTimeEndAfter": "Element remaining time", + "ElementTimeEndAfterGCD": "Element remaining time", + "HasFire": "Has Firestarter", + "HasThunder": "Has Thunder", + "IsPolyglotStacksMaxed": "Whether Polyglot already has the maximum number of charge stacks at the current level", + "SoulVoice": "SoulVoice", + "SongEndAfter": "Song End After (s)", + "SongEndAfterGCD": "Song End After (GCDs)", + "Repertoire": "Song Gauge Stacks", + "IsDancing": "Is Dancing", + "Esprit": "Esprit", + "Feathers": "Feathers", + "CompletedSteps": "Completed Steps", + "FinishStepGCD": "Finish Step GCD", + "ExcutionStepGCD": "Excution Step GCD", + "Blood": "Blood", + "HasDarkArts": "Has Dark Arts", + "DarkSideEndAfter": "DarkSide End After (s)", + "DarkSideEndAfterGCD": "DarkSide End After (GCDs)", + "Ammo": "Ammo", + "AmmoComboStep": "Ammo Combo Step", + "IsOverheated": "Is Over heated", + "Heat": "Heat", + "Battery": "Battery", + "OverheatedEndAfter": "OverheatedEndAfter", + "OverheatedEndAfterGCD": "OverheatedEndAfterGCD", + "Chakra": "Chakra" + }, + "HighEndWarning": "You'd better not use Rotation Solver in {0}!" +} \ No newline at end of file diff --git a/RotationSolver/Properties/AssemblyInfo.cs b/RotationSolver.Old/Properties/AssemblyInfo.cs similarity index 100% rename from RotationSolver/Properties/AssemblyInfo.cs rename to RotationSolver.Old/Properties/AssemblyInfo.cs diff --git a/RotationSolver.Old/RotationSolver.Old.csproj b/RotationSolver.Old/RotationSolver.Old.csproj new file mode 100644 index 000000000..4396fd841 --- /dev/null +++ b/RotationSolver.Old/RotationSolver.Old.csproj @@ -0,0 +1,54 @@ + + + RotationSolver + False + net7.0 + x64 + false + true + Debug;Release + AnyCPU;ARM64 + + + Preview + True + + + + $(AppData)\XIVLauncher\addon\Hooks\dev\ + + + + + + + + $(DalamudLibPath)Dalamud.dll + False + + + $(DalamudLibPath)ImGui.NET.dll + False + + + $(DalamudLibPath)ImGuiScene.dll + False + + + $(DalamudLibPath)Lumina.dll + False + + + $(DalamudLibPath)Lumina.Excel.dll + False + + + $(DalamudLibPath)FFXIVClientStructs.dll + False + + + $(DalamudLibPath)Newtonsoft.Json.dll + False + + + \ No newline at end of file diff --git a/RotationSolver.Old/RotationSolver.yaml b/RotationSolver.Old/RotationSolver.yaml new file mode 100644 index 000000000..e3897276b --- /dev/null +++ b/RotationSolver.Old/RotationSolver.yaml @@ -0,0 +1,24 @@ + +name: Rotation Solver +author: ArchiTed +description: |- + Based on the pve combat information in one frame, find the best action. + + The `information` is almost all the information available in one frame in combat, including the status of the all players in party, the status of the hostile targets, action cooling, the number of action stack, the MP and HP of characters, the location of characters, casting action of the hostile target, combo ID, combat duration, player level, etc. In this case, opener is also a kind of information that a lot of abilities are not cooling down. + + Then, it will highlight the best action one the hot bar, or help you to click on it. + + It is designed for general combat, not for savage or ultimate. So use it carefully. +punchline: Based on the pve combat information in one frame, find the best action. +repo_url: https://github.com/ArchiDog1998/RotationSolver +icon_url: https://raw.githubusercontent.com/ArchiDog1998/RotationSolver/main/docs/RotationSolverIcon_128.png +dalamud_api_level: 8 +tags: + - combat + - rotation +category_tags: + - jobs +image_urls: +download_link_install: https://github.com/ArchiDog1998/RotationSolver/releases/latest/download/latest.zip +download_link_update: https://github.com/ArchiDog1998/RotationSolver/releases/latest/download/latest.zip +download_link_testing: https://github.com/ArchiDog1998/RotationSolver/releases/latest/download/latest.zip \ No newline at end of file diff --git a/RotationSolver.Old/RotationSolverPlugin.cs b/RotationSolver.Old/RotationSolverPlugin.cs new file mode 100644 index 000000000..2d2fdcf95 --- /dev/null +++ b/RotationSolver.Old/RotationSolverPlugin.cs @@ -0,0 +1,112 @@ +using Dalamud.Interface.Windowing; +using Dalamud.Plugin; +using Lumina.Excel.GeneratedSheets; +using RotationSolver.Actions; +using RotationSolver.Commands; +using RotationSolver.Configuration; +using RotationSolver.Data; +using RotationSolver.Localization; +using RotationSolver.SigReplacers; +using RotationSolver.Updaters; +using RotationSolver.Windows; +using RotationSolver.Windows.RotationConfigWindow; +using System; + +namespace RotationSolver; + +public sealed class RotationSolverPlugin : IDalamudPlugin, IDisposable +{ + private readonly WindowSystem windowSystem; + + private static RotationConfigWindow _comboConfigWindow; + public string Name => "Rotation Solver"; + + public unsafe RotationSolverPlugin(DalamudPluginInterface pluginInterface) + { + pluginInterface.Create(); + + try + { + Service.Configuration = pluginInterface.GetPluginConfig() as PluginConfiguration ?? new PluginConfiguration(); + } + catch + { + Service.Configuration = new PluginConfiguration(); + } + Service.Address = new PluginAddressResolver(); + Service.Address.Setup(); + + Service.IconReplacer = new IconReplacer(); + + _comboConfigWindow = new(); + windowSystem = new WindowSystem(Name); + windowSystem.AddWindow(_comboConfigWindow); + + Service.Interface.UiBuilder.OpenConfigUi += OnOpenConfigUi; + Service.Interface.UiBuilder.Draw += windowSystem.Draw; + Service.Interface.UiBuilder.Draw += OverlayWindow.Draw; + + MajorUpdater.Enable(); + TimeLineUpdater.Enable(pluginInterface.ConfigDirectory.FullName); + SocialUpdater.Enable(); + Watcher.Enable(); + CountDown.Enable(); + + Service.Localization = new LocalizationManager(); +#if DEBUG + Service.Localization.ExportLocalization(); +#endif + Service.ClientState.TerritoryChanged += ClientState_TerritoryChanged; + ChangeUITranslation(); + } + + private void ClientState_TerritoryChanged(object sender, ushort e) + { + RSCommands.UpdateStateNamePlate(); + var territory = Service.DataManager.GetExcelSheet().GetRow(e); + if(territory?.ContentFinderCondition?.Value?.RowId != 0) + { + SocialUpdater.CanSaying = true; + } + } + + internal static void ChangeUITranslation() + { + _comboConfigWindow.WindowName = LocalizationManager.RightLang.ConfigWindow_Header + + typeof(RotationConfigWindow).Assembly.GetName().Version.ToString(); + + RSCommands.Disable(); + RSCommands.Enable(); + } + + public void Dispose() + { + Service.ClientState.TerritoryChanged -= ClientState_TerritoryChanged; + + RSCommands.Disable(); + Service.Interface.UiBuilder.OpenConfigUi -= OnOpenConfigUi; + Service.Interface.UiBuilder.Draw -= windowSystem.Draw; + Service.Interface.UiBuilder.Draw -= OverlayWindow.Draw; + + Service.IconReplacer.Dispose(); + Service.Localization.Dispose(); + + MajorUpdater.Dispose(); + TimeLineUpdater.SaveFiles(); + Watcher.Dispose(); + CountDown.Dispose(); + SocialUpdater.Disable(); + + IconSet.Dispose(); + } + + private void OnOpenConfigUi() + { + _comboConfigWindow.IsOpen = true; + } + + internal static void OpenConfigWindow() + { + _comboConfigWindow.Toggle(); + } +} diff --git a/RotationSolver/Rotations/Basic/AST_Base.cs b/RotationSolver.Old/Rotations/Basic/AST_Base.cs similarity index 100% rename from RotationSolver/Rotations/Basic/AST_Base.cs rename to RotationSolver.Old/Rotations/Basic/AST_Base.cs diff --git a/RotationSolver/Rotations/Basic/BLM_Base.cs b/RotationSolver.Old/Rotations/Basic/BLM_Base.cs similarity index 100% rename from RotationSolver/Rotations/Basic/BLM_Base.cs rename to RotationSolver.Old/Rotations/Basic/BLM_Base.cs diff --git a/RotationSolver/Rotations/Basic/BLU_Base.cs b/RotationSolver.Old/Rotations/Basic/BLU_Base.cs similarity index 100% rename from RotationSolver/Rotations/Basic/BLU_Base.cs rename to RotationSolver.Old/Rotations/Basic/BLU_Base.cs diff --git a/RotationSolver/Rotations/Basic/BRD_Base.cs b/RotationSolver.Old/Rotations/Basic/BRD_Base.cs similarity index 100% rename from RotationSolver/Rotations/Basic/BRD_Base.cs rename to RotationSolver.Old/Rotations/Basic/BRD_Base.cs diff --git a/RotationSolver/Rotations/Basic/DNC_Base.cs b/RotationSolver.Old/Rotations/Basic/DNC_Base.cs similarity index 100% rename from RotationSolver/Rotations/Basic/DNC_Base.cs rename to RotationSolver.Old/Rotations/Basic/DNC_Base.cs diff --git a/RotationSolver/Rotations/Basic/DRG_Base.cs b/RotationSolver.Old/Rotations/Basic/DRG_Base.cs similarity index 100% rename from RotationSolver/Rotations/Basic/DRG_Base.cs rename to RotationSolver.Old/Rotations/Basic/DRG_Base.cs diff --git a/RotationSolver/Rotations/Basic/DRK_Base.cs b/RotationSolver.Old/Rotations/Basic/DRK_Base.cs similarity index 100% rename from RotationSolver/Rotations/Basic/DRK_Base.cs rename to RotationSolver.Old/Rotations/Basic/DRK_Base.cs diff --git a/RotationSolver/Rotations/Basic/GNB_Base.cs b/RotationSolver.Old/Rotations/Basic/GNB_Base.cs similarity index 100% rename from RotationSolver/Rotations/Basic/GNB_Base.cs rename to RotationSolver.Old/Rotations/Basic/GNB_Base.cs diff --git a/RotationSolver/Rotations/Basic/MCH_Base.cs b/RotationSolver.Old/Rotations/Basic/MCH_Base.cs similarity index 100% rename from RotationSolver/Rotations/Basic/MCH_Base.cs rename to RotationSolver.Old/Rotations/Basic/MCH_Base.cs diff --git a/RotationSolver/Rotations/Basic/MNK_Base.cs b/RotationSolver.Old/Rotations/Basic/MNK_Base.cs similarity index 100% rename from RotationSolver/Rotations/Basic/MNK_Base.cs rename to RotationSolver.Old/Rotations/Basic/MNK_Base.cs diff --git a/RotationSolver/Rotations/Basic/NIN_Base.cs b/RotationSolver.Old/Rotations/Basic/NIN_Base.cs similarity index 100% rename from RotationSolver/Rotations/Basic/NIN_Base.cs rename to RotationSolver.Old/Rotations/Basic/NIN_Base.cs diff --git a/RotationSolver/Rotations/Basic/PLD_Base.cs b/RotationSolver.Old/Rotations/Basic/PLD_Base.cs similarity index 100% rename from RotationSolver/Rotations/Basic/PLD_Base.cs rename to RotationSolver.Old/Rotations/Basic/PLD_Base.cs diff --git a/RotationSolver/Rotations/Basic/RDM_Base.cs b/RotationSolver.Old/Rotations/Basic/RDM_Base.cs similarity index 100% rename from RotationSolver/Rotations/Basic/RDM_Base.cs rename to RotationSolver.Old/Rotations/Basic/RDM_Base.cs diff --git a/RotationSolver/Rotations/Basic/RPR_Base.cs b/RotationSolver.Old/Rotations/Basic/RPR_Base.cs similarity index 100% rename from RotationSolver/Rotations/Basic/RPR_Base.cs rename to RotationSolver.Old/Rotations/Basic/RPR_Base.cs diff --git a/RotationSolver/Rotations/Basic/SAM_Base.cs b/RotationSolver.Old/Rotations/Basic/SAM_Base.cs similarity index 100% rename from RotationSolver/Rotations/Basic/SAM_Base.cs rename to RotationSolver.Old/Rotations/Basic/SAM_Base.cs diff --git a/RotationSolver/Rotations/Basic/SCH_Base.cs b/RotationSolver.Old/Rotations/Basic/SCH_Base.cs similarity index 100% rename from RotationSolver/Rotations/Basic/SCH_Base.cs rename to RotationSolver.Old/Rotations/Basic/SCH_Base.cs diff --git a/RotationSolver/Rotations/Basic/SGE_Base.cs b/RotationSolver.Old/Rotations/Basic/SGE_Base.cs similarity index 100% rename from RotationSolver/Rotations/Basic/SGE_Base.cs rename to RotationSolver.Old/Rotations/Basic/SGE_Base.cs diff --git a/RotationSolver/Rotations/Basic/SMN_Base.cs b/RotationSolver.Old/Rotations/Basic/SMN_Base.cs similarity index 100% rename from RotationSolver/Rotations/Basic/SMN_Base.cs rename to RotationSolver.Old/Rotations/Basic/SMN_Base.cs diff --git a/RotationSolver/Rotations/Basic/WAR_Base.cs b/RotationSolver.Old/Rotations/Basic/WAR_Base.cs similarity index 100% rename from RotationSolver/Rotations/Basic/WAR_Base.cs rename to RotationSolver.Old/Rotations/Basic/WAR_Base.cs diff --git a/RotationSolver/Rotations/Basic/WHM_Base.cs b/RotationSolver.Old/Rotations/Basic/WHM_Base.cs similarity index 100% rename from RotationSolver/Rotations/Basic/WHM_Base.cs rename to RotationSolver.Old/Rotations/Basic/WHM_Base.cs diff --git a/RotationSolver/Rotations/CustomRotation/CustomRotation_Ability.cs b/RotationSolver.Old/Rotations/CustomRotation/CustomRotation_Ability.cs similarity index 100% rename from RotationSolver/Rotations/CustomRotation/CustomRotation_Ability.cs rename to RotationSolver.Old/Rotations/CustomRotation/CustomRotation_Ability.cs diff --git a/RotationSolver/Rotations/CustomRotation/CustomRotation_Actions.cs b/RotationSolver.Old/Rotations/CustomRotation/CustomRotation_Actions.cs similarity index 100% rename from RotationSolver/Rotations/CustomRotation/CustomRotation_Actions.cs rename to RotationSolver.Old/Rotations/CustomRotation/CustomRotation_Actions.cs diff --git a/RotationSolver/Rotations/CustomRotation/CustomRotation_BasicInfo.cs b/RotationSolver.Old/Rotations/CustomRotation/CustomRotation_BasicInfo.cs similarity index 100% rename from RotationSolver/Rotations/CustomRotation/CustomRotation_BasicInfo.cs rename to RotationSolver.Old/Rotations/CustomRotation/CustomRotation_BasicInfo.cs diff --git a/RotationSolver/Rotations/CustomRotation/CustomRotation_Display.cs b/RotationSolver.Old/Rotations/CustomRotation/CustomRotation_Display.cs similarity index 100% rename from RotationSolver/Rotations/CustomRotation/CustomRotation_Display.cs rename to RotationSolver.Old/Rotations/CustomRotation/CustomRotation_Display.cs diff --git a/RotationSolver/Rotations/CustomRotation/CustomRotation_GCD.cs b/RotationSolver.Old/Rotations/CustomRotation/CustomRotation_GCD.cs similarity index 100% rename from RotationSolver/Rotations/CustomRotation/CustomRotation_GCD.cs rename to RotationSolver.Old/Rotations/CustomRotation/CustomRotation_GCD.cs diff --git a/RotationSolver/Rotations/CustomRotation/CustomRotation_Invoke.cs b/RotationSolver.Old/Rotations/CustomRotation/CustomRotation_Invoke.cs similarity index 100% rename from RotationSolver/Rotations/CustomRotation/CustomRotation_Invoke.cs rename to RotationSolver.Old/Rotations/CustomRotation/CustomRotation_Invoke.cs diff --git a/RotationSolver/Rotations/CustomRotation/CustomRotation_Medicine.cs b/RotationSolver.Old/Rotations/CustomRotation/CustomRotation_Medicine.cs similarity index 100% rename from RotationSolver/Rotations/CustomRotation/CustomRotation_Medicine.cs rename to RotationSolver.Old/Rotations/CustomRotation/CustomRotation_Medicine.cs diff --git a/RotationSolver/Rotations/CustomRotation/CustomRotation_OtherInfo.cs b/RotationSolver.Old/Rotations/CustomRotation/CustomRotation_OtherInfo.cs similarity index 100% rename from RotationSolver/Rotations/CustomRotation/CustomRotation_OtherInfo.cs rename to RotationSolver.Old/Rotations/CustomRotation/CustomRotation_OtherInfo.cs diff --git a/RotationSolver/Rotations/CustomRotation/ICustomRotation.cs b/RotationSolver.Old/Rotations/CustomRotation/ICustomRotation.cs similarity index 100% rename from RotationSolver/Rotations/CustomRotation/ICustomRotation.cs rename to RotationSolver.Old/Rotations/CustomRotation/ICustomRotation.cs diff --git a/RotationSolver/Rotations/Healer/AST/AST_Default.cs b/RotationSolver.Old/Rotations/Healer/AST/AST_Default.cs similarity index 100% rename from RotationSolver/Rotations/Healer/AST/AST_Default.cs rename to RotationSolver.Old/Rotations/Healer/AST/AST_Default.cs diff --git a/RotationSolver/Rotations/Healer/SCH/SCH_Default.cs b/RotationSolver.Old/Rotations/Healer/SCH/SCH_Default.cs similarity index 100% rename from RotationSolver/Rotations/Healer/SCH/SCH_Default.cs rename to RotationSolver.Old/Rotations/Healer/SCH/SCH_Default.cs diff --git a/RotationSolver/Rotations/Healer/SGE/SGE_Default.cs b/RotationSolver.Old/Rotations/Healer/SGE/SGE_Default.cs similarity index 100% rename from RotationSolver/Rotations/Healer/SGE/SGE_Default.cs rename to RotationSolver.Old/Rotations/Healer/SGE/SGE_Default.cs diff --git a/RotationSolver/Rotations/Healer/WHM/WHM_Default.cs b/RotationSolver.Old/Rotations/Healer/WHM/WHM_Default.cs similarity index 100% rename from RotationSolver/Rotations/Healer/WHM/WHM_Default.cs rename to RotationSolver.Old/Rotations/Healer/WHM/WHM_Default.cs diff --git a/RotationSolver/Rotations/Magicial/BLM/BLM_Default.cs b/RotationSolver.Old/Rotations/Magicial/BLM/BLM_Default.cs similarity index 100% rename from RotationSolver/Rotations/Magicial/BLM/BLM_Default.cs rename to RotationSolver.Old/Rotations/Magicial/BLM/BLM_Default.cs diff --git a/RotationSolver/Rotations/Magicial/BLU/BLU_Default.cs b/RotationSolver.Old/Rotations/Magicial/BLU/BLU_Default.cs similarity index 100% rename from RotationSolver/Rotations/Magicial/BLU/BLU_Default.cs rename to RotationSolver.Old/Rotations/Magicial/BLU/BLU_Default.cs diff --git a/RotationSolver/Rotations/Magicial/BLU/BLU_Simplify.cs b/RotationSolver.Old/Rotations/Magicial/BLU/BLU_Simplify.cs similarity index 100% rename from RotationSolver/Rotations/Magicial/BLU/BLU_Simplify.cs rename to RotationSolver.Old/Rotations/Magicial/BLU/BLU_Simplify.cs diff --git a/RotationSolver/Rotations/Magicial/RDM/RDM_Default.cs b/RotationSolver.Old/Rotations/Magicial/RDM/RDM_Default.cs similarity index 100% rename from RotationSolver/Rotations/Magicial/RDM/RDM_Default.cs rename to RotationSolver.Old/Rotations/Magicial/RDM/RDM_Default.cs diff --git a/RotationSolver/Rotations/Magicial/SMN/SMN_Default.cs b/RotationSolver.Old/Rotations/Magicial/SMN/SMN_Default.cs similarity index 100% rename from RotationSolver/Rotations/Magicial/SMN/SMN_Default.cs rename to RotationSolver.Old/Rotations/Magicial/SMN/SMN_Default.cs diff --git a/RotationSolver/Rotations/Melee/DRG/DRG_Default.cs b/RotationSolver.Old/Rotations/Melee/DRG/DRG_Default.cs similarity index 100% rename from RotationSolver/Rotations/Melee/DRG/DRG_Default.cs rename to RotationSolver.Old/Rotations/Melee/DRG/DRG_Default.cs diff --git a/RotationSolver/Rotations/Melee/MNK/MNK_Default.cs b/RotationSolver.Old/Rotations/Melee/MNK/MNK_Default.cs similarity index 100% rename from RotationSolver/Rotations/Melee/MNK/MNK_Default.cs rename to RotationSolver.Old/Rotations/Melee/MNK/MNK_Default.cs diff --git a/RotationSolver/Rotations/Melee/NIN/NIN_Default.cs b/RotationSolver.Old/Rotations/Melee/NIN/NIN_Default.cs similarity index 100% rename from RotationSolver/Rotations/Melee/NIN/NIN_Default.cs rename to RotationSolver.Old/Rotations/Melee/NIN/NIN_Default.cs diff --git a/RotationSolver/Rotations/Melee/RPR/RPR_Default.cs b/RotationSolver.Old/Rotations/Melee/RPR/RPR_Default.cs similarity index 100% rename from RotationSolver/Rotations/Melee/RPR/RPR_Default.cs rename to RotationSolver.Old/Rotations/Melee/RPR/RPR_Default.cs diff --git a/RotationSolver/Rotations/Melee/SAM/SAM_Default.cs b/RotationSolver.Old/Rotations/Melee/SAM/SAM_Default.cs similarity index 100% rename from RotationSolver/Rotations/Melee/SAM/SAM_Default.cs rename to RotationSolver.Old/Rotations/Melee/SAM/SAM_Default.cs diff --git a/RotationSolver/Rotations/Ranged/BRD/BRD_Default.cs b/RotationSolver.Old/Rotations/Ranged/BRD/BRD_Default.cs similarity index 100% rename from RotationSolver/Rotations/Ranged/BRD/BRD_Default.cs rename to RotationSolver.Old/Rotations/Ranged/BRD/BRD_Default.cs diff --git a/RotationSolver/Rotations/Ranged/DNC/DNC_Default.cs b/RotationSolver.Old/Rotations/Ranged/DNC/DNC_Default.cs similarity index 100% rename from RotationSolver/Rotations/Ranged/DNC/DNC_Default.cs rename to RotationSolver.Old/Rotations/Ranged/DNC/DNC_Default.cs diff --git a/RotationSolver/Rotations/Ranged/MCH/MCH_Default.cs b/RotationSolver.Old/Rotations/Ranged/MCH/MCH_Default.cs similarity index 100% rename from RotationSolver/Rotations/Ranged/MCH/MCH_Default.cs rename to RotationSolver.Old/Rotations/Ranged/MCH/MCH_Default.cs diff --git a/RotationSolver.Old/Rotations/Rotations.cd b/RotationSolver.Old/Rotations/Rotations.cd new file mode 100644 index 000000000..2e7d1999c --- /dev/null +++ b/RotationSolver.Old/Rotations/Rotations.cd @@ -0,0 +1,214 @@ + + + + + + + + Rotations\CustomRotation\CustomRotation_Actions.cs + + + + + WwAaPo4ACN0pVr3Bp8UcJ6QRgCAOWQEBCJmiS8pABUU= + Rotations\CustomRotation\CustomRotation_Ability.cs + + + + + + + AAEMEAEAEAEBEAPAChQQAAOYoBgFCAEIAAqAgCBAAAA= + Rotations\Basic\PLD_Base.cs + + + + + + AAAgAEIBIQEAMAEBBCAgAYIAJAABUAiBIAgQAgAAAAA= + Rotations\Basic\WAR_Base.cs + + + + + + BwSEACEjQAMgUAEBAEAjKBAAIAAAAoAAAEgAAAAUAAA= + Rotations\Basic\DRK_Base.cs + + + + + + AGwGIiQmAREAEEFAAIBAAAAEIAAFgASAAQgBAEhABAQ= + Rotations\Basic\GNB_Base.cs + + + + + + AAAIQAAiIQAEEIQAQCOCABAIIARAQQAQEAAAAEAwAAA= + Rotations\Basic\WHM_Base.cs + + + + + + AAAAAABIgRgAENA4CiAAAACEYAIwEJAjCgAAAAgAAgA= + Rotations\Basic\SCH_Base.cs + + + + + + AggSAMAAAAQIG9gAQBgAAgAAIAEkAAAAAAhEFoAACAA= + Rotations\Basic\AST_Base.cs + + + + + + EAAKEAAIAAAAENEaBiAoZ0AIIAAAAAYDAAAECAACAKI= + Rotations\Basic\SGE_Base.cs + + + + + + + + Rotations\Basic\BLU_Base.cs + + + + + Rotations\Basic\BLU_Base.cs + + + + + Rotations\Basic\BLU_Base.cs + + + + + Rotations\Basic\BLU_Base.cs + + + + + + yklKQVgAhPYrHIlAMfglZ6xrCerhDAyLChiAOgYdskE= + Rotations\Basic\BLU_Base.cs + + + + + + + + Rotations\Basic\BLM_Base.cs + + + + + Rotations\Basic\BLM_Base.cs + + + + + Rotations\Basic\BLM_Base.cs + + + + + AQAIIBIBAEAGUAAIAAmoAAQAKQEUOQQEFAEQARqIAgI= + Rotations\Basic\BLM_Base.cs + + + + + + ASEIAkhIoBIEUoEIAyMQgACEIQIUAACMAIAEAAAEGAA= + Rotations\Basic\SMN_Base.cs + + + + + + AABJIAIIQBAA0YEIBCCAAAAQMAAsAABigAAEAGIgAAg= + Rotations\Basic\RDM_Base.cs + + + + + + AMCCAAkgMCgBUCEQAAAIcCWAYEIAEAgAABAAAAAABgo= + Rotations\Basic\DNC_Base.cs + + + + + + JAIAAAAAAiCA8QiREBAAAAEAIiAAAAAAAEgQAggAAAA= + Rotations\Basic\MCH_Base.cs + + + + + + AlEACAgFBAAIUAQAkAABAAoAMABCCCAAgRqCAAIAIQA= + Rotations\Basic\BRD_Base.cs + + + + + + CAEAAAggEAIBUAFCCADAggEAIAAAAEQQJIAYAJBkSAA= + Rotations\Basic\MNK_Base.cs + + + + + + BEwAAAAIgkAAVAAEBgQAAAEGIAAgAIIgAABAAABAAAA= + Rotations\Basic\DRG_Base.cs + + + + + + + + Rotations\Basic\NIN_Base.cs + + + + + + EEEII1hAAACB0EHAGAgQQEAAIAYldAACgIEEAYUBkCA= + Rotations\Basic\NIN_Base.cs + + + + + + BAABAAkRogAAVAnABQAGBHFGIAQBEkQBEEAJAAAAAGA= + Rotations\Basic\SAM_Base.cs + + + + + + + + Rotations\Basic\RPR_Base.cs + + + + + AIABEEgEjiAgWAEAACRRAAgwLAEAgBAAgIAABBCIAEA= + Rotations\Basic\RPR_Base.cs + + + + \ No newline at end of file diff --git a/RotationSolver/Rotations/Tank/DRK/DRK_Default.cs b/RotationSolver.Old/Rotations/Tank/DRK/DRK_Default.cs similarity index 100% rename from RotationSolver/Rotations/Tank/DRK/DRK_Default.cs rename to RotationSolver.Old/Rotations/Tank/DRK/DRK_Default.cs diff --git a/RotationSolver/Rotations/Tank/GNB/GNB_Default.cs b/RotationSolver.Old/Rotations/Tank/GNB/GNB_Default.cs similarity index 100% rename from RotationSolver/Rotations/Tank/GNB/GNB_Default.cs rename to RotationSolver.Old/Rotations/Tank/GNB/GNB_Default.cs diff --git a/RotationSolver/Rotations/Tank/PLD/PLD_Default.cs b/RotationSolver.Old/Rotations/Tank/PLD/PLD_Default.cs similarity index 100% rename from RotationSolver/Rotations/Tank/PLD/PLD_Default.cs rename to RotationSolver.Old/Rotations/Tank/PLD/PLD_Default.cs diff --git a/RotationSolver/Rotations/Tank/WAR/WAR_Default.cs b/RotationSolver.Old/Rotations/Tank/WAR/WAR_Default.cs similarity index 100% rename from RotationSolver/Rotations/Tank/WAR/WAR_Default.cs rename to RotationSolver.Old/Rotations/Tank/WAR/WAR_Default.cs diff --git a/RotationSolver/Service.cs b/RotationSolver.Old/Service.cs similarity index 100% rename from RotationSolver/Service.cs rename to RotationSolver.Old/Service.cs diff --git a/RotationSolver/SigReplacers/CountDown.cs b/RotationSolver.Old/SigReplacers/CountDown.cs similarity index 100% rename from RotationSolver/SigReplacers/CountDown.cs rename to RotationSolver.Old/SigReplacers/CountDown.cs diff --git a/RotationSolver.Old/SigReplacers/IconReplacer.cs b/RotationSolver.Old/SigReplacers/IconReplacer.cs new file mode 100644 index 000000000..702630d53 --- /dev/null +++ b/RotationSolver.Old/SigReplacers/IconReplacer.cs @@ -0,0 +1,101 @@ +using Dalamud.Game.ClientState.Objects.SubKinds; +using Dalamud.Hooking; +using FFXIVClientStructs.FFXIV.Client.Game; +using Lumina.Excel.GeneratedSheets; +using RotationSolver.Actions; +using RotationSolver.Data; +using RotationSolver.Rotations.CustomRotation; +using RotationSolver.Updaters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace RotationSolver.SigReplacers; + +internal sealed class IconReplacer : IDisposable +{ + private delegate ulong IsIconReplaceableDelegate(uint actionID); + + private delegate uint GetIconDelegate(IntPtr actionManager, uint actionID); + + private delegate IntPtr GetActionCooldownSlotDelegate(IntPtr actionManager, int cooldownGroup); + + public static TargetHostileType RightNowTargetToHostileType + { + get + { + if (Service.ClientState.LocalPlayer == null) return 0; + var id = Service.ClientState.LocalPlayer.ClassJob.Id; + return GetTargetHostileType(Service.DataManager.GetExcelSheet().GetRow(id)); + } + } + + public static TargetHostileType GetTargetHostileType(ClassJob classJob) + { + if (Service.Configuration.TargetToHostileTypes.TryGetValue(classJob.RowId, out var type)) + { + return (TargetHostileType)type; + } + + return classJob.GetJobRole() == JobRole.Tank ? TargetHostileType.AllTargetsCanAttack : TargetHostileType.TargetsHaveTarget; + } + + private readonly Hook isIconReplaceableHook; + + private readonly Hook getIconHook; + + private IntPtr actionManager = IntPtr.Zero; + + public IconReplacer() + { + unsafe + { + getIconHook = Hook.FromAddress((IntPtr)ActionManager.MemberFunctionPointers.GetAdjustedActionId, GetIconDetour); + } + isIconReplaceableHook = Hook.FromAddress(Service.Address.IsActionIdReplaceable, IsIconReplaceableDetour); + + getIconHook.Enable(); + isIconReplaceableHook.Enable(); + } + + public void Dispose() + { + getIconHook.Dispose(); + isIconReplaceableHook.Dispose(); + } + + internal ActionID OriginalHook(ActionID actionID) + { + return (ActionID)getIconHook.Original.Invoke(actionManager, (uint)actionID); + } + + internal uint OriginalHook(uint actionID) + { + return getIconHook.Original.Invoke(actionManager, actionID); + } + + private unsafe uint GetIconDetour(IntPtr actionManager, uint actionID) + { + this.actionManager = actionManager; + return RemapActionID(actionID); + } + + internal static ActionID KeyActionID => ActionID.Repose; + + private uint RemapActionID(uint actionID) + { + PlayerCharacter localPlayer = Service.ClientState.LocalPlayer; + + if (localPlayer == null || actionID != (uint)KeyActionID || Service.Configuration.NeverReplaceIcon) + return OriginalHook(actionID); + + return ActionUpdater.NextAction?.AdjustedID ?? OriginalHook(actionID); + + } + + private ulong IsIconReplaceableDetour(uint actionID) + { + return 1uL; + } +} diff --git a/RotationSolver/SigReplacers/PluginAddressResolver.cs b/RotationSolver.Old/SigReplacers/PluginAddressResolver.cs similarity index 100% rename from RotationSolver/SigReplacers/PluginAddressResolver.cs rename to RotationSolver.Old/SigReplacers/PluginAddressResolver.cs diff --git a/RotationSolver.Old/SigReplacers/Watcher.cs b/RotationSolver.Old/SigReplacers/Watcher.cs new file mode 100644 index 000000000..d3e6389bb --- /dev/null +++ b/RotationSolver.Old/SigReplacers/Watcher.cs @@ -0,0 +1,172 @@ +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Hooking; +using Dalamud.Interface.Colors; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; +using ImGuiNET; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Localization; +using RotationSolver.Updaters; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.RegularExpressions; +using Action = Lumina.Excel.GeneratedSheets.Action; + +namespace RotationSolver.SigReplacers +{ + public static class Watcher + { + public record ActionRec(DateTime UsedTime, Action Action); + + private delegate void ReceiveAbiltyDelegate(uint sourceId, IntPtr sourceCharacter, IntPtr pos, IntPtr effectHeader, IntPtr effectArray, IntPtr effectTrail); + + private static Hook _receivAbilityHook; + + [EditorBrowsable(EditorBrowsableState.Never)] + internal static ActionID LastAction { get; set; } = 0; + + [EditorBrowsable(EditorBrowsableState.Never)] + internal static ActionID LastGCD { get; set; } = 0; + + [EditorBrowsable(EditorBrowsableState.Never)] + internal static ActionID LastAbility { get; set; } = 0; + + internal static TimeSpan TimeSinceLastAction => DateTime.Now - _timeLastActionUsed; + + private static DateTime _timeLastActionUsed = DateTime.Now; + + const int QUEUECAPACITY = 32; + private static Queue _actions = new Queue(QUEUECAPACITY); + + [EditorBrowsable(EditorBrowsableState.Never)] + internal static ActionRec[] RecordActions => _actions.Reverse().ToArray(); + + internal static unsafe void Enable() + { + _receivAbilityHook = Hook.FromAddress(Service.Address.ReceiveAbilty, ReceiveAbilityEffect); + + _receivAbilityHook?.Enable(); + } + + private static void ReceiveAbilityEffect(uint sourceId, IntPtr sourceCharacter, IntPtr pos, IntPtr effectHeader, IntPtr effectArray, IntPtr effectTrail) + { + _receivAbilityHook.Original(sourceId, sourceCharacter, pos, effectHeader, effectArray, effectTrail); + + //不是自己放出来的 + if (Service.ClientState.LocalPlayer == null || sourceId != Service.ClientState.LocalPlayer.ObjectId) return; + + //不是一个Spell + if (Marshal.ReadByte(effectHeader, 31) != 1) return; + + //获得行为 + var action = Service.DataManager.GetExcelSheet().GetRow((uint)Marshal.ReadInt32(effectHeader, 0x8)); + + //获得目标 + var tar = Service.ObjectTable.SearchById((uint)Marshal.ReadInt32(effectHeader)) ?? Service.ClientState.LocalPlayer; + + //获得身为技能是否正确flag + var flag = Marshal.ReadByte(effectArray + 3); + RecordAction(tar, action, flag); + } + + private static unsafe void RecordAction(GameObject tar, Action action, byte flag) + { + var id = (ActionID)action.RowId; + + //Record + switch (action.GetActinoType()) + { + case ActionCate.Spell: //魔法 + case ActionCate.Weaponskill: //战技 + LastGCD = id; + break; + case ActionCate.Ability: //能力 + LastAbility = id; + break; + default: + return; + } + _timeLastActionUsed = DateTime.Now; + LastAction = id; + + if (_actions.Count >= QUEUECAPACITY) + { + _actions.Dequeue(); + } + _actions.Enqueue(new ActionRec(_timeLastActionUsed, action)); + + //Macro + foreach (var item in Service.Configuration.Events) + { + if (!new Regex(item.Name).Match(action.Name).Success) continue; + if (item.AddMacro(tar)) break; + } + + if (flag != 0 && Service.Configuration.ShowActionFlag) + { + Service.FlyTextGui.AddFlyText(Dalamud.Game.Gui.FlyText.FlyTextKind.NamedIcon, 0, 0, 0, "Flag:" + flag.ToString(), "", + ImGui.GetColorU32(ImGuiColors.DPSRed), 0, action.Icon); + } + + //事后骂人! + if (Service.Configuration.PositionalFeedback + && ConfigurationHelper.ActionPositionals.TryGetValue(id, out var pos) + && pos.Tags.Length > 0 && !pos.Tags.Contains(flag)) + { + Service.FlyTextGui.AddFlyText(Dalamud.Game.Gui.FlyText.FlyTextKind.NamedIcon, 0, 0, 0, pos.Pos.ToName(), "", + ImGui.GetColorU32(ImGuiColors.DPSRed), 94662, action.Icon); + if (!string.IsNullOrEmpty(Service.Configuration.PositionalErrorText)) + { + Speak(Service.Configuration.PositionalErrorText); + } + } + } + + internal static void Speak(string text, bool wait = false) + { + ExecuteCommand( + $@"Add-Type -AssemblyName System.speech; + $speak = New-Object System.Speech.Synthesis.SpeechSynthesizer; + $speak.Volume = ""{Service.Configuration.VoiceVolume}""; + $speak.Speak(""{text}"");"); + + void ExecuteCommand(string command) + { + string path = Path.GetTempPath() + Guid.NewGuid() + ".ps1"; + + // make sure to be using System.Text + using (StreamWriter sw = new StreamWriter(path, false, Encoding.UTF8)) + { + sw.Write(command); + + ProcessStartInfo start = new ProcessStartInfo() + { + FileName = @"C:\Windows\System32\windowspowershell\v1.0\powershell.exe", + LoadUserProfile = false, + UseShellExecute = false, + CreateNoWindow = true, + Arguments = $"-executionpolicy bypass -File {path}", + WindowStyle = ProcessWindowStyle.Hidden + }; + + Process process = Process.Start(start); + + if (wait) + process.WaitForExit(); + } + } + } + + public static void Dispose() + { + _receivAbilityHook?.Dispose(); + } + } +} diff --git a/RotationSolver.Old/Timeline/ActionCondition.cs b/RotationSolver.Old/Timeline/ActionCondition.cs new file mode 100644 index 000000000..f30394e27 --- /dev/null +++ b/RotationSolver.Old/Timeline/ActionCondition.cs @@ -0,0 +1,187 @@ +using ImGuiNET; +using Newtonsoft.Json; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Localization; +using RotationSolver.Rotations.CustomRotation; +using System; + +namespace RotationSolver.Timeline; + +internal class ActionCondition : ICondition +{ + private BaseAction _action; + + public ActionID ID { get; set; } = ActionID.None; + + public ActionConditonType ActionConditonType = ActionConditonType.Elapsed; + + public bool Condition { get; set; } + + public int Param1; + public int Param2; + public float Time; + + + public bool IsTrue(ICustomRotation combo) + { + if (!ConditionHelper.CheckBaseAction(combo, ID, ref _action)) return false; + + var result = false; + + switch (ActionConditonType) + { + case ActionConditonType.Elapsed: + result = _action.ElapsedAfter(Time); // Bigger + break; + + case ActionConditonType.ElapsedGCD: + result = _action.ElapsedAfterGCD((uint)Param1, (uint)Param2); // Bigger + break; + + case ActionConditonType.Remain: + result = !_action.WillHaveOneCharge(Time); //Smaller + break; + + case ActionConditonType.RemainGCD: + result = !_action.WillHaveOneChargeGCD((uint)Param1, (uint)Param2); // Smaller + break; + + case ActionConditonType.ShouldUse: + result = _action.CanUse(out _, Param1 > 0, Param2 > 0); + break; + + case ActionConditonType.EnoughLevel: + result = _action.EnoughLevel; + break; + + case ActionConditonType.IsCoolDown: + result = _action.IsCoolingDown; + break; + + case ActionConditonType.CurrentCharges: + result = _action.CurrentCharges > Param1; + break; + + case ActionConditonType.MaxCharges: + result = _action.MaxCharges > Param1; + break; + } + + return Condition ? !result : result; + } + + + + [JsonIgnore] + public float Height => ICondition.DefaultHeight; + + string searchTxt = string.Empty; + + public void Draw(ICustomRotation combo) + { + ConditionHelper.CheckBaseAction(combo, ID, ref _action); + + ImGuiHelper.DrawCondition(IsTrue(combo)); + ImGui.SameLine(); + + var name = _action?.Name ?? string.Empty; + ImGui.SetNextItemWidth(Math.Max(80, ImGui.CalcTextSize(name).X + 30)); + + ImGuiHelper.SearchCombo($"##ActionChoice{GetHashCode()}", name, ref searchTxt, combo.AllBaseActions, i => + { + _action = (BaseAction)i; + ID = (ActionID)_action.ID; + }); + + ImGui.SameLine(); + + ConditionHelper.DrawIntEnum($"##Category{GetHashCode()}", ref ActionConditonType, EnumTranslations.ToName); + + var condition = Condition ? 1 : 0; + var combos = new string[0]; + switch (ActionConditonType) + { + case ActionConditonType.ElapsedGCD: + case ActionConditonType.RemainGCD: + case ActionConditonType.Elapsed: + case ActionConditonType.Remain: + case ActionConditonType.CurrentCharges: + case ActionConditonType.MaxCharges: + combos = new string[] { ">", "<=" }; + break; + + case ActionConditonType.ShouldUse: + combos = new string[] + { + LocalizationManager.RightLang.Timeline_Can, + LocalizationManager.RightLang.Timeline_Cannot, + }; + break; + + case ActionConditonType.EnoughLevel: + case ActionConditonType.IsCoolDown: + combos = new string[] + { + LocalizationManager.RightLang.Timeline_Is, + LocalizationManager.RightLang.Timeline_Isnot, + }; + break; + } + ImGui.SameLine(); + ImGuiHelper.SetNextWidthWithName(combos[condition]); + if (ImGui.Combo($"##Comparation{GetHashCode()}", ref condition, combos, combos.Length)) + { + Condition = condition > 0; + } + + + switch (ActionConditonType) + { + case ActionConditonType.Elapsed: + case ActionConditonType.Remain: + ConditionHelper.DrawDragFloat($"s##Seconds{GetHashCode()}", ref Time); + break; + + case ActionConditonType.ElapsedGCD: + case ActionConditonType.RemainGCD: + if (ConditionHelper.DrawDragInt($"GCD##GCD{GetHashCode()}", ref Param1)) + { + Param1 = Math.Max(0, Param1); + } + if (ConditionHelper.DrawDragInt($"{LocalizationManager.RightLang.Timeline_Ability}##Ability{GetHashCode()}", ref Param2)) + { + Param2 = Math.Max(0, Param2); + } + break; + + case ActionConditonType.ShouldUse: + + ConditionHelper.DrawCheckBox($"{LocalizationManager.RightLang.Timeline_MustUse}##MustUse{GetHashCode()}", ref Param1, LocalizationManager.RightLang.Timeline_MustUseDesc); + ConditionHelper.DrawCheckBox($"{LocalizationManager.RightLang.Timeline_Empty}##MustUse{GetHashCode()}", ref Param2, LocalizationManager.RightLang.Timeline_EmptyDesc); + break; + + case ActionConditonType.CurrentCharges: + case ActionConditonType.MaxCharges: + if (ConditionHelper.DrawDragInt($"{LocalizationManager.RightLang.Timeline_Charges}##Charges{GetHashCode()}", ref Param1)) + { + Param1 = Math.Max(0, Param1); + } + break; + } + } +} + +public enum ActionConditonType : int +{ + Elapsed, + ElapsedGCD, + Remain, + RemainGCD, + ShouldUse, + EnoughLevel, + IsCoolDown, + CurrentCharges, + MaxCharges, +} diff --git a/RotationSolver.Old/Timeline/BaseStatus.cs b/RotationSolver.Old/Timeline/BaseStatus.cs new file mode 100644 index 000000000..752ea581e --- /dev/null +++ b/RotationSolver.Old/Timeline/BaseStatus.cs @@ -0,0 +1,21 @@ +using Lumina.Excel.GeneratedSheets; +using RotationSolver.Data; + +namespace RotationSolver.Timeline; + +internal class BaseStatus : ITexture +{ + public Status _status; + public uint IconID => _status.Icon; + public StatusID ID => (StatusID)_status.RowId; + public string Name => $"{_status.Name}[{_status.RowId}]"; + + public string Description => string.Empty; + + public bool IsEnabled { get; set; } + + public BaseStatus(StatusID id) + { + _status = Service.DataManager.GetExcelSheet().GetRow((uint)id); + } +} diff --git a/RotationSolver.Old/Timeline/ConditionSet.cs b/RotationSolver.Old/Timeline/ConditionSet.cs new file mode 100644 index 000000000..296997398 --- /dev/null +++ b/RotationSolver.Old/Timeline/ConditionSet.cs @@ -0,0 +1,82 @@ +using Dalamud.Interface; +using ImGuiNET; +using Newtonsoft.Json; +using RotationSolver.Helpers; +using RotationSolver.Localization; +using RotationSolver.Rotations.CustomRotation; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace RotationSolver.Timeline; + +internal class ConditionSet : ICondition +{ + public bool IsTrue(ICustomRotation combo) => Conditions.Count == 0 ? false : + IsAnd ? Conditions.All(c => c.IsTrue(combo)) + : Conditions.Any(c => c.IsTrue(combo)); + public List Conditions { get; set; } = new List(); + public bool IsAnd { get; set; } + + [JsonIgnore] + public float Height => Conditions.Sum(c => c is ConditionSet ? c.Height + 10 : c.Height) + ICondition.DefaultHeight + 12; + public void Draw(ICustomRotation combo) + { + if (ImGui.BeginChild("ConditionSet" + GetHashCode().ToString(), new System.Numerics.Vector2(-1f, Height), true)) + { + AddButton(); + + ImGui.SameLine(); + + ImGuiHelper.DrawCondition(IsTrue(combo)); + + ImGui.SameLine(); + + int isAnd = IsAnd ? 1 : 0; + if (ImGui.Combo("##Rule" + GetHashCode().ToString(), ref isAnd, new string[] + { + "OR", "AND", + }, 2)) + { + IsAnd = isAnd != 0; + } + + ImGui.Separator(); + + var relay = Conditions; + if (ImGuiHelper.DrawEditorList(relay, i => i.Draw(combo))) + { + Conditions = relay; + } + + ImGui.EndChild(); + } + } + + private void AddButton() + { + if (ImGuiHelper.IconButton(FontAwesomeIcon.Plus, "AddButton" + GetHashCode().ToString())) + { + ImGui.OpenPopup("Popup" + GetHashCode().ToString()); + } + + if (ImGui.BeginPopup("Popup" + GetHashCode().ToString())) + { + AddOneCondition(LocalizationManager.RightLang.Timeline_ConditionSet); + AddOneCondition(LocalizationManager.RightLang.Timeline_ActionCondition); + AddOneCondition(LocalizationManager.RightLang.Timeline_TargetCondition); + AddOneCondition(LocalizationManager.RightLang.Timeline_RotationCondition); + + ImGui.EndPopup(); + } + } + + private void AddOneCondition(string name) where T : ICondition + { + if (ImGui.Selectable(name)) + { + Conditions.Add(Activator.CreateInstance()); + ImGui.CloseCurrentPopup(); + } + } +} diff --git a/RotationSolver.Old/Timeline/ICondition.cs b/RotationSolver.Old/Timeline/ICondition.cs new file mode 100644 index 000000000..a0db3dcab --- /dev/null +++ b/RotationSolver.Old/Timeline/ICondition.cs @@ -0,0 +1,11 @@ +using RotationSolver.Rotations.CustomRotation; + +namespace RotationSolver.Timeline; + +internal interface ICondition +{ + const float DefaultHeight = 33; + bool IsTrue(ICustomRotation rotation); + void Draw(ICustomRotation rotation); + float Height { get; } +} \ No newline at end of file diff --git a/RotationSolver.Old/Timeline/IConditionConverter.cs b/RotationSolver.Old/Timeline/IConditionConverter.cs new file mode 100644 index 000000000..6d9583d1d --- /dev/null +++ b/RotationSolver.Old/Timeline/IConditionConverter.cs @@ -0,0 +1,67 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; + +namespace RotationSolver.Timeline; + +internal class IConditionConverter : JsonCreationConverter +{ + protected override ICondition Create(JObject jObject) + { + if (FieldExists(nameof(ConditionSet.Conditions), jObject)) + { + return new ConditionSet(); + } + else if (FieldExists(nameof(ActionCondition.ActionConditonType), jObject)) + { + return new ActionCondition(); + } + else if (FieldExists(nameof(TargetCondition.TargetConditionType), jObject)) + { + return new TargetCondition(); + } + else if (FieldExists(nameof(RotationCondition.ComboConditionType), jObject)) + { + return new RotationCondition(); + } + else + { + return null; + } + } + + private bool FieldExists(string fieldName, JObject jObject) + { + return jObject[fieldName] != null; + } +} + +public abstract class JsonCreationConverter : JsonConverter +{ + protected abstract T Create(JObject jObject); + + public override bool CanConvert(Type objectType) + { + return typeof(T).IsAssignableFrom(objectType); + } + + public override bool CanWrite => false; + + public sealed override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + } + + public sealed override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + // Load JObject from stream + JObject jObject = JObject.Load(reader); + + // Create target object based on JObject + T target = Create(jObject); + + // Populate the object properties + serializer.Populate(jObject.CreateReader(), target); + + return target; + } +} diff --git a/RotationSolver.Old/Timeline/MajorConditionSet.cs b/RotationSolver.Old/Timeline/MajorConditionSet.cs new file mode 100644 index 000000000..ca103ce03 --- /dev/null +++ b/RotationSolver.Old/Timeline/MajorConditionSet.cs @@ -0,0 +1,48 @@ +using Newtonsoft.Json; +using RotationSolver.Data; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace RotationSolver.Timeline; + +internal class MajorConditionSet +{ + /// + /// Key for action id. + /// + public Dictionary Conditions { get; } = new Dictionary(); + + public string Name; + + public MajorConditionSet() + { + + } + + public MajorConditionSet(string name) + { + Name = name; + } + + public void Save(string folder) + { + if (!Directory.Exists(folder)) return; + var path = Path.Combine(folder, Name + ".json"); + + var str = JsonConvert.SerializeObject(this); + File.WriteAllText(path, str); + } + + public static MajorConditionSet[] Read(string folder) + { + if (!Directory.Exists(folder)) return new MajorConditionSet[0]; + + return Directory.EnumerateFiles(folder, "*.json").Select(p => + { + var str = File.ReadAllText(p); + + return JsonConvert.DeserializeObject(str, new IConditionConverter()); + }).Where(set => set != null && !string.IsNullOrEmpty(set.Name)).ToArray(); + } +} diff --git a/RotationSolver.Old/Timeline/RotationCondition.cs b/RotationSolver.Old/Timeline/RotationCondition.cs new file mode 100644 index 000000000..166ec8a2b --- /dev/null +++ b/RotationSolver.Old/Timeline/RotationCondition.cs @@ -0,0 +1,249 @@ +using ImGuiNET; +using Newtonsoft.Json; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Localization; +using RotationSolver.Rotations.CustomRotation; +using System.Reflection; + +namespace RotationSolver.Timeline; + +internal class RotationCondition : ICondition +{ + public ComboConditionType ComboConditionType; + PropertyInfo _prop; + public string PropertyName { get; set; } = string.Empty; + + MethodInfo _method; + public string MethodName { get; set; } = string.Empty; + + BaseAction _action; + + public ActionID ID { get; set; } = ActionID.None; + + public int Condition; + + public int Param1; + public int Param2; + public float Time; + + private void UpdateInfo(ICustomRotation rotation) + { + ConditionHelper.CheckBaseAction(rotation, ID, ref _action); + ConditionHelper.CheckMemberInfo(rotation, PropertyName, ref _prop); + ConditionHelper.CheckMemberInfo(rotation, MethodName, ref _method); + } + + public bool IsTrue(ICustomRotation rotation) + { + if (Service.ClientState.LocalPlayer == null) return false; + UpdateInfo(rotation); + + switch (ComboConditionType) + { + case ComboConditionType.Bool: + if (_prop == null) return false; + if (_prop.GetValue(rotation) is bool b) + { + return Condition > 0 ? !b : b; + } + return false; + + case ComboConditionType.Byte: + if (_prop == null) return false; + if (_prop.GetValue(rotation) is byte by) + { + switch (Condition) + { + case 0: + return by > Param1; + case 1: + return by == Param1; + case 2: + return by < Param1; + } + } + return false; + + case ComboConditionType.Time: + try + { + if (_method?.Invoke(rotation, new object[] { Time }) is bool bo) + { + return Condition > 0 ? bo : !bo; + } + return false; + } + catch + { + return false; + } + + + case ComboConditionType.TimeGCD: + try + { + if (_method?.Invoke(rotation, new object[] { (uint)Param1, (uint)Param2 }) is bool boo) + { + return Condition > 0 ? boo : !boo; + } + return false; + } + catch + { + return false; + } + + case ComboConditionType.Last: + try + { + if (_method?.Invoke(rotation, new object[] { Param1 > 0, new IAction[] { _action } }) is bool boo) + { + return Condition > 0 ? boo : !boo; + } + return false; + } + catch + { + return false; + } + } + + return false; + } + + + [JsonIgnore] + public float Height => ICondition.DefaultHeight; + + string searchTxt = string.Empty; + public void Draw(ICustomRotation rotation) + { + UpdateInfo(rotation); + + + ImGuiHelper.DrawCondition(IsTrue(rotation)); + ImGui.SameLine(); + + ConditionHelper.DrawIntEnum($"##Category{GetHashCode()}", ref ComboConditionType, EnumTranslations.ToName); + + switch (ComboConditionType) + { + case ComboConditionType.Bool: + ImGui.SameLine(); + ImGuiHelper.SearchItemsReflection($"##Comparation{GetHashCode()}", _prop?.GetMemberName(), ref searchTxt, rotation.AllBools, i => + { + _prop = i; + PropertyName = i.Name; + }); + + ImGui.SameLine(); + ImGui.SetNextItemWidth(80); + ImGui.Combo($"##IsOrNot{GetHashCode()}", ref Condition, new string[] + { + LocalizationManager.RightLang.Timeline_Is, + LocalizationManager.RightLang.Timeline_Isnot, + }, 2); + break; + + case ComboConditionType.Byte: + ImGui.SameLine(); + ImGuiHelper.SearchItemsReflection($"##ByteChoice{GetHashCode()}", _prop?.GetMemberName(), ref searchTxt, rotation.AllBytes, i => + { + _prop = i; + PropertyName = i.Name; + }); + + + ImGui.SameLine(); + ImGui.SetNextItemWidth(50); + ImGui.Combo($"##Comparation{GetHashCode()}", ref Condition, new string[] { ">", "<", "=" }, 3); + + ImGui.SameLine(); + ImGui.SetNextItemWidth(50); + + ImGui.DragInt($"##Value{GetHashCode()}", ref Param1); + + break; + case ComboConditionType.Time: + ImGui.SameLine(); + ImGuiHelper.SearchItemsReflection($"##Time{GetHashCode()}", _method?.GetMemberName(), ref searchTxt, rotation.AllTimes, i => + { + _method = i; + MethodName = i.Name; + }); + + ImGui.SameLine(); + ImGui.SetNextItemWidth(50); + ImGui.Combo($"##Comparation{GetHashCode()}", ref Condition, new string[] { ">", "<=" }, 2); + + ImGui.SameLine(); + ImGui.SetNextItemWidth(50); + ImGui.DragFloat($"s##s{GetHashCode()}", ref Time); + break; + + case ComboConditionType.TimeGCD: + ImGui.SameLine(); + ImGuiHelper.SearchItemsReflection($"##Time{GetHashCode()}", _method?.GetMemberName(), ref searchTxt, rotation.AllGCDs, i => + { + _method = i; + MethodName = i.Name; + }); + + ImGui.SameLine(); + ImGui.SetNextItemWidth(50); + ImGui.Combo($"##Comparation{GetHashCode()}", ref Condition, new string[] { ">", "<=" }, 2); + + ImGui.SameLine(); + ImGui.SetNextItemWidth(50); + ImGui.DragInt($"GCD##GCD{GetHashCode()}", ref Param1); + + ImGui.SameLine(); + ImGui.SetNextItemWidth(50); + var ability = Param2; + ImGui.DragInt($"Ability##Ability{GetHashCode()}", ref Param2); + break; + + case ComboConditionType.Last: + ImGui.SameLine(); + ImGuiHelper.SearchItemsReflection($"##Time{GetHashCode()}", _method?.GetMemberName(), ref searchTxt, rotation.AllLast, i => + { + _method = i; + MethodName = i.Name; + }); + + ImGui.SameLine(); + ImGui.SetNextItemWidth(80); + ImGui.Combo($"##IsNot{GetHashCode()}", ref Condition, new string[] + { + LocalizationManager.RightLang.Timeline_Is, + LocalizationManager.RightLang.Timeline_Isnot, + }, 2); + + ImGui.SameLine(); + var name = _action?.Name ?? string.Empty; + ImGuiHelper.SearchCombo($"##ActionChoice{GetHashCode()}", name, ref searchTxt, rotation.AllBaseActions, i => + { + _action = (BaseAction)i; + ID = (ActionID)_action.ID; + }); + + ImGui.SameLine(); + ImGui.SetNextItemWidth(50); + ImGui.Combo($"##Adjust{GetHashCode()}", ref Param1, new string[] { "Original", "Adjusted" }, 2); + + break; + } + } +} + +public enum ComboConditionType : int +{ + Bool, + Byte, + Time, + TimeGCD, + Last, +} diff --git a/RotationSolver.Old/Timeline/TargetCondition.cs b/RotationSolver.Old/Timeline/TargetCondition.cs new file mode 100644 index 000000000..da5ae4015 --- /dev/null +++ b/RotationSolver.Old/Timeline/TargetCondition.cs @@ -0,0 +1,280 @@ +using Dalamud.Game.ClientState.Objects.Types; +using ImGuiNET; +using Lumina.Excel.GeneratedSheets; +using Newtonsoft.Json; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Localization; +using RotationSolver.Rotations.CustomRotation; +using System; +using System.Linq; + +namespace RotationSolver.Timeline; + +internal class TargetCondition : ICondition +{ + private static BaseStatus[] _allStatus = null; + private static BaseStatus[] AllStatus + { + get + { + if (_allStatus == null) + { + _allStatus = Enum.GetValues().Select(id => new BaseStatus(id)).ToArray(); + } + return _allStatus; + } + } + + private BaseAction _action; + public ActionID ID { get; set; } = ActionID.None; + + public bool Condition; + public bool FromSelf; + private BaseStatus _status { get; set; } + public StatusID Status { get; set; } + public bool IsTarget; + public TargetConditionType TargetConditionType; + + public float DistanceOrTime; + + public int GCD; + public int Ability; + + public string CastingActionName = string.Empty; + + public bool IsTrue(ICustomRotation combo) + { + if (Service.ClientState.LocalPlayer == null) return false; + + BattleChara tar = null; + if (_action != null) + { + _action.CanUse(out _, true); + tar = _action.Target; + } + else + { + tar = IsTarget ? (BattleChara)Service.TargetManager.Target : Service.ClientState.LocalPlayer; + tar ??= Service.ClientState.LocalPlayer; + } + + if (tar == null) return false; + + var result = false; + + switch (TargetConditionType) + { + case TargetConditionType.HaveStatus: + result = tar.HasStatus(FromSelf, Status); + break; + + case TargetConditionType.IsBoss: + result = tar.IsBoss(); + break; + + case TargetConditionType.IsDying: + result = tar.IsDying(); + break; + + case TargetConditionType.Distance: + result = tar.DistanceToPlayer() > DistanceOrTime; + break; + + case TargetConditionType.StatusEnd: + result = !tar.WillStatusEnd(DistanceOrTime, FromSelf, Status); + break; + + case TargetConditionType.StatusEndGCD: + result = !tar.WillStatusEndGCD((uint)GCD, (uint)Ability, FromSelf, Status); + break; + + case TargetConditionType.CastingAction: + if (string.IsNullOrEmpty(CastingActionName) || tar.CastActionId == 0) + { + result = false; + break; + } + + var castName = Service.DataManager.GetExcelSheet().GetRow(tar.CastActionId)?.Name.ToString(); + + result = CastingActionName == castName; + break; + } + + return Condition ? !result : result; + } + + [JsonIgnore] + public float Height => ICondition.DefaultHeight; + + string searchTxt = string.Empty; + public void Draw(ICustomRotation combo) + { + ConditionHelper.CheckBaseAction(combo, ID, ref _action); + + if (Status != StatusID.None && (_status == null || _status.ID != Status)) + { + _status = AllStatus.FirstOrDefault(a => a.ID == Status); + } + + ImGuiHelper.DrawCondition(IsTrue(combo)); + ImGui.SameLine(); + + var name = _action != null ? string.Format(LocalizationManager.RightLang.Timeline_ActionTarget, _action.Name) + : IsTarget + ? LocalizationManager.RightLang.Timeline_Target + : LocalizationManager.RightLang.Timeline_Player; + + ImGui.SetNextItemWidth(Math.Max(80, ImGui.CalcTextSize(name).X + 30)); + if (ImGui.BeginCombo($"##ActionChoice{GetHashCode()}", name, ImGuiComboFlags.HeightLargest)) + { + if (ImGui.Selectable(LocalizationManager.RightLang.Timeline_Target)) + { + _action = null; + ID = ActionID.None; + IsTarget = true; + } + + if (ImGui.Selectable(LocalizationManager.RightLang.Timeline_Player)) + { + _action = null; + ID = ActionID.None; + IsTarget = false; + } + + ImGuiHelper.SearchItems(ref searchTxt, combo.AllBaseActions, i => + { + _action = (BaseAction)i; + ID = (ActionID)_action.ID; + }); + + ImGui.EndCombo(); + } + + ImGui.SameLine(); + + ConditionHelper.DrawIntEnum($"##Category{GetHashCode()}", ref TargetConditionType, EnumTranslations.ToName); + + var condition = Condition ? 1 : 0; + var combos = new string[0]; + switch (TargetConditionType) + { + case TargetConditionType.HaveStatus: + combos = new string[] + { + LocalizationManager.RightLang.Timeline_Have, + LocalizationManager.RightLang.Timeline_Havenot, + }; + break; + case TargetConditionType.IsDying: + case TargetConditionType.IsBoss: + case TargetConditionType.CastingAction: + combos = new string[] + { + LocalizationManager.RightLang.Timeline_Is, + LocalizationManager.RightLang.Timeline_Isnot, + }; + break; + + case TargetConditionType.Distance: + case TargetConditionType.StatusEnd: + combos = new string[] { ">", "<=" }; + break; + } + + ImGui.SameLine(); + ImGui.SetNextItemWidth(60); + if (ImGui.Combo($"##Comparation{GetHashCode()}", ref condition, combos, combos.Length)) + { + Condition = condition > 0; + } + + switch (TargetConditionType) + { + case TargetConditionType.HaveStatus: + ImGui.SameLine(); + ImGuiHelper.SetNextWidthWithName(_status?.Name); + ImGuiHelper.SearchCombo($"##Status{GetHashCode()}", _status?.Name, ref searchTxt, AllStatus, i => + { + _status = i; + Status = _status.ID; + }); + + ImGui.SameLine(); + + ImGui.Checkbox($"{LocalizationManager.RightLang.Timeline_StatusSelf}##Self{GetHashCode()}", ref FromSelf); + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(LocalizationManager.RightLang.Timeline_StatusSelfDesc); + } + break; + + case TargetConditionType.StatusEnd: + ImGui.SameLine(); + ImGuiHelper.SetNextWidthWithName(_status?.Name); + ImGuiHelper.SearchCombo($"##Status{GetHashCode()}", _status?.Name, ref searchTxt, AllStatus, i => + { + _status = i; + Status = _status.ID; + }); + + ImGui.SameLine(); + + ImGui.Checkbox($"{LocalizationManager.RightLang.Timeline_StatusSelf}##Self{GetHashCode()}", ref FromSelf); + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(LocalizationManager.RightLang.Timeline_StatusSelfDesc); + } + + ConditionHelper.DrawDragFloat($"s##Seconds{GetHashCode()}", ref DistanceOrTime); + break; + + + case TargetConditionType.StatusEndGCD: + ImGui.SameLine(); + ImGuiHelper.SetNextWidthWithName(_status?.Name); + ImGuiHelper.SearchCombo($"##Status{GetHashCode()}", _status?.Name, ref searchTxt, AllStatus, i => + { + _status = i; + Status = _status.ID; + }); + + ImGui.SameLine(); + + ImGui.Checkbox($"{LocalizationManager.RightLang.Timeline_StatusSelf}##Self{GetHashCode()}", ref FromSelf); + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(LocalizationManager.RightLang.Timeline_StatusSelfDesc); + } + + ConditionHelper.DrawDragInt($"GCD##GCD{GetHashCode()}", ref GCD); + ConditionHelper.DrawDragInt($"{LocalizationManager.RightLang.Timeline_Ability}##Ability{GetHashCode()}", ref Ability); + break; + + case TargetConditionType.Distance: + if (ConditionHelper.DrawDragFloat($"m##m{GetHashCode()}", ref DistanceOrTime)) + { + DistanceOrTime = Math.Max(0, DistanceOrTime); + } + break; + + case TargetConditionType.CastingAction: + ImGui.SameLine(); + ImGui.InputText("##CastingActionName", ref CastingActionName, 100); + break; + } + } +} + +public enum TargetConditionType : int +{ + HaveStatus, + IsDying, + IsBoss, + Distance, + StatusEnd, + StatusEndGCD, + CastingAction, +} \ No newline at end of file diff --git a/RotationSolver.Old/Timeline/Timeline.cd b/RotationSolver.Old/Timeline/Timeline.cd new file mode 100644 index 000000000..2bd5f0b5c --- /dev/null +++ b/RotationSolver.Old/Timeline/Timeline.cd @@ -0,0 +1,93 @@ + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAACAAAAAAAAAA= + Timeline\IConditionConverter.cs + + + + + + AAAAAAAAAQQAAAAAEAAAAAAAAAAAAEAAAAAAAAAACAA= + Timeline\IConditionConverter.cs + + + + + + AAAAAAIABACAgggAAAAAAAAAAAAAAAAAAIAAgAAAAQA= + Timeline\ActionCondition.cs + + + + + + + CAAAAAAABAAAAggAABAAEAAAAAAAAAAAAAAAAIAAAAA= + Timeline\ConditionSet.cs + + + + + + + AAAAAIAAAAAAAAAAABAAAAQgAAAAAAAAAAAAAAAAAAA= + Timeline\MajorConditionSet.cs + + + + + + AAAAAAIABECAwggAAABAAAAAAAAAAAAAAAACggBAAQA= + Timeline\RotationCondition.cs + + + + + + + AEAAAAAAAAAggAgAAAEAAAQAAAAAAAAAAAAAAAAAAAA= + Timeline\BaseStatus.cs + + + + + + + AEAAAQQkJACAkggAAQAABAAAAAAAAAAAAAAAgAAAAAA= + Timeline\TargetCondition.cs + + + + + + + AAAAAAAABAAAAggAAAAAAAAAAAAAAAAAAAAAAAAIAAA= + Timeline\ICondition.cs + + + + + + AAAgAAIAAAAABIAgAAAAAAAAACAAAAAAAAABAAAAgIA= + Timeline\ActionCondition.cs + + + + + + AAAAAAAAAAAAAAAgAAAAAAAABAAAAAAAAAACAAAAAQI= + Timeline\RotationCondition.cs + + + + + + AAAAAAAAAAAAAAAAACCAAAAAAAAQAAhAAAEAAIAAAAA= + Timeline\TargetCondition.cs + + + + \ No newline at end of file diff --git a/RotationSolver.Old/Updaters/ActionUpdater.cs b/RotationSolver.Old/Updaters/ActionUpdater.cs new file mode 100644 index 000000000..afa2e7408 --- /dev/null +++ b/RotationSolver.Old/Updaters/ActionUpdater.cs @@ -0,0 +1,212 @@ +using Dalamud.Game.ClientState.Conditions; +using Dalamud.Game.ClientState.Objects.SubKinds; +using FFXIVClientStructs.FFXIV.Client.Game; +using RotationSolver.Actions; +using RotationSolver.Commands; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.SigReplacers; +using System; + +namespace RotationSolver.Updaters; + +internal static class ActionUpdater +{ + public static unsafe float ComboTime => ActionManager.Instance()->Combo.Timer; + public static unsafe ActionID LastComboAction => (ActionID)ActionManager.Instance()->Combo.Action; + + static DateTime _startCombatTime = DateTime.MinValue; + public static TimeSpan CombatTime + { + get + { + if(_startCombatTime == DateTime.MinValue) return TimeSpan.Zero; + return DateTime.Now - _startCombatTime; + } + } + + static RandomDelay _GCDDelay = new RandomDelay(() => (Service.Configuration.WeaponDelayMin, Service.Configuration.WeaponDelayMax)); + + internal static float WeaponRemain { get; private set; } = 0; + + internal static float WeaponTotal { get; private set; } = 0; + + internal static float WeaponElapsed { get; private set; } = 0; + + internal static bool InCombat { get; private set; } = false; + + internal static byte AbilityRemainCount { get; private set; } = 0; + + internal static float AbilityRemain { get; private set; } = 0; + + internal static uint[] BluSlots { get; private set; } = new uint[24]; + + internal static IAction NextAction { get; private set; } + +#if DEBUG + internal static Exception exception; +#endif + + internal static void UpdateNextAction() + { + + PlayerCharacter localPlayer = Service.ClientState.LocalPlayer; + if (localPlayer == null) return; + + try + { + var customRotation = RotationUpdater.RightNowRotation; + + if (customRotation?.TryInvoke(out var newAction) ?? false) + { + NextAction = newAction; + return; + } + } +#if DEBUG + catch (Exception ex) + { + exception = ex; + } +#else + catch { } +#endif + + NextAction = null; + } + + internal unsafe static void UpdateActionInfo() + { + var last = InCombat; + InCombat = Service.Conditions[ConditionFlag.InCombat]; + if(!last && InCombat) + { + _startCombatTime = DateTime.Now; + } + else if(last && !InCombat) + { + _startCombatTime = DateTime.MinValue; + } + + for (int i = 0; i < BluSlots.Length; i++) + { + BluSlots[i] = ActionManager.Instance()->GetActiveBlueMageActionInSlot(i); + } + UPdateMPTimer(); + } + + internal static unsafe void UpdateWeaponTime() + { + var player = Service.ClientState.LocalPlayer; + if (player == null) return; + + var instance = ActionManager.Instance(); + + var castTotal = player.TotalCastTime; + castTotal = castTotal > 2.5f ? castTotal + 0.1f : castTotal; + + var weapontotal = instance->GetRecastTime(ActionType.Spell, 11); + if (player.IsCasting) weapontotal = Math.Max(weapontotal, castTotal); + + WeaponElapsed = instance->GetRecastTimeElapsed(ActionType.Spell, 11); + WeaponRemain = WeaponElapsed == 0 ? player.TotalCastTime - player.CurrentCastTime + : Math.Max(weapontotal - WeaponElapsed, player.TotalCastTime - player.CurrentCastTime); + + //确定读条时间。 + if (WeaponElapsed < 0.3) _lastCastingTotal = castTotal; + + //确认能力技的相关信息 + var interval = Service.Configuration.AbilitiesInterval; + if (WeaponRemain < interval || WeaponElapsed == 0) + { + AbilityRemain = 0; + if (WeaponRemain > 0) + { + AbilityRemain = WeaponRemain + interval; + } + AbilityRemainCount = 0; + } + else if (WeaponRemain < 2 * interval) + { + AbilityRemain = WeaponRemain - interval; + AbilityRemainCount = 1; + } + else + { + var abilityWhole = (int)(weapontotal / Service.Configuration.AbilitiesInterval - 1); + AbilityRemain = interval - WeaponElapsed % interval; + AbilityRemainCount = (byte)(abilityWhole - (int)(WeaponElapsed / interval)); + } + + if (weapontotal > 0) WeaponTotal = weapontotal; + } + + static uint _lastMP = 0; + static DateTime _lastMPUpdate = DateTime.Now; + /// + /// 跳蓝经过时间 + /// + internal static float MPUpdateElapsed => (float)(DateTime.Now - _lastMPUpdate).TotalSeconds % 3; + + private static void UPdateMPTimer() + { + var player = Service.ClientState.LocalPlayer; + if (player == null) return; + + //不是黑魔不考虑啊 + if (player.ClassJob.Id != (uint)ClassJobID.BlackMage) return; + + //有醒梦,就算了啊 + if (player.HasStatus(true, StatusID.LucidDreaming)) return; + + if (_lastMP < player.CurrentMp) + { + _lastMPUpdate = DateTime.Now; + } + _lastMP = player.CurrentMp; + } + + internal static float _lastCastingTotal = 0; + internal unsafe static void DoAction() + { + if (Service.Conditions[ConditionFlag.OccupiedInQuestEvent] + || Service.Conditions[ConditionFlag.OccupiedInCutSceneEvent] + || Service.Conditions[ConditionFlag.Occupied33] + || Service.Conditions[ConditionFlag.Occupied38] + || Service.Conditions[ConditionFlag.Jumping61] + || Service.Conditions[ConditionFlag.BetweenAreas] + || Service.Conditions[ConditionFlag.BetweenAreas51] + || Service.Conditions[ConditionFlag.Mounted] + //|| Service.Conditions[ConditionFlag.SufferingStatusAffliction] //Because of BLU30! + || Service.Conditions[ConditionFlag.SufferingStatusAffliction2] + || Service.Conditions[ConditionFlag.RolePlaying] + || Service.Conditions[ConditionFlag.InFlight] + || ActionManager.Instance()->ActionQueued) return; + + //GCD + var canUseGCD = WeaponRemain <= Service.Configuration.ActionAhead; + if (_GCDDelay.Delay(canUseGCD)) RSCommands.DoAnAction(true); + if (canUseGCD) return; + + //要超出GCD了,那就不放技能了。 + if (WeaponRemain < Service.Configuration.AbilitiesInterval + || WeaponElapsed < Service.Configuration.AbilitiesInterval) + { + return; + } + + //还在咏唱,就不放技能了。 + if (WeaponElapsed <= _lastCastingTotal) return; + + //只剩下最后一个能力技了,然后卡最后! + if (WeaponRemain < 2 * Service.Configuration.AbilitiesInterval) + { + if (WeaponRemain > Service.Configuration.AbilitiesInterval + Service.Configuration.ActionAhead) return; + RSCommands.DoAnAction(false); + } + else if ((WeaponElapsed - _lastCastingTotal) % Service.Configuration.AbilitiesInterval <= Service.Configuration.ActionAhead) + { + RSCommands.DoAnAction(false); + } + } +} diff --git a/RotationSolver.Old/Updaters/MacroUpdater.cs b/RotationSolver.Old/Updaters/MacroUpdater.cs new file mode 100644 index 000000000..fec2a0299 --- /dev/null +++ b/RotationSolver.Old/Updaters/MacroUpdater.cs @@ -0,0 +1,45 @@ +using Dalamud.Game.ClientState.Objects.Types; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; +using Lumina.Excel.GeneratedSheets; +using RotationSolver.Configuration; +using RotationSolver.Data; +using System.Collections.Generic; + +namespace RotationSolver.Updaters; + +internal static class MacroUpdater +{ + internal static readonly Queue Macros = new Queue(); + internal static MacroItem DoingMacro; + + public static void UpdateMacro() + { + //如果没有有正在运行的宏,弄一个出来 + if (DoingMacro == null && Macros.TryDequeue(out var macro)) + { + DoingMacro = macro; + } + + //如果有正在处理的宏 + if (DoingMacro != null) + { + //正在跑的话,就尝试停止,停止成功就放弃它。 + if (DoingMacro.IsRunning) + { + if (DoingMacro.EndUseMacro()) + { + DoingMacro = null; + } + else + { + return; + } + } + //否则,始终开始。 + else + { + DoingMacro.StartUseMacro(); + } + } + } +} diff --git a/RotationSolver.Old/Updaters/MajorUpdater.cs b/RotationSolver.Old/Updaters/MajorUpdater.cs new file mode 100644 index 000000000..87fe980fd --- /dev/null +++ b/RotationSolver.Old/Updaters/MajorUpdater.cs @@ -0,0 +1,100 @@ +using Dalamud.Game; +using Dalamud.Logging; +using RotationSolver.Commands; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Threading.Tasks.Dataflow; + +namespace RotationSolver.Updaters; + +internal static class MajorUpdater +{ + private static bool IsValid => Service.Conditions.Any() && Service.ClientState.LocalPlayer != null; + + //#if DEBUG + // private static readonly Dictionary _valus = new Dictionary(); + //#endif + + private static void FrameworkUpdate(Framework framework) + { + if (!IsValid) return; + + //#if DEBUG + // //Get changed condition. + // string[] enumNames = Enum.GetNames(typeof(Dalamud.Game.ClientState.Conditions.ConditionFlag)); + // int[] indexs = (int[])Enum.GetValues(typeof(Dalamud.Game.ClientState.Conditions.ConditionFlag)); + // if (enumNames.Length == indexs.Length) + // { + // for (int i = 0; i < enumNames.Length; i++) + // { + // string key = enumNames[i]; + // bool newValue = Service.Conditions[(Dalamud.Game.ClientState.Conditions.ConditionFlag)indexs[i]]; + // if (_valus.ContainsKey(i) && _valus[i] != newValue && indexs[i] != 48 && indexs[i] != 27) + // { + // Service.ToastGui.ShowQuest(indexs[i].ToString() + " " + key + ": " + newValue.ToString()); + // } + // _valus[i] = newValue; + // } + // } + //#endif + + SocialUpdater.UpdateSocial(); + PreviewUpdater.UpdatePreview(); + ActionUpdater.UpdateWeaponTime(); + + ActionUpdater.DoAction(); + + MacroUpdater.UpdateMacro(); + + if (Service.Configuration.UseWorkTask) + { + Task.Run(UpdateWork); + } + else + { + UpdateWork(); + } + } + + public static void Enable() + { + Service.Framework.Update += FrameworkUpdate; + MovingUpdater.Enable(); + } + + static bool _work; + private static void UpdateWork() + { + if (!IsValid) return; + if (_work) return; + _work = true; + + try + { + PreviewUpdater.UpdateCastBarState(); + TargetUpdater.UpdateTarget(); + ActionUpdater.UpdateActionInfo(); + + RotationUpdater.UpdateRotation(); + + TimeLineUpdater.UpdateTimelineAction(); + ActionUpdater.UpdateNextAction(); + RSCommands.UpdateRotationState(); + } + catch (Exception ex) + { + PluginLog.Error(ex, "Worker Exception"); + } + + _work = false; + } + + public static void Dispose() + { + Service.Framework.Update -= FrameworkUpdate; + PreviewUpdater.Dispose(); + MovingUpdater.Dispose(); + } +} diff --git a/RotationSolver/Updaters/MovingUpdater.cs b/RotationSolver.Old/Updaters/MovingUpdater.cs similarity index 100% rename from RotationSolver/Updaters/MovingUpdater.cs rename to RotationSolver.Old/Updaters/MovingUpdater.cs diff --git a/RotationSolver.Old/Updaters/PreviewUpdater.cs b/RotationSolver.Old/Updaters/PreviewUpdater.cs new file mode 100644 index 000000000..556b8b169 --- /dev/null +++ b/RotationSolver.Old/Updaters/PreviewUpdater.cs @@ -0,0 +1,276 @@ +using Dalamud.Game.ClientState.Objects.SubKinds; +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Game.Gui.Dtr; +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling.Payloads; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.Graphics; +using FFXIVClientStructs.FFXIV.Client.System.Framework; +using FFXIVClientStructs.FFXIV.Client.System.Memory; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; +using FFXIVClientStructs.FFXIV.Component.GUI; +using RotationSolver.Commands; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.SigReplacers; +using System; +using System.Runtime.InteropServices; + +namespace RotationSolver.Updaters; + +internal static class PreviewUpdater +{ + internal static void UpdatePreview() + { + UpdateEntry(); + UpdateCastBar(); + UpdateHightLight(); + } + + static DtrBarEntry _dtrEntry; + static string _showValue; + private static void UpdateEntry() + { + var showStr = RSCommands.EntryString; + if (Service.Configuration.ShowInfoOnDtr && !string.IsNullOrEmpty(showStr)) + { + try + { + _dtrEntry ??= Service.DtrBar.Get("Rotation Solver"); + } + catch + { + return; + } + + if (!_dtrEntry.Shown) _dtrEntry.Shown = true; + if(_showValue != showStr) + { + _showValue = showStr; + _dtrEntry.Text = new SeString( + new IconPayload(BitmapFontIcon.DPS), + new TextPayload(_showValue) + ); + } + } + else if (_dtrEntry != null && _dtrEntry.Shown) + { + _dtrEntry.Shown = false; + } + } + + static bool _canMove; + static bool _isTarNoNeedCast; + static RandomDelay _tarStopCastDelay = new RandomDelay(() => + (Service.Configuration.StopCastingDelayMin, Service.Configuration.StopCastingDelayMax)); + internal static void UpdateCastBarState() + { + var tardead = Service.Configuration.UseStopCasting ? + Service.ObjectTable.SearchById(Service.ClientState.LocalPlayer.CastTargetObjectId) is BattleChara b + && (b is PlayerCharacter ? b.HasStatus(false, StatusID.Raise) : b.CurrentHp == 0 ): false; + _isTarNoNeedCast = _tarStopCastDelay.Delay(tardead); + + bool canMove = !Service.Conditions[Dalamud.Game.ClientState.Conditions.ConditionFlag.OccupiedInEvent] + && !Service.Conditions[Dalamud.Game.ClientState.Conditions.ConditionFlag.Casting]; + + //For lock + var specialStatus = Service.ClientState.LocalPlayer.HasStatus(true, StatusID.PhantomFlurry, StatusID.TenChiJin); + + MovingUpdater.IsMoving = _canMove = specialStatus ? false : canMove; + } + + static bool _showCanMove; + static readonly ByteColor _redColor = new ByteColor() { A = 255, R = 120, G = 0, B = 60 }; + static readonly ByteColor _greenColor = new ByteColor() { A = 255, R = 60, G = 120, B = 30 }; + private static unsafe void UpdateCastBar() + { + if (_isTarNoNeedCast) + { + UIState.Instance()->Hotbar.CancelCast(); + } + + var nowMove = _canMove && Service.Configuration.CastingDisplay; + if (nowMove == _showCanMove) return; + _showCanMove = nowMove; + + ByteColor c = _showCanMove ? _greenColor : _redColor; + + var castBar = Service.GameGui.GetAddonByName("_CastBar", 1); + if (castBar == IntPtr.Zero) return; + AtkResNode* progressBar = ((AtkUnitBase*)castBar)->UldManager.NodeList[5]; + + progressBar->AddRed = c.R; + progressBar->AddGreen = c.G; + progressBar->AddBlue = c.B; + } + + static uint _higtLightId; + private unsafe static void UpdateHightLight() + { + var actId = ActionUpdater.NextAction?.AdjustedID ?? 0; + if (_higtLightId == actId) return; + _higtLightId = actId; + + HigglightAtionBar((slot, hot) => + { + return Service.Configuration.TeachingMode && IsActionSlotRight(slot, hot, _higtLightId); + }); + } + + internal static unsafe void PulseAtionBar(uint actionID) + { + LoopAllSlotBar((bar, hotbar, index) => + { + return IsActionSlotRight(bar, hotbar, actionID); + }); + } + + private unsafe static bool IsActionSlotRight(ActionBarSlot* slot, HotBarSlot* hot, uint actionID) + { + if (hot->IconTypeA != HotbarSlotType.CraftAction && hot->IconTypeA != HotbarSlotType.Action) return false; + if (hot->IconTypeB != HotbarSlotType.CraftAction && hot->IconTypeB != HotbarSlotType.Action) return false; + if (slot->ActionId == (uint)IconReplacer.KeyActionID) return false; + + return Service.IconReplacer.OriginalHook((uint)slot->ActionId) == actionID; + } + + const int ActionBarSlotsCount = 12; + static readonly string[] _barsName = new string[] + { + "_ActionBar", + "_ActionBar01", + "_ActionBar02", + "_ActionBar03", + "_ActionBar04", + "_ActionBar05", + "_ActionBar06", + "_ActionBar07", + "_ActionBar08", + "_ActionBar09", + "_ActionCross", + }; + unsafe delegate bool ActionBarAction(ActionBarSlot* bar, HotBarSlot* hot, uint highLightID); + unsafe delegate bool ActionBarPredicate(ActionBarSlot* bar, HotBarSlot* hot); + private static unsafe void LoopAllSlotBar(ActionBarAction doingSomething) + { + for (int i = 0; i < _barsName.Length; i++) + { + var name = _barsName[i]; + var actBarPtr = Service.GameGui.GetAddonByName(name, 1); + if (actBarPtr == IntPtr.Zero) continue; + var actBar = (AddonActionBarBase*)actBarPtr; + var hotbar = Framework.Instance()->GetUiModule()->GetRaptureHotbarModule()->HotBar[i]; + + for (int slotIndex = 0; slotIndex < ActionBarSlotsCount; slotIndex++) + { + var hotBarSlot = hotbar->Slot[slotIndex]; + var actionBarSlot = &actBar->ActionBarSlots[slotIndex]; + var highLightId = 0x53550000 + (uint)i * ActionBarSlotsCount + (uint)slotIndex; + if (doingSomething(actionBarSlot, hotBarSlot, highLightId)) + { + actBar->PulseActionBarSlot(slotIndex); + UIModule.PlaySound(12, 0, 0, 0); + return; + } + } + } + } + + + private static unsafe void HigglightAtionBar(ActionBarPredicate shouldShow = null) + { + LoopAllSlotBar((slot, hotbar, highLightId) => + { + var iconAddon = slot->Icon; + if (!iconAddon->AtkResNode.IsVisible) return false; + + AtkImageNode* highLightPtr = null; + AtkResNode* lastHightLigth = null; + AtkResNode* nextNode = null; + + for (int nodeIndex = 8; nodeIndex < iconAddon->Component->UldManager.NodeListCount; nodeIndex++) + { + var node = iconAddon->Component->UldManager.NodeList[nodeIndex]; + if (node->NodeID == highLightId) + { + highLightPtr = (AtkImageNode*)node; + } + else if (node->Type == NodeType.Image) + { + var mayLastNode = (AtkImageNode*)node; + if (mayLastNode->PartId == 16) + { + lastHightLigth = node; + continue; + } + } + if (lastHightLigth != null && highLightPtr == null) + { + nextNode = node; + break; + } + } + + if (highLightPtr == null) + { + //Create new Addon + highLightPtr = CloneNode((AtkImageNode*)lastHightLigth); + highLightPtr->AtkResNode.NodeID = highLightId; + + //Change LinkList + lastHightLigth->PrevSiblingNode = (AtkResNode*)highLightPtr; + highLightPtr->AtkResNode.PrevSiblingNode = nextNode; + + nextNode->NextSiblingNode = (AtkResNode*)highLightPtr; + highLightPtr->AtkResNode.NextSiblingNode = lastHightLigth; + + iconAddon->Component->UldManager.UpdateDrawNodeList(); + } + + //Refine Color + highLightPtr->AtkResNode.AddRed = 0; + highLightPtr->AtkResNode.AddGreen = 10; + highLightPtr->AtkResNode.AddBlue = 40; + + //Change Color + var color = Service.Configuration.TeachingModeColor; + highLightPtr->AtkResNode.MultiplyRed = (byte)(color.X * 100); + highLightPtr->AtkResNode.MultiplyGreen = (byte)(color.Y * 100); + highLightPtr->AtkResNode.MultiplyBlue = (byte)(color.Z * 100); + + //Update Location + highLightPtr->AtkResNode.SetPositionFloat(lastHightLigth->X, lastHightLigth->Y); + highLightPtr->AtkResNode.SetWidth(lastHightLigth->Width); + highLightPtr->AtkResNode.SetHeight(lastHightLigth->Height); + + //Update Visibility + highLightPtr->AtkResNode.ToggleVisibility(shouldShow?.Invoke(slot, hotbar) ?? false); + + return false; + }); + } + + private unsafe static AtkImageNode* CloneNode(AtkImageNode* original) + { + var size = sizeof(AtkImageNode); + + var allocation = new IntPtr(IMemorySpace.GetUISpace()->Malloc((ulong)size, 8UL)); + var bytes = new byte[size]; + Marshal.Copy(new IntPtr(original), bytes, 0, bytes.Length); + Marshal.Copy(bytes, 0, allocation, bytes.Length); + + var newNode = (AtkResNode*)allocation; + newNode->ChildNode = null; + newNode->ChildCount = 0; + newNode->PrevSiblingNode = null; + newNode->NextSiblingNode = null; + return (AtkImageNode*)newNode; + } + public unsafe static void Dispose() + { + //Hide All highLight. + HigglightAtionBar(); + _dtrEntry?.Dispose(); + } +} diff --git a/RotationSolver.Old/Updaters/RotationUpdater.cs b/RotationSolver.Old/Updaters/RotationUpdater.cs new file mode 100644 index 000000000..25d2824ae --- /dev/null +++ b/RotationSolver.Old/Updaters/RotationUpdater.cs @@ -0,0 +1,119 @@ +using Lumina.Data.Parsing; +using RotationSolver.Actions; +using RotationSolver.Attributes; +using RotationSolver.Data; +using RotationSolver.Rotations.CustomRotation; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace RotationSolver.Updaters; + +internal static class RotationUpdater +{ + public record CustomRotationGroup(ClassJobID jobId, ClassJobID[] classJobIds, ICustomRotation[] rotations); + + private static SortedList _customRotationsDict; + internal static SortedList CustomRotationsDict + { + get + { + if (_customRotationsDict == null) + { + GetAllCustomRotations(); + } + return _customRotationsDict; + } + } + private static CustomRotationGroup[] _customRotations; + private static CustomRotationGroup[] CustomRotations + { + get + { + if (_customRotations == null) + { + GetAllCustomRotations(); + } + return _customRotations; + } + } + + private static void GetAllCustomRotations() + { + //var thisPath = Assembly.GetAssembly(typeof(ICustomRotation)).Location; + + //var types = Directory.GetFiles(Path.GetDirectoryName(Assembly.GetAssembly(typeof(ICustomRotation)).Location), "*.dll") + // .Where(l => l != thisPath) + // .Select(Assembly.LoadFrom) + // .SelectMany(a => a.GetTypes()); + + //foreach (var t in types) + //{ + // Service.ChatGui.Print(t.Name); + //} + + _customRotations = (from t in Assembly.GetAssembly(typeof(ICustomRotation)).GetTypes() + where t.GetInterfaces().Contains(typeof(ICustomRotation)) + && !t.IsAbstract && !t.IsInterface + select (ICustomRotation)Activator.CreateInstance(t) into rotation + group rotation by rotation.JobIDs[0] into rotationGrp + select new CustomRotationGroup(rotationGrp.Key, rotationGrp.First().JobIDs, CreateRotationSet(rotationGrp.ToArray()))).ToArray(); + + _customRotationsDict = new SortedList + (_customRotations.GroupBy(g => g.rotations[0].Job.GetJobRole()) + .ToDictionary(set => set.Key, set => set.OrderBy(i => i.jobId).ToArray())); + } + + private static ICustomRotation[] CreateRotationSet(ICustomRotation[] combos) + { + var result = new List(combos.Length); + + foreach (var combo in combos) + { + if (!result.Any(c => c.RotationName == combo.RotationName)) + { + result.Add(combo); + } + } + return result.ToArray(); + } + + public static ICustomRotation RightNowRotation { get; private set; } + + public static IBaseAction[] RightRotationBaseActions { get; private set; } = new IBaseAction[0]; + + static ClassJobID _job; + static string _rotationName; + public static void UpdateRotation() + { + var nowJob = (ClassJobID)Service.ClientState.LocalPlayer.ClassJob.Id; + Service.Configuration.RotationChoices.TryGetValue((uint)nowJob, out var newName); + + if (_job == nowJob && _rotationName == newName) return; + + _job = nowJob; + _rotationName = newName; + + foreach (var group in CustomRotations) + { + if (!group.classJobIds.Contains(_job)) continue; + + RightNowRotation = GetChooseRotation(group, _rotationName); + RightRotationBaseActions = RightNowRotation.AllBaseActions; + break; + } + } + + internal static ICustomRotation GetChooseRotation(CustomRotationGroup group, string name) + { + var rotation = group.rotations.FirstOrDefault(r => r.RotationName == name); + rotation ??= group.rotations.FirstOrDefault(r => r.GetType().GetCustomAttribute() != null); + rotation ??= group.rotations.FirstOrDefault(r => r.GetType().Name.Contains("Default")); + rotation ??= group.rotations.FirstOrDefault(); + return rotation; + } +} diff --git a/RotationSolver.Old/Updaters/SocialUpdater.cs b/RotationSolver.Old/Updaters/SocialUpdater.cs new file mode 100644 index 000000000..f1613281b --- /dev/null +++ b/RotationSolver.Old/Updaters/SocialUpdater.cs @@ -0,0 +1,143 @@ +using Dalamud.Game.ClientState.Conditions; +using Dalamud.Game.ClientState.Objects.SubKinds; +using FFXIVClientStructs.FFXIV.Client.UI; +using Lumina.Excel.GeneratedSheets; +using RotationSolver.Commands; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Localization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace RotationSolver.Updaters; + +internal class SocialUpdater +{ + static List macroToAuthor = new List() + { + "blush", + "hug", + "thumbsup", + "yes", + "clap", + "cheer", + "stroke", + }; + + public static bool CanSaying { get; set; } = false; + + static bool CanSocial + { + get + { + if (Service.Conditions[ConditionFlag.OccupiedInQuestEvent] + || Service.Conditions[ConditionFlag.WaitingForDuty] + || Service.Conditions[ConditionFlag.WaitingForDutyFinder] + || Service.Conditions[ConditionFlag.OccupiedInCutSceneEvent] + || Service.Conditions[ConditionFlag.BetweenAreas] + || Service.Conditions[ConditionFlag.BetweenAreas51]) return false; + + if(Service.ClientState.LocalPlayer == null) return false; + if (!Service.ClientState.LocalPlayer.IsTargetable()) return false; + + return Service.Conditions[ConditionFlag.BoundByDuty]; + } + } + + internal static void Enable() + { + Service.DutyState.DutyStarted += DutyState_DutyStarted; + Service.DutyState.DutyCompleted += DutyState_DutyCompleted; + } + + static async void DutyState_DutyCompleted(object sender, ushort e) + { + RSCommands.CancelState(); + + if (TargetUpdater.PartyMembers.Count() < 2) return; + + await Task.Delay(new Random().Next(4000, 6000)); + + Service.Configuration.DutyEnd.AddMacro(); + } + + static void DutyState_DutyStarted(object sender, ushort e) + { + var territory = Service.DataManager.GetExcelSheet().GetRow(e); + if (territory?.ContentFinderCondition?.Value?.HighEndDuty ?? false) + { + var str = territory.PlaceName?.Value?.Name.ToString() ?? "High-end Duty"; + Service.ToastGui.ShowError(string.Format(LocalizationManager.RightLang.HighEndWarning, str)); + } + } + + internal static void Disable() + { + Service.DutyState.DutyStarted -= DutyState_DutyStarted; + Service.DutyState.DutyCompleted -= DutyState_DutyCompleted; + } + + internal static async void UpdateSocial() + { + if (ActionUpdater.InCombat || TargetUpdater.PartyMembers.Count() < 2) return; + if (CanSaying && CanSocial) + { + CanSaying = false; + await Task.Delay(new Random().Next(3000, 5000)); + +#if DEBUG + Service.ChatGui.PrintError("Macro now."); +#endif + Service.Configuration.DutyStart.AddMacro(); + await Task.Delay(new Random().Next(1000, 1500)); + SayHelloToAuthor(); + } + } + + private static async void SayHelloToAuthor() + { + var author = TargetUpdater.AllianceMembers.OfType() + .FirstOrDefault(c => +#if DEBUG +#else + c.ObjectId != Service.ClientState.LocalPlayer.ObjectId && +#endif + ConfigurationHelper.AuthorKeys.Contains(EncryptString(c))); + + if (author != null) + { + while(!author.IsTargetable() && !ActionUpdater.InCombat) + { + await Task.Delay(100); + } + +#if DEBUG + Service.ChatGui.PrintError("Author Time"); +#else + Service.TargetManager.SetTarget(author); + RSCommands.SubmitToChat($"/{macroToAuthor[new Random().Next(macroToAuthor.Count)]} "); + Service.ChatGui.PrintChat(new Dalamud.Game.Text.XivChatEntry() + { + Message = string.Format(LocalizationManager.RightLang.Commands_SayHelloToAuthor, author.Name), + Type = Dalamud.Game.Text.XivChatType.Notice, + }); + UIModule.PlaySound(20, 0, 0, 0); + Service.TargetManager.SetTarget(null); +#endif + } + } + + internal static string EncryptString(PlayerCharacter player) + { + byte[] inputByteArray = Encoding.UTF8.GetBytes(player.HomeWorld.GameData.InternalName.ToString() + + " - " + player.Name.ToString() + "U6Wy.zCG"); + + var tmpHash = MD5.Create().ComputeHash(inputByteArray); + var retB = Convert.ToBase64String(tmpHash.ToArray()); + return retB; + } +} diff --git a/RotationSolver.Old/Updaters/TargetUpdater_Friends.cs b/RotationSolver.Old/Updaters/TargetUpdater_Friends.cs new file mode 100644 index 000000000..b9be3e98c --- /dev/null +++ b/RotationSolver.Old/Updaters/TargetUpdater_Friends.cs @@ -0,0 +1,224 @@ +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.ClientState.Objects.SubKinds; +using Dalamud.Game.ClientState.Objects.Types; +using FFXIVClientStructs.FFXIV.Client.Game.Group; +using FFXIVClientStructs.FFXIV.Client.UI; +using RotationSolver.Commands; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Localization; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Numerics; +using System.Security.Cryptography; +using System.Text; + +namespace RotationSolver.Updaters; + +internal static partial class TargetUpdater +{ + /// + /// 小队成员们 + /// + public static IEnumerable PartyMembers { get; private set; } = new PlayerCharacter[0]; + /// + /// 团队成员们 + /// + internal static IEnumerable AllianceMembers { get; private set; } = new PlayerCharacter[0]; + + /// + /// 小队坦克们 + /// + internal static IEnumerable PartyTanks { get; private set; } = new PlayerCharacter[0]; + /// + /// 小队治疗们 + /// + internal static IEnumerable PartyHealers { get; private set; } = new PlayerCharacter[0]; + + /// + /// 团队坦克们 + /// + internal static IEnumerable AllianceTanks { get; private set; } = new PlayerCharacter[0]; + + internal static ObjectListDelay DeathPeopleAll { get; } = new ( + ()=>(Service.Configuration.DeathDelayMin, Service.Configuration.DeathDelayMax)); + + internal static ObjectListDelay DeathPeopleParty { get; } = new( + () => (Service.Configuration.DeathDelayMin, Service.Configuration.DeathDelayMax)); + + internal static ObjectListDelay WeakenPeople { get; } = new( + () => (Service.Configuration.WeakenDelayMin, Service.Configuration.WeakenDelayMax)); + + internal static ObjectListDelay DyingPeople { get; } = new( + () => (Service.Configuration.WeakenDelayMin, Service.Configuration.WeakenDelayMax)); + /// + /// 小队成员HP + /// + internal static IEnumerable PartyMembersHP { get; private set; } = new float[0]; + /// + /// 小队成员最小的HP + /// + internal static float PartyMembersMinHP { get; private set; } = 0; + /// + /// 小队成员平均HP + /// + internal static float PartyMembersAverHP { get; private set; } = 0; + /// + /// 小队成员HP差值 + /// + internal static float PartyMembersDifferHP { get; private set; } = 0; + + + [EditorBrowsable(EditorBrowsableState.Never)] + internal static bool CanHealAreaAbility { get; private set; } = false; + + [EditorBrowsable(EditorBrowsableState.Never)] + internal static bool CanHealAreaSpell { get; private set; } = false; + + [EditorBrowsable(EditorBrowsableState.Never)] + internal static bool CanHealSingleAbility { get; private set; } = false; + + [EditorBrowsable(EditorBrowsableState.Never)] + internal static bool CanHealSingleSpell { get; private set; } = false; + + internal static unsafe bool HavePet { get; private set; } + + internal static unsafe bool HaveCompanion => (IntPtr)Service.CharacterManager->LookupBuddyByOwnerObject(Service.Player) != IntPtr.Zero; + + internal static bool HPNotFull { get; private set; } = false; + + private static IEnumerable GetPartyMembers(IEnumerable allTargets) + { + var party = Service.PartyList.Select(p => p.GameObject).OfType().Where(b => b.DistanceToPlayer() <= 30); + + if (!party.Any()) party = new BattleChara[] { Service.ClientState.LocalPlayer }; + + return party.Union(allTargets.Where(obj => obj.SubKind == 9)); + } + + private unsafe static void UpdateFriends(IEnumerable allTargets) + { + PartyMembers = GetPartyMembers(allTargets); + AllianceMembers = allTargets.OfType(); + + var mayPet = allTargets.OfType().Where(npc => npc.OwnerId == Service.ClientState.LocalPlayer.ObjectId); + HavePet = mayPet.Any(npc => npc.BattleNpcKind == BattleNpcSubKind.Pet); + + PartyTanks = PartyMembers.GetJobCategory(JobRole.Tank); + PartyHealers = PartyMembers.GetJobCategory(JobRole.Healer); + AllianceTanks = AllianceMembers.GetJobCategory(JobRole.Tank); + + var deathAll = AllianceMembers.GetDeath(); + var deathParty = PartyMembers.GetDeath(); + MaintainDeathPeople(ref deathAll, ref deathParty); + DeathPeopleAll.Delay(deathAll); + DeathPeopleParty.Delay(deathParty); + + WeakenPeople.Delay(PartyMembers.Where(p => p.StatusList.Any(StatusHelper.CanDispel))); + DyingPeople.Delay(WeakenPeople.Where(p => p.StatusList.Any(StatusHelper.IsDangerous))); + + PartyMembersHP = PartyMembers.Select(ObjectHelper.GetHealthRatio).Where(r => r > 0); + if (PartyMembersHP.Any()) + { + PartyMembersAverHP = PartyMembersHP.Average(); + PartyMembersDifferHP = (float)Math.Sqrt(PartyMembersHP.Average(d => Math.Pow(d - PartyMembersAverHP, 2))); + } + else + { + PartyMembersAverHP = PartyMembersDifferHP = 0; + } + + UpdateCanHeal(Service.ClientState.LocalPlayer); + } + + static RandomDelay _healDelay1 = new RandomDelay(GetHealRange); + static RandomDelay _healDelay2 = new RandomDelay(GetHealRange); + static RandomDelay _healDelay3 = new RandomDelay(GetHealRange); + static RandomDelay _healDelay4 = new RandomDelay(GetHealRange); + + static (float min, float max) GetHealRange() => (Service.Configuration.HealDelayMin, Service.Configuration.HealDelayMax); + + static void UpdateCanHeal(PlayerCharacter player) + { + var job = (ClassJobID)player.ClassJob.Id; + + var hotSubSingle = job.GetHealingOfTimeSubtractSingle(); + var singleAbility = ShouldHealSingle(StatusHelper.SingleHots, job.GetHealSingleAbility(), hotSubSingle); + var singleSpell = ShouldHealSingle(StatusHelper.SingleHots, job.GetHealSingleSpell(), hotSubSingle); + CanHealSingleAbility = singleAbility > 0; + CanHealSingleSpell = singleSpell > 0; + CanHealAreaAbility = singleAbility > 2; + CanHealAreaSpell = singleSpell > 2; + + if (PartyMembers.Count() > 2) + { + //TODO:少了所有罩子类技能 + var ratio = GetHealingOfTimeRatio(player, StatusHelper.AreaHots) * job.GetHealingOfTimeSubtractArea(); + + if(!CanHealAreaAbility) + CanHealAreaAbility = PartyMembersDifferHP < Service.Configuration.HealthDifference && PartyMembersAverHP < ConfigurationHelper.GetHealAreaAbility(job) - ratio; + + if (!CanHealAreaSpell) + CanHealAreaSpell = PartyMembersDifferHP < Service.Configuration.HealthDifference && PartyMembersAverHP < ConfigurationHelper.GetHealAreafSpell(job) - ratio; + } + + //Delay + CanHealSingleAbility = _healDelay1.Delay(CanHealSingleAbility); + CanHealSingleSpell = _healDelay2.Delay(CanHealSingleSpell); + CanHealAreaAbility = _healDelay3.Delay(CanHealAreaAbility); + CanHealAreaSpell = _healDelay4.Delay(CanHealAreaSpell); + + PartyMembersMinHP = PartyMembersHP.Any() ? PartyMembersHP.Min() : 0; + HPNotFull = PartyMembersMinHP < 1; + } + + static int ShouldHealSingle(StatusID[] hotStatus, float healSingle, float hotSubSingle) => PartyMembers.Count(p => + { + var ratio = GetHealingOfTimeRatio(p, hotStatus); + + var h = p.GetHealthRatio(); + if (h == 0 || !p.NeedHealing()) return false; + + return h < healSingle - hotSubSingle * ratio; + }); + + static float GetHealingOfTimeRatio(BattleChara target, params StatusID[] statusIds) + { + const float buffWholeTime = 15; + + var buffTime = target.StatusTime(false, statusIds); + + return Math.Min(1, buffTime / buffWholeTime); + } + + static SortedDictionary _locations = new SortedDictionary(); + private static void MaintainDeathPeople(ref IEnumerable deathAll, ref IEnumerable deathParty) + { + SortedDictionary locs = new SortedDictionary(); + foreach (var item in deathAll) + { + locs[item.ObjectId] = item.Position; + } + foreach (var item in deathParty) + { + locs[item.ObjectId] = item.Position; + } + + deathAll = FilterForDeath(deathAll); + deathParty = FilterForDeath(deathParty); + + _locations = locs; + } + + private static IEnumerable FilterForDeath(IEnumerable battleCharas) + => battleCharas.Where(b => + { + if (!_locations.TryGetValue(b.ObjectId, out var loc)) return false; + + return loc == b.Position; + }); + + +} diff --git a/RotationSolver.Old/Updaters/TargetUpdater_Hostile.cs b/RotationSolver.Old/Updaters/TargetUpdater_Hostile.cs new file mode 100644 index 000000000..9de9216ee --- /dev/null +++ b/RotationSolver.Old/Updaters/TargetUpdater_Hostile.cs @@ -0,0 +1,206 @@ +using Dalamud.Game.ClientState.Fates; +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Logging; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.Control; +using FFXIVClientStructs.FFXIV.Client.Game.Fate; +using FFXIVClientStructs.FFXIV.Client.UI; +using Lumina.Excel.GeneratedSheets; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.SigReplacers; +using System; +using System.Collections.Generic; +using System.Linq; +using Action = Lumina.Excel.GeneratedSheets.Action; + +namespace RotationSolver.Updaters; + +internal static partial class TargetUpdater +{ + /// + /// 敌人 + /// + internal static ObjectListDelay HostileTargets { get; } = new ObjectListDelay( + () => (Service.Configuration.HostileDelayMin, Service.Configuration.HostileDelayMax)); + + internal static IEnumerable AllHostileTargets { get; private set; } = new BattleChara[0]; + + internal static IEnumerable TarOnMeTargets { get; private set; } = new BattleChara[0]; + + internal static ObjectListDelay CanInterruptTargets { get;} = new ObjectListDelay( + () => (Service.Configuration.InterruptDelayMin, Service.Configuration.InterruptDelayMax)); + internal static bool HasHostilesInRange { get; private set; } = false; + + internal static bool IsHostileCastingAOE { get; private set; } = false; + + internal static bool IsHostileCastingToTank { get; private set; } = false; + + internal static unsafe ushort FateId + { + get + { + try + { + if(Service.Configuration.ChangeTargetForFate && (IntPtr)FateManager.Instance() != IntPtr.Zero + && (IntPtr)FateManager.Instance()->CurrentFate != IntPtr.Zero + && Service.ClientState.LocalPlayer.Level <= FateManager.Instance()->CurrentFate->MaxLevel) + { + return FateManager.Instance()->CurrentFate->FateId; + } + } + catch(Exception ex) + { + PluginLog.Error(ex.StackTrace); + } + return 0; + } + } + + private static float JobRange + { + get + { + float radius = 25; + if(Service.ClientState.LocalPlayer == null) return radius; + switch (Service.DataManager.GetExcelSheet().GetRow( + Service.ClientState.LocalPlayer.ClassJob.Id).GetJobRole()) + { + case JobRole.Tank: + case JobRole.Melee: + radius = 3; + break; + } + return radius; + } + } + + private unsafe static void UpdateHostileTargets(IEnumerable allTargets) + { + AllHostileTargets = allTargets.Where(b => + { + if (!b.IsNPCEnemy()) return false; + + //Dead. + if (b.CurrentHp == 0) return false; + + if (!b.IsTargetable()) return false; + + if (b.StatusList.Any(StatusHelper.IsInvincible)) return false; + + if (Service.Configuration.OnlyAttackInView) + { + if(!Service.GameGui.WorldToScreen(b.Position, out _)) return false; + } + + return true; + }); + + HostileTargets.Delay(GetHostileTargets(AllHostileTargets)); + + CanInterruptTargets.Delay(HostileTargets.Where(ObjectHelper.CanInterrupt)); + + TarOnMeTargets = HostileTargets.Where(tar => tar.TargetObjectId == Service.ClientState.LocalPlayer.ObjectId); + + HasHostilesInRange = TargetFilter.GetObjectInRadius(HostileTargets, JobRange).Any(); + + if (HostileTargets.Count() == 1) + { + var tar = HostileTargets.FirstOrDefault(); + + IsHostileCastingToTank = IsHostileCastingTank(tar); + IsHostileCastingAOE = IsHostileCastingArea(tar); + } + else + { + IsHostileCastingToTank = IsHostileCastingAOE = false; + } + } + + private static IEnumerable GetHostileTargets(IEnumerable allattackableTargets) + { + var type = IconReplacer.RightNowTargetToHostileType; + if (type == TargetHostileType.AllTargetsCanAttack || CountDown.CountDownTime > 0) + { + return allattackableTargets; + } + + uint[] ids = GetEnemies(); + var fateId = FateId; + + var hostiles = allattackableTargets.Where(t => + { + if (ids.Contains(t.ObjectId)) return true; + if (t.TargetObject == Service.ClientState.LocalPlayer + || t.TargetObject?.OwnerId == Service.ClientState.LocalPlayer.ObjectId) return true; + + //Remove other's treasure. + if (t.IsOthersPlayers()) return false; + + if (t.IsTopPriorityHostile()) return true; + + return fateId > 0 ? t.FateId() == fateId : t.TargetObject is BattleChara; + }); + + if (type == TargetHostileType.TargetsHaveTargetOrAllTargetsCanAttack) + { + if (!hostiles.Any()) hostiles = allattackableTargets; + } + + return hostiles; + } + + private static unsafe uint[] GetEnemies() + { + if (!Service.Configuration.AddEnemyListToHostile) return new uint[0]; + + var addonByName = Service.GameGui.GetAddonByName("_EnemyList", 1); + if (addonByName == IntPtr.Zero) return new uint[0]; + + var addon = (AddonEnemyList*)addonByName; + var numArray = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance()->GetUiModule()->GetRaptureAtkModule()->AtkModule.AtkArrayDataHolder.NumberArrays[19]; + List list = new List(addon->EnemyCount); + for (var i = 0; i < addon->EnemyCount; i++) + { + list.Add((uint)numArray->IntArray[8 + i * 6]); + } + return list.ToArray(); + } + + private static bool IsHostileCastingTank(BattleChara h) + { + return IsHostileCastingBase(h, (act) => + { + return h.CastTargetObjectId == h.TargetObjectId; + }); + } + + private static bool IsHostileCastingArea(BattleChara h) + { + return IsHostileCastingBase(h, (act) => + { + if (h.CastTargetObjectId == h.TargetObjectId) return false; + if ((act.CastType == 1 || act.CastType == 2) && + act.Range == 0 && + act.EffectRange >= 40) + return true; + return false; + }); + } + + private static bool IsHostileCastingBase(BattleChara h, Func check) + { + if (h.IsCasting) + { + if (h.IsCastInterruptible) return false; + var last = h.TotalCastTime - h.CurrentCastTime; + + if (!(h.TotalCastTime > 2.5 && + CooldownHelper.RecastAfterGCD(last, 2) && !CooldownHelper.RecastAfterGCD(last, 0))) return false; + + var action = Service.DataManager.GetExcelSheet().GetRow(h.CastActionId); + return check?.Invoke(action) ?? false; + } + return false; + } +} diff --git a/RotationSolver.Old/Updaters/TargetUpdater_Major.cs b/RotationSolver.Old/Updaters/TargetUpdater_Major.cs new file mode 100644 index 000000000..96c24d14f --- /dev/null +++ b/RotationSolver.Old/Updaters/TargetUpdater_Major.cs @@ -0,0 +1,23 @@ +using Dalamud.Game.ClientState.Objects.Types; +using RotationSolver.Helpers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RotationSolver.Updaters +{ + internal static partial class TargetUpdater + { + internal static IEnumerable AllTargets { get; private set; } + internal static void UpdateTarget() + { + AllTargets = Service.ObjectTable.GetObjectInRadius(30); + var battles = AllTargets.OfType(); + UpdateHostileTargets(battles); + UpdateFriends(battles); + UpdateNamePlate(Service.ObjectTable.OfType()); + } + } +} diff --git a/RotationSolver.Old/Updaters/TargetUpdater_NamePlate.cs b/RotationSolver.Old/Updaters/TargetUpdater_NamePlate.cs new file mode 100644 index 000000000..7202ebe59 --- /dev/null +++ b/RotationSolver.Old/Updaters/TargetUpdater_NamePlate.cs @@ -0,0 +1,27 @@ +using Dalamud.Game.ClientState.Objects.Types; +using RotationSolver.Commands; +using RotationSolver.Helpers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RotationSolver.Updaters; + +internal static partial class TargetUpdater +{ + public static uint[] TreasureCharas { get; private set; } = new uint[0]; + private static void UpdateNamePlate(IEnumerable allTargets) + { + List charas = new List(5); + //60687 - 60691 For treasure hunt. + for (int i = 60687; i <= 60691; i++) + { + var b = allTargets.FirstOrDefault(obj => obj.GetNamePlateIcon() == i); + if (b == null || b.CurrentHp == 0) continue; + charas.Add(b.ObjectId); + } + TreasureCharas = charas.ToArray(); + } +} diff --git a/RotationSolver.Old/Updaters/TimeLineUpdater.cs b/RotationSolver.Old/Updaters/TimeLineUpdater.cs new file mode 100644 index 000000000..1e73474ad --- /dev/null +++ b/RotationSolver.Old/Updaters/TimeLineUpdater.cs @@ -0,0 +1,136 @@ +using ImGuiNET; +using Newtonsoft.Json; +using RotationSolver.Actions; +using RotationSolver.Helpers; +using RotationSolver.SigReplacers; +using RotationSolver.Timeline; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; + +namespace RotationSolver.Updaters +{ + internal class TimeLineUpdater + { + static string _timelineFolder; + + static IEnumerable _conditionSet; + public static MajorConditionSet RightSet => _conditionSet?.ElementAtOrDefault(Service.Configuration.TimelineIndex); + + public static string[] ConditionSetsName => _conditionSet?.Select(s => s.Name).ToArray() ?? new string[0]; + + public static IBaseAction TimeLineAction { get; private set; } + public static void UpdateTimelineAction() + { + if (_conditionSet == null) return; + var customRotation = RotationUpdater.RightNowRotation; + if (customRotation == null) return; + + var allActions = RotationUpdater.RightRotationBaseActions; + + var set = RightSet; + if (set == null) return; + + foreach (var conditionPair in set.Conditions) + { + var nextAct = allActions.FirstOrDefault(a => a.ID == conditionPair.Key); + if (nextAct == null) continue; + + if (!conditionPair.Value.IsTrue(customRotation)) continue; + + TimeLineAction = nextAct; + break; + } + } + + public static void Enable(string folder) + { + _timelineFolder = folder; + if (!Directory.Exists(_timelineFolder)) Directory.CreateDirectory(_timelineFolder); + + _conditionSet = MajorConditionSet.Read(_timelineFolder); + } + + public static void SaveFiles() + { + try + { + Directory.Delete(_timelineFolder); + Directory.CreateDirectory(_timelineFolder); + } + catch + { + + } + foreach (var set in _conditionSet) + { + set.Save(_timelineFolder); + } + } + + private static void AddNew() + { + const string conditionName = "Unnamed"; + if (!_conditionSet.Any(c => c.Name == conditionName)) + { + _conditionSet = _conditionSet.Union(new[] { new MajorConditionSet(conditionName) }); + } + } + + private static void Delete(string name) + { + _conditionSet = _conditionSet.Where(c => c.Name != name); + } + + public static void DrawHeader() + { + var set = RightSet; + bool hasSet = set != null; + + if (hasSet) + { + ImGuiHelper.SetNextWidthWithName(set.Name); + ImGui.InputText("##MajorConditionSet", ref set.Name, 100); + + ImGui.SameLine(); + } + + var combos = ConditionSetsName; + if (combos != null && combos.Length > Service.Configuration.TimelineIndex) + { + ImGui.SetNextItemWidth(ImGui.CalcTextSize(combos[Service.Configuration.TimelineIndex]).X + 30); + } + else + { + ImGui.SetNextItemWidth(30); + } + + ImGui.Combo("##MajorConditionCombo", ref Service.Configuration.TimelineIndex, combos, combos.Length); + + ImGui.SameLine(); + + + if (hasSet) + { + if (ImGuiHelper.IconButton(Dalamud.Interface.FontAwesomeIcon.Ban, "##DeleteTimelineConditionSet")) + { + Delete(set.Name); + } + + ImGui.SameLine(); + } + + if (ImGuiHelper.IconButton(Dalamud.Interface.FontAwesomeIcon.Plus, "##AddNewTimelineConditionSet")) + { + AddNew(); + } + + ImGui.SameLine(); + if (ImGuiHelper.IconButton(Dalamud.Interface.FontAwesomeIcon.Folder, "##OpenDefinationFolder")) + { + Process.Start("explorer.exe", _timelineFolder); + } + } + } +} diff --git a/RotationSolver.Old/Windows/OverlayWindow.cs b/RotationSolver.Old/Windows/OverlayWindow.cs new file mode 100644 index 000000000..53bb2f7ac --- /dev/null +++ b/RotationSolver.Old/Windows/OverlayWindow.cs @@ -0,0 +1,207 @@ +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Interface; +using FFXIVClientStructs.FFXIV.Client.Game.Control; +using ImGuiNET; +using RotationSolver.Actions; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.SigReplacers; +using RotationSolver.Updaters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +namespace RotationSolver.Windows; + +internal static class OverlayWindow +{ + internal static IBaseAction MeleeAction { get; set; } + public static void Draw() + { + if (Service.GameGui == null || Service.ClientState.LocalPlayer == null || !Service.Configuration.UseOverlayWindow) return; + + if (Service.Conditions[Dalamud.Game.ClientState.Conditions.ConditionFlag.OccupiedInCutSceneEvent] + || Service.Conditions[Dalamud.Game.ClientState.Conditions.ConditionFlag.BetweenAreas] + || Service.Conditions[Dalamud.Game.ClientState.Conditions.ConditionFlag.BetweenAreas51]) return; + + ImGui.PushID("AutoActionOverlay"); + + ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0)); + + ImGuiHelpers.SetNextWindowPosRelativeMainViewport(new Vector2(0, 0)); + ImGui.Begin("Ring", + ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoBringToFrontOnFocus + | ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking + | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoInputs + ); + + ImGui.SetWindowSize(ImGui.GetIO().DisplaySize); + + DrawPositional(); + DrawTarget(); + DrawMoveTarget(); + DrawHealthRatio(); + + ImGui.PopStyleVar(); + ImGui.End(); + + ImGui.PopID(); + } + + private static void DrawTarget() + { + if (!Service.Configuration.ShowTarget) return; + + if (ActionUpdater.NextAction is not BaseAction act) return; + + if (act.Target == null) return; + + if (act.Target != Service.ClientState.LocalPlayer) + { + var c = Service.Configuration.TargetColor; + var Tcolor = ImGui.GetColorU32(new Vector4(c.X, c.Y, c.Z, 1)); + DrawTarget(act.Target, Tcolor, 8, out _); + } + + if (TargetUpdater.HostileTargets.Contains(act.Target)) + { + var c = Service.Configuration.SubTargetColor; + var Scolor = ImGui.GetColorU32(new Vector4(c.X, c.Y, c.Z, 1)); + + foreach (var t in TargetUpdater.HostileTargets) + { + if (t == act.Target) continue; + if (act.CanGetTarget(act.Target, t)) + { + DrawTarget(t, Scolor, 5, out _); + } + } + } + } + + static readonly uint HealthRatioColor = ImGui.GetColorU32(new Vector4(0, 1, 0.8f, 1)); + private static void DrawHealthRatio() + { + if(!Service.Configuration.ShowHealthRatio) return; + + var calHealth = (double)ObjectHelper.GetHealthFromMulty(1); + foreach (BattleChara t in TargetUpdater.AllTargets) + { + if (t == null) continue; + if (Service.GameGui.WorldToScreen(t.Position, out var p)) + { + ImGui.GetWindowDrawList().AddText(p, HealthRatioColor, $"Health Ratio: {t.CurrentHp / calHealth:F2} / {t.MaxHp / calHealth:F2}"); + } + } + } + + private unsafe static void DrawMoveTarget() + { + if (!Service.Configuration.ShowMoveTarget) return; + + var c = Service.Configuration.MovingTargetColor; + var color = ImGui.GetColorU32(new Vector4(c.X, c.Y, c.Z, 1)); + + var tar = RotationUpdater.RightNowRotation?.MoveTarget; + if (tar == null || tar == Service.ClientState.LocalPlayer) return; + + DrawTarget(tar, color, 8, out var scrPos); + + if (Service.GameGui.WorldToScreen(Service.ClientState.LocalPlayer.Position, out var plyPos)) + { + var dir = scrPos - plyPos; + + dir /= dir.Length(); + dir *= 50; + var end = dir + plyPos; + ImGui.GetWindowDrawList().AddLine(plyPos, end, color, 3); + + var radius = 3; + + ImGui.GetWindowDrawList().AddCircle(plyPos, radius, color, COUNT, radius * 2); + } + } + + private static void DrawTarget(BattleChara tar, uint color, float radius, out Vector2 scrPos) + { + if (Service.GameGui.WorldToScreen(tar.Position, out scrPos)) + { + ImGui.GetWindowDrawList().AddCircle(scrPos, radius, color, COUNT, radius * 0.8f); + } + } + + const int COUNT = 20; + private static void DrawPositional() + { + if (MeleeAction == null) return; + + Vector3 pPosition = MeleeAction.Target.Position; + Service.GameGui.WorldToScreen(pPosition, out var scrPos); + + float radius = MeleeAction.Target.HitboxRadius + Service.ClientState.LocalPlayer.HitboxRadius + 3; + float rotation = MeleeAction.Target.Rotation; + bool wrong = MeleeAction.Target.DistanceToPlayer() > 3; + List pts = new List(4 * COUNT); + + if(Service.Configuration.DrawPositional && !Service.ClientState.LocalPlayer.HasStatus(true, StatusID.TrueNorth)) + { + var shouldPos = MeleeAction.EnermyPositonal; + + switch (shouldPos) + { + case EnemyPositional.Flank: + pts.Add(scrPos); + SectorPlots(ref pts, pPosition, radius, Math.PI * 0.25 + rotation, COUNT); + pts.Add(scrPos); + SectorPlots(ref pts, pPosition, radius, Math.PI * 1.25 + rotation, COUNT); + pts.Add(scrPos); + break; + case EnemyPositional.Rear: + pts.Add(scrPos); + SectorPlots(ref pts, pPosition, radius, Math.PI * 0.75 + rotation, COUNT); + pts.Add(scrPos); + break; + } + if (!wrong && pts.Count > 0) + { + wrong = shouldPos != MeleeAction.Target.FindEnemyPositional(); + } + } + if(pts.Count == 0 && Service.Configuration.DrawMeleeRange) + { + SectorPlots(ref pts, pPosition, radius, 0, 4 * COUNT, 2 * Math.PI); + } + + if(pts.Count > 0) DrawRange(pts, wrong); + } + + static void DrawRange(List pts, bool wrong) + { + var color = wrong ? new Vector3(0.3f, 0.8f, 0.2f) : new Vector3(1, 1, 1); + + pts.ForEach(pt => ImGui.GetWindowDrawList().PathLineTo(pt)); + ImGui.GetWindowDrawList().PathFillConvex(ImGui.GetColorU32(new Vector4(color.X, color.Y, color.Z, 0.15f))); + + pts.ForEach(pt => ImGui.GetWindowDrawList().PathLineTo(pt)); + ImGui.GetWindowDrawList().PathStroke(ImGui.GetColorU32(new Vector4(color.X, color.Y, color.Z, 1f)), ImDrawFlags.None, 2); + } + + private static void SectorPlots(ref List pts, Vector3 centre, float radius, double rotation, int segments, double round = Math.PI / 2) + { + var step = round / segments; + for (int i = 0; i <= segments; i++) + { + Service.GameGui.WorldToScreen(ChangePoint(centre, radius, rotation + i * step), out var pt); + pts.Add(pt); + } + } + + private static Vector3 ChangePoint(Vector3 pt, double radius, double rotation) + { + var x = Math.Sin(rotation) * radius + pt.X; + var z = Math.Cos(rotation) * radius + pt.Z; + return new Vector3((float)x, pt.Y, (float)z); + } +} diff --git a/RotationSolver.Old/Windows/RotationConfigWindow_Action.cs b/RotationSolver.Old/Windows/RotationConfigWindow_Action.cs new file mode 100644 index 000000000..06a9bd078 --- /dev/null +++ b/RotationSolver.Old/Windows/RotationConfigWindow_Action.cs @@ -0,0 +1,75 @@ +using ImGuiNET; +using RotationSolver.Actions; +using RotationSolver.Data; +using RotationSolver.Localization; +using RotationSolver.SigReplacers; +using RotationSolver.Updaters; +using System.Linq; +using System.Numerics; + +namespace RotationSolver.Windows.RotationConfigWindow; + +internal partial class RotationConfigWindow +{ + internal static IAction ActiveAction { get; set; } + + private void DrawActionTab() + { + ImGui.Columns(2); + + DrawActionList(); + + ImGui.NextColumn(); + + DrawTimelineCondition(); + + ImGui.Columns(1); + } + + private void DrawActionList() + { + ImGui.TextWrapped(LocalizationManager.RightLang.ConfigWindow_ActionItem_Description); + + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0f, 5f)); + + if (ImGui.BeginChild("Action List", new Vector2(0f, -1f), true)) + { + foreach (var pair in RotationUpdater.RightNowRotation.AllActions.GroupBy(a => a.CateName).OrderBy(g => g.Key)) + { + if (ImGui.CollapsingHeader(pair.Key)) + { + foreach (var item in pair) + { + item.Display(ActiveAction == item); + ImGui.Separator(); + } + } + } + ImGui.EndChild(); + } + ImGui.PopStyleVar(); + } + + private void DrawTimelineCondition() + { + ImGui.TextWrapped(LocalizationManager.RightLang.Timeline_TimelineDescription); + + var rotation = RotationUpdater.RightNowRotation; + if (rotation == null) return; + + TimeLineUpdater.DrawHeader(); + + if (ActiveAction == null) return; + if (!RotationUpdater.RightRotationBaseActions.Any(a => a.ID == ActiveAction.ID)) return; + + var set = TimeLineUpdater.RightSet; + if (set == null) return; + + if (!set.Conditions.TryGetValue(ActiveAction.ID, out var conditionSet)) + { + conditionSet = set.Conditions[ActiveAction.ID] = new Timeline.ConditionSet(); + } + + conditionSet?.Draw(rotation); + } +} diff --git a/RotationSolver.Old/Windows/RotationConfigWindow_Debug.cs b/RotationSolver.Old/Windows/RotationConfigWindow_Debug.cs new file mode 100644 index 000000000..e99f1cea3 --- /dev/null +++ b/RotationSolver.Old/Windows/RotationConfigWindow_Debug.cs @@ -0,0 +1,166 @@ +using Dalamud.Game.ClientState.Objects.Types; +using FFXIVClientStructs.FFXIV.Client.Game.Fate; +using FFXIVClientStructs.FFXIV.Client.Game.Group; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using ImGuiNET; +using Lumina.Excel.GeneratedSheets; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Commands; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Localization; +using RotationSolver.Rotations.RangedMagicial.BLM; +using RotationSolver.SigReplacers; +using RotationSolver.Updaters; +using System; +using System.Linq; +using System.Numerics; + +namespace RotationSolver.Windows.RotationConfigWindow; +#if DEBUG +internal partial class RotationConfigWindow +{ + private void DrawDebugTab() + { + var str = SocialUpdater.EncryptString(Service.ClientState.LocalPlayer); + ImGui.SetNextItemWidth(ImGui.CalcTextSize(str).X + 10); + ImGui.InputText("That is your HASH", ref str, 100); + + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0f, 5f)); + + if (ImGui.BeginTabBar("Debug Items")) + { + DrawParamTabItem("Status", DrawStatus); + DrawParamTabItem("Party", DrawParty); + DrawParamTabItem("Target Data", DrawTargetData); + DrawParamTabItem("Next Action", DrawNextAction); + DrawParamTabItem("Last Action", DrawLastAction); + DrawParamTabItem("CD, EX", DrawCDEX); + DrawParamTabItem("Icon", DrawIcon); + + ImGui.EndTabBar(); + } + ImGui.PopStyleVar(); + } + + private unsafe void DrawStatus() + { + if ((IntPtr)FateManager.Instance() != IntPtr.Zero) + { + ImGui.Text("Fate: " + TargetUpdater.FateId.ToString()); + } + + ImGui.Text("Have pet: " + TargetUpdater.HavePet.ToString()); + ImGui.Text("Have Companion: " + TargetUpdater.HaveCompanion.ToString()); + ImGui.Text("Targetable: " + Service.ClientState.LocalPlayer.IsTargetable().ToString()); + + + foreach (var status in Service.ClientState.LocalPlayer.StatusList) + { + var source = Service.ObjectTable.SearchById(status.SourceId)?.Name ?? "None"; + ImGui.Text($"{status.GameData.Name}: {status.StatusId} From: {source}"); + } + } + private unsafe void DrawParty() + { + //var status = AgentDeepDungeonStatus.Instance(); + //if ((IntPtr)status != IntPtr.Zero) + //{ + // foreach (var item in status->Data->PomanderSpan) + // { + // ImGui.Text(item.Name.ToString() + " : " + item.ItemId.ToString()); + // } + + // foreach (var item in status->Data->MagiciteSpan) + // { + // ImGui.Text(item.Name.ToString() + " : " + item.ItemId.ToString()); + // } + //} + + ImGui.Text("Party: " + TargetUpdater.PartyMembers.Count().ToString()); + ImGui.Text("CanHealSingleAbility: " + TargetUpdater.CanHealSingleAbility.ToString()); + ImGui.Text("CanHealSingleSpell: " + TargetUpdater.CanHealSingleSpell.ToString()); + ImGui.Text("CanHealAreaAbility: " + TargetUpdater.CanHealAreaAbility.ToString()); + ImGui.Text("CanHealAreaSpell: " + TargetUpdater.CanHealAreaSpell.ToString()); + + foreach (var member in TargetUpdater.PartyMembers) + { + var cha = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)member.GetAddress(); + + ImGui.Text("StatusVfxId:" + cha->StatusEffectVFXId.ToString()); + } + } + + private unsafe void DrawTargetData() + { + if (Service.TargetManager.Target is BattleChara b) + { + ImGui.Text("HP: " + b.CurrentHp + " / " + b.MaxHp); + ImGui.Text("Is Boss: " + b.IsBoss().ToString()); + ImGui.Text("Has Positional: " + b.HasPositional().ToString()); + ImGui.Text("Is Dying: " + b.IsDying().ToString()); + ImGui.Text("Kind: " + b.GetObjectKind().ToString()); + ImGui.Text("Subkind: " + b.GetBattleNPCSubkind().ToString()); + ImGui.Text("EventType: " + b.GetEventType().ToString()); + ImGui.Text("NamePlate: " + b.GetNamePlateIcon().ToString()); + ImGui.Text("StatusFlags: " + b.StatusFlags.ToString()); + ImGui.Text("InView: " + Service.GameGui.WorldToScreen(b.Position, out _).ToString()); + ImGui.Text("NameId: " + b.NameId.ToString()); + + foreach (var status in b.StatusList) + { + var source = Service.ObjectTable.SearchById(status.SourceId)?.Name ?? "None"; + ImGui.Text($"{status.GameData.Name}: {status.StatusId} From: {source}"); + } + } + + ImGui.Text("All: " + TargetUpdater.AllTargets.Count().ToString()); + ImGui.Text("Hostile: " + TargetUpdater.HostileTargets.Count().ToString()); + foreach (var item in TargetUpdater.HostileTargets) + { + ImGui.Text(item.Name.ToString()); + } + } + private void DrawNextAction() + { + ImGui.Text(RotationUpdater.RightNowRotation.RotationName); + ImGui.Text(RSCommands.SpecialType.ToString()); + + ActionUpdater.NextAction?.Display(false); + ImGui.Text("Ability Remain: " + ActionUpdater.AbilityRemain.ToString()); + ImGui.Text("Ability Count: " + ActionUpdater.AbilityRemainCount.ToString()); + + } + private void DrawLastAction() + { + DrawAction(Watcher.LastAction, nameof(Watcher.LastAction)); + DrawAction(Watcher.LastAbility, nameof(Watcher.LastAbility)); + DrawAction(Watcher.LastGCD, nameof(Watcher.LastGCD)); + DrawAction(ActionUpdater.LastComboAction, nameof(ActionUpdater.LastComboAction)); + } + + private void DrawCDEX() + { + ImGui.Text("Count Down: " + CountDown.CountDownTime.ToString()); + + if (ActionUpdater.exception != null) + { + ImGui.Text(ActionUpdater.exception.Message); + ImGui.Text(ActionUpdater.exception.StackTrace); + } + } + + private void DrawIcon() + { + ImGui.Image(IconSet.GetTexture(60094).ImGuiHandle, new Vector2(24, 24)); + ImGui.Image(IconSet.GetTexture(71224).ImGuiHandle, new Vector2(24, 24)); + } + + private static void DrawAction(ActionID id, string type) + { + var action = new BaseAction(id); + + ImGui.Text($"{type}: {action}"); + } +} +#endif diff --git a/RotationSolver.Old/Windows/RotationConfigWindow_Events.cs b/RotationSolver.Old/Windows/RotationConfigWindow_Events.cs new file mode 100644 index 000000000..1d6d24e66 --- /dev/null +++ b/RotationSolver.Old/Windows/RotationConfigWindow_Events.cs @@ -0,0 +1,64 @@ +using ImGuiNET; +using Lumina.Excel.GeneratedSheets; +using RotationSolver.Configuration; +using RotationSolver.Helpers; +using RotationSolver.Localization; +using System.Numerics; + +namespace RotationSolver.Windows.RotationConfigWindow; + +internal partial class RotationConfigWindow +{ + private void DrawEventTab() + { + if (ImGui.Button(LocalizationManager.RightLang.Configwindow_Events_AddEvent)) + { + Service.Configuration.Events.Add(new ActionEventInfo()); + Service.Configuration.Save(); + } + ImGui.SameLine(); + ImGuiHelper.Spacing(); + + ImGui.TextWrapped(LocalizationManager.RightLang.Configwindow_Events_Description); + + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0f, 5f)); + +#if DEBUG + ImGui.Text(LocalizationManager.RightLang.Configwindow_Events_DutyStart); + ImGui.SameLine(); + ImGuiHelper.Spacing(); + Service.Configuration.DutyStart.DisplayMacro(); + + ImGui.Text(LocalizationManager.RightLang.Configwindow_Events_DutyEnd); + ImGui.SameLine(); + ImGuiHelper.Spacing(); + Service.Configuration.DutyEnd.DisplayMacro(); +#endif + + if (ImGui.BeginChild("Events List", new Vector2(0f, -1f), true)) + { + ActionEventInfo remove = null; + foreach (var eve in Service.Configuration.Events) + { + eve.DisplayMacro(); + + ImGui.SameLine(); + ImGuiHelper.Spacing(); + + if (ImGui.Button($"{LocalizationManager.RightLang.Configwindow_Events_RemoveEvent}##RemoveEvent{eve.GetHashCode()}")) + { + remove = eve; + } + ImGui.Separator(); + } + if(remove!= null) + { + Service.Configuration.Events.Remove(remove); + Service.Configuration.Save(); + } + + ImGui.EndChild(); + } + ImGui.PopStyleVar(); + } +} diff --git a/RotationSolver.Old/Windows/RotationConfigWindow_Help.cs b/RotationSolver.Old/Windows/RotationConfigWindow_Help.cs new file mode 100644 index 000000000..c11218c94 --- /dev/null +++ b/RotationSolver.Old/Windows/RotationConfigWindow_Help.cs @@ -0,0 +1,92 @@ +using Dalamud.Utility; +using ImGuiNET; +using RotationSolver.Commands; +using RotationSolver.Localization; +using System.Numerics; + +namespace RotationSolver.Windows.RotationConfigWindow +{ + internal partial class RotationConfigWindow + { + private void DrawHelpTab() + { + if (ImGui.Button("Github")) + { + Util.OpenLink("https://github.com/ArchiDog1998/RotationSolver"); + } + + ImGui.SameLine(); + + if (ImGui.Button("Discord")) + { + Util.OpenLink("https://discord.gg/4fECHunam9"); + } + + ImGui.SameLine(); + + if (ImGui.Button("Wiki")) + { + Util.OpenLink("https://archidog1998.github.io/RotationSolver/"); + } + + ImGui.SameLine(); + + if (ImGui.Button("Changelog")) + { + Util.OpenLink("https://github.com/ArchiDog1998/RotationSolver/blob/release/CHANGELOG.md"); + } + + ImGui.SameLine(); + + var support = "Support on Ko-fi"; + ImGui.SetCursorPosX(ImGui.GetWindowSize().X - ImGui.CalcTextSize(support).X - ImGui.GetStyle().ItemSpacing.X * 2); + ImGui.PushStyleColor(ImGuiCol.Button, 0xFF5E5BFF); + ImGui.PushStyleColor(ImGuiCol.ButtonActive, 0xDD5E5BFF); + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, 0xAA5E5BFF); + if (ImGui.Button(support)) + { + Util.OpenLink("https://ko-fi.com/rotationsolver"); + } + ImGui.PopStyleColor(3); + + ImGui.TextWrapped(LocalizationManager.RightLang.ConfigWindow_HelpItem_Description); + + if (ImGui.BeginChild("Help Infomation", new Vector2(0f, -1f), true)) + { + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0f, 5f)); + + StateCommandType.Smart.DisplayCommandHelp(getHelp: EnumTranslations.ToHelp); + + StateCommandType.Manual.DisplayCommandHelp(getHelp: EnumTranslations.ToHelp); + + StateCommandType.Cancel.DisplayCommandHelp(getHelp: EnumTranslations.ToHelp); + + ImGui.Separator(); + + SpecialCommandType.EndSpecial.DisplayCommandHelp(getHelp: EnumTranslations.ToHelp); + + SpecialCommandType.HealArea.DisplayCommandHelp(getHelp: EnumTranslations.ToHelp); + + SpecialCommandType.HealSingle.DisplayCommandHelp(getHelp: EnumTranslations.ToHelp); + + SpecialCommandType.DefenseArea.DisplayCommandHelp(getHelp: EnumTranslations.ToHelp); + + SpecialCommandType.DefenseSingle.DisplayCommandHelp(getHelp: EnumTranslations.ToHelp); + + SpecialCommandType.MoveForward.DisplayCommandHelp(getHelp: EnumTranslations.ToHelp); + + SpecialCommandType.MoveBack.DisplayCommandHelp(getHelp: EnumTranslations.ToHelp); + + SpecialCommandType.EsunaStanceNorth.DisplayCommandHelp(getHelp: EnumTranslations.ToHelp); + + SpecialCommandType.RaiseShirk.DisplayCommandHelp(getHelp: EnumTranslations.ToHelp); + + SpecialCommandType.AntiKnockback.DisplayCommandHelp(getHelp: EnumTranslations.ToHelp); + + SpecialCommandType.Burst.DisplayCommandHelp(getHelp: EnumTranslations.ToHelp); + + ImGui.PopStyleVar(); + } + } + } +} diff --git a/RotationSolver.Old/Windows/RotationConfigWindow_Major.cs b/RotationSolver.Old/Windows/RotationConfigWindow_Major.cs new file mode 100644 index 000000000..a2d07680e --- /dev/null +++ b/RotationSolver.Old/Windows/RotationConfigWindow_Major.cs @@ -0,0 +1,75 @@ +using Dalamud.Interface.Windowing; +using ImGuiNET; +using RotationSolver.Data; +using RotationSolver.Localization; +using RotationSolver.Rotations.CustomRotation; +using System.Collections.Generic; +using System.Numerics; + +namespace RotationSolver.Windows.RotationConfigWindow; + +internal partial class RotationConfigWindow : Window +{ + const float DRAG_NUMBER_WIDTH = 100; + + public RotationConfigWindow() + : base(nameof(RotationConfigWindow), 0, false) + { + SizeCondition = ImGuiCond.FirstUseEver; + Size = new Vector2(740f, 490f); + RespectCloseHotkey = true; + } + + //private static readonly Dictionary _roleDescriptionValue = new Dictionary() + //{ + // {JobRole.Tank, $"{DescType.DefenseSingle.ToName()} �� {CustomRotation.Rampart}, {CustomRotation.Reprisal}" }, + // {JobRole.Melee, $"{DescType.DefenseArea.ToName()} �� {CustomRotation.Feint}" }, + // {JobRole.RangedMagicial, $"{DescType.DefenseArea.ToName()} �� {CustomRotation.Addle}" }, + //}; + + public override unsafe void Draw() + { + if (ImGui.BeginTabBar("RotationSolverSettings")) + { +#if DEBUG + if (Service.ClientState.LocalPlayer != null && ImGui.BeginTabItem("Debug")) + { + DrawDebugTab(); + ImGui.EndTabItem(); + } +#endif + if (ImGui.BeginTabItem(LocalizationManager.RightLang.ConfigWindow_RotationItem)) + { + DrawRotationTab(); + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem(LocalizationManager.RightLang.ConfigWindow_ParamItem)) + { + DrawParamTab(); + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem(LocalizationManager.RightLang.ConfigWindow_EventItem)) + { + DrawEventTab(); + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem(LocalizationManager.RightLang.ConfigWindow_ActionItem)) + { + DrawActionTab(); + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem(LocalizationManager.RightLang.ConfigWindow_HelpItem)) + { + DrawHelpTab(); + ImGui.EndTabItem(); + } + + ImGui.EndTabBar(); + } + ImGui.End(); + } +} diff --git a/RotationSolver.Old/Windows/RotationConfigWindow_Param.cs b/RotationSolver.Old/Windows/RotationConfigWindow_Param.cs new file mode 100644 index 000000000..b138526aa --- /dev/null +++ b/RotationSolver.Old/Windows/RotationConfigWindow_Param.cs @@ -0,0 +1,553 @@ +using ImGuiNET; +using RotationSolver.Commands; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Localization; +using System; +using System.Numerics; + +namespace RotationSolver.Windows.RotationConfigWindow; + +internal partial class RotationConfigWindow +{ + private static void DrawParamTabItem(string name, Action act) + { + if (act == null) return; + if (ImGui.BeginTabItem(name)) + { + if (ImGui.BeginChild("Param", new Vector2(0f, -1f), true)) + { + act(); + ImGui.EndChild(); + } + ImGui.EndTabItem(); + } + } + + private void DrawParamTab() + { + ImGui.TextWrapped(LocalizationManager.RightLang.Configwindow_Params_Description); + + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0f, 5f)); + + if (ImGui.BeginTabBar("Param Items")) + { + DrawParamTabItem(LocalizationManager.RightLang.Configwindow_Param_Basic, DrawParamBasic); + DrawParamTabItem(LocalizationManager.RightLang.Configwindow_Param_Delay, DrawParamDelay); + DrawParamTabItem(LocalizationManager.RightLang.Configwindow_Param_Display, DrawParamDisplay); + DrawParamTabItem(LocalizationManager.RightLang.Configwindow_Param_Action, DrawParamAction); + DrawParamTabItem(LocalizationManager.RightLang.Configwindow_Param_Conditon, DrawParamCondition); + DrawParamTabItem(LocalizationManager.RightLang.Configwindow_Param_Target, DrawParamTarget); + DrawParamTabItem(LocalizationManager.RightLang.Configwindow_Param_Hostile, DrawParamHostile); + DrawParamTabItem(LocalizationManager.RightLang.Configwindow_Param_Advanced, DrawParamAdvanced); + + ImGui.EndTabBar(); + } + ImGui.PopStyleVar(); + } + + private void DrawParamBasic() + { + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_NeverReplaceIcon, + ref Service.Configuration.NeverReplaceIcon, + LocalizationManager.RightLang.Configwindow_Param_NeverReplaceIconDesc); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_UseOverlayWindow, + ref Service.Configuration.UseOverlayWindow, + LocalizationManager.RightLang.Configwindow_Param_UseOverlayWindowDesc); + + DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_AbilitiesInterval, + ref Service.Configuration.AbilitiesInterval, min: 0.5f, max: 0.7f); + + DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_ActionAhead, + ref Service.Configuration.ActionAhead, max: 0.1f); + + DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_CountDownAhead, + ref Service.Configuration.CountDownAhead, min: 0.5f, max: 0.7f); + + DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_SpecialDuration, + ref Service.Configuration.SpecialDuration, speed: 0.02f, min: 1, max: 20); + + DrawIntNumber(LocalizationManager.RightLang.Configwindow_Param_AddDotGCDCount, + ref Service.Configuration.AddDotGCDCount, min: 0, max: 3); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_AutoOffBetweenArea, + ref Service.Configuration.AutoOffBetweenArea); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_UseWorkTask, + ref Service.Configuration.UseWorkTask); + } + + private void DrawParamDelay() + { + DrawRangedFloat(LocalizationManager.RightLang.Configwindow_Param_WeaponDelay, + ref Service.Configuration.WeaponDelayMin, ref Service.Configuration.WeaponDelayMax, speed: 0.002f, max: 1); + + DrawRangedFloat(LocalizationManager.RightLang.Configwindow_Param_HostileDelay, + ref Service.Configuration.HostileDelayMin, ref Service.Configuration.HostileDelayMax); + + DrawRangedFloat(LocalizationManager.RightLang.Configwindow_Param_InterruptDelay, + ref Service.Configuration.InterruptDelayMin, ref Service.Configuration.InterruptDelayMax); + + DrawRangedFloat(LocalizationManager.RightLang.Configwindow_Param_DeathDelay, + ref Service.Configuration.DeathDelayMin, ref Service.Configuration.DeathDelayMax); + + DrawRangedFloat(LocalizationManager.RightLang.Configwindow_Param_WeakenDelay, + ref Service.Configuration.WeakenDelayMin, ref Service.Configuration.WeakenDelayMax); + + DrawRangedFloat(LocalizationManager.RightLang.Configwindow_Param_HealDelay, + ref Service.Configuration.HealDelayMin, ref Service.Configuration.HealDelayMax); + + DrawRangedFloat(LocalizationManager.RightLang.Configwindow_Param_NotInCombatDelay, + ref Service.Configuration.NotInCombatDelayMin, ref Service.Configuration.NotInCombatDelayMax); + + if (Service.Configuration.UseStopCasting) + { + DrawRangedFloat(LocalizationManager.RightLang.Configwindow_Param_StopCastingDelay, + ref Service.Configuration.StopCastingDelayMin, ref Service.Configuration.StopCastingDelayMax); + } + } + + private void DrawParamAdvanced() + { + DrawIntNumber(LocalizationManager.RightLang.Configwindow_Params_VoiceVolume, + ref Service.Configuration.VoiceVolume, max: 100); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_SayOutStateChanged, + ref Service.Configuration.SayOutStateChanged); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_SayPositional, + ref Service.Configuration.SayPotional); + + DrawInputText(LocalizationManager.RightLang.Configwindow_Param_PositionaErrorText, + ref Service.Configuration.PositionalErrorText, 100, + LocalizationManager.RightLang.Configwindow_Params_LocationWrongTextDesc); + + ImGui.Separator(); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_PoslockCasting, + ref Service.Configuration.PoslockCasting); + + if (Service.Configuration.PoslockCasting) + { + ImGui.SameLine(); + ImGuiHelper.Spacing(); + + DrawCombo(LocalizationManager.RightLang.Configwindow_Param_PoslockModifier, + ref Service.Configuration.PoslockModifier, EnumTranslations.ToName, + ConfigurationHelper.Keys, + LocalizationManager.RightLang.Configwindow_Param_PoslockDescription); + } + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_UseStopCasting, + ref Service.Configuration.UseStopCasting); + + ImGui.Separator(); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_ShowHealthRatio, + ref Service.Configuration.ShowHealthRatio); + + DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_HealthRatioBoss, + ref Service.Configuration.HealthRatioBoss, speed: 0.02f, min: 0, max: 10); + + DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_HealthRatioDying, + ref Service.Configuration.HealthRatioDying, speed: 0.02f, min: 0, max: 10); + + DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_HealthRatioDot, + ref Service.Configuration.HealthRatioDot, speed: 0.02f, min: 0, max: 10); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_ShowActionFlag, + ref Service.Configuration.ShowActionFlag); + } + + private void DrawParamDisplay() + { + var useOverlayWindow = Service.Configuration.UseOverlayWindow; + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_TeachingMode, + ref Service.Configuration.TeachingMode); + + if (Service.Configuration.TeachingMode) + { + ImGuiHelper.Spacing(); + + DrawColor(LocalizationManager.RightLang.Configwindow_Param_TeachingModeColor, + ref Service.Configuration.TeachingModeColor); + } + + if (useOverlayWindow) + { + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_ShowMoveTarget, + ref Service.Configuration.ShowMoveTarget); + + if (Service.Configuration.ShowMoveTarget) + { + ImGuiHelper.Spacing(); + + DrawColor(LocalizationManager.RightLang.Configwindow_Param_MovingTargetColor, + ref Service.Configuration.MovingTargetColor); + } + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_ShowTarget, + ref Service.Configuration.ShowTarget); + + if (Service.Configuration.ShowTarget) + { + ImGuiHelper.Spacing(); + + DrawColor(LocalizationManager.RightLang.Configwindow_Param_TargetColor, + ref Service.Configuration.TargetColor); + + ImGuiHelper.Spacing(); + + DrawColor(LocalizationManager.RightLang.Configwindow_Params_SubTargetColor, + ref Service.Configuration.SubTargetColor); + } + } + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_KeyBoardNoise, + ref Service.Configuration.KeyBoardNoise); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_ShowInfoOnDtr, + ref Service.Configuration.ShowInfoOnDtr); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_ShowInfoOnToast, + ref Service.Configuration.ShowInfoOnToast); + + DrawIntNumber(LocalizationManager.RightLang.Configwindow_Param_NamePlateIconId, + ref Service.Configuration.NamePlateIconId, 5, 0, 150000, otherThing: RSCommands.UpdateStateNamePlate); + + ImGui.Spacing(); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_CastingDisplay, + ref Service.Configuration.CastingDisplay); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_FlytextPositional, + ref Service.Configuration.FlytextPositional); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_PositionalFeedback, + ref Service.Configuration.PositionalFeedback, + LocalizationManager.RightLang.Configwindow_Param_PositionalFeedbackDesc); + + if (useOverlayWindow) + { + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_DrawPositional, + ref Service.Configuration.DrawPositional, + LocalizationManager.RightLang.Configwindow_Param_PositionalFeedbackDesc); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_DrawMeleeRange, + ref Service.Configuration.DrawMeleeRange); + } + } + + private void DrawParamAction() + { + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_UseAOEAction, + ref Service.Configuration.UseAOEAction); + + if(Service.Configuration.UseAOEAction) + { + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_UseAOEWhenManual, + ref Service.Configuration.UseAOEWhenManual); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_NoNewHostiles, + ref Service.Configuration.NoNewHostiles, + LocalizationManager.RightLang.Configwindow_Params_NoNewHostilesDesc); + } + + ImGui.Separator(); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_AutoBurst, + ref Service.Configuration.AutoBurst); + + ImGui.SameLine(); + ImGuiHelper.Spacing(); + RSCommands.DisplayCommandHelp(OtherCommandType.Settings, nameof(Service.Configuration.AutoBurst)); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_UseItem, + ref Service.Configuration.UseItem, + LocalizationManager.RightLang.Configwindow_Param_UseItemDesc); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_UseAbility, + ref Service.Configuration.UseAbility); + + if (Service.Configuration.UseAbility) + { + ImGui.Indent(); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_UseDefenceAbility, + ref Service.Configuration.UseDefenceAbility, + LocalizationManager.RightLang.Configwindow_Param_UseDefenceAbilityDesc); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_AutoShield, + ref Service.Configuration.AutoShield); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_AutoProvokeForTank, + ref Service.Configuration.AutoProvokeForTank, + LocalizationManager.RightLang.Configwindow_Param_AutoProvokeForTankDesc); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_AutoUseTrueNorth, + ref Service.Configuration.AutoUseTrueNorth); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_RaisePlayerBySwift, + ref Service.Configuration.RaisePlayerBySwift); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_UseGroundBeneficialAbility, + ref Service.Configuration.UseGroundBeneficialAbility); + + ImGui.Unindent(); + } + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_RaisePlayerByCasting, + ref Service.Configuration.RaisePlayerByCasting); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_UseHealWhenNotAHealer, + ref Service.Configuration.UseHealWhenNotAHealer); + + DrawIntNumber(LocalizationManager.RightLang.Configwindow_Param_LessMPNoRaise, + ref Service.Configuration.LessMPNoRaise, 200, 0, 2000000); + } + + private void DrawParamCondition() + { + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_InterruptibleMoreCheck, + ref Service.Configuration.InterruptibleMoreCheck); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_StartOnCountdown, + ref Service.Configuration.StartOnCountdown); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_EsunaAll, + ref Service.Configuration.EsunaAll); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_HealOutOfCombat, + ref Service.Configuration.HealOutOfCombat); + + const float speed = 0.005f; + + DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_MeleeRangeOffset, + ref Service.Configuration.MeleeRangeOffset, 5 * speed, max: 5); + + DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_HealthDifference, + ref Service.Configuration.HealthDifference, + speed * 2, 0, 0.5f); + + DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_HealthAreaAbility, + ref Service.Configuration.HealthAreaAbility, speed); + + DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_HealthAreaSpell, + ref Service.Configuration.HealthAreafSpell, speed); + + DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_HealthSingleAbility, + ref Service.Configuration.HealthSingleAbility, speed); + + DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_HealthSingleSpell, + ref Service.Configuration.HealthSingleSpell, speed); + } + + private void DrawParamTarget() + { + DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_ObjectMinRadius, + ref Service.Configuration.ObjectMinRadius, 0.02f, 0, 10); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_AddEnemyListToHostile, + ref Service.Configuration.AddEnemyListToHostile); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_ChooseAttackMark, + ref Service.Configuration.ChooseAttackMark); + + if (Service.Configuration.ChooseAttackMark && Service.Configuration.UseAOEAction) + { + ImGui.Indent(); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_CanAttackMarkAOE, + ref Service.Configuration.CanAttackMarkAOE, + LocalizationManager.RightLang.Configwindow_Param_AttackMarkAOEDesc); + + ImGui.Unindent(); + } + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_FilterStopMark, + ref Service.Configuration.FilterStopMark); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_ChangeTargetForFate, + ref Service.Configuration.ChangeTargetForFate); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_OnlyAttackInView, + ref Service.Configuration.OnlyAttackInView); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_MoveTowardsScreen, + ref Service.Configuration.MoveTowardsScreenCenter, + LocalizationManager.RightLang.Configwindow_Param_MoveTowardsScreenDesc); + + DrawIntNumber(LocalizationManager.RightLang.Configwindow_Param_MoveTargetAngle, + ref Service.Configuration.MoveTargetAngle, 0.02f, 0, 100, + LocalizationManager.RightLang.Configwindow_Param_MoveTargetAngleDesc); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_MoveAreaActionFarthest, + ref Service.Configuration.MoveAreaActionFarthest, + LocalizationManager.RightLang.Configwindow_Param_MoveAreaActionFarthestDesc); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_TargetFriendly, + ref Service.Configuration.TargetFriendly); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_RaiseAll, + ref Service.Configuration.RaiseAll); + + DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_RaiseBrinkofDeath, + ref Service.Configuration.RaiseBrinkofDeath); + } + + private void DrawParamHostile() + { + if (ImGui.Button(LocalizationManager.RightLang.Configwindow_Param_AddHostileCondition)) + { + Service.Configuration.TargetingTypes.Add(TargetingType.Big); + } + ImGui.SameLine(); + ImGuiHelper.Spacing(); + ImGui.TextWrapped(LocalizationManager.RightLang.Configwindow_Param_HostileDesc); + for (int i = 0; i < Service.Configuration.TargetingTypes.Count; i++) + { + ImGui.Separator(); + + var names = Enum.GetNames(typeof(TargetingType)); + var targingType = (int)Service.Configuration.TargetingTypes[i]; + if (ImGui.Combo(LocalizationManager.RightLang.Configwindow_Param_HostileCondition + "##HostileCondition" + i.ToString(), ref targingType, names, names.Length)) + { + Service.Configuration.TargetingTypes[i] = (TargetingType)targingType; + Service.Configuration.Save(); + } + + if (ImGui.Button(LocalizationManager.RightLang.Configwindow_Param_ConditionUp + "##HostileUp" + i.ToString())) + { + if (i != 0) + { + var value = Service.Configuration.TargetingTypes[i]; + Service.Configuration.TargetingTypes.RemoveAt(i); + Service.Configuration.TargetingTypes.Insert(i - 1, value); + } + } + ImGui.SameLine(); + ImGuiHelper.Spacing(); + if (ImGui.Button(LocalizationManager.RightLang.Configwindow_Param_ConditionDown + "##HostileDown" + i.ToString())) + { + if (i < Service.Configuration.TargetingTypes.Count - 1) + { + var value = Service.Configuration.TargetingTypes[i]; + Service.Configuration.TargetingTypes.RemoveAt(i); + Service.Configuration.TargetingTypes.Insert(i + 1, value); + } + } + + ImGui.SameLine(); + ImGuiHelper.Spacing(); + + if (ImGui.Button(LocalizationManager.RightLang.Configwindow_Param_ConditionDelete + "##HostileDelete" + i.ToString())) + { + Service.Configuration.TargetingTypes.RemoveAt(i); + } + } + } + + private static void DrawCheckBox(string name, ref bool value, string description = "", Action otherThing = null) + { + if (ImGui.Checkbox(name, ref value)) + { + Service.Configuration.Save(); + otherThing?.Invoke(); + } + if (!string.IsNullOrEmpty(description) && ImGui.IsItemHovered()) + { + ImGui.SetTooltip(description); + } + } + + private static void DrawRangedFloat(string name, ref float minValue, ref float maxValue, float speed = 0.01f, float min = 0, float max = 3, string description = "") + { + ImGui.SetNextItemWidth(100); + if (ImGui.DragFloatRange2(name, ref minValue, ref maxValue, speed, min, max)) + { + Service.Configuration.Save(); + } + if (!string.IsNullOrEmpty(description) && ImGui.IsItemHovered()) + { + ImGui.SetTooltip(description); + } + } + + private static void DrawFloatNumber(string name, ref float value, float speed = 0.002f, float min = 0, float max = 1, string description = "") + { + ImGui.SetNextItemWidth(100); + if (ImGui.DragFloat(name, ref value, speed, min, max)) + { + Service.Configuration.Save(); + } + if (!string.IsNullOrEmpty(description) && ImGui.IsItemHovered()) + { + ImGui.SetTooltip(description); + } + } + + private static void DrawIntNumber(string name, ref int value, float speed = 0.2f, int min = 0, int max = 1, string description = "", Action otherThing = null) + { + ImGui.SetNextItemWidth(100); + if (ImGui.DragInt(name, ref value, speed, min, max)) + { + Service.Configuration.Save(); + otherThing?.Invoke(); + } + if (!string.IsNullOrEmpty(description) && ImGui.IsItemHovered()) + { + ImGui.SetTooltip(description); + } + } + + private static void DrawColor(string name, ref Vector3 value, string description = "") + { + ImGui.SetNextItemWidth(210); + if (ImGui.ColorEdit3(name, ref value)) + { + Service.Configuration.Save(); + } + if (!string.IsNullOrEmpty(description) && ImGui.IsItemHovered()) + { + ImGui.SetTooltip(description); + } + } + + private static void DrawCombo(string name, ref int value, Func toString, T[] choices = null, string description = "") where T : struct, Enum + { + choices = choices ?? Enum.GetValues(); + + ImGui.SetNextItemWidth(100); + if (ImGui.BeginCombo(name, toString(choices[value]))) + { + for (int i = 0; i < choices.Length; i++) + { + if (ImGui.Selectable(toString(choices[i]))) + { + value = i; + Service.Configuration.Save(); + } + } + ImGui.EndCombo(); + } + if (!string.IsNullOrEmpty(description) && ImGui.IsItemHovered()) + { + ImGui.SetTooltip(description); + } + } + + private static void DrawInputText(string name, ref string value, uint maxLength, string description = "") + { + ImGui.SetNextItemWidth(210); + if (ImGui.InputText(name, ref value, maxLength)) + { + Service.Configuration.Save(); + } + if (!string.IsNullOrEmpty(description) && ImGui.IsItemHovered()) + { + ImGui.SetTooltip(description); + } + } +} diff --git a/RotationSolver.Old/Windows/RotationConfigWindow_Rotation.cs b/RotationSolver.Old/Windows/RotationConfigWindow_Rotation.cs new file mode 100644 index 000000000..d70d9a90a --- /dev/null +++ b/RotationSolver.Old/Windows/RotationConfigWindow_Rotation.cs @@ -0,0 +1,159 @@ +using Dalamud.Interface.Colors; +using Dalamud.Utility; +using ImGuiNET; +using RotationSolver.Data; +using RotationSolver.Helpers; +using RotationSolver.Localization; +using RotationSolver.Rotations.CustomRotation; +using RotationSolver.SigReplacers; +using RotationSolver.Updaters; +using System; +using System.Linq; +using System.Numerics; + +namespace RotationSolver.Windows.RotationConfigWindow; + +internal partial class RotationConfigWindow +{ + private void DrawRotationTab() + { + ImGui.TextWrapped(LocalizationManager.RightLang.Configwindow_Rotation_Description); + + ImGui.SameLine(); + if (ImGui.Button("Dev Wiki")) + { + Util.OpenLink("https://archidog1998.github.io/RotationSolver/#/RotationDev/"); + } + + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0f, 5f)); + + if (ImGui.BeginTabBar("Job Items")) + { + DrawRoleItems(); + + ImGui.EndTabBar(); + } + ImGui.PopStyleVar(); + } + + private static void DrawRoleItems() + { + foreach (var key in RotationUpdater.CustomRotationsDict.Keys) + { + var rotations = RotationUpdater.CustomRotationsDict[key]; + if (rotations == null || rotations.Length == 0) continue; + + if (ImGui.BeginTabItem(key.ToName())) + { + if (ImGui.BeginChild("Rotation Items", new Vector2(0f, -1f), true)) + { + DrawRotations(rotations); + ImGui.EndChild(); + } + ImGui.EndTabItem(); + } + } + } + + private static void DrawRotations(RotationUpdater.CustomRotationGroup[] rotations) + { + for (int i = 0; i < rotations.Length; i++) + { + if (i > 0) ImGui.Separator(); + + var group = rotations[i]; + Service.Configuration.RotationChoices.TryGetValue((uint)group.jobId, out var rotationName); + var rotation = RotationUpdater.GetChooseRotation(group, rotationName); + + var canAddButton = Service.ClientState.LocalPlayer != null + && rotation.JobIDs.Contains((ClassJobID)Service.ClientState.LocalPlayer.ClassJob.Id); + + rotation.Display(group.rotations, canAddButton); + } + } + + internal static void DrawRotationRole(ICustomRotation rotation) + { + DrawTargetHostileTYpe(rotation); + DrawSpecialRoleSettings(rotation.Job.GetJobRole(), rotation.JobIDs[0]); + + ImGui.Spacing(); + } + + private static void DrawTargetHostileTYpe(ICustomRotation rotation) + { + var isAllTargetAsHostile = (int)IconReplacer.GetTargetHostileType(rotation.Job); + ImGui.SetNextItemWidth(300); + if (ImGui.Combo(LocalizationManager.RightLang.Configwindow_Param_RightNowTargetToHostileType + $"##HostileType{rotation.GetHashCode()}", ref isAllTargetAsHostile, new string[] + { + LocalizationManager.RightLang.Configwindow_Param_TargetToHostileType1, + LocalizationManager.RightLang.Configwindow_Param_TargetToHostileType2, + LocalizationManager.RightLang.Configwindow_Param_TargetToHostileType3, + }, 3)) + { + Service.Configuration.TargetToHostileTypes[rotation.Job.RowId] = (byte)isAllTargetAsHostile; + Service.Configuration.Save(); + } + + if (isAllTargetAsHostile != 2 && !Service.Configuration.AutoOffBetweenArea) + { + ImGui.TextColored(ImGuiColors.DPSRed, LocalizationManager.RightLang.Configwindow_Param_NoticeUnexpectedCombat); + } + } + + private static void DrawSpecialRoleSettings(JobRole role, ClassJobID job) + { + if (role == JobRole.Healer) + { + DrawHealerSettings(job); + } + else if (role == JobRole.Tank) + { + DrawDragFloat(job, LocalizationManager.RightLang.Configwindow_Param_HealthForDyingTank, + () => ConfigurationHelper.GetHealthForDyingTank(job), + (value) => Service.Configuration.HealthForDyingTanks[job] = value); + } + } + + private static void DrawHealerSettings(ClassJobID job) + { + DrawDragFloat(job, LocalizationManager.RightLang.Configwindow_Param_HealthAreaAbility, + () => ConfigurationHelper.GetHealAreaAbility(job), + (value) => Service.Configuration.HealthAreaAbilities[job] = value); + + DrawDragFloat(job, LocalizationManager.RightLang.Configwindow_Param_HealthAreaSpell, + () => ConfigurationHelper.GetHealAreafSpell(job), + (value) => Service.Configuration.HealthAreafSpells[job] = value); + + DrawDragFloat(job, LocalizationManager.RightLang.Configwindow_Param_HealingOfTimeSubtractArea, + () => ConfigurationHelper.GetHealingOfTimeSubtractArea(job), + (value) => Service.Configuration.HealingOfTimeSubtractAreas[job] = value); + + DrawDragFloat(job, LocalizationManager.RightLang.Configwindow_Param_HealthSingleAbility, + () => ConfigurationHelper.GetHealSingleAbility(job), + (value) => Service.Configuration.HealthSingleAbilities[job] = value); + + DrawDragFloat(job, LocalizationManager.RightLang.Configwindow_Param_HealthSingleSpell, + () => ConfigurationHelper.GetHealSingleSpell(job), + (value) => Service.Configuration.HealthSingleSpells[job] = value); + + DrawDragFloat(job, LocalizationManager.RightLang.Configwindow_Param_HealingOfTimeSubtractSingle, + () => ConfigurationHelper.GetHealingOfTimeSubtractSingle(job), + (value) => Service.Configuration.HealingOfTimeSubtractSingles[job] = value); + } + + private static void DrawDragFloat(ClassJobID job, string desc, Func getValue, Action setValue) + { + const float speed = 0.005f; + + if (getValue == null || setValue == null) return; + + var value = getValue(); + ImGui.SetNextItemWidth(DRAG_NUMBER_WIDTH); + if (ImGui.DragFloat($"{desc}##{job}{desc}", ref value, speed, 0, 1)) + { + setValue(value); + Service.Configuration.Save(); + } + } +} diff --git a/RotationSolver/packages.lock.json b/RotationSolver.Old/packages.lock.json similarity index 100% rename from RotationSolver/packages.lock.json rename to RotationSolver.Old/packages.lock.json diff --git a/RotationSolver.sln b/RotationSolver.sln index e8e6d9010..f19ca86b3 100644 --- a/RotationSolver.sln +++ b/RotationSolver.sln @@ -8,9 +8,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .gitignore = .gitignore EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RotationSolver", "RotationSolver\RotationSolver.csproj", "{77862F19-976E-43C8-82F2-391C44295D9D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RotationSolver.Default", "RotationSolver.Default\RotationSolver.Default.csproj", "{CC7BE595-27DE-4206-AE11-8184E7FCF0CD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RotationSolver.Default", "RotationSolver.Default\RotationSolver.Default.csproj", "{CC7BE595-27DE-4206-AE11-8184E7FCF0CD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RotationSolver.Basic", "RotationSolver.Basic\RotationSolver.Basic.csproj", "{140CC27C-0923-4183-9C30-26A7BDEBE606}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RotationSolver", "RotationSolver\RotationSolver.csproj", "{99EA3D95-33DC-424F-A9E6-FE6064F11592}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RotationSolver.Old", "RotationSolver.Old\RotationSolver.Old.csproj", "{4C6B3AFA-44B5-433D-99DF-D26595275EEF}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -18,14 +22,22 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {77862F19-976E-43C8-82F2-391C44295D9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {77862F19-976E-43C8-82F2-391C44295D9D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {77862F19-976E-43C8-82F2-391C44295D9D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {77862F19-976E-43C8-82F2-391C44295D9D}.Release|Any CPU.Build.0 = Release|Any CPU {CC7BE595-27DE-4206-AE11-8184E7FCF0CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CC7BE595-27DE-4206-AE11-8184E7FCF0CD}.Debug|Any CPU.Build.0 = Debug|Any CPU {CC7BE595-27DE-4206-AE11-8184E7FCF0CD}.Release|Any CPU.ActiveCfg = Release|Any CPU {CC7BE595-27DE-4206-AE11-8184E7FCF0CD}.Release|Any CPU.Build.0 = Release|Any CPU + {140CC27C-0923-4183-9C30-26A7BDEBE606}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {140CC27C-0923-4183-9C30-26A7BDEBE606}.Debug|Any CPU.Build.0 = Debug|Any CPU + {140CC27C-0923-4183-9C30-26A7BDEBE606}.Release|Any CPU.ActiveCfg = Release|Any CPU + {140CC27C-0923-4183-9C30-26A7BDEBE606}.Release|Any CPU.Build.0 = Release|Any CPU + {99EA3D95-33DC-424F-A9E6-FE6064F11592}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99EA3D95-33DC-424F-A9E6-FE6064F11592}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99EA3D95-33DC-424F-A9E6-FE6064F11592}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99EA3D95-33DC-424F-A9E6-FE6064F11592}.Release|Any CPU.Build.0 = Release|Any CPU + {4C6B3AFA-44B5-433D-99DF-D26595275EEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C6B3AFA-44B5-433D-99DF-D26595275EEF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C6B3AFA-44B5-433D-99DF-D26595275EEF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C6B3AFA-44B5-433D-99DF-D26595275EEF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/RotationSolver/Commands/RSCommands_Actions.cs b/RotationSolver/Commands/RSCommands_Actions.cs index be295f214..0db9f7275 100644 --- a/RotationSolver/Commands/RSCommands_Actions.cs +++ b/RotationSolver/Commands/RSCommands_Actions.cs @@ -1,6 +1,7 @@ using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Objects.SubKinds; using RotationSolver.Actions.BaseAction; +using RotationSolver.Basic; using RotationSolver.Data; using RotationSolver.Helpers; using RotationSolver.SigReplacers; @@ -10,7 +11,7 @@ namespace RotationSolver.Commands { - internal static partial class RSCommands + public static partial class RSCommands { private static DateTime _fastClickStopwatch = DateTime.Now; @@ -18,7 +19,7 @@ internal static unsafe void DoAnAction(bool isGCD) { if (StateType == StateCommandType.Cancel) return; - var localPlayer = Service.ClientState.LocalPlayer; + var localPlayer = Service.Player; if (localPlayer == null) return; //0.2s内,不能重复按按钮。 @@ -36,7 +37,8 @@ internal static unsafe void DoAnAction(bool isGCD) if (nextAction.Use()) { - if (Service.Configuration.KeyBoardNoise) PreviewUpdater.PulseAtionBar(nextAction.AdjustedID); + if (nextAction is BaseAction a && a.ShouldEndSpecial) ResetSpecial(); + if (Service.Config.KeyBoardNoise) PreviewUpdater.PulseActionBar(nextAction.AdjustedID); if (nextAction is BaseAction act) { #if DEBUG @@ -61,12 +63,12 @@ internal static void CancelState() internal static void UpdateRotationState() { - if (Service.ClientState.LocalPlayer.CurrentHp == 0 + if (Service.Player.CurrentHp == 0 || Service.Conditions[ConditionFlag.LoggingOut]) { CancelState(); } - else if (Service.Configuration.AutoOffBetweenArea && ( + else if (Service.Config.AutoOffBetweenArea && ( Service.Conditions[ConditionFlag.BetweenAreas] || Service.Conditions[ConditionFlag.BetweenAreas51]) || Service.Conditions[ConditionFlag.OccupiedInCutSceneEvent]) @@ -74,7 +76,7 @@ internal static void UpdateRotationState() CancelState(); } //Auto start at count Down. - else if (Service.Configuration.StartOnCountdown && CountDown.CountDownTime > 0) + else if (Service.Config.StartOnCountdown && CountDown.CountDownTime > 0) { if (StateType == StateCommandType.Cancel) { @@ -83,24 +85,5 @@ internal static void UpdateRotationState() } } - /// - /// Submit text/command to outgoing chat. - /// Can be used to enter chat commands. - /// - /// Text to submit. - public unsafe static void SubmitToChat(string text) - { - IntPtr uiModule = Service.GameGui.GetUIModule(); - - using (ChatPayload payload = new ChatPayload(text)) - { - IntPtr mem1 = Marshal.AllocHGlobal(400); - Marshal.StructureToPtr(payload, mem1, false); - - Service.Address.GetChatBox(uiModule, mem1, IntPtr.Zero, 0); - - Marshal.FreeHGlobal(mem1); - } - } } } diff --git a/RotationSolver/Commands/RSCommands_BasicInfo.cs b/RotationSolver/Commands/RSCommands_BasicInfo.cs index 73d1b5b0e..61147bbd6 100644 --- a/RotationSolver/Commands/RSCommands_BasicInfo.cs +++ b/RotationSolver/Commands/RSCommands_BasicInfo.cs @@ -1,5 +1,6 @@ using Dalamud.Game.Command; using FFXIVClientStructs.FFXIV.Client.Game.UI; +using RotationSolver.Basic; using RotationSolver.Data; using RotationSolver.Localization; using System; @@ -7,7 +8,7 @@ namespace RotationSolver.Commands { - internal static partial class RSCommands + public static partial class RSCommands { internal const string _command = "/rotation"; @@ -15,14 +16,14 @@ internal static TargetingType TargetingType { get { - if (Service.Configuration.TargetingTypes.Count == 0) + if (Service.Config.TargetingTypes.Count == 0) { - Service.Configuration.TargetingTypes.Add(TargetingType.Big); - Service.Configuration.TargetingTypes.Add(TargetingType.Small); - Service.Configuration.Save(); + Service.Config.TargetingTypes.Add(TargetingType.Big); + Service.Config.TargetingTypes.Add(TargetingType.Small); + Service.Config.Save(); } - return Service.Configuration.TargetingTypes[Service.Configuration.TargetingIndex %= Service.Configuration.TargetingTypes.Count]; + return Service.Config.TargetingTypes[Service.Config.TargetingIndex %= Service.Config.TargetingTypes.Count]; } } diff --git a/RotationSolver/Commands/RSCommands_OtherCommand.cs b/RotationSolver/Commands/RSCommands_OtherCommand.cs index d20f21056..395e56e37 100644 --- a/RotationSolver/Commands/RSCommands_OtherCommand.cs +++ b/RotationSolver/Commands/RSCommands_OtherCommand.cs @@ -1,8 +1,10 @@ using RotationSolver.Actions; using RotationSolver.Actions.BaseAction; +using RotationSolver.Basic; +using RotationSolver.Basic.Data; +using RotationSolver.Basic.Rotations; using RotationSolver.Helpers; using RotationSolver.Localization; -using RotationSolver.Rotations.CustomRotation; using RotationSolver.SigReplacers; using RotationSolver.Updaters; using System; @@ -11,28 +13,8 @@ namespace RotationSolver.Commands { - internal static partial class RSCommands + public static partial class RSCommands { - internal record NextAct(IBaseAction act, DateTime deadTime); - - private static List NextActs = new List(); - internal static IBaseAction NextAction - { - get - { - if (TimeLineUpdater.TimeLineAction != null) return TimeLineUpdater.TimeLineAction; - - var next = NextActs.FirstOrDefault(); - - while (next != null && NextActs.Count > 0 && (next.deadTime < DateTime.Now || IActionHelper.IsLastAction(true, next.act))) - { - NextActs.RemoveAt(0); - next = NextActs.FirstOrDefault(); - } - return next?.act; - } - } - private static void DoOtherCommand(OtherCommandType otherType, string str) { switch (otherType) @@ -60,13 +42,13 @@ private static void DoOtherCommand(OtherCommandType otherType, string str) private static void DoSettingCommand(string str) { - if (str.Contains(nameof(Service.Configuration.AutoBurst))) + if (str.Contains(nameof(Service.Config.AutoBurst))) { - Service.Configuration.AutoBurst = !Service.Configuration.AutoBurst; + Service.Config.AutoBurst = !Service.Config.AutoBurst; //Say out. Service.ChatGui.Print(string.Format(LocalizationManager.RightLang.Commands_ChangeAutoBurst, - Service.Configuration.AutoBurst)); + Service.Config.AutoBurst)); } } @@ -99,22 +81,12 @@ private static void DoActionCommand(string str) var actName = strs[0]; foreach (var iAct in RotationUpdater.RightRotationBaseActions) { - if (iAct is not BaseAction act) continue; + if (iAct is not IBaseAction act) continue; if (!act.IsTimeline) continue; if (actName == act.Name) { - var index = NextActs.FindIndex(i => i.act.ID == act.ID); - var newItem = new NextAct(act, DateTime.Now.AddSeconds(time)); - if (index < 0) - { - NextActs.Add(newItem); - } - else - { - NextActs[index] = newItem; - } - NextActs = NextActs.OrderBy(i => i.deadTime).ToList(); + DataCenter.AddOneTimelineAction(act, time); Service.ToastGui.ShowQuest(string.Format(LocalizationManager.RightLang.Commands_InsertAction, time), new Dalamud.Game.Gui.Toast.QuestToastOptions() diff --git a/RotationSolver/Commands/RSCommands_StateSpecialCommand.cs b/RotationSolver/Commands/RSCommands_StateSpecialCommand.cs index 0c646b0b9..1a224e589 100644 --- a/RotationSolver/Commands/RSCommands_StateSpecialCommand.cs +++ b/RotationSolver/Commands/RSCommands_StateSpecialCommand.cs @@ -1,4 +1,5 @@ using Lumina.Excel.GeneratedSheets; +using RotationSolver.Basic; using RotationSolver.Data; using RotationSolver.Helpers; using RotationSolver.Localization; @@ -7,16 +8,16 @@ namespace RotationSolver.Commands { - internal static partial class RSCommands + public static partial class RSCommands { private static DateTime _specialStateStartTime = DateTime.MinValue; - private static double SpecialTimeLeft => Service.Configuration.SpecialDuration - (DateTime.Now - _specialStateStartTime).TotalSeconds; + private static double SpecialTimeLeft => Service.Config.SpecialDuration - (DateTime.Now - _specialStateStartTime).TotalSeconds; private static SpecialCommandType _specialType = SpecialCommandType.EndSpecial; - internal static SpecialCommandType SpecialType => + public static SpecialCommandType SpecialType => SpecialTimeLeft < 0 ? SpecialCommandType.EndSpecial : _specialType; - internal static StateCommandType StateType { get; private set; } = StateCommandType.Cancel; + public static StateCommandType StateType { get; private set; } = StateCommandType.Cancel; private static string _stateString = "Off", _specialString = string.Empty; @@ -25,7 +26,7 @@ internal static partial class RSCommands private static void UpdateToast() { - if (!Service.Configuration.ShowInfoOnToast) return; + if (!Service.Config.ShowInfoOnToast) return; Service.ToastGui.ShowQuest(" " + EntryString, new Dalamud.Game.Gui.Toast.QuestToastOptions() { @@ -38,8 +39,8 @@ private static unsafe void DoStateCommandType(StateCommandType stateType) => DoO if (StateType == StateCommandType.Smart && stateType == StateCommandType.Smart) { - Service.Configuration.TargetingIndex += 1; - Service.Configuration.TargetingIndex %= Service.Configuration.TargetingTypes.Count; + Service.Config.TargetingIndex += 1; + Service.Config.TargetingIndex %= Service.Config.TargetingTypes.Count; } StateType = stateType; @@ -52,10 +53,10 @@ private static unsafe void DoStateCommandType(StateCommandType stateType) => DoO public static unsafe void UpdateStateNamePlate() { - if (Service.ClientState.LocalPlayer == null) return; + if (Service.Player == null) return; - Service.ClientState.LocalPlayer.GetAddress()->NamePlateIconId = - StateType == StateCommandType.Cancel ? 0u : (uint)Service.Configuration.NamePlateIconId; + Service.Player.GetAddress()->NamePlateIconId = + StateType == StateCommandType.Cancel ? 0u : (uint)Service.Config.NamePlateIconId; } private static void DoSpecialCommandType(SpecialCommandType specialType, bool sayout = true) => DoOneCommandType(specialType, sayout ? EnumTranslations.ToSayout : (s, r) => string.Empty, role => @@ -71,13 +72,13 @@ private static void DoOneCommandType(T type, Func sayout, where T : struct, Enum { //Get jobrole. - var role = Service.DataManager.GetExcelSheet().GetRow( - Service.ClientState.LocalPlayer.ClassJob.Id).GetJobRole(); + var role = Service.GetSheet().GetRow( + Service.Player.ClassJob.Id).GetJobRole(); doingSomething(role); //Saying out. - if (Service.Configuration.SayOutStateChanged) Watcher.Speak(sayout(type, role)); + if (Service.Config.SayOutStateChanged) Watcher.Speak(sayout(type, role)); } } } diff --git a/RotationSolver/Localization/EnumTranslations.cs b/RotationSolver/Localization/EnumTranslations.cs index 973999a06..2383c291b 100644 --- a/RotationSolver/Localization/EnumTranslations.cs +++ b/RotationSolver/Localization/EnumTranslations.cs @@ -76,8 +76,8 @@ internal static class EnumTranslations DescType.MoveBackAbility => LocalizationManager.RightLang.DescType_MoveBackAbility, DescType.HealSingleAbility => LocalizationManager.RightLang.DescType_HealSingleAbility, DescType.HealAreaAbility => LocalizationManager.RightLang.DescType_HealAreaAbility, - DescType.DefenceSingleAbility => LocalizationManager.RightLang.DescType_DefenseSingleAbility, - DescType.DefenceAreaAbility => LocalizationManager.RightLang.DescType_DefenseAreaAbility, + DescType.DefenseSingleAbility => LocalizationManager.RightLang.DescType_DefenseSingleAbility, + DescType.DefenseAreaAbility => LocalizationManager.RightLang.DescType_DefenseAreaAbility, _ => string.Empty, }; @@ -90,9 +90,9 @@ internal static class EnumTranslations JobRole.Ranged => LocalizationManager.RightLang.JobRole_Ranged, JobRole.Healer => LocalizationManager.RightLang.JobRole_Healer, JobRole.RangedPhysical => LocalizationManager.RightLang.JobRole_RangedPhysical, - JobRole.RangedMagicial => LocalizationManager.RightLang.JobRole_RangedMagicial, - JobRole.DiscipleoftheLand => LocalizationManager.RightLang.JobRole_DiscipleoftheLand, - JobRole.DiscipleoftheHand => LocalizationManager.RightLang.JobRole_DiscipleoftheHand, + JobRole.RangedMagical => LocalizationManager.RightLang.JobRole_RangedMagicial, + JobRole.DiscipleOfTheLand => LocalizationManager.RightLang.JobRole_DiscipleoftheLand, + JobRole.DiscipleOfTheHand => LocalizationManager.RightLang.JobRole_DiscipleoftheHand, _ => string.Empty, }; diff --git a/RotationSolver/Localization/LocalizationManager.cs b/RotationSolver/Localization/LocalizationManager.cs index 9e541c2ff..cf4558886 100644 --- a/RotationSolver/Localization/LocalizationManager.cs +++ b/RotationSolver/Localization/LocalizationManager.cs @@ -1,5 +1,6 @@ using Dalamud.Logging; using Newtonsoft.Json; +using RotationSolver.Basic; using System; using System.Collections.Generic; using System.IO; diff --git a/RotationSolver/RotationSolver.csproj b/RotationSolver/RotationSolver.csproj index c9613a0cf..840d79ce6 100644 --- a/RotationSolver/RotationSolver.csproj +++ b/RotationSolver/RotationSolver.csproj @@ -1,53 +1,53 @@ - + + - RotationSolver - False - net7.0 - x64 - false - true - Debug;Release + net7.0-windows + enable + $(AppData)\XIVLauncher\addon\Hooks\dev\ + Based on the pve combat information in one frame, find the best action. + ArchiTed + 1.5.1 + true + AnyCPU;ARM64 - - Preview - True - - - - $(AppData)\XIVLauncher\addon\Hooks\dev\ - + - - - - - - $(DalamudLibPath)Dalamud.dll - False - - - $(DalamudLibPath)ImGui.NET.dll - False - - - $(DalamudLibPath)ImGuiScene.dll - False - - - $(DalamudLibPath)Lumina.dll - False - - - $(DalamudLibPath)Lumina.Excel.dll - False - - - $(DalamudLibPath)FFXIVClientStructs.dll - False - - - $(DalamudLibPath)Newtonsoft.Json.dll - False - + - \ No newline at end of file + + + + + + + + $(DalamudLibPath)Dalamud.dll + False + + + $(DalamudLibPath)ImGui.NET.dll + False + + + $(DalamudLibPath)ImGuiScene.dll + False + + + $(DalamudLibPath)Lumina.dll + False + + + $(DalamudLibPath)Lumina.Excel.dll + False + + + $(DalamudLibPath)FFXIVClientStructs.dll + False + + + $(DalamudLibPath)Newtonsoft.Json.dll + False + + + + diff --git a/RotationSolver/RotationSolverPlugin.cs b/RotationSolver/RotationSolverPlugin.cs index 2d2fdcf95..2cbc9c080 100644 --- a/RotationSolver/RotationSolverPlugin.cs +++ b/RotationSolver/RotationSolverPlugin.cs @@ -2,6 +2,7 @@ using Dalamud.Plugin; using Lumina.Excel.GeneratedSheets; using RotationSolver.Actions; +using RotationSolver.Basic; using RotationSolver.Commands; using RotationSolver.Configuration; using RotationSolver.Data; @@ -19,6 +20,11 @@ public sealed class RotationSolverPlugin : IDalamudPlugin, IDisposable private readonly WindowSystem windowSystem; private static RotationConfigWindow _comboConfigWindow; + + private Watcher _watcher; + private CountDown _countDown; + LocalizationManager _manager; + IconReplacer _iconReplacer; public string Name => "Rotation Solver"; public unsafe RotationSolverPlugin(DalamudPluginInterface pluginInterface) @@ -27,16 +33,16 @@ public unsafe RotationSolverPlugin(DalamudPluginInterface pluginInterface) try { - Service.Configuration = pluginInterface.GetPluginConfig() as PluginConfiguration ?? new PluginConfiguration(); + Service.Config = pluginInterface.GetPluginConfig() as PluginConfiguration ?? new PluginConfiguration(); } catch { - Service.Configuration = new PluginConfiguration(); + Service.Config = new PluginConfiguration(); } - Service.Address = new PluginAddressResolver(); - Service.Address.Setup(); + //Service.Address = new PluginAddressResolver(); + //Service.Address.Setup(); - Service.IconReplacer = new IconReplacer(); + _iconReplacer = new IconReplacer(); _comboConfigWindow = new(); windowSystem = new WindowSystem(Name); @@ -49,12 +55,12 @@ public unsafe RotationSolverPlugin(DalamudPluginInterface pluginInterface) MajorUpdater.Enable(); TimeLineUpdater.Enable(pluginInterface.ConfigDirectory.FullName); SocialUpdater.Enable(); - Watcher.Enable(); - CountDown.Enable(); + _watcher = new(); + _countDown = new(); - Service.Localization = new LocalizationManager(); + _manager = new LocalizationManager(); #if DEBUG - Service.Localization.ExportLocalization(); + _manager.ExportLocalization(); #endif Service.ClientState.TerritoryChanged += ClientState_TerritoryChanged; ChangeUITranslation(); @@ -63,7 +69,7 @@ public unsafe RotationSolverPlugin(DalamudPluginInterface pluginInterface) private void ClientState_TerritoryChanged(object sender, ushort e) { RSCommands.UpdateStateNamePlate(); - var territory = Service.DataManager.GetExcelSheet().GetRow(e); + var territory = Service.GetSheet().GetRow(e); if(territory?.ContentFinderCondition?.Value?.RowId != 0) { SocialUpdater.CanSaying = true; @@ -88,13 +94,13 @@ public void Dispose() Service.Interface.UiBuilder.Draw -= windowSystem.Draw; Service.Interface.UiBuilder.Draw -= OverlayWindow.Draw; - Service.IconReplacer.Dispose(); - Service.Localization.Dispose(); + _iconReplacer.Dispose(); + _manager.Dispose(); MajorUpdater.Dispose(); TimeLineUpdater.SaveFiles(); - Watcher.Dispose(); - CountDown.Dispose(); + _watcher.Dispose(); + _countDown.Dispose(); SocialUpdater.Disable(); IconSet.Dispose(); diff --git a/RotationSolver/SigReplacers/IconReplacer.cs b/RotationSolver/SigReplacers/IconReplacer.cs index 702630d53..3dda6fa89 100644 --- a/RotationSolver/SigReplacers/IconReplacer.cs +++ b/RotationSolver/SigReplacers/IconReplacer.cs @@ -1,15 +1,11 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Hooking; +using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game; using Lumina.Excel.GeneratedSheets; -using RotationSolver.Actions; +using RotationSolver.Basic; using RotationSolver.Data; -using RotationSolver.Rotations.CustomRotation; using RotationSolver.Updaters; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; namespace RotationSolver.SigReplacers; @@ -25,15 +21,15 @@ public static TargetHostileType RightNowTargetToHostileType { get { - if (Service.ClientState.LocalPlayer == null) return 0; - var id = Service.ClientState.LocalPlayer.ClassJob.Id; - return GetTargetHostileType(Service.DataManager.GetExcelSheet().GetRow(id)); + if (Service.Player == null) return 0; + var id = Service.Player.ClassJob.Id; + return GetTargetHostileType(Service.GetSheet().GetRow(id)); } } public static TargetHostileType GetTargetHostileType(ClassJob classJob) { - if (Service.Configuration.TargetToHostileTypes.TryGetValue(classJob.RowId, out var type)) + if (Service.Config.TargetToHostileTypes.TryGetValue(classJob.RowId, out var type)) { return (TargetHostileType)type; } @@ -41,9 +37,13 @@ public static TargetHostileType GetTargetHostileType(ClassJob classJob) return classJob.GetJobRole() == JobRole.Tank ? TargetHostileType.AllTargetsCanAttack : TargetHostileType.TargetsHaveTarget; } + /// + /// https://github.com/attickdoor/XIVComboPlugin/blob/master/XIVComboPlugin/IconReplacerAddressResolver.cs + /// + [Signature("E8 ?? ?? ?? ?? 84 C0 74 4C 8B D3", DetourName = nameof(IsIconReplaceableDetour))] private readonly Hook isIconReplaceableHook; - private readonly Hook getIconHook; + private readonly Hook getIconHook; private IntPtr actionManager = IntPtr.Zero; @@ -53,16 +53,16 @@ public IconReplacer() { getIconHook = Hook.FromAddress((IntPtr)ActionManager.MemberFunctionPointers.GetAdjustedActionId, GetIconDetour); } - isIconReplaceableHook = Hook.FromAddress(Service.Address.IsActionIdReplaceable, IsIconReplaceableDetour); + SignatureHelper.Initialise(this); - getIconHook.Enable(); - isIconReplaceableHook.Enable(); + getIconHook?.Enable(); + isIconReplaceableHook?.Enable(); } public void Dispose() { - getIconHook.Dispose(); - isIconReplaceableHook.Dispose(); + getIconHook?.Dispose(); + isIconReplaceableHook?.Dispose(); } internal ActionID OriginalHook(ActionID actionID) @@ -85,13 +85,12 @@ private unsafe uint GetIconDetour(IntPtr actionManager, uint actionID) private uint RemapActionID(uint actionID) { - PlayerCharacter localPlayer = Service.ClientState.LocalPlayer; + PlayerCharacter localPlayer = Service.Player; - if (localPlayer == null || actionID != (uint)KeyActionID || Service.Configuration.NeverReplaceIcon) + if (localPlayer == null || actionID != (uint)KeyActionID || Service.Config.NeverReplaceIcon) return OriginalHook(actionID); return ActionUpdater.NextAction?.AdjustedID ?? OriginalHook(actionID); - } private ulong IsIconReplaceableDetour(uint actionID) diff --git a/RotationSolver/SigReplacers/MovingController.cs b/RotationSolver/SigReplacers/MovingController.cs new file mode 100644 index 000000000..b6e4f7196 --- /dev/null +++ b/RotationSolver/SigReplacers/MovingController.cs @@ -0,0 +1,50 @@ +using Dalamud.Game.ClientState.Conditions; +using Dalamud.Hooking; +using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using RotationSolver.Basic; +using RotationSolver.Data; +using RotationSolver.Helpers; +using System; +using System.Numerics; + +namespace RotationSolver.SigReplacers; + +internal class MovingController : IDisposable +{ + private static bool _posLocker = false; + private delegate bool MovingControllerDelegate(IntPtr ptr); + + [Signature("40 55 53 48 8D 6C 24 ?? 48 81 EC ?? ?? ?? ?? 48 83 79 ?? ??", DetourName = nameof(MovingDetour))] + private static Hook movingHook = null; + internal static unsafe bool IsMoving + { + set => _posLocker = !value; + } + public MovingController() + { + SignatureHelper.Initialise(this); + movingHook?.Enable(); + + } + public void Dispose() + { + movingHook.Disable(); + } + + private static bool MovingDetour(IntPtr ptr) + { + if (Service.Conditions[ConditionFlag.OccupiedInEvent]) + return movingHook.Original(ptr); + + if (Service.Config.PoslockCasting && _posLocker) + { + //没有键盘取消 + if (!Service.KeyState[ConfigurationHelper.Keys[Service.Config.PoslockModifier]] + //也没有手柄取消 + && Service.GamepadState.Raw(Dalamud.Game.ClientState.GamePad.GamepadButtons.L2) <= 0.5f) return false; + } + return movingHook.Original(ptr); + } + +} diff --git a/RotationSolver/SigReplacers/Watcher.cs b/RotationSolver/SigReplacers/Watcher.cs index d3e6389bb..48160cd96 100644 --- a/RotationSolver/SigReplacers/Watcher.cs +++ b/RotationSolver/SigReplacers/Watcher.cs @@ -1,172 +1,136 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Hooking; using Dalamud.Interface.Colors; -using FFXIVClientStructs.FFXIV.Client.UI.Misc; +using Dalamud.Utility.Signatures; using ImGuiNET; +using RotationSolver.Basic; using RotationSolver.Data; using RotationSolver.Helpers; using RotationSolver.Localization; -using RotationSolver.Updaters; -using System; -using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Numerics; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using Action = Lumina.Excel.GeneratedSheets.Action; -namespace RotationSolver.SigReplacers -{ - public static class Watcher - { - public record ActionRec(DateTime UsedTime, Action Action); - - private delegate void ReceiveAbiltyDelegate(uint sourceId, IntPtr sourceCharacter, IntPtr pos, IntPtr effectHeader, IntPtr effectArray, IntPtr effectTrail); +namespace RotationSolver.SigReplacers; - private static Hook _receivAbilityHook; +public class Watcher : IDisposable +{ + private delegate void ReceiveAbilityDelegate(uint sourceId, IntPtr sourceCharacter, IntPtr pos, IntPtr effectHeader, IntPtr effectArray, IntPtr effectTrail); - [EditorBrowsable(EditorBrowsableState.Never)] - internal static ActionID LastAction { get; set; } = 0; + /// + /// https://github.com/Tischel/ActionTimeline/blob/master/ActionTimeline/Helpers/TimelineManager.cs#L86 + /// + [Signature("4C 89 44 24 ?? 55 56 41 54 41 55 41 56", DetourName = nameof(ReceiveAbilityEffect))] + private static Hook _receiveAbilityHook; - [EditorBrowsable(EditorBrowsableState.Never)] - internal static ActionID LastGCD { get; set; } = 0; + [EditorBrowsable(EditorBrowsableState.Never)] + internal static ActionID LastAction { get; set; } = 0; - [EditorBrowsable(EditorBrowsableState.Never)] - internal static ActionID LastAbility { get; set; } = 0; + [EditorBrowsable(EditorBrowsableState.Never)] + internal static ActionID LastGCD { get; set; } = 0; - internal static TimeSpan TimeSinceLastAction => DateTime.Now - _timeLastActionUsed; + [EditorBrowsable(EditorBrowsableState.Never)] + internal static ActionID LastAbility { get; set; } = 0; - private static DateTime _timeLastActionUsed = DateTime.Now; + public Watcher() + { + SignatureHelper.Initialise(this); - const int QUEUECAPACITY = 32; - private static Queue _actions = new Queue(QUEUECAPACITY); + _receiveAbilityHook?.Enable(); + } - [EditorBrowsable(EditorBrowsableState.Never)] - internal static ActionRec[] RecordActions => _actions.Reverse().ToArray(); + private static void ReceiveAbilityEffect(uint sourceId, IntPtr sourceCharacter, IntPtr pos, IntPtr effectHeader, IntPtr effectArray, IntPtr effectTrail) + { + _receiveAbilityHook.Original(sourceId, sourceCharacter, pos, effectHeader, effectArray, effectTrail); - internal static unsafe void Enable() - { - _receivAbilityHook = Hook.FromAddress(Service.Address.ReceiveAbilty, ReceiveAbilityEffect); + //不是自己放出来的 + if (Service.Player == null || sourceId != Service.Player.ObjectId) return; - _receivAbilityHook?.Enable(); - } + //不是一个Spell + if (Marshal.ReadByte(effectHeader, 31) != 1) return; - private static void ReceiveAbilityEffect(uint sourceId, IntPtr sourceCharacter, IntPtr pos, IntPtr effectHeader, IntPtr effectArray, IntPtr effectTrail) - { - _receivAbilityHook.Original(sourceId, sourceCharacter, pos, effectHeader, effectArray, effectTrail); + //获得行为 + var action = Service.GetSheet().GetRow((uint)Marshal.ReadInt32(effectHeader, 0x8)); - //不是自己放出来的 - if (Service.ClientState.LocalPlayer == null || sourceId != Service.ClientState.LocalPlayer.ObjectId) return; + //获得目标 + var tar = Service.ObjectTable.SearchById((uint)Marshal.ReadInt32(effectHeader)) ?? Service.Player; - //不是一个Spell - if (Marshal.ReadByte(effectHeader, 31) != 1) return; - - //获得行为 - var action = Service.DataManager.GetExcelSheet().GetRow((uint)Marshal.ReadInt32(effectHeader, 0x8)); + //获得身为技能是否正确flag + var flag = Marshal.ReadByte(effectArray + 3); + RecordAction(tar, action, flag); + } - //获得目标 - var tar = Service.ObjectTable.SearchById((uint)Marshal.ReadInt32(effectHeader)) ?? Service.ClientState.LocalPlayer; + private static unsafe void RecordAction(GameObject tar, Action action, byte flag) + { + DataCenter.AddActionRec(action); - //获得身为技能是否正确flag - var flag = Marshal.ReadByte(effectArray + 3); - RecordAction(tar, action, flag); + //Macro + foreach (var item in Service.Config.Events) + { + if (!new Regex(item.Name).Match(action.Name).Success) continue; + if (item.AddMacro(tar)) break; } - private static unsafe void RecordAction(GameObject tar, Action action, byte flag) + if (flag != 0 && Service.Config.ShowActionFlag) { - var id = (ActionID)action.RowId; - - //Record - switch (action.GetActinoType()) - { - case ActionCate.Spell: //魔法 - case ActionCate.Weaponskill: //战技 - LastGCD = id; - break; - case ActionCate.Ability: //能力 - LastAbility = id; - break; - default: - return; - } - _timeLastActionUsed = DateTime.Now; - LastAction = id; - - if (_actions.Count >= QUEUECAPACITY) - { - _actions.Dequeue(); - } - _actions.Enqueue(new ActionRec(_timeLastActionUsed, action)); - - //Macro - foreach (var item in Service.Configuration.Events) - { - if (!new Regex(item.Name).Match(action.Name).Success) continue; - if (item.AddMacro(tar)) break; - } - - if (flag != 0 && Service.Configuration.ShowActionFlag) - { - Service.FlyTextGui.AddFlyText(Dalamud.Game.Gui.FlyText.FlyTextKind.NamedIcon, 0, 0, 0, "Flag:" + flag.ToString(), "", - ImGui.GetColorU32(ImGuiColors.DPSRed), 0, action.Icon); - } + Service.FlyTextGui.AddFlyText(Dalamud.Game.Gui.FlyText.FlyTextKind.NamedIcon, 0, 0, 0, "Flag:" + flag.ToString(), "", + ImGui.GetColorU32(ImGuiColors.DPSRed), 0, action.Icon); + } - //事后骂人! - if (Service.Configuration.PositionalFeedback - && ConfigurationHelper.ActionPositionals.TryGetValue(id, out var pos) - && pos.Tags.Length > 0 && !pos.Tags.Contains(flag)) + //事后骂人! + if (Service.Config.PositionalFeedback + && ConfigurationHelper.ActionPositional.TryGetValue((ActionID)action.RowId, out var pos) + && pos.Tags.Length > 0 && !pos.Tags.Contains(flag)) + { + Service.FlyTextGui.AddFlyText(Dalamud.Game.Gui.FlyText.FlyTextKind.NamedIcon, 0, 0, 0, pos.Pos.ToName(), "", + ImGui.GetColorU32(ImGuiColors.DPSRed), 94662, action.Icon); + if (!string.IsNullOrEmpty(Service.Config.PositionalErrorText)) { - Service.FlyTextGui.AddFlyText(Dalamud.Game.Gui.FlyText.FlyTextKind.NamedIcon, 0, 0, 0, pos.Pos.ToName(), "", - ImGui.GetColorU32(ImGuiColors.DPSRed), 94662, action.Icon); - if (!string.IsNullOrEmpty(Service.Configuration.PositionalErrorText)) - { - Speak(Service.Configuration.PositionalErrorText); - } + Speak(Service.Config.PositionalErrorText); } } + } - internal static void Speak(string text, bool wait = false) - { - ExecuteCommand( - $@"Add-Type -AssemblyName System.speech; + internal static void Speak(string text, bool wait = false) + { + ExecuteCommand( + $@"Add-Type -AssemblyName System.speech; $speak = New-Object System.Speech.Synthesis.SpeechSynthesizer; - $speak.Volume = ""{Service.Configuration.VoiceVolume}""; + $speak.Volume = ""{Service.Config.VoiceVolume}""; $speak.Speak(""{text}"");"); - void ExecuteCommand(string command) + void ExecuteCommand(string command) + { + string path = Path.GetTempPath() + Guid.NewGuid() + ".ps1"; + + // make sure to be using System.Text + using (StreamWriter sw = new StreamWriter(path, false, Encoding.UTF8)) { - string path = Path.GetTempPath() + Guid.NewGuid() + ".ps1"; + sw.Write(command); - // make sure to be using System.Text - using (StreamWriter sw = new StreamWriter(path, false, Encoding.UTF8)) + ProcessStartInfo start = new ProcessStartInfo() { - sw.Write(command); - - ProcessStartInfo start = new ProcessStartInfo() - { - FileName = @"C:\Windows\System32\windowspowershell\v1.0\powershell.exe", - LoadUserProfile = false, - UseShellExecute = false, - CreateNoWindow = true, - Arguments = $"-executionpolicy bypass -File {path}", - WindowStyle = ProcessWindowStyle.Hidden - }; - - Process process = Process.Start(start); - - if (wait) - process.WaitForExit(); - } + FileName = @"C:\Windows\System32\windowspowershell\v1.0\powershell.exe", + LoadUserProfile = false, + UseShellExecute = false, + CreateNoWindow = true, + Arguments = $"-executionpolicy bypass -File {path}", + WindowStyle = ProcessWindowStyle.Hidden + }; + + Process process = Process.Start(start); + + if (wait) + process.WaitForExit(); } } + } - public static void Dispose() - { - _receivAbilityHook?.Dispose(); - } + public void Dispose() + { + _receiveAbilityHook?.Dispose(); } } diff --git a/RotationSolver/Timeline/ActionCondition.cs b/RotationSolver/Timeline/ActionCondition.cs index f30394e27..8a2555ddd 100644 --- a/RotationSolver/Timeline/ActionCondition.cs +++ b/RotationSolver/Timeline/ActionCondition.cs @@ -1,10 +1,10 @@ using ImGuiNET; using Newtonsoft.Json; using RotationSolver.Actions.BaseAction; +using RotationSolver.Basic.Rotations; using RotationSolver.Data; using RotationSolver.Helpers; using RotationSolver.Localization; -using RotationSolver.Rotations.CustomRotation; using System; namespace RotationSolver.Timeline; diff --git a/RotationSolver/Timeline/BaseStatus.cs b/RotationSolver/Timeline/BaseStatus.cs index 752ea581e..4a055f02c 100644 --- a/RotationSolver/Timeline/BaseStatus.cs +++ b/RotationSolver/Timeline/BaseStatus.cs @@ -1,4 +1,5 @@ using Lumina.Excel.GeneratedSheets; +using RotationSolver.Basic; using RotationSolver.Data; namespace RotationSolver.Timeline; @@ -16,6 +17,6 @@ internal class BaseStatus : ITexture public BaseStatus(StatusID id) { - _status = Service.DataManager.GetExcelSheet().GetRow((uint)id); + _status = Service.GetSheet().GetRow((uint)id); } } diff --git a/RotationSolver/Timeline/ConditionSet.cs b/RotationSolver/Timeline/ConditionSet.cs index 296997398..66d2540a8 100644 --- a/RotationSolver/Timeline/ConditionSet.cs +++ b/RotationSolver/Timeline/ConditionSet.cs @@ -1,9 +1,9 @@ using Dalamud.Interface; using ImGuiNET; using Newtonsoft.Json; +using RotationSolver.Basic.Rotations; using RotationSolver.Helpers; using RotationSolver.Localization; -using RotationSolver.Rotations.CustomRotation; using System; using System.Collections.Generic; using System.Linq; diff --git a/RotationSolver/Timeline/ICondition.cs b/RotationSolver/Timeline/ICondition.cs index a0db3dcab..5c2b12b23 100644 --- a/RotationSolver/Timeline/ICondition.cs +++ b/RotationSolver/Timeline/ICondition.cs @@ -1,4 +1,4 @@ -using RotationSolver.Rotations.CustomRotation; +using RotationSolver.Basic.Rotations; namespace RotationSolver.Timeline; diff --git a/RotationSolver/Timeline/RotationCondition.cs b/RotationSolver/Timeline/RotationCondition.cs index 166ec8a2b..efd486254 100644 --- a/RotationSolver/Timeline/RotationCondition.cs +++ b/RotationSolver/Timeline/RotationCondition.cs @@ -2,10 +2,11 @@ using Newtonsoft.Json; using RotationSolver.Actions; using RotationSolver.Actions.BaseAction; +using RotationSolver.Basic; +using RotationSolver.Basic.Rotations; using RotationSolver.Data; using RotationSolver.Helpers; using RotationSolver.Localization; -using RotationSolver.Rotations.CustomRotation; using System.Reflection; namespace RotationSolver.Timeline; @@ -38,7 +39,7 @@ private void UpdateInfo(ICustomRotation rotation) public bool IsTrue(ICustomRotation rotation) { - if (Service.ClientState.LocalPlayer == null) return false; + if (Service.Player == null) return false; UpdateInfo(rotation); switch (ComboConditionType) diff --git a/RotationSolver/Timeline/TargetCondition.cs b/RotationSolver/Timeline/TargetCondition.cs index da5ae4015..17289347b 100644 --- a/RotationSolver/Timeline/TargetCondition.cs +++ b/RotationSolver/Timeline/TargetCondition.cs @@ -3,10 +3,11 @@ using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; using RotationSolver.Actions.BaseAction; +using RotationSolver.Basic; +using RotationSolver.Basic.Rotations; using RotationSolver.Data; using RotationSolver.Helpers; using RotationSolver.Localization; -using RotationSolver.Rotations.CustomRotation; using System; using System.Linq; @@ -46,7 +47,7 @@ private static BaseStatus[] AllStatus public bool IsTrue(ICustomRotation combo) { - if (Service.ClientState.LocalPlayer == null) return false; + if (Service.Player == null) return false; BattleChara tar = null; if (_action != null) @@ -56,8 +57,8 @@ public bool IsTrue(ICustomRotation combo) } else { - tar = IsTarget ? (BattleChara)Service.TargetManager.Target : Service.ClientState.LocalPlayer; - tar ??= Service.ClientState.LocalPlayer; + tar = IsTarget ? (BattleChara)Service.TargetManager.Target : Service.Player; + tar ??= Service.Player; } if (tar == null) return false; @@ -97,7 +98,7 @@ public bool IsTrue(ICustomRotation combo) break; } - var castName = Service.DataManager.GetExcelSheet().GetRow(tar.CastActionId)?.Name.ToString(); + var castName = Service.GetSheet().GetRow(tar.CastActionId)?.Name.ToString(); result = CastingActionName == castName; break; diff --git a/RotationSolver/Updaters/ActionUpdater.cs b/RotationSolver/Updaters/ActionUpdater.cs index afa2e7408..e2a2cc68e 100644 --- a/RotationSolver/Updaters/ActionUpdater.cs +++ b/RotationSolver/Updaters/ActionUpdater.cs @@ -2,6 +2,7 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using FFXIVClientStructs.FFXIV.Client.Game; using RotationSolver.Actions; +using RotationSolver.Basic; using RotationSolver.Commands; using RotationSolver.Data; using RotationSolver.Helpers; @@ -12,37 +13,18 @@ namespace RotationSolver.Updaters; internal static class ActionUpdater { - public static unsafe float ComboTime => ActionManager.Instance()->Combo.Timer; - public static unsafe ActionID LastComboAction => (ActionID)ActionManager.Instance()->Combo.Action; static DateTime _startCombatTime = DateTime.MinValue; - public static TimeSpan CombatTime - { - get - { - if(_startCombatTime == DateTime.MinValue) return TimeSpan.Zero; - return DateTime.Now - _startCombatTime; - } - } - - static RandomDelay _GCDDelay = new RandomDelay(() => (Service.Configuration.WeaponDelayMin, Service.Configuration.WeaponDelayMax)); - - internal static float WeaponRemain { get; private set; } = 0; - - internal static float WeaponTotal { get; private set; } = 0; - - internal static float WeaponElapsed { get; private set; } = 0; - internal static bool InCombat { get; private set; } = false; + static RandomDelay _GCDDelay = new RandomDelay(() => (Service.Config.WeaponDelayMin, Service.Config.WeaponDelayMax)); - internal static byte AbilityRemainCount { get; private set; } = 0; - - internal static float AbilityRemain { get; private set; } = 0; internal static uint[] BluSlots { get; private set; } = new uint[24]; internal static IAction NextAction { get; private set; } + public static float LastCastingTotal { get; set; } + #if DEBUG internal static Exception exception; #endif @@ -50,14 +32,14 @@ public static TimeSpan CombatTime internal static void UpdateNextAction() { - PlayerCharacter localPlayer = Service.ClientState.LocalPlayer; + PlayerCharacter localPlayer = Service.Player; if (localPlayer == null) return; try { var customRotation = RotationUpdater.RightNowRotation; - if (customRotation?.TryInvoke(out var newAction) ?? false) + if (customRotation?.TryInvoke(out var newAction, out var gcdAction) ?? false) { NextAction = newAction; return; @@ -77,27 +59,35 @@ internal static void UpdateNextAction() internal unsafe static void UpdateActionInfo() { - var last = InCombat; - InCombat = Service.Conditions[ConditionFlag.InCombat]; - if(!last && InCombat) + var last = DataCenter.InCombat; + DataCenter.InCombat = Service.Conditions[ConditionFlag.InCombat]; + if(!last && DataCenter.InCombat) { _startCombatTime = DateTime.Now; } - else if(last && !InCombat) + else if(last && !DataCenter.InCombat) { _startCombatTime = DateTime.MinValue; } + if (_startCombatTime == DateTime.MinValue) + { + DataCenter.CombatTime = 0; + } + else + { + DataCenter.CombatTime = (float)(DateTime.Now - _startCombatTime).TotalSeconds; + } for (int i = 0; i < BluSlots.Length; i++) { BluSlots[i] = ActionManager.Instance()->GetActiveBlueMageActionInSlot(i); } - UPdateMPTimer(); + UpdateMPTimer(); } internal static unsafe void UpdateWeaponTime() { - var player = Service.ClientState.LocalPlayer; + var player = Service.Player; if (player == null) return; var instance = ActionManager.Instance(); @@ -108,37 +98,37 @@ internal static unsafe void UpdateWeaponTime() var weapontotal = instance->GetRecastTime(ActionType.Spell, 11); if (player.IsCasting) weapontotal = Math.Max(weapontotal, castTotal); - WeaponElapsed = instance->GetRecastTimeElapsed(ActionType.Spell, 11); - WeaponRemain = WeaponElapsed == 0 ? player.TotalCastTime - player.CurrentCastTime - : Math.Max(weapontotal - WeaponElapsed, player.TotalCastTime - player.CurrentCastTime); + DataCenter.WeaponElapsed = instance->GetRecastTimeElapsed(ActionType.Spell, 11); + DataCenter.WeaponRemain = DataCenter.WeaponElapsed == 0 ? player.TotalCastTime - player.CurrentCastTime + : Math.Max(weapontotal - DataCenter.WeaponElapsed, player.TotalCastTime - player.CurrentCastTime); //确定读条时间。 - if (WeaponElapsed < 0.3) _lastCastingTotal = castTotal; + if (DataCenter.WeaponElapsed < 0.3) LastCastingTotal = castTotal; //确认能力技的相关信息 - var interval = Service.Configuration.AbilitiesInterval; - if (WeaponRemain < interval || WeaponElapsed == 0) + var interval = Service.Config.AbilitiesInterval; + if (DataCenter.WeaponRemain < interval || DataCenter.WeaponElapsed == 0) { - AbilityRemain = 0; - if (WeaponRemain > 0) + DataCenter.AbilityRemain = 0; + if (DataCenter.WeaponRemain > 0) { - AbilityRemain = WeaponRemain + interval; + DataCenter.AbilityRemain = DataCenter.WeaponRemain + interval; } - AbilityRemainCount = 0; + DataCenter.AbilityRemainCount = 0; } - else if (WeaponRemain < 2 * interval) + else if (DataCenter.WeaponRemain < 2 * interval) { - AbilityRemain = WeaponRemain - interval; - AbilityRemainCount = 1; + DataCenter.AbilityRemain = DataCenter.WeaponRemain - interval; + DataCenter.AbilityRemainCount = 1; } else { - var abilityWhole = (int)(weapontotal / Service.Configuration.AbilitiesInterval - 1); - AbilityRemain = interval - WeaponElapsed % interval; - AbilityRemainCount = (byte)(abilityWhole - (int)(WeaponElapsed / interval)); + var abilityWhole = (int)(weapontotal / Service.Config.AbilitiesInterval - 1); + DataCenter.AbilityRemain = interval - DataCenter.WeaponElapsed % interval; + DataCenter.AbilityRemainCount = (byte)(abilityWhole - (int)(DataCenter.WeaponElapsed / interval)); } - if (weapontotal > 0) WeaponTotal = weapontotal; + if (weapontotal > 0) DataCenter.WeaponTotal = weapontotal; } static uint _lastMP = 0; @@ -148,9 +138,9 @@ internal static unsafe void UpdateWeaponTime() /// internal static float MPUpdateElapsed => (float)(DateTime.Now - _lastMPUpdate).TotalSeconds % 3; - private static void UPdateMPTimer() + private static void UpdateMPTimer() { - var player = Service.ClientState.LocalPlayer; + var player = Service.Player; if (player == null) return; //不是黑魔不考虑啊 @@ -166,7 +156,6 @@ private static void UPdateMPTimer() _lastMP = player.CurrentMp; } - internal static float _lastCastingTotal = 0; internal unsafe static void DoAction() { if (Service.Conditions[ConditionFlag.OccupiedInQuestEvent] @@ -184,27 +173,27 @@ internal unsafe static void DoAction() || ActionManager.Instance()->ActionQueued) return; //GCD - var canUseGCD = WeaponRemain <= Service.Configuration.ActionAhead; + var canUseGCD = DataCenter.WeaponRemain <= Service.Config.ActionAhead; if (_GCDDelay.Delay(canUseGCD)) RSCommands.DoAnAction(true); if (canUseGCD) return; //要超出GCD了,那就不放技能了。 - if (WeaponRemain < Service.Configuration.AbilitiesInterval - || WeaponElapsed < Service.Configuration.AbilitiesInterval) + if (DataCenter.WeaponRemain < Service.Config.AbilitiesInterval + || DataCenter.WeaponElapsed < Service.Config.AbilitiesInterval) { return; } //还在咏唱,就不放技能了。 - if (WeaponElapsed <= _lastCastingTotal) return; + if (DataCenter.WeaponElapsed <= LastCastingTotal) return; //只剩下最后一个能力技了,然后卡最后! - if (WeaponRemain < 2 * Service.Configuration.AbilitiesInterval) + if (DataCenter.WeaponRemain < 2 * Service.Config.AbilitiesInterval) { - if (WeaponRemain > Service.Configuration.AbilitiesInterval + Service.Configuration.ActionAhead) return; + if (DataCenter.WeaponRemain > Service.Config.AbilitiesInterval + Service.Config.ActionAhead) return; RSCommands.DoAnAction(false); } - else if ((WeaponElapsed - _lastCastingTotal) % Service.Configuration.AbilitiesInterval <= Service.Configuration.ActionAhead) + else if ((DataCenter.WeaponElapsed - LastCastingTotal) % Service.Config.AbilitiesInterval <= Service.Config.ActionAhead) { RSCommands.DoAnAction(false); } diff --git a/RotationSolver/Updaters/MacroUpdater.cs b/RotationSolver/Updaters/MacroUpdater.cs index fec2a0299..460e92fa0 100644 --- a/RotationSolver/Updaters/MacroUpdater.cs +++ b/RotationSolver/Updaters/MacroUpdater.cs @@ -1,6 +1,7 @@ using Dalamud.Game.ClientState.Objects.Types; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Lumina.Excel.GeneratedSheets; +using RotationSolver.Basic; using RotationSolver.Configuration; using RotationSolver.Data; using System.Collections.Generic; @@ -9,13 +10,12 @@ namespace RotationSolver.Updaters; internal static class MacroUpdater { - internal static readonly Queue Macros = new Queue(); internal static MacroItem DoingMacro; public static void UpdateMacro() { //如果没有有正在运行的宏,弄一个出来 - if (DoingMacro == null && Macros.TryDequeue(out var macro)) + if (DoingMacro == null && DataCenter.Macros.TryDequeue(out var macro)) { DoingMacro = macro; } diff --git a/RotationSolver/Updaters/MajorUpdater.cs b/RotationSolver/Updaters/MajorUpdater.cs index 87fe980fd..e1cdbfea5 100644 --- a/RotationSolver/Updaters/MajorUpdater.cs +++ b/RotationSolver/Updaters/MajorUpdater.cs @@ -1,6 +1,8 @@ using Dalamud.Game; using Dalamud.Logging; +using RotationSolver.Basic; using RotationSolver.Commands; +using RotationSolver.SigReplacers; using System; using System.Collections.Generic; using System.Threading; @@ -11,7 +13,7 @@ namespace RotationSolver.Updaters; internal static class MajorUpdater { - private static bool IsValid => Service.Conditions.Any() && Service.ClientState.LocalPlayer != null; + private static bool IsValid => Service.Conditions.Any() && Service.Player != null; //#if DEBUG // private static readonly Dictionary _valus = new Dictionary(); @@ -48,9 +50,16 @@ private static void FrameworkUpdate(Framework framework) MacroUpdater.UpdateMacro(); - if (Service.Configuration.UseWorkTask) + if (Service.Config.UseWorkTask) { - Task.Run(UpdateWork); + try + { + Task.Run(UpdateWork); + } + catch (Exception ex) + { + PluginLog.Error(ex, "Worker Exception"); + } } else { @@ -61,7 +70,6 @@ private static void FrameworkUpdate(Framework framework) public static void Enable() { Service.Framework.Update += FrameworkUpdate; - MovingUpdater.Enable(); } static bool _work; @@ -71,22 +79,15 @@ private static void UpdateWork() if (_work) return; _work = true; - try - { - PreviewUpdater.UpdateCastBarState(); - TargetUpdater.UpdateTarget(); - ActionUpdater.UpdateActionInfo(); + PreviewUpdater.UpdateCastBarState(); + TargetUpdater.UpdateTarget(); + ActionUpdater.UpdateActionInfo(); - RotationUpdater.UpdateRotation(); + RotationUpdater.UpdateRotation(); - TimeLineUpdater.UpdateTimelineAction(); - ActionUpdater.UpdateNextAction(); - RSCommands.UpdateRotationState(); - } - catch (Exception ex) - { - PluginLog.Error(ex, "Worker Exception"); - } + TimeLineUpdater.UpdateTimelineAction(); + ActionUpdater.UpdateNextAction(); + RSCommands.UpdateRotationState(); _work = false; } @@ -95,6 +96,5 @@ public static void Dispose() { Service.Framework.Update -= FrameworkUpdate; PreviewUpdater.Dispose(); - MovingUpdater.Dispose(); } } diff --git a/RotationSolver/Updaters/PreviewUpdater.cs b/RotationSolver/Updaters/PreviewUpdater.cs index 556b8b169..caa1c0a2e 100644 --- a/RotationSolver/Updaters/PreviewUpdater.cs +++ b/RotationSolver/Updaters/PreviewUpdater.cs @@ -10,6 +10,7 @@ using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Component.GUI; +using RotationSolver.Basic; using RotationSolver.Commands; using RotationSolver.Data; using RotationSolver.Helpers; @@ -33,7 +34,7 @@ internal static void UpdatePreview() private static void UpdateEntry() { var showStr = RSCommands.EntryString; - if (Service.Configuration.ShowInfoOnDtr && !string.IsNullOrEmpty(showStr)) + if (Service.Config.ShowInfoOnDtr && !string.IsNullOrEmpty(showStr)) { try { @@ -63,11 +64,11 @@ private static void UpdateEntry() static bool _canMove; static bool _isTarNoNeedCast; static RandomDelay _tarStopCastDelay = new RandomDelay(() => - (Service.Configuration.StopCastingDelayMin, Service.Configuration.StopCastingDelayMax)); + (Service.Config.StopCastingDelayMin, Service.Config.StopCastingDelayMax)); internal static void UpdateCastBarState() { - var tardead = Service.Configuration.UseStopCasting ? - Service.ObjectTable.SearchById(Service.ClientState.LocalPlayer.CastTargetObjectId) is BattleChara b + var tardead = Service.Config.UseStopCasting ? + Service.ObjectTable.SearchById(Service.Player.CastTargetObjectId) is BattleChara b && (b is PlayerCharacter ? b.HasStatus(false, StatusID.Raise) : b.CurrentHp == 0 ): false; _isTarNoNeedCast = _tarStopCastDelay.Delay(tardead); @@ -75,9 +76,9 @@ internal static void UpdateCastBarState() && !Service.Conditions[Dalamud.Game.ClientState.Conditions.ConditionFlag.Casting]; //For lock - var specialStatus = Service.ClientState.LocalPlayer.HasStatus(true, StatusID.PhantomFlurry, StatusID.TenChiJin); + var specialStatus = Service.Player.HasStatus(true, StatusID.PhantomFlurry, StatusID.TenChiJin); - MovingUpdater.IsMoving = _canMove = specialStatus ? false : canMove; + MovingController.IsMoving = _canMove = specialStatus ? false : canMove; } static bool _showCanMove; @@ -90,7 +91,7 @@ private static unsafe void UpdateCastBar() UIState.Instance()->Hotbar.CancelCast(); } - var nowMove = _canMove && Service.Configuration.CastingDisplay; + var nowMove = _canMove && Service.Config.CastingDisplay; if (nowMove == _showCanMove) return; _showCanMove = nowMove; @@ -114,11 +115,11 @@ private unsafe static void UpdateHightLight() HigglightAtionBar((slot, hot) => { - return Service.Configuration.TeachingMode && IsActionSlotRight(slot, hot, _higtLightId); + return Service.Config.TeachingMode && IsActionSlotRight(slot, hot, _higtLightId); }); } - internal static unsafe void PulseAtionBar(uint actionID) + internal static unsafe void PulseActionBar(uint actionID) { LoopAllSlotBar((bar, hotbar, index) => { @@ -132,7 +133,7 @@ private unsafe static bool IsActionSlotRight(ActionBarSlot* slot, HotBarSlot* ho if (hot->IconTypeB != HotbarSlotType.CraftAction && hot->IconTypeB != HotbarSlotType.Action) return false; if (slot->ActionId == (uint)IconReplacer.KeyActionID) return false; - return Service.IconReplacer.OriginalHook((uint)slot->ActionId) == actionID; + return Service.GetAdjustedActionId((uint)slot->ActionId) == actionID; } const int ActionBarSlotsCount = 12; @@ -234,7 +235,7 @@ private static unsafe void HigglightAtionBar(ActionBarPredicate shouldShow = nul highLightPtr->AtkResNode.AddBlue = 40; //Change Color - var color = Service.Configuration.TeachingModeColor; + var color = Service.Config.TeachingModeColor; highLightPtr->AtkResNode.MultiplyRed = (byte)(color.X * 100); highLightPtr->AtkResNode.MultiplyGreen = (byte)(color.Y * 100); highLightPtr->AtkResNode.MultiplyBlue = (byte)(color.Z * 100); diff --git a/RotationSolver/Updaters/RotationUpdater.cs b/RotationSolver/Updaters/RotationUpdater.cs index 25d2824ae..52181964e 100644 --- a/RotationSolver/Updaters/RotationUpdater.cs +++ b/RotationSolver/Updaters/RotationUpdater.cs @@ -1,8 +1,9 @@ using Lumina.Data.Parsing; using RotationSolver.Actions; using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Basic.Rotations; using RotationSolver.Data; -using RotationSolver.Rotations.CustomRotation; using System; using System.Collections.Generic; using System.IO; @@ -90,8 +91,8 @@ private static ICustomRotation[] CreateRotationSet(ICustomRotation[] combos) static string _rotationName; public static void UpdateRotation() { - var nowJob = (ClassJobID)Service.ClientState.LocalPlayer.ClassJob.Id; - Service.Configuration.RotationChoices.TryGetValue((uint)nowJob, out var newName); + var nowJob = (ClassJobID)Service.Player.ClassJob.Id; + Service.Config.RotationChoices.TryGetValue((uint)nowJob, out var newName); if (_job == nowJob && _rotationName == newName) return; @@ -111,7 +112,7 @@ public static void UpdateRotation() internal static ICustomRotation GetChooseRotation(CustomRotationGroup group, string name) { var rotation = group.rotations.FirstOrDefault(r => r.RotationName == name); - rotation ??= group.rotations.FirstOrDefault(r => r.GetType().GetCustomAttribute() != null); + //rotation ??= group.rotations.FirstOrDefault(r => r.GetType().GetCustomAttribute() != null); rotation ??= group.rotations.FirstOrDefault(r => r.GetType().Name.Contains("Default")); rotation ??= group.rotations.FirstOrDefault(); return rotation; diff --git a/RotationSolver/Updaters/SocialUpdater.cs b/RotationSolver/Updaters/SocialUpdater.cs index f1613281b..09d95e35f 100644 --- a/RotationSolver/Updaters/SocialUpdater.cs +++ b/RotationSolver/Updaters/SocialUpdater.cs @@ -2,6 +2,7 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using FFXIVClientStructs.FFXIV.Client.UI; using Lumina.Excel.GeneratedSheets; +using RotationSolver.Basic; using RotationSolver.Commands; using RotationSolver.Data; using RotationSolver.Helpers; @@ -41,8 +42,8 @@ static bool CanSocial || Service.Conditions[ConditionFlag.BetweenAreas] || Service.Conditions[ConditionFlag.BetweenAreas51]) return false; - if(Service.ClientState.LocalPlayer == null) return false; - if (!Service.ClientState.LocalPlayer.IsTargetable()) return false; + if(Service.Player == null) return false; + if (!Service.Player.IsTargetable()) return false; return Service.Conditions[ConditionFlag.BoundByDuty]; } @@ -58,16 +59,16 @@ static async void DutyState_DutyCompleted(object sender, ushort e) { RSCommands.CancelState(); - if (TargetUpdater.PartyMembers.Count() < 2) return; + if (DataCenter.PartyMembers.Count() < 2) return; await Task.Delay(new Random().Next(4000, 6000)); - Service.Configuration.DutyEnd.AddMacro(); + Service.Config.DutyEnd.AddMacro(); } static void DutyState_DutyStarted(object sender, ushort e) { - var territory = Service.DataManager.GetExcelSheet().GetRow(e); + var territory = Service.GetSheet().GetRow(e); if (territory?.ContentFinderCondition?.Value?.HighEndDuty ?? false) { var str = territory.PlaceName?.Value?.Name.ToString() ?? "High-end Duty"; @@ -83,7 +84,7 @@ internal static void Disable() internal static async void UpdateSocial() { - if (ActionUpdater.InCombat || TargetUpdater.PartyMembers.Count() < 2) return; + if (DataCenter.InCombat || DataCenter.PartyMembers.Count() < 2) return; if (CanSaying && CanSocial) { CanSaying = false; @@ -92,7 +93,7 @@ internal static async void UpdateSocial() #if DEBUG Service.ChatGui.PrintError("Macro now."); #endif - Service.Configuration.DutyStart.AddMacro(); + Service.Config.DutyStart.AddMacro(); await Task.Delay(new Random().Next(1000, 1500)); SayHelloToAuthor(); } @@ -100,7 +101,7 @@ internal static async void UpdateSocial() private static async void SayHelloToAuthor() { - var author = TargetUpdater.AllianceMembers.OfType() + var author = DataCenter.AllianceMembers.OfType() .FirstOrDefault(c => #if DEBUG #else @@ -110,7 +111,7 @@ private static async void SayHelloToAuthor() if (author != null) { - while(!author.IsTargetable() && !ActionUpdater.InCombat) + while(!author.IsTargetable() && !DataCenter.InCombat) { await Task.Delay(100); } diff --git a/RotationSolver/Updaters/TargetUpdater_Friends.cs b/RotationSolver/Updaters/TargetUpdater_Friends.cs index b9be3e98c..6980d94fd 100644 --- a/RotationSolver/Updaters/TargetUpdater_Friends.cs +++ b/RotationSolver/Updaters/TargetUpdater_Friends.cs @@ -1,136 +1,57 @@ using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; -using FFXIVClientStructs.FFXIV.Client.Game.Group; -using FFXIVClientStructs.FFXIV.Client.UI; -using RotationSolver.Commands; +using RotationSolver.Basic; using RotationSolver.Data; using RotationSolver.Helpers; -using RotationSolver.Localization; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; using System.Numerics; -using System.Security.Cryptography; -using System.Text; namespace RotationSolver.Updaters; internal static partial class TargetUpdater { - /// - /// 小队成员们 - /// - public static IEnumerable PartyMembers { get; private set; } = new PlayerCharacter[0]; - /// - /// 团队成员们 - /// - internal static IEnumerable AllianceMembers { get; private set; } = new PlayerCharacter[0]; - - /// - /// 小队坦克们 - /// - internal static IEnumerable PartyTanks { get; private set; } = new PlayerCharacter[0]; - /// - /// 小队治疗们 - /// - internal static IEnumerable PartyHealers { get; private set; } = new PlayerCharacter[0]; - - /// - /// 团队坦克们 - /// - internal static IEnumerable AllianceTanks { get; private set; } = new PlayerCharacter[0]; - - internal static ObjectListDelay DeathPeopleAll { get; } = new ( - ()=>(Service.Configuration.DeathDelayMin, Service.Configuration.DeathDelayMax)); - - internal static ObjectListDelay DeathPeopleParty { get; } = new( - () => (Service.Configuration.DeathDelayMin, Service.Configuration.DeathDelayMax)); - - internal static ObjectListDelay WeakenPeople { get; } = new( - () => (Service.Configuration.WeakenDelayMin, Service.Configuration.WeakenDelayMax)); - - internal static ObjectListDelay DyingPeople { get; } = new( - () => (Service.Configuration.WeakenDelayMin, Service.Configuration.WeakenDelayMax)); - /// - /// 小队成员HP - /// - internal static IEnumerable PartyMembersHP { get; private set; } = new float[0]; - /// - /// 小队成员最小的HP - /// - internal static float PartyMembersMinHP { get; private set; } = 0; - /// - /// 小队成员平均HP - /// - internal static float PartyMembersAverHP { get; private set; } = 0; - /// - /// 小队成员HP差值 - /// - internal static float PartyMembersDifferHP { get; private set; } = 0; - - - [EditorBrowsable(EditorBrowsableState.Never)] - internal static bool CanHealAreaAbility { get; private set; } = false; - - [EditorBrowsable(EditorBrowsableState.Never)] - internal static bool CanHealAreaSpell { get; private set; } = false; - - [EditorBrowsable(EditorBrowsableState.Never)] - internal static bool CanHealSingleAbility { get; private set; } = false; - - [EditorBrowsable(EditorBrowsableState.Never)] - internal static bool CanHealSingleSpell { get; private set; } = false; - - internal static unsafe bool HavePet { get; private set; } - - internal static unsafe bool HaveCompanion => (IntPtr)Service.CharacterManager->LookupBuddyByOwnerObject(Service.Player) != IntPtr.Zero; - - internal static bool HPNotFull { get; private set; } = false; - private static IEnumerable GetPartyMembers(IEnumerable allTargets) { var party = Service.PartyList.Select(p => p.GameObject).OfType().Where(b => b.DistanceToPlayer() <= 30); - if (!party.Any()) party = new BattleChara[] { Service.ClientState.LocalPlayer }; + if (!party.Any()) party = new BattleChara[] { Service.Player }; return party.Union(allTargets.Where(obj => obj.SubKind == 9)); } private unsafe static void UpdateFriends(IEnumerable allTargets) { - PartyMembers = GetPartyMembers(allTargets); - AllianceMembers = allTargets.OfType(); + DataCenter.PartyMembers = GetPartyMembers(allTargets); + DataCenter.AllianceMembers = allTargets.OfType(); - var mayPet = allTargets.OfType().Where(npc => npc.OwnerId == Service.ClientState.LocalPlayer.ObjectId); - HavePet = mayPet.Any(npc => npc.BattleNpcKind == BattleNpcSubKind.Pet); + var mayPet = allTargets.OfType().Where(npc => npc.OwnerId == Service.Player.ObjectId); + DataCenter.HasPet = mayPet.Any(npc => npc.BattleNpcKind == BattleNpcSubKind.Pet); - PartyTanks = PartyMembers.GetJobCategory(JobRole.Tank); - PartyHealers = PartyMembers.GetJobCategory(JobRole.Healer); - AllianceTanks = AllianceMembers.GetJobCategory(JobRole.Tank); + DataCenter.PartyTanks = DataCenter.PartyMembers.GetJobCategory(JobRole.Tank); + DataCenter.PartyHealers = DataCenter.PartyMembers.GetJobCategory(JobRole.Healer); + DataCenter.AllianceTanks = DataCenter.AllianceMembers.GetJobCategory(JobRole.Tank); - var deathAll = AllianceMembers.GetDeath(); - var deathParty = PartyMembers.GetDeath(); + var deathAll = DataCenter.AllianceMembers.GetDeath(); + var deathParty = DataCenter.PartyMembers.GetDeath(); MaintainDeathPeople(ref deathAll, ref deathParty); - DeathPeopleAll.Delay(deathAll); - DeathPeopleParty.Delay(deathParty); + DataCenter.DeathPeopleAll.Delay(deathAll); + DataCenter.DeathPeopleParty.Delay(deathParty); - WeakenPeople.Delay(PartyMembers.Where(p => p.StatusList.Any(StatusHelper.CanDispel))); - DyingPeople.Delay(WeakenPeople.Where(p => p.StatusList.Any(StatusHelper.IsDangerous))); + DataCenter.WeakenPeople.Delay(DataCenter.PartyMembers.Where(p => p.StatusList.Any(StatusHelper.CanDispel))); + DataCenter.DyingPeople.Delay(DataCenter.WeakenPeople.Where(p => p.StatusList.Any(StatusHelper.IsDangerous))); - PartyMembersHP = PartyMembers.Select(ObjectHelper.GetHealthRatio).Where(r => r > 0); - if (PartyMembersHP.Any()) + DataCenter.PartyMembersHP = DataCenter.PartyMembers.Select(ObjectHelper.GetHealthRatio).Where(r => r > 0); + if (DataCenter.PartyMembersHP.Any()) { - PartyMembersAverHP = PartyMembersHP.Average(); - PartyMembersDifferHP = (float)Math.Sqrt(PartyMembersHP.Average(d => Math.Pow(d - PartyMembersAverHP, 2))); + DataCenter.PartyMembersAverHP = DataCenter.PartyMembersHP.Average(); + DataCenter.PartyMembersDifferHP = (float)Math.Sqrt(DataCenter.PartyMembersHP.Average(d => Math.Pow(d - DataCenter.PartyMembersAverHP, 2))); } else { - PartyMembersAverHP = PartyMembersDifferHP = 0; + DataCenter.PartyMembersAverHP = DataCenter.PartyMembersDifferHP = 0; } - UpdateCanHeal(Service.ClientState.LocalPlayer); + UpdateCanHeal(Service.Player); } static RandomDelay _healDelay1 = new RandomDelay(GetHealRange); @@ -138,7 +59,7 @@ private unsafe static void UpdateFriends(IEnumerable allTargets) static RandomDelay _healDelay3 = new RandomDelay(GetHealRange); static RandomDelay _healDelay4 = new RandomDelay(GetHealRange); - static (float min, float max) GetHealRange() => (Service.Configuration.HealDelayMin, Service.Configuration.HealDelayMax); + static (float min, float max) GetHealRange() => (Service.Config.HealDelayMin, Service.Config.HealDelayMax); static void UpdateCanHeal(PlayerCharacter player) { @@ -147,34 +68,34 @@ static void UpdateCanHeal(PlayerCharacter player) var hotSubSingle = job.GetHealingOfTimeSubtractSingle(); var singleAbility = ShouldHealSingle(StatusHelper.SingleHots, job.GetHealSingleAbility(), hotSubSingle); var singleSpell = ShouldHealSingle(StatusHelper.SingleHots, job.GetHealSingleSpell(), hotSubSingle); - CanHealSingleAbility = singleAbility > 0; - CanHealSingleSpell = singleSpell > 0; - CanHealAreaAbility = singleAbility > 2; - CanHealAreaSpell = singleSpell > 2; + DataCenter.CanHealSingleAbility = singleAbility > 0; + DataCenter.CanHealSingleSpell = singleSpell > 0; + DataCenter.CanHealAreaAbility = singleAbility > 2; + DataCenter.CanHealAreaSpell = singleSpell > 2; - if (PartyMembers.Count() > 2) + if (DataCenter.PartyMembers.Count() > 2) { //TODO:少了所有罩子类技能 var ratio = GetHealingOfTimeRatio(player, StatusHelper.AreaHots) * job.GetHealingOfTimeSubtractArea(); - if(!CanHealAreaAbility) - CanHealAreaAbility = PartyMembersDifferHP < Service.Configuration.HealthDifference && PartyMembersAverHP < ConfigurationHelper.GetHealAreaAbility(job) - ratio; + if(!DataCenter.CanHealAreaAbility) + DataCenter.CanHealAreaAbility = DataCenter.PartyMembersDifferHP < Service.Config.HealthDifference && DataCenter.PartyMembersAverHP < ConfigurationHelper.GetHealAreaAbility(job) - ratio; - if (!CanHealAreaSpell) - CanHealAreaSpell = PartyMembersDifferHP < Service.Configuration.HealthDifference && PartyMembersAverHP < ConfigurationHelper.GetHealAreafSpell(job) - ratio; + if (!DataCenter.CanHealAreaSpell) + DataCenter.CanHealAreaSpell = DataCenter.PartyMembersDifferHP < Service.Config.HealthDifference && DataCenter.PartyMembersAverHP < ConfigurationHelper.GetHealAreaSpell(job) - ratio; } //Delay - CanHealSingleAbility = _healDelay1.Delay(CanHealSingleAbility); - CanHealSingleSpell = _healDelay2.Delay(CanHealSingleSpell); - CanHealAreaAbility = _healDelay3.Delay(CanHealAreaAbility); - CanHealAreaSpell = _healDelay4.Delay(CanHealAreaSpell); + DataCenter.CanHealSingleAbility = _healDelay1.Delay(DataCenter.CanHealSingleAbility); + DataCenter.CanHealSingleSpell = _healDelay2.Delay(DataCenter.CanHealSingleSpell); + DataCenter.CanHealAreaAbility = _healDelay3.Delay(DataCenter.CanHealAreaAbility); + DataCenter.CanHealAreaSpell = _healDelay4.Delay(DataCenter.CanHealAreaSpell); - PartyMembersMinHP = PartyMembersHP.Any() ? PartyMembersHP.Min() : 0; - HPNotFull = PartyMembersMinHP < 1; + DataCenter.PartyMembersMinHP = DataCenter.PartyMembersHP.Any() ? DataCenter.PartyMembersHP.Min() : 0; + DataCenter.HPNotFull = DataCenter.PartyMembersMinHP < 1; } - static int ShouldHealSingle(StatusID[] hotStatus, float healSingle, float hotSubSingle) => PartyMembers.Count(p => + static int ShouldHealSingle(StatusID[] hotStatus, float healSingle, float hotSubSingle) => DataCenter.PartyMembers.Count(p => { var ratio = GetHealingOfTimeRatio(p, hotStatus); @@ -219,6 +140,4 @@ private static IEnumerable FilterForDeath(IEnumerable return loc == b.Position; }); - - } diff --git a/RotationSolver/Updaters/TargetUpdater_Hostile.cs b/RotationSolver/Updaters/TargetUpdater_Hostile.cs index 9de9216ee..324f1c3ed 100644 --- a/RotationSolver/Updaters/TargetUpdater_Hostile.cs +++ b/RotationSolver/Updaters/TargetUpdater_Hostile.cs @@ -6,6 +6,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Fate; using FFXIVClientStructs.FFXIV.Client.UI; using Lumina.Excel.GeneratedSheets; +using RotationSolver.Basic; using RotationSolver.Data; using RotationSolver.Helpers; using RotationSolver.SigReplacers; @@ -18,53 +19,16 @@ namespace RotationSolver.Updaters; internal static partial class TargetUpdater { - /// - /// 敌人 - /// - internal static ObjectListDelay HostileTargets { get; } = new ObjectListDelay( - () => (Service.Configuration.HostileDelayMin, Service.Configuration.HostileDelayMax)); - internal static IEnumerable AllHostileTargets { get; private set; } = new BattleChara[0]; - - internal static IEnumerable TarOnMeTargets { get; private set; } = new BattleChara[0]; - - internal static ObjectListDelay CanInterruptTargets { get;} = new ObjectListDelay( - () => (Service.Configuration.InterruptDelayMin, Service.Configuration.InterruptDelayMax)); - internal static bool HasHostilesInRange { get; private set; } = false; - - internal static bool IsHostileCastingAOE { get; private set; } = false; - - internal static bool IsHostileCastingToTank { get; private set; } = false; - - internal static unsafe ushort FateId - { - get - { - try - { - if(Service.Configuration.ChangeTargetForFate && (IntPtr)FateManager.Instance() != IntPtr.Zero - && (IntPtr)FateManager.Instance()->CurrentFate != IntPtr.Zero - && Service.ClientState.LocalPlayer.Level <= FateManager.Instance()->CurrentFate->MaxLevel) - { - return FateManager.Instance()->CurrentFate->FateId; - } - } - catch(Exception ex) - { - PluginLog.Error(ex.StackTrace); - } - return 0; - } - } private static float JobRange { get { float radius = 25; - if(Service.ClientState.LocalPlayer == null) return radius; - switch (Service.DataManager.GetExcelSheet().GetRow( - Service.ClientState.LocalPlayer.ClassJob.Id).GetJobRole()) + if(Service.Player == null) return radius; + switch (Service.GetSheet().GetRow( + Service.Player.ClassJob.Id).GetJobRole()) { case JobRole.Tank: case JobRole.Melee: @@ -77,7 +41,7 @@ private static float JobRange private unsafe static void UpdateHostileTargets(IEnumerable allTargets) { - AllHostileTargets = allTargets.Where(b => + DataCenter.AllHostileTargets = allTargets.Where(b => { if (!b.IsNPCEnemy()) return false; @@ -88,7 +52,7 @@ private unsafe static void UpdateHostileTargets(IEnumerable allTarg if (b.StatusList.Any(StatusHelper.IsInvincible)) return false; - if (Service.Configuration.OnlyAttackInView) + if (Service.Config.OnlyAttackInView) { if(!Service.GameGui.WorldToScreen(b.Position, out _)) return false; } @@ -96,24 +60,24 @@ private unsafe static void UpdateHostileTargets(IEnumerable allTarg return true; }); - HostileTargets.Delay(GetHostileTargets(AllHostileTargets)); + DataCenter.HostileTargets.Delay(GetHostileTargets(DataCenter.AllHostileTargets)); - CanInterruptTargets.Delay(HostileTargets.Where(ObjectHelper.CanInterrupt)); + DataCenter.CanInterruptTargets.Delay(DataCenter.HostileTargets.Where(ObjectHelper.CanInterrupt)); - TarOnMeTargets = HostileTargets.Where(tar => tar.TargetObjectId == Service.ClientState.LocalPlayer.ObjectId); + DataCenter.TarOnMeTargets = DataCenter.HostileTargets.Where(tar => tar.TargetObjectId == Service.Player.ObjectId); - HasHostilesInRange = TargetFilter.GetObjectInRadius(HostileTargets, JobRange).Any(); + DataCenter.HasHostilesInRange = TargetFilter.GetObjectInRadius(DataCenter.HostileTargets, JobRange).Any(); - if (HostileTargets.Count() == 1) + if (DataCenter.HostileTargets.Count() == 1) { - var tar = HostileTargets.FirstOrDefault(); + var tar = DataCenter.HostileTargets.FirstOrDefault(); - IsHostileCastingToTank = IsHostileCastingTank(tar); - IsHostileCastingAOE = IsHostileCastingArea(tar); + DataCenter.IsHostileCastingToTank = IsHostileCastingTank(tar); + DataCenter.IsHostileCastingAOE = IsHostileCastingArea(tar); } else { - IsHostileCastingToTank = IsHostileCastingAOE = false; + DataCenter.IsHostileCastingToTank = DataCenter.IsHostileCastingAOE = false; } } @@ -126,13 +90,13 @@ private static IEnumerable GetHostileTargets(IEnumerable { if (ids.Contains(t.ObjectId)) return true; - if (t.TargetObject == Service.ClientState.LocalPlayer - || t.TargetObject?.OwnerId == Service.ClientState.LocalPlayer.ObjectId) return true; + if (t.TargetObject == Service.Player + || t.TargetObject?.OwnerId == Service.Player.ObjectId) return true; //Remove other's treasure. if (t.IsOthersPlayers()) return false; @@ -152,7 +116,7 @@ private static IEnumerable GetHostileTargets(IEnumerable check if (!(h.TotalCastTime > 2.5 && CooldownHelper.RecastAfterGCD(last, 2) && !CooldownHelper.RecastAfterGCD(last, 0))) return false; - var action = Service.DataManager.GetExcelSheet().GetRow(h.CastActionId); + var action = Service.GetSheet().GetRow(h.CastActionId); return check?.Invoke(action) ?? false; } return false; diff --git a/RotationSolver/Updaters/TargetUpdater_Major.cs b/RotationSolver/Updaters/TargetUpdater_Major.cs index 96c24d14f..e919f2477 100644 --- a/RotationSolver/Updaters/TargetUpdater_Major.cs +++ b/RotationSolver/Updaters/TargetUpdater_Major.cs @@ -1,4 +1,5 @@ using Dalamud.Game.ClientState.Objects.Types; +using RotationSolver.Basic; using RotationSolver.Helpers; using System; using System.Collections.Generic; @@ -10,11 +11,10 @@ namespace RotationSolver.Updaters { internal static partial class TargetUpdater { - internal static IEnumerable AllTargets { get; private set; } internal static void UpdateTarget() { - AllTargets = Service.ObjectTable.GetObjectInRadius(30); - var battles = AllTargets.OfType(); + DataCenter.AllTargets = Service.ObjectTable.GetObjectInRadius(30); + var battles = DataCenter.AllTargets.OfType(); UpdateHostileTargets(battles); UpdateFriends(battles); UpdateNamePlate(Service.ObjectTable.OfType()); diff --git a/RotationSolver/Updaters/TimeLineUpdater.cs b/RotationSolver/Updaters/TimeLineUpdater.cs index 1e73474ad..9001a80e5 100644 --- a/RotationSolver/Updaters/TimeLineUpdater.cs +++ b/RotationSolver/Updaters/TimeLineUpdater.cs @@ -1,6 +1,8 @@ using ImGuiNET; using Newtonsoft.Json; using RotationSolver.Actions; +using RotationSolver.Basic; +using RotationSolver.Basic.Data; using RotationSolver.Helpers; using RotationSolver.SigReplacers; using RotationSolver.Timeline; @@ -16,11 +18,11 @@ internal class TimeLineUpdater static string _timelineFolder; static IEnumerable _conditionSet; - public static MajorConditionSet RightSet => _conditionSet?.ElementAtOrDefault(Service.Configuration.TimelineIndex); + public static MajorConditionSet RightSet => _conditionSet?.ElementAtOrDefault(Service.Config.TimelineIndex); public static string[] ConditionSetsName => _conditionSet?.Select(s => s.Name).ToArray() ?? new string[0]; - public static IBaseAction TimeLineAction { get; private set; } + public static void UpdateTimelineAction() { if (_conditionSet == null) return; @@ -32,6 +34,7 @@ public static void UpdateTimelineAction() var set = RightSet; if (set == null) return; + bool find = false; foreach (var conditionPair in set.Conditions) { var nextAct = allActions.FirstOrDefault(a => a.ID == conditionPair.Key); @@ -39,9 +42,14 @@ public static void UpdateTimelineAction() if (!conditionPair.Value.IsTrue(customRotation)) continue; - TimeLineAction = nextAct; + DataCenter.TimeLineAction = nextAct; + find = true; break; } + if (!find) + { + DataCenter.TimeLineAction = null; + } } public static void Enable(string folder) @@ -97,16 +105,16 @@ public static void DrawHeader() } var combos = ConditionSetsName; - if (combos != null && combos.Length > Service.Configuration.TimelineIndex) + if (combos != null && combos.Length > Service.Config.TimelineIndex) { - ImGui.SetNextItemWidth(ImGui.CalcTextSize(combos[Service.Configuration.TimelineIndex]).X + 30); + ImGui.SetNextItemWidth(ImGui.CalcTextSize(combos[Service.Config.TimelineIndex]).X + 30); } else { ImGui.SetNextItemWidth(30); } - ImGui.Combo("##MajorConditionCombo", ref Service.Configuration.TimelineIndex, combos, combos.Length); + ImGui.Combo("##MajorConditionCombo", ref Service.Config.TimelineIndex, combos, combos.Length); ImGui.SameLine(); diff --git a/RotationSolver/Windows/ImGuiHelper.cs b/RotationSolver/Windows/ImGuiHelper.cs new file mode 100644 index 000000000..83d246f1c --- /dev/null +++ b/RotationSolver/Windows/ImGuiHelper.cs @@ -0,0 +1,712 @@ +using Dalamud.Interface; +using Dalamud.Interface.Colors; +using Dalamud.Interface.Components; +using Dalamud.Utility; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.Game; +using ImGuiNET; +using RotationSolver.Attributes; +using RotationSolver.Basic; +using RotationSolver.Basic.Rotations; +using RotationSolver.Commands; +using RotationSolver.Data; +using RotationSolver.Localization; +using RotationSolver.Rotations.CustomRotation; +using RotationSolver.Windows.RotationConfigWindow; +using System.ComponentModel; +using System.Numerics; +using System.Reflection; +using RotationSolver.Actions.BaseAction; +using RotationSolver.Actions; +using RotationSolver.Configuration; +using RotationSolver.Configuration.RotationConfig; + +namespace RotationSolver.Helpers; + +internal static class ImGuiHelper +{ + const ImGuiWindowFlags TOOLTIP_FLAG = + ImGuiWindowFlags.Tooltip | + ImGuiWindowFlags.NoMove | + ImGuiWindowFlags.NoSavedSettings | + ImGuiWindowFlags.NoBringToFrontOnFocus | + ImGuiWindowFlags.NoDecoration | + ImGuiWindowFlags.NoInputs | + ImGuiWindowFlags.AlwaysAutoResize; + + public static void DrawTooltip(Action act, string id) + { + if (act == null) return; + ImGui.SetWindowPos(id, ImGui.GetIO().MousePos); + if (ImGui.Begin(id, TOOLTIP_FLAG)) + { + act(); + ImGui.End(); + } + } + public static void DrawEnableTexture(this T texture, bool isSelected, Action selected, + Action showToolTip = null, Action> additionalHeader = null, + Action otherThing = null) where T : class, ITexture + { + showToolTip ??= text => + { + if (!string.IsNullOrEmpty(text)) ImGui.SetTooltip(text); + }; + + ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(3f, 3f)); + + ImGui.Columns(2, texture.Name, false); + + var t = texture.GetTexture(); + + ImGui.SetColumnWidth(0, t.Width + 5); + + ImGui.Image(t.ImGuiHandle, new Vector2(t.Width, t.Height)); + + var desc = texture?.Description; + if (ImGui.IsItemHovered()) + { + showToolTip(desc); + if (ImGui.IsMouseDown(ImGuiMouseButton.Left)) + { + selected?.Invoke(); + } + } + ImGui.NextColumn(); + + bool enable = false; + + if (isSelected) ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow); + enable = texture.IsEnabled; + if (ImGui.Checkbox($"{texture.Name}##{texture.Name}", ref enable)) + { + texture.IsEnabled = enable; + Service.Config.Save(); + } + if (isSelected) ImGui.PopStyleColor(); + + if (ImGui.IsItemHovered()) + { + showToolTip(desc); + } + + additionalHeader?.Invoke(showToolTip); + + if (enable) + { + ImGui.Indent(20); + ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(1f, 1f)); + otherThing?.Invoke(); + ImGui.PopStyleVar(); + ImGui.Unindent(20); + } + ImGui.Columns(1); + + ImGui.PopStyleVar(); + } + + public static bool IconButton(FontAwesomeIcon icon, string name) + { + ImGui.PushFont(UiBuilder.IconFont); + var result = ImGui.Button($"{icon.ToIconString()}##{name}"); + ImGui.PopFont(); + return result; + + //ImGuiComponents.IconButton(icon) + } + + public static void HoveredString(string text, Action selected = null) + { + if (ImGui.IsItemHovered()) + { + if (!string.IsNullOrEmpty(text)) ImGui.SetTooltip(text); + if (ImGui.IsMouseDown(ImGuiMouseButton.Left)) + { + selected?.Invoke(); + } + } + } + + internal unsafe static bool DrawEditorList(List items, Action draw) + { + ImGui.Indent(); + int moveFrom = -1, moveTo = -1; + for (int i = 0; i < items.Count; i++) + { + var item = items[i]; + + ImGuiComponents.IconButton(item.GetHashCode(), FontAwesomeIcon.ArrowsAltV); + + ImGuiDragDropFlags src_flags = 0; + src_flags |= ImGuiDragDropFlags.SourceNoDisableHover; // Keep the source displayed as hovered + src_flags |= ImGuiDragDropFlags.SourceNoHoldToOpenOthers; // Because our dragging is local, we disable the feature of opening foreign tree nodes/tabs while dragging + //src_flags |= ImGuiDragDropFlags_SourceNoPreviewTooltip; // Hide the tooltip + if (ImGui.BeginDragDropSource(src_flags)) + { + ImGui.SetDragDropPayload("List Movement", (IntPtr)(&i), sizeof(int)); + ImGui.EndDragDropSource(); + } + else if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(LocalizationManager.RightLang.Timeline_DragdropDescription); + + if ((ImGui.IsKeyDown(ImGuiKey.LeftCtrl) || ImGui.IsKeyDown(ImGuiKey.RightCtrl)) + && (ImGui.IsKeyDown(ImGuiKey.LeftAlt) || ImGui.IsKeyDown(ImGuiKey.RightAlt)) + && ImGui.IsMouseReleased(ImGuiMouseButton.Right)) + { + moveFrom = i; + } + } + + if (ImGui.BeginDragDropTarget()) + { + ImGuiDragDropFlags target_flags = 0; + target_flags |= ImGuiDragDropFlags.AcceptBeforeDelivery; // Don't wait until the delivery (release mouse button on a target) to do something + //target_flags |= ImGuiDragDropFlags.AcceptNoDrawDefaultRect; // Don't display the yellow rectangle + var ptr = ImGui.AcceptDragDropPayload("List Movement", target_flags); + + { + moveFrom = *(int*)ptr.Data; + moveTo = i; + } + ImGui.EndDragDropTarget(); + } + + ImGui.SameLine(); + + draw?.Invoke(item); + } + + bool result = false; + if (moveFrom > -1) + { + //Move. + if (moveTo > -1) + { + if (moveFrom != moveTo) + { + var moveItem = items[moveFrom]; + items.RemoveAt(moveFrom); + + items.Insert(moveTo, moveItem); + + result = true; + } + } + //Remove. + else + { + items.RemoveAt(moveFrom); + result = true; + } + } + + ImGui.Unindent(); + return result; + } + + internal static void DrawCondition(bool? tag) + { + if (!tag.HasValue) + { + ImGui.TextColored(ImGuiColors.DalamudGrey3, "Null"); + } + else if (tag.Value) + { + ImGui.TextColored(ImGuiColors.HealerGreen, "True"); + } + else + { + ImGui.TextColored(ImGuiColors.DalamudRed, "False"); + } + } + + internal static void Spacing(byte count = 1) + { + string s = string.Empty; + for (int i = 0; i < count; i++) + { + s += " "; + } + ImGui.Text(s); + ImGui.SameLine(); + } + + internal static void SetNextWidthWithName(string name) + { + ImGui.SetNextItemWidth(ImGui.CalcTextSize(name).X + 30); + } + + internal static void SearchCombo(string popId, string name, ref string searchTxt, T[] actions, Action selectAction) where T : ITexture + { + if (ImGui.BeginCombo(popId, name, ImGuiComboFlags.HeightLargest)) + { + SearchItems(ref searchTxt, actions, selectAction); + + ImGui.EndCombo(); + } + } + + internal static void SearchItems(ref string searchTxt, IEnumerable actions, Action selectAction) where T : ITexture + { + SearchItems(ref searchTxt, actions, i => i.Name, selectAction, i => ImGui.Image(i.GetTexture().ImGuiHandle, new Vector2(24, 24))); + } + + internal static void SearchItemsReflection(string popId, string name, ref string searchTxt, T[] actions, Action selectAction) where T : MemberInfo + { + ImGui.SetNextItemWidth(Math.Max(80, ImGui.CalcTextSize(name).X + 30)); + + if (ImGui.BeginCombo(popId, name, ImGuiComboFlags.HeightLargest)) + { + SearchItems(ref searchTxt, actions, i => i.GetMemberName(), selectAction, getDesc: i => i.GetMemberDescription()); + + ImGui.EndCombo(); + } + } + + public static string GetMemberName(this MemberInfo info) + { + if (LocalizationManager.RightLang.MemberInfoName.TryGetValue(info.Name, out var memberName)) return memberName; + + return info.GetCustomAttribute()?.DisplayName ?? info.Name; + } + + private static string GetMemberDescription(this MemberInfo info) + { + if (LocalizationManager.RightLang.MemberInfoDesc.TryGetValue(info.Name, out var memberDesc)) return memberDesc; + + return info.GetCustomAttribute()?.Description ?? string.Empty; + } + + internal static void SearchItems(ref string searchTxt, IEnumerable actions, Func getName, Action selectAction, Action extraDraw = null, Func getDesc = null) + { + ImGui.Text(LocalizationManager.RightLang.Timeline_SearchBar + ": "); + ImGui.SetNextItemWidth(150); + ImGui.InputText("##SearchBar", ref searchTxt, 16); + + if (!string.IsNullOrWhiteSpace(searchTxt)) + { + var src = searchTxt; + actions = actions.OrderBy(a => !getName(a).Contains(src)).ToArray(); + } + + if (ImGui.BeginChild($"##ActionsCandidateList", new Vector2(150, 400), true)) + { + foreach (var item in actions) + { + if (extraDraw != null) + { + extraDraw(item); + ImGui.SameLine(); + } + + if (ImGui.Selectable(getName(item))) + { + selectAction?.Invoke(item); + + ImGui.CloseCurrentPopup(); + } + + if (getDesc != null && ImGui.IsItemHovered()) + { + var desc = getDesc(item); + if (!string.IsNullOrEmpty(desc)) + { + ImGui.SetTooltip(desc); + } + } + } + ImGui.EndChild(); + } + } + + const float INDENT_WIDTH = 180; + + internal static void DisplayCommandHelp(this T command, string extraCommand = "", Func getHelp = null, bool sameLine = true) where T : struct, Enum + { + var cmdStr = Service.Command + " " + command.ToString(); + if (!string.IsNullOrEmpty(extraCommand)) + { + cmdStr += " " + extraCommand; + } + + if (ImGui.Button(cmdStr)) + { + Service.CommandManager.ProcessCommand(cmdStr); + } + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip($"{LocalizationManager.RightLang.Configwindow_Helper_RunCommand}: {cmdStr}\n{LocalizationManager.RightLang.Configwindow_Helper_CopyCommand}: {cmdStr}"); + + if (ImGui.IsMouseDown(ImGuiMouseButton.Right)) + { + ImGui.SetClipboardText(cmdStr); + } + } + + var help = getHelp?.Invoke(command); + + if (!string.IsNullOrEmpty(help)) + { + if (sameLine) + { + ImGui.SameLine(); + ImGui.Indent(INDENT_WIDTH); + } + else ImGuiHelper.Spacing(); + ImGui.Text(" → "); + ImGui.SameLine(); + ImGui.TextWrapped(help); + if (sameLine) + { + ImGui.Unindent(INDENT_WIDTH); + } + } + } + + public unsafe static void Display(this ICustomRotation rotation, ICustomRotation[] rotations, bool canAddButton) + => rotation.DrawEnableTexture(canAddButton, null, + text => + { + ImGui.SetNextWindowSizeConstraints(new Vector2(0, 0), new Vector2 (1000, 1500)); + ImGuiHelper.DrawTooltip(() => + { + var t = IconSet.GetTexture(IconSet.GetJobIcon(rotation, IconType.Framed)); + ImGui.Image(t.ImGuiHandle, new Vector2(t.Width, t.Height)); + + if (!string.IsNullOrEmpty(text)) + { + ImGui.SameLine(); + ImGui.Text(" "); + ImGui.SameLine(); + ImGui.Text(text); + } + + var type = rotation.GetType(); + + var attrs = new List { RotationDescAttribute.MergeToOne (type.GetCustomAttributes()) }; + + foreach (var m in type.GetAllMethodInfo()) + { + attrs.Add(RotationDescAttribute.MergeToOne (m.GetCustomAttributes())); + } + + foreach (var a in RotationDescAttribute.Merge(attrs)) + { + RotationDescAttribute.MergeToOne(a)?.Display(rotation); + } + }, "Popup" + rotation.GetHashCode().ToString()); + }, + showToolTip => + { + if (!string.IsNullOrEmpty(rotation.RotationName) && rotations != null) + { + ImGui.SameLine(); + ImGui.TextDisabled(" - "); + ImGui.SameLine(); + ImGui.SetNextItemWidth(ImGui.CalcTextSize (rotation.RotationName).X + 30); + if (ImGui.BeginCombo("##RotationName:" + rotation.Name, rotation.RotationName)) + { + foreach (var r in rotations) + { + if (ImGui.Selectable(r.RotationName)) + { + Service.Config.RotationChoices[rotation.Job.RowId] = r.RotationName; + Service.Config.Save(); + } + if (ImGui.IsItemHovered()) + { + showToolTip?.Invoke(r.Description); + } + } + ImGui.EndCombo(); + } + ImGuiHelper.HoveredString (LocalizationManager.RightLang.Configwindow_Helper_SwitchRotation); + } + + ImGui.SameLine(); + ImGui.TextDisabled(" - " + LocalizationManager.RightLang.Configwindow_Helper_GameVersion + ": "); + ImGui.SameLine(); + ImGui.Text(rotation.GameVersion); + ImGui.SameLine(); + Spacing(); + + if (ImGuiHelper.IconButton(FontAwesomeIcon.Globe, rotation.GetHashCode ().ToString())) + { + var url = @"https://github.com/ArchiDog1998/RotationSolver/ blob/ main/" + rotation.GetType().FullName.Replace(".", @"/") + ".cs"; + + Util.OpenLink(url); + } + ImGuiHelper.HoveredString (LocalizationManager.RightLang.Configwindow_Helper_OpenSource); + + var attrs = rotation.GetType ().GetCustomAttributes (); + if (attrs.Any()) + { + ImGui.SameLine(); + Spacing(); + + foreach (var texture in attrs) + { + if (IconButton(FontAwesomeIcon.Question, + "Button" + texture.GetHashCode().ToString())) + { + Util.OpenLink(texture.Path); + } + //if (ImGui.IsItemHovered() && texture.Texture != null) + //{ + // ImGuiHelper.DrawTooltip(() => + // { + // var ratio = Math.Min(1, 1000f / texture.Texture.Width); + // var size = new Vector2(texture.Texture.Width * ratio, + // texture.Texture.Height * ratio); + // ImGui.Image(texture.Texture.ImGuiHandle, size); + // }, "Picture" + texture.GetHashCode().ToString()); + //} + } + } + }, () => + { + RotationConfigWindow.DrawRotationRole(rotation); + + rotation.Configs.Draw(canAddButton); + }); + + #region IAction + public static void Display(this IAction action, bool IsActive) + { + if(action is BaseAction act) + { + act.Display(IsActive); + } + else if(action is IBaseItem item) + { + item.Display(IsActive); + } + } + private unsafe static void Display(this BaseAction action, bool IsActive) => action.DrawEnableTexture(IsActive, () => + { + if (action.IsTimeline) RotationConfigWindow.ActiveAction = action; + }, otherThing: () => + { + ImGui.SameLine(); + ImGuiHelper.Spacing(); + + OtherCommandType.ToggleActions.DisplayCommandHelp(action.ToString()); + + if (action.IsTimeline) OtherCommandType.DoActions.DisplayCommandHelp($"{action}-{5}", + type => string.Format(LocalizationManager.RightLang.Configwindow_Helper_InsertCommand, action), false); + +#if DEBUG + try + { + ImGui.Text("Can Target: " + action.CanUseTo(action.Target)); + ImGui.Text("Have One:" + action.HaveOneChargeDEBUG.ToString()); + ImGui.Text("Is Real GCD: " + action.IsRealGCD.ToString()); + ImGui.Text("Recast One: " + action.RecastTimeOneChargeDEBUG.ToString()); + ImGui.Text("Recast Elapsed: " + action.RecastTimeElapsedDEBUG.ToString()); + ImGui.Text("Recast Remain: " + action.RecastTimeRemainDEBUG.ToString()); + ImGui.Text("Status: " + ActionManager.Instance()->GetActionStatus(ActionType.Spell, action.AdjustedID).ToString()); + + ImGui.Text("Cast Time: " + action.CastTime.ToString()); + ImGui.Text("MP: " + action.MPNeed.ToString()); + ImGui.Text($"Can Use: {action.CanUse(out _)} "); + ImGui.Text("Must Use:" + action.CanUse(out _, mustUse: true).ToString()); + ImGui.Text("Empty Use:" + action.CanUse(out _, emptyOrSkipCombo: true).ToString()); + ImGui.Text("IsUnlocked: " + UIState.Instance()->IsUnlockLinkUnlocked(action.AdjustedID).ToString()); + if (action.Target != null) + { + ImGui.Text("Target Name: " + action.Target.Name); + } + } + catch + { + + } +#endif + }); + + public unsafe static void Display(this IBaseItem item, bool IsActive) => item.DrawEnableTexture(false, null, otherThing: () => + { +#if DEBUG + ImGui.Text("Status: " + ActionManager.Instance()->GetActionStatus(ActionType.Item, item.ID).ToString()); + var remain = ActionManager.Instance()->GetRecastTime(ActionType.Item, item.ID) - ActionManager.Instance()->GetRecastTimeElapsed(ActionType.Item, item.ID); + ImGui.Text("remain: " + remain.ToString()); +#endif + }); + #endregion + + public static void DisplayMacro(this MacroInfo info) + { + ImGui.SetNextItemWidth(50); + if (ImGui.DragInt($"{LocalizationManager.RightLang.Configwindow_Events_MacroIndex}##MacroIndex{info.GetHashCode()}", + ref info.MacroIndex, 1, -1, 99)) + { + Service.Config.Save(); + } + + ImGui.SameLine(); + ImGuiHelper.Spacing(); + + if (ImGui.Checkbox($"{LocalizationManager.RightLang.Configwindow_Events_ShareMacro}##ShareMacro{info.GetHashCode()}", + ref info.IsShared)) + { + Service.Config.Save(); + } + } + + public static void DisplayEvent(this ActionEventInfo info) + { + if (ImGui.InputText($"{LocalizationManager.RightLang.Configwindow_Events_ActionName}##ActionName{info.GetHashCode()}", + ref info.Name, 100)) + { + Service.Config.Save(); + } + + info.DisplayMacro(); + } + + #region Rotation Config Display + static void Draw(this IRotationConfigSet set, bool canAddButton) + { + foreach (var config in set.Configs) + { + if (config is RotationConfigCombo c) c.Draw(set, canAddButton); + else if (config is RotationConfigBoolean b) b.Draw(set, canAddButton); + else if (config is RotationConfigFloat f) f.Draw(set, canAddButton); + else if (config is RotationConfigString s) s.Draw(set, canAddButton); + } + } + + static void Draw(this RotationConfigCombo config, IRotationConfigSet set, bool canAddButton) + { + var val = set.GetCombo(config.Name); + if (ImGui.BeginCombo($"{config.DisplayName}##{config.GetHashCode()}_{config.Name}", config.Items[val])) + { + for (int comboIndex = 0; comboIndex < config.Items.Length; comboIndex++) + { + if (ImGui.Selectable(config.Items[comboIndex])) + { + set.SetValue(config.Name, comboIndex.ToString()); + Service.Config.Save(); + } + if (canAddButton) + { + ImGui.SameLine(); + Spacing(); + DisplayCommandHelp(OtherCommandType.Rotations, config.Name + " " + comboIndex.ToString()); + + ImGui.SameLine(); + Spacing(); + DisplayCommandHelp(OtherCommandType.Rotations, config.Name + " " + config.Items[comboIndex]); + } + } + ImGui.EndCombo(); + } + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(LocalizationManager.RightLang.Configwindow_Rotation_KeyName + ": " + config.Name); + } + + //显示可以设置的按键 + if (canAddButton) + { + ImGui.SameLine(); + Spacing(); + DisplayCommandHelp(OtherCommandType.Rotations, config.Name); + } + } + + static void Draw(this RotationConfigBoolean config, IRotationConfigSet set, bool canAddButton) + { + bool val = set.GetBool(config.Name); + if (ImGui.Checkbox($"{config.DisplayName}##{config.GetHashCode()}_{config.DisplayName}", ref val)) + { + set.SetValue(config.Name, val.ToString()); + Service.Config.Save(); + } + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(LocalizationManager.RightLang.Configwindow_Rotation_KeyName + ": " + config.Name); + } + + //显示可以设置的案件 + if (canAddButton) + { + ImGui.SameLine(); + Spacing(); + DisplayCommandHelp(OtherCommandType.Rotations, config.Name); + } + } + + static void Draw(this RotationConfigFloat config, IRotationConfigSet set, bool canAddButton) + { + float val = set.GetFloat(config.Name); + ImGui.SetNextItemWidth(100); + if (ImGui.DragFloat($"{config.DisplayName}##{config.GetHashCode()}_{config.Name}", ref val, config.Speed, config.Min, config.Max)) + { + set.SetValue(config.Name, val.ToString()); + Service.Config.Save(); + } + } + + static void Draw(this RotationConfigString config, IRotationConfigSet set, bool canAddButton) + { + string val = set.GetString(config.Name); + if (ImGui.InputText($"{config.DisplayName}##{config.GetHashCode()}_{config.Name}", ref val, 15)) + { + set.SetValue(config.Name, val); + Service.Config.Save(); + } + } + #endregion + + + static readonly System.Numerics.Vector2 PIC_SIZE = new System.Numerics.Vector2(24, 24); + const float ATTR_INDENT = 170; + + public static void Display(this RotationDescAttribute attr, ICustomRotation rotation) + { + var acts = rotation.AllBaseActions; + + var allActions = attr.Actions.Select(i => acts.FirstOrDefault(a => a.ID == (uint)i)) + .Where(i => i != null); + + bool hasDesc = !string.IsNullOrEmpty(attr.Description); + + if (!hasDesc && !allActions.Any()) return; + ImGui.Separator(); + + ImGui.Image(IconSet.GetTexture(attr.IconID).ImGuiHandle, PIC_SIZE); + ImGui.SameLine(); + + var isOnCommand = attr.IsOnCommand; + if (isOnCommand) ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow); + ImGui.Text(" " + attr.Type.ToName()); + if (isOnCommand) ImGui.PopStyleColor(); + + ImGui.SameLine(); + ImGui.Indent(ATTR_INDENT); + + if (hasDesc) + { + ImGui.Text(attr.Description); + } + + bool notStart = false; + foreach (var item in allActions) + { + if (item == null) continue; + + if (notStart) + { + ImGui.SameLine(); + ImGui.Text(" "); + ImGui.SameLine(); + } + + ImGui.Image(item.GetTexture().ImGuiHandle, PIC_SIZE); + notStart = true; + } + ImGui.Unindent(ATTR_INDENT); + } +} diff --git a/RotationSolver/Windows/OverlayWindow.cs b/RotationSolver/Windows/OverlayWindow.cs index 53bb2f7ac..c8a48eefc 100644 --- a/RotationSolver/Windows/OverlayWindow.cs +++ b/RotationSolver/Windows/OverlayWindow.cs @@ -4,6 +4,7 @@ using ImGuiNET; using RotationSolver.Actions; using RotationSolver.Actions.BaseAction; +using RotationSolver.Basic; using RotationSolver.Data; using RotationSolver.Helpers; using RotationSolver.SigReplacers; @@ -20,7 +21,7 @@ internal static class OverlayWindow internal static IBaseAction MeleeAction { get; set; } public static void Draw() { - if (Service.GameGui == null || Service.ClientState.LocalPlayer == null || !Service.Configuration.UseOverlayWindow) return; + if (Service.GameGui == null || Service.Player == null || !Service.Config.UseOverlayWindow) return; if (Service.Conditions[Dalamud.Game.ClientState.Conditions.ConditionFlag.OccupiedInCutSceneEvent] || Service.Conditions[Dalamud.Game.ClientState.Conditions.ConditionFlag.BetweenAreas] @@ -52,25 +53,25 @@ public static void Draw() private static void DrawTarget() { - if (!Service.Configuration.ShowTarget) return; + if (!Service.Config.ShowTarget) return; if (ActionUpdater.NextAction is not BaseAction act) return; if (act.Target == null) return; - if (act.Target != Service.ClientState.LocalPlayer) + if (act.Target != Service.Player) { - var c = Service.Configuration.TargetColor; + var c = Service.Config.TargetColor; var Tcolor = ImGui.GetColorU32(new Vector4(c.X, c.Y, c.Z, 1)); DrawTarget(act.Target, Tcolor, 8, out _); } - if (TargetUpdater.HostileTargets.Contains(act.Target)) + if (DataCenter.HostileTargets.Contains(act.Target)) { - var c = Service.Configuration.SubTargetColor; + var c = Service.Config.SubTargetColor; var Scolor = ImGui.GetColorU32(new Vector4(c.X, c.Y, c.Z, 1)); - foreach (var t in TargetUpdater.HostileTargets) + foreach (var t in DataCenter.HostileTargets) { if (t == act.Target) continue; if (act.CanGetTarget(act.Target, t)) @@ -84,10 +85,10 @@ private static void DrawTarget() static readonly uint HealthRatioColor = ImGui.GetColorU32(new Vector4(0, 1, 0.8f, 1)); private static void DrawHealthRatio() { - if(!Service.Configuration.ShowHealthRatio) return; + if(!Service.Config.ShowHealthRatio) return; var calHealth = (double)ObjectHelper.GetHealthFromMulty(1); - foreach (BattleChara t in TargetUpdater.AllTargets) + foreach (BattleChara t in DataCenter.AllTargets) { if (t == null) continue; if (Service.GameGui.WorldToScreen(t.Position, out var p)) @@ -99,17 +100,17 @@ private static void DrawHealthRatio() private unsafe static void DrawMoveTarget() { - if (!Service.Configuration.ShowMoveTarget) return; + if (!Service.Config.ShowMoveTarget) return; - var c = Service.Configuration.MovingTargetColor; + var c = Service.Config.MovingTargetColor; var color = ImGui.GetColorU32(new Vector4(c.X, c.Y, c.Z, 1)); var tar = RotationUpdater.RightNowRotation?.MoveTarget; - if (tar == null || tar == Service.ClientState.LocalPlayer) return; + if (tar == null || tar == Service.Player) return; DrawTarget(tar, color, 8, out var scrPos); - if (Service.GameGui.WorldToScreen(Service.ClientState.LocalPlayer.Position, out var plyPos)) + if (Service.GameGui.WorldToScreen(Service.Player.Position, out var plyPos)) { var dir = scrPos - plyPos; @@ -140,14 +141,14 @@ private static void DrawPositional() Vector3 pPosition = MeleeAction.Target.Position; Service.GameGui.WorldToScreen(pPosition, out var scrPos); - float radius = MeleeAction.Target.HitboxRadius + Service.ClientState.LocalPlayer.HitboxRadius + 3; + float radius = MeleeAction.Target.HitboxRadius + Service.Player.HitboxRadius + 3; float rotation = MeleeAction.Target.Rotation; bool wrong = MeleeAction.Target.DistanceToPlayer() > 3; List pts = new List(4 * COUNT); - if(Service.Configuration.DrawPositional && !Service.ClientState.LocalPlayer.HasStatus(true, StatusID.TrueNorth)) + if(Service.Config.DrawPositional && !Service.Player.HasStatus(true, StatusID.TrueNorth)) { - var shouldPos = MeleeAction.EnermyPositonal; + var shouldPos = MeleeAction.EnemyPositional; switch (shouldPos) { @@ -169,7 +170,7 @@ private static void DrawPositional() wrong = shouldPos != MeleeAction.Target.FindEnemyPositional(); } } - if(pts.Count == 0 && Service.Configuration.DrawMeleeRange) + if(pts.Count == 0 && Service.Config.DrawMeleeRange) { SectorPlots(ref pts, pPosition, radius, 0, 4 * COUNT, 2 * Math.PI); } diff --git a/RotationSolver/Windows/RotationConfigWindow_Action.cs b/RotationSolver/Windows/RotationConfigWindow_Action.cs index 06a9bd078..a55cb6bff 100644 --- a/RotationSolver/Windows/RotationConfigWindow_Action.cs +++ b/RotationSolver/Windows/RotationConfigWindow_Action.cs @@ -1,9 +1,11 @@ using ImGuiNET; using RotationSolver.Actions; using RotationSolver.Data; +using RotationSolver.Helpers; using RotationSolver.Localization; using RotationSolver.SigReplacers; using RotationSolver.Updaters; +using System.Collections.Generic; using System.Linq; using System.Numerics; @@ -34,7 +36,39 @@ private void DrawActionList() if (ImGui.BeginChild("Action List", new Vector2(0f, -1f), true)) { - foreach (var pair in RotationUpdater.RightNowRotation.AllActions.GroupBy(a => a.CateName).OrderBy(g => g.Key)) + foreach (var pair in RotationUpdater.RightNowRotation.AllActions.GroupBy(a => + { + if(a is IBaseAction act) + { + string result; + + if (act.IsFriendly) + { + result = LocalizationManager.RightLang.Action_Friendly; + if (act.IsEot) + { + result += "Hot"; + } + } + else + { + result = LocalizationManager.RightLang.Action_Attack; + + if (act.IsEot) + { + result += "Dot"; + } + } + result += "-" + (act.IsRealGCD ? "GCD" : LocalizationManager.RightLang.Timeline_Ability); + return result; + } + else if(a is IBaseItem) + { + return "Item"; + } + return string.Empty; + + }).OrderBy(g => g.Key)) { if (ImGui.CollapsingHeader(pair.Key)) { diff --git a/RotationSolver/Windows/RotationConfigWindow_Debug.cs b/RotationSolver/Windows/RotationConfigWindow_Debug.cs index e99f1cea3..40ee942ea 100644 --- a/RotationSolver/Windows/RotationConfigWindow_Debug.cs +++ b/RotationSolver/Windows/RotationConfigWindow_Debug.cs @@ -1,19 +1,13 @@ using Dalamud.Game.ClientState.Objects.Types; using FFXIVClientStructs.FFXIV.Client.Game.Fate; -using FFXIVClientStructs.FFXIV.Client.Game.Group; -using FFXIVClientStructs.FFXIV.Client.UI.Agent; using ImGuiNET; -using Lumina.Excel.GeneratedSheets; using RotationSolver.Actions.BaseAction; +using RotationSolver.Basic; using RotationSolver.Commands; using RotationSolver.Data; using RotationSolver.Helpers; -using RotationSolver.Localization; -using RotationSolver.Rotations.RangedMagicial.BLM; using RotationSolver.SigReplacers; using RotationSolver.Updaters; -using System; -using System.Linq; using System.Numerics; namespace RotationSolver.Windows.RotationConfigWindow; @@ -22,7 +16,7 @@ internal partial class RotationConfigWindow { private void DrawDebugTab() { - var str = SocialUpdater.EncryptString(Service.ClientState.LocalPlayer); + var str = SocialUpdater.EncryptString(Service.Player); ImGui.SetNextItemWidth(ImGui.CalcTextSize(str).X + 10); ImGui.InputText("That is your HASH", ref str, 100); @@ -47,15 +41,15 @@ private unsafe void DrawStatus() { if ((IntPtr)FateManager.Instance() != IntPtr.Zero) { - ImGui.Text("Fate: " + TargetUpdater.FateId.ToString()); + ImGui.Text("Fate: " + DataCenter.FateId.ToString()); } - ImGui.Text("Have pet: " + TargetUpdater.HavePet.ToString()); - ImGui.Text("Have Companion: " + TargetUpdater.HaveCompanion.ToString()); - ImGui.Text("Targetable: " + Service.ClientState.LocalPlayer.IsTargetable().ToString()); + ImGui.Text("Have pet: " + DataCenter.HasPet.ToString()); + ImGui.Text("Have Companion: " + DataCenter.HasCompanion.ToString()); + ImGui.Text("Targetable: " + Service.Player.IsTargetable().ToString()); - foreach (var status in Service.ClientState.LocalPlayer.StatusList) + foreach (var status in Service.Player.StatusList) { var source = Service.ObjectTable.SearchById(status.SourceId)?.Name ?? "None"; ImGui.Text($"{status.GameData.Name}: {status.StatusId} From: {source}"); @@ -77,13 +71,13 @@ private unsafe void DrawParty() // } //} - ImGui.Text("Party: " + TargetUpdater.PartyMembers.Count().ToString()); - ImGui.Text("CanHealSingleAbility: " + TargetUpdater.CanHealSingleAbility.ToString()); - ImGui.Text("CanHealSingleSpell: " + TargetUpdater.CanHealSingleSpell.ToString()); - ImGui.Text("CanHealAreaAbility: " + TargetUpdater.CanHealAreaAbility.ToString()); - ImGui.Text("CanHealAreaSpell: " + TargetUpdater.CanHealAreaSpell.ToString()); + ImGui.Text("Party: " + DataCenter.PartyMembers.Count().ToString()); + ImGui.Text("CanHealSingleAbility: " + DataCenter.CanHealSingleAbility.ToString()); + ImGui.Text("CanHealSingleSpell: " + DataCenter.CanHealSingleSpell.ToString()); + ImGui.Text("CanHealAreaAbility: " + DataCenter.CanHealAreaAbility.ToString()); + ImGui.Text("CanHealAreaSpell: " + DataCenter.CanHealAreaSpell.ToString()); - foreach (var member in TargetUpdater.PartyMembers) + foreach (var member in DataCenter.PartyMembers) { var cha = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)member.GetAddress(); @@ -114,9 +108,9 @@ private unsafe void DrawTargetData() } } - ImGui.Text("All: " + TargetUpdater.AllTargets.Count().ToString()); - ImGui.Text("Hostile: " + TargetUpdater.HostileTargets.Count().ToString()); - foreach (var item in TargetUpdater.HostileTargets) + ImGui.Text("All: " + DataCenter.AllTargets.Count().ToString()); + ImGui.Text("Hostile: " + DataCenter.HostileTargets.Count().ToString()); + foreach (var item in DataCenter.HostileTargets) { ImGui.Text(item.Name.ToString()); } @@ -127,8 +121,8 @@ private void DrawNextAction() ImGui.Text(RSCommands.SpecialType.ToString()); ActionUpdater.NextAction?.Display(false); - ImGui.Text("Ability Remain: " + ActionUpdater.AbilityRemain.ToString()); - ImGui.Text("Ability Count: " + ActionUpdater.AbilityRemainCount.ToString()); + ImGui.Text("Ability Remain: " + DataCenter.AbilityRemain.ToString()); + ImGui.Text("Ability Count: " + DataCenter.AbilityRemainCount.ToString()); } private void DrawLastAction() @@ -136,7 +130,7 @@ private void DrawLastAction() DrawAction(Watcher.LastAction, nameof(Watcher.LastAction)); DrawAction(Watcher.LastAbility, nameof(Watcher.LastAbility)); DrawAction(Watcher.LastGCD, nameof(Watcher.LastGCD)); - DrawAction(ActionUpdater.LastComboAction, nameof(ActionUpdater.LastComboAction)); + DrawAction(DataCenter.LastComboAction, nameof(DataCenter.LastComboAction)); } private void DrawCDEX() diff --git a/RotationSolver/Windows/RotationConfigWindow_Events.cs b/RotationSolver/Windows/RotationConfigWindow_Events.cs index 1d6d24e66..d9d4b7940 100644 --- a/RotationSolver/Windows/RotationConfigWindow_Events.cs +++ b/RotationSolver/Windows/RotationConfigWindow_Events.cs @@ -1,5 +1,6 @@ using ImGuiNET; using Lumina.Excel.GeneratedSheets; +using RotationSolver.Basic; using RotationSolver.Configuration; using RotationSolver.Helpers; using RotationSolver.Localization; @@ -13,8 +14,8 @@ private void DrawEventTab() { if (ImGui.Button(LocalizationManager.RightLang.Configwindow_Events_AddEvent)) { - Service.Configuration.Events.Add(new ActionEventInfo()); - Service.Configuration.Save(); + Service.Config.Events.Add(new ActionEventInfo()); + Service.Config.Save(); } ImGui.SameLine(); ImGuiHelper.Spacing(); @@ -27,18 +28,18 @@ private void DrawEventTab() ImGui.Text(LocalizationManager.RightLang.Configwindow_Events_DutyStart); ImGui.SameLine(); ImGuiHelper.Spacing(); - Service.Configuration.DutyStart.DisplayMacro(); + Service.Config.DutyStart.DisplayMacro(); ImGui.Text(LocalizationManager.RightLang.Configwindow_Events_DutyEnd); ImGui.SameLine(); ImGuiHelper.Spacing(); - Service.Configuration.DutyEnd.DisplayMacro(); + Service.Config.DutyEnd.DisplayMacro(); #endif if (ImGui.BeginChild("Events List", new Vector2(0f, -1f), true)) { ActionEventInfo remove = null; - foreach (var eve in Service.Configuration.Events) + foreach (var eve in Service.Config.Events) { eve.DisplayMacro(); @@ -53,8 +54,8 @@ private void DrawEventTab() } if(remove!= null) { - Service.Configuration.Events.Remove(remove); - Service.Configuration.Save(); + Service.Config.Events.Remove(remove); + Service.Config.Save(); } ImGui.EndChild(); diff --git a/RotationSolver/Windows/RotationConfigWindow_Help.cs b/RotationSolver/Windows/RotationConfigWindow_Help.cs index c11218c94..32ffe59fa 100644 --- a/RotationSolver/Windows/RotationConfigWindow_Help.cs +++ b/RotationSolver/Windows/RotationConfigWindow_Help.cs @@ -1,6 +1,7 @@ using Dalamud.Utility; using ImGuiNET; using RotationSolver.Commands; +using RotationSolver.Helpers; using RotationSolver.Localization; using System.Numerics; diff --git a/RotationSolver/Windows/RotationConfigWindow_Major.cs b/RotationSolver/Windows/RotationConfigWindow_Major.cs index a2d07680e..066c8d399 100644 --- a/RotationSolver/Windows/RotationConfigWindow_Major.cs +++ b/RotationSolver/Windows/RotationConfigWindow_Major.cs @@ -1,5 +1,6 @@ using Dalamud.Interface.Windowing; using ImGuiNET; +using RotationSolver.Basic; using RotationSolver.Data; using RotationSolver.Localization; using RotationSolver.Rotations.CustomRotation; @@ -32,7 +33,7 @@ public override unsafe void Draw() if (ImGui.BeginTabBar("RotationSolverSettings")) { #if DEBUG - if (Service.ClientState.LocalPlayer != null && ImGui.BeginTabItem("Debug")) + if (Service.Player != null && ImGui.BeginTabItem("Debug")) { DrawDebugTab(); ImGui.EndTabItem(); diff --git a/RotationSolver/Windows/RotationConfigWindow_Param.cs b/RotationSolver/Windows/RotationConfigWindow_Param.cs index b138526aa..261480956 100644 --- a/RotationSolver/Windows/RotationConfigWindow_Param.cs +++ b/RotationSolver/Windows/RotationConfigWindow_Param.cs @@ -1,4 +1,5 @@ using ImGuiNET; +using RotationSolver.Basic; using RotationSolver.Commands; using RotationSolver.Data; using RotationSolver.Helpers; @@ -49,393 +50,393 @@ private void DrawParamTab() private void DrawParamBasic() { DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_NeverReplaceIcon, - ref Service.Configuration.NeverReplaceIcon, + ref Service.Config.NeverReplaceIcon, LocalizationManager.RightLang.Configwindow_Param_NeverReplaceIconDesc); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_UseOverlayWindow, - ref Service.Configuration.UseOverlayWindow, + ref Service.Config.UseOverlayWindow, LocalizationManager.RightLang.Configwindow_Param_UseOverlayWindowDesc); DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_AbilitiesInterval, - ref Service.Configuration.AbilitiesInterval, min: 0.5f, max: 0.7f); + ref Service.Config.AbilitiesInterval, min: 0.5f, max: 0.7f); DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_ActionAhead, - ref Service.Configuration.ActionAhead, max: 0.1f); + ref Service.Config.ActionAhead, max: 0.1f); DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_CountDownAhead, - ref Service.Configuration.CountDownAhead, min: 0.5f, max: 0.7f); + ref Service.Config.CountDownAhead, min: 0.5f, max: 0.7f); DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_SpecialDuration, - ref Service.Configuration.SpecialDuration, speed: 0.02f, min: 1, max: 20); + ref Service.Config.SpecialDuration, speed: 0.02f, min: 1, max: 20); DrawIntNumber(LocalizationManager.RightLang.Configwindow_Param_AddDotGCDCount, - ref Service.Configuration.AddDotGCDCount, min: 0, max: 3); + ref Service.Config.AddDotGCDCount, min: 0, max: 3); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_AutoOffBetweenArea, - ref Service.Configuration.AutoOffBetweenArea); + ref Service.Config.AutoOffBetweenArea); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_UseWorkTask, - ref Service.Configuration.UseWorkTask); + ref Service.Config.UseWorkTask); } private void DrawParamDelay() { DrawRangedFloat(LocalizationManager.RightLang.Configwindow_Param_WeaponDelay, - ref Service.Configuration.WeaponDelayMin, ref Service.Configuration.WeaponDelayMax, speed: 0.002f, max: 1); + ref Service.Config.WeaponDelayMin, ref Service.Config.WeaponDelayMax, speed: 0.002f, max: 1); DrawRangedFloat(LocalizationManager.RightLang.Configwindow_Param_HostileDelay, - ref Service.Configuration.HostileDelayMin, ref Service.Configuration.HostileDelayMax); + ref Service.Config.HostileDelayMin, ref Service.Config.HostileDelayMax); DrawRangedFloat(LocalizationManager.RightLang.Configwindow_Param_InterruptDelay, - ref Service.Configuration.InterruptDelayMin, ref Service.Configuration.InterruptDelayMax); + ref Service.Config.InterruptDelayMin, ref Service.Config.InterruptDelayMax); DrawRangedFloat(LocalizationManager.RightLang.Configwindow_Param_DeathDelay, - ref Service.Configuration.DeathDelayMin, ref Service.Configuration.DeathDelayMax); + ref Service.Config.DeathDelayMin, ref Service.Config.DeathDelayMax); DrawRangedFloat(LocalizationManager.RightLang.Configwindow_Param_WeakenDelay, - ref Service.Configuration.WeakenDelayMin, ref Service.Configuration.WeakenDelayMax); + ref Service.Config.WeakenDelayMin, ref Service.Config.WeakenDelayMax); DrawRangedFloat(LocalizationManager.RightLang.Configwindow_Param_HealDelay, - ref Service.Configuration.HealDelayMin, ref Service.Configuration.HealDelayMax); + ref Service.Config.HealDelayMin, ref Service.Config.HealDelayMax); DrawRangedFloat(LocalizationManager.RightLang.Configwindow_Param_NotInCombatDelay, - ref Service.Configuration.NotInCombatDelayMin, ref Service.Configuration.NotInCombatDelayMax); + ref Service.Config.NotInCombatDelayMin, ref Service.Config.NotInCombatDelayMax); - if (Service.Configuration.UseStopCasting) + if (Service.Config.UseStopCasting) { DrawRangedFloat(LocalizationManager.RightLang.Configwindow_Param_StopCastingDelay, - ref Service.Configuration.StopCastingDelayMin, ref Service.Configuration.StopCastingDelayMax); + ref Service.Config.StopCastingDelayMin, ref Service.Config.StopCastingDelayMax); } } private void DrawParamAdvanced() { DrawIntNumber(LocalizationManager.RightLang.Configwindow_Params_VoiceVolume, - ref Service.Configuration.VoiceVolume, max: 100); + ref Service.Config.VoiceVolume, max: 100); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_SayOutStateChanged, - ref Service.Configuration.SayOutStateChanged); + ref Service.Config.SayOutStateChanged); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_SayPositional, - ref Service.Configuration.SayPotional); + ref Service.Config.SayPositional); DrawInputText(LocalizationManager.RightLang.Configwindow_Param_PositionaErrorText, - ref Service.Configuration.PositionalErrorText, 100, + ref Service.Config.PositionalErrorText, 100, LocalizationManager.RightLang.Configwindow_Params_LocationWrongTextDesc); ImGui.Separator(); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_PoslockCasting, - ref Service.Configuration.PoslockCasting); + ref Service.Config.PoslockCasting); - if (Service.Configuration.PoslockCasting) + if (Service.Config.PoslockCasting) { ImGui.SameLine(); ImGuiHelper.Spacing(); DrawCombo(LocalizationManager.RightLang.Configwindow_Param_PoslockModifier, - ref Service.Configuration.PoslockModifier, EnumTranslations.ToName, + ref Service.Config.PoslockModifier, EnumTranslations.ToName, ConfigurationHelper.Keys, LocalizationManager.RightLang.Configwindow_Param_PoslockDescription); } DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_UseStopCasting, - ref Service.Configuration.UseStopCasting); + ref Service.Config.UseStopCasting); ImGui.Separator(); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_ShowHealthRatio, - ref Service.Configuration.ShowHealthRatio); + ref Service.Config.ShowHealthRatio); DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_HealthRatioBoss, - ref Service.Configuration.HealthRatioBoss, speed: 0.02f, min: 0, max: 10); + ref Service.Config.HealthRatioBoss, speed: 0.02f, min: 0, max: 10); DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_HealthRatioDying, - ref Service.Configuration.HealthRatioDying, speed: 0.02f, min: 0, max: 10); + ref Service.Config.HealthRatioDying, speed: 0.02f, min: 0, max: 10); DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_HealthRatioDot, - ref Service.Configuration.HealthRatioDot, speed: 0.02f, min: 0, max: 10); + ref Service.Config.HealthRatioDot, speed: 0.02f, min: 0, max: 10); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_ShowActionFlag, - ref Service.Configuration.ShowActionFlag); + ref Service.Config.ShowActionFlag); } private void DrawParamDisplay() { - var useOverlayWindow = Service.Configuration.UseOverlayWindow; + var useOverlayWindow = Service.Config.UseOverlayWindow; DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_TeachingMode, - ref Service.Configuration.TeachingMode); + ref Service.Config.TeachingMode); - if (Service.Configuration.TeachingMode) + if (Service.Config.TeachingMode) { ImGuiHelper.Spacing(); DrawColor(LocalizationManager.RightLang.Configwindow_Param_TeachingModeColor, - ref Service.Configuration.TeachingModeColor); + ref Service.Config.TeachingModeColor); } if (useOverlayWindow) { DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_ShowMoveTarget, - ref Service.Configuration.ShowMoveTarget); + ref Service.Config.ShowMoveTarget); - if (Service.Configuration.ShowMoveTarget) + if (Service.Config.ShowMoveTarget) { ImGuiHelper.Spacing(); DrawColor(LocalizationManager.RightLang.Configwindow_Param_MovingTargetColor, - ref Service.Configuration.MovingTargetColor); + ref Service.Config.MovingTargetColor); } DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_ShowTarget, - ref Service.Configuration.ShowTarget); + ref Service.Config.ShowTarget); - if (Service.Configuration.ShowTarget) + if (Service.Config.ShowTarget) { ImGuiHelper.Spacing(); DrawColor(LocalizationManager.RightLang.Configwindow_Param_TargetColor, - ref Service.Configuration.TargetColor); + ref Service.Config.TargetColor); ImGuiHelper.Spacing(); DrawColor(LocalizationManager.RightLang.Configwindow_Params_SubTargetColor, - ref Service.Configuration.SubTargetColor); + ref Service.Config.SubTargetColor); } } DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_KeyBoardNoise, - ref Service.Configuration.KeyBoardNoise); + ref Service.Config.KeyBoardNoise); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_ShowInfoOnDtr, - ref Service.Configuration.ShowInfoOnDtr); + ref Service.Config.ShowInfoOnDtr); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_ShowInfoOnToast, - ref Service.Configuration.ShowInfoOnToast); + ref Service.Config.ShowInfoOnToast); DrawIntNumber(LocalizationManager.RightLang.Configwindow_Param_NamePlateIconId, - ref Service.Configuration.NamePlateIconId, 5, 0, 150000, otherThing: RSCommands.UpdateStateNamePlate); + ref Service.Config.NamePlateIconId, 5, 0, 150000, otherThing: RSCommands.UpdateStateNamePlate); ImGui.Spacing(); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_CastingDisplay, - ref Service.Configuration.CastingDisplay); + ref Service.Config.CastingDisplay); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_FlytextPositional, - ref Service.Configuration.FlytextPositional); + ref Service.Config.FlytextPositional); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_PositionalFeedback, - ref Service.Configuration.PositionalFeedback, + ref Service.Config.PositionalFeedback, LocalizationManager.RightLang.Configwindow_Param_PositionalFeedbackDesc); if (useOverlayWindow) { DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_DrawPositional, - ref Service.Configuration.DrawPositional, + ref Service.Config.DrawPositional, LocalizationManager.RightLang.Configwindow_Param_PositionalFeedbackDesc); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_DrawMeleeRange, - ref Service.Configuration.DrawMeleeRange); + ref Service.Config.DrawMeleeRange); } } private void DrawParamAction() { DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_UseAOEAction, - ref Service.Configuration.UseAOEAction); + ref Service.Config.UseAOEAction); - if(Service.Configuration.UseAOEAction) + if(Service.Config.UseAOEAction) { DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_UseAOEWhenManual, - ref Service.Configuration.UseAOEWhenManual); + ref Service.Config.UseAOEWhenManual); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_NoNewHostiles, - ref Service.Configuration.NoNewHostiles, + ref Service.Config.NoNewHostiles, LocalizationManager.RightLang.Configwindow_Params_NoNewHostilesDesc); } ImGui.Separator(); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_AutoBurst, - ref Service.Configuration.AutoBurst); + ref Service.Config.AutoBurst); ImGui.SameLine(); ImGuiHelper.Spacing(); - RSCommands.DisplayCommandHelp(OtherCommandType.Settings, nameof(Service.Configuration.AutoBurst)); + ImGuiHelper.DisplayCommandHelp(OtherCommandType.Settings, nameof(Service.Config.AutoBurst)); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_UseItem, - ref Service.Configuration.UseItem, + ref Service.Config.UseItem, LocalizationManager.RightLang.Configwindow_Param_UseItemDesc); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_UseAbility, - ref Service.Configuration.UseAbility); + ref Service.Config.UseAbility); - if (Service.Configuration.UseAbility) + if (Service.Config.UseAbility) { ImGui.Indent(); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_UseDefenceAbility, - ref Service.Configuration.UseDefenceAbility, + ref Service.Config.UseDefenseAbility, LocalizationManager.RightLang.Configwindow_Param_UseDefenceAbilityDesc); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_AutoShield, - ref Service.Configuration.AutoShield); + ref Service.Config.AutoShield); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_AutoProvokeForTank, - ref Service.Configuration.AutoProvokeForTank, + ref Service.Config.AutoProvokeForTank, LocalizationManager.RightLang.Configwindow_Param_AutoProvokeForTankDesc); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_AutoUseTrueNorth, - ref Service.Configuration.AutoUseTrueNorth); + ref Service.Config.AutoUseTrueNorth); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_RaisePlayerBySwift, - ref Service.Configuration.RaisePlayerBySwift); + ref Service.Config.RaisePlayerBySwift); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_UseGroundBeneficialAbility, - ref Service.Configuration.UseGroundBeneficialAbility); + ref Service.Config.UseGroundBeneficialAbility); ImGui.Unindent(); } DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_RaisePlayerByCasting, - ref Service.Configuration.RaisePlayerByCasting); + ref Service.Config.RaisePlayerByCasting); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_UseHealWhenNotAHealer, - ref Service.Configuration.UseHealWhenNotAHealer); + ref Service.Config.UseHealWhenNotAHealer); DrawIntNumber(LocalizationManager.RightLang.Configwindow_Param_LessMPNoRaise, - ref Service.Configuration.LessMPNoRaise, 200, 0, 2000000); + ref Service.Config.LessMPNoRaise, 200, 0, 2000000); } private void DrawParamCondition() { DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_InterruptibleMoreCheck, - ref Service.Configuration.InterruptibleMoreCheck); + ref Service.Config.InterruptibleMoreCheck); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_StartOnCountdown, - ref Service.Configuration.StartOnCountdown); + ref Service.Config.StartOnCountdown); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_EsunaAll, - ref Service.Configuration.EsunaAll); + ref Service.Config.EsunaAll); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_HealOutOfCombat, - ref Service.Configuration.HealOutOfCombat); + ref Service.Config.HealOutOfCombat); const float speed = 0.005f; DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_MeleeRangeOffset, - ref Service.Configuration.MeleeRangeOffset, 5 * speed, max: 5); + ref Service.Config.MeleeRangeOffset, 5 * speed, max: 5); DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_HealthDifference, - ref Service.Configuration.HealthDifference, + ref Service.Config.HealthDifference, speed * 2, 0, 0.5f); DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_HealthAreaAbility, - ref Service.Configuration.HealthAreaAbility, speed); + ref Service.Config.HealthAreaAbility, speed); DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_HealthAreaSpell, - ref Service.Configuration.HealthAreafSpell, speed); + ref Service.Config.HealthAreaSpell, speed); DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_HealthSingleAbility, - ref Service.Configuration.HealthSingleAbility, speed); + ref Service.Config.HealthSingleAbility, speed); DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_HealthSingleSpell, - ref Service.Configuration.HealthSingleSpell, speed); + ref Service.Config.HealthSingleSpell, speed); } private void DrawParamTarget() { DrawFloatNumber(LocalizationManager.RightLang.Configwindow_Param_ObjectMinRadius, - ref Service.Configuration.ObjectMinRadius, 0.02f, 0, 10); + ref Service.Config.ObjectMinRadius, 0.02f, 0, 10); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_AddEnemyListToHostile, - ref Service.Configuration.AddEnemyListToHostile); + ref Service.Config.AddEnemyListToHostile); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_ChooseAttackMark, - ref Service.Configuration.ChooseAttackMark); + ref Service.Config.ChooseAttackMark); - if (Service.Configuration.ChooseAttackMark && Service.Configuration.UseAOEAction) + if (Service.Config.ChooseAttackMark && Service.Config.UseAOEAction) { ImGui.Indent(); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_CanAttackMarkAOE, - ref Service.Configuration.CanAttackMarkAOE, + ref Service.Config.CanAttackMarkAOE, LocalizationManager.RightLang.Configwindow_Param_AttackMarkAOEDesc); ImGui.Unindent(); } DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_FilterStopMark, - ref Service.Configuration.FilterStopMark); + ref Service.Config.FilterStopMark); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_ChangeTargetForFate, - ref Service.Configuration.ChangeTargetForFate); + ref Service.Config.ChangeTargetForFate); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_OnlyAttackInView, - ref Service.Configuration.OnlyAttackInView); + ref Service.Config.OnlyAttackInView); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_MoveTowardsScreen, - ref Service.Configuration.MoveTowardsScreenCenter, + ref Service.Config.MoveTowardsScreenCenter, LocalizationManager.RightLang.Configwindow_Param_MoveTowardsScreenDesc); DrawIntNumber(LocalizationManager.RightLang.Configwindow_Param_MoveTargetAngle, - ref Service.Configuration.MoveTargetAngle, 0.02f, 0, 100, + ref Service.Config.MoveTargetAngle, 0.02f, 0, 100, LocalizationManager.RightLang.Configwindow_Param_MoveTargetAngleDesc); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_MoveAreaActionFarthest, - ref Service.Configuration.MoveAreaActionFarthest, + ref Service.Config.MoveAreaActionFarthest, LocalizationManager.RightLang.Configwindow_Param_MoveAreaActionFarthestDesc); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_TargetFriendly, - ref Service.Configuration.TargetFriendly); + ref Service.Config.TargetFriendly); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_RaiseAll, - ref Service.Configuration.RaiseAll); + ref Service.Config.RaiseAll); DrawCheckBox(LocalizationManager.RightLang.Configwindow_Param_RaiseBrinkofDeath, - ref Service.Configuration.RaiseBrinkofDeath); + ref Service.Config.RaiseBrinkOfDeath); } private void DrawParamHostile() { if (ImGui.Button(LocalizationManager.RightLang.Configwindow_Param_AddHostileCondition)) { - Service.Configuration.TargetingTypes.Add(TargetingType.Big); + Service.Config.TargetingTypes.Add(TargetingType.Big); } ImGui.SameLine(); ImGuiHelper.Spacing(); ImGui.TextWrapped(LocalizationManager.RightLang.Configwindow_Param_HostileDesc); - for (int i = 0; i < Service.Configuration.TargetingTypes.Count; i++) + for (int i = 0; i < Service.Config.TargetingTypes.Count; i++) { ImGui.Separator(); var names = Enum.GetNames(typeof(TargetingType)); - var targingType = (int)Service.Configuration.TargetingTypes[i]; + var targingType = (int)Service.Config.TargetingTypes[i]; if (ImGui.Combo(LocalizationManager.RightLang.Configwindow_Param_HostileCondition + "##HostileCondition" + i.ToString(), ref targingType, names, names.Length)) { - Service.Configuration.TargetingTypes[i] = (TargetingType)targingType; - Service.Configuration.Save(); + Service.Config.TargetingTypes[i] = (TargetingType)targingType; + Service.Config.Save(); } if (ImGui.Button(LocalizationManager.RightLang.Configwindow_Param_ConditionUp + "##HostileUp" + i.ToString())) { if (i != 0) { - var value = Service.Configuration.TargetingTypes[i]; - Service.Configuration.TargetingTypes.RemoveAt(i); - Service.Configuration.TargetingTypes.Insert(i - 1, value); + var value = Service.Config.TargetingTypes[i]; + Service.Config.TargetingTypes.RemoveAt(i); + Service.Config.TargetingTypes.Insert(i - 1, value); } } ImGui.SameLine(); ImGuiHelper.Spacing(); if (ImGui.Button(LocalizationManager.RightLang.Configwindow_Param_ConditionDown + "##HostileDown" + i.ToString())) { - if (i < Service.Configuration.TargetingTypes.Count - 1) + if (i < Service.Config.TargetingTypes.Count - 1) { - var value = Service.Configuration.TargetingTypes[i]; - Service.Configuration.TargetingTypes.RemoveAt(i); - Service.Configuration.TargetingTypes.Insert(i + 1, value); + var value = Service.Config.TargetingTypes[i]; + Service.Config.TargetingTypes.RemoveAt(i); + Service.Config.TargetingTypes.Insert(i + 1, value); } } @@ -444,7 +445,7 @@ private void DrawParamHostile() if (ImGui.Button(LocalizationManager.RightLang.Configwindow_Param_ConditionDelete + "##HostileDelete" + i.ToString())) { - Service.Configuration.TargetingTypes.RemoveAt(i); + Service.Config.TargetingTypes.RemoveAt(i); } } } @@ -453,7 +454,7 @@ private static void DrawCheckBox(string name, ref bool value, string description { if (ImGui.Checkbox(name, ref value)) { - Service.Configuration.Save(); + Service.Config.Save(); otherThing?.Invoke(); } if (!string.IsNullOrEmpty(description) && ImGui.IsItemHovered()) @@ -467,7 +468,7 @@ private static void DrawRangedFloat(string name, ref float minValue, ref float m ImGui.SetNextItemWidth(100); if (ImGui.DragFloatRange2(name, ref minValue, ref maxValue, speed, min, max)) { - Service.Configuration.Save(); + Service.Config.Save(); } if (!string.IsNullOrEmpty(description) && ImGui.IsItemHovered()) { @@ -480,7 +481,7 @@ private static void DrawFloatNumber(string name, ref float value, float speed = ImGui.SetNextItemWidth(100); if (ImGui.DragFloat(name, ref value, speed, min, max)) { - Service.Configuration.Save(); + Service.Config.Save(); } if (!string.IsNullOrEmpty(description) && ImGui.IsItemHovered()) { @@ -493,7 +494,7 @@ private static void DrawIntNumber(string name, ref int value, float speed = 0.2f ImGui.SetNextItemWidth(100); if (ImGui.DragInt(name, ref value, speed, min, max)) { - Service.Configuration.Save(); + Service.Config.Save(); otherThing?.Invoke(); } if (!string.IsNullOrEmpty(description) && ImGui.IsItemHovered()) @@ -507,7 +508,7 @@ private static void DrawColor(string name, ref Vector3 value, string description ImGui.SetNextItemWidth(210); if (ImGui.ColorEdit3(name, ref value)) { - Service.Configuration.Save(); + Service.Config.Save(); } if (!string.IsNullOrEmpty(description) && ImGui.IsItemHovered()) { @@ -527,7 +528,7 @@ private static void DrawCombo(string name, ref int value, Func toS if (ImGui.Selectable(toString(choices[i]))) { value = i; - Service.Configuration.Save(); + Service.Config.Save(); } } ImGui.EndCombo(); @@ -543,7 +544,7 @@ private static void DrawInputText(string name, ref string value, uint maxLength, ImGui.SetNextItemWidth(210); if (ImGui.InputText(name, ref value, maxLength)) { - Service.Configuration.Save(); + Service.Config.Save(); } if (!string.IsNullOrEmpty(description) && ImGui.IsItemHovered()) { diff --git a/RotationSolver/Windows/RotationConfigWindow_Rotation.cs b/RotationSolver/Windows/RotationConfigWindow_Rotation.cs index d70d9a90a..b16551156 100644 --- a/RotationSolver/Windows/RotationConfigWindow_Rotation.cs +++ b/RotationSolver/Windows/RotationConfigWindow_Rotation.cs @@ -1,10 +1,11 @@ using Dalamud.Interface.Colors; using Dalamud.Utility; using ImGuiNET; +using RotationSolver.Basic; +using RotationSolver.Basic.Rotations; using RotationSolver.Data; using RotationSolver.Helpers; using RotationSolver.Localization; -using RotationSolver.Rotations.CustomRotation; using RotationSolver.SigReplacers; using RotationSolver.Updaters; using System; @@ -62,11 +63,11 @@ private static void DrawRotations(RotationUpdater.CustomRotationGroup[] rotation if (i > 0) ImGui.Separator(); var group = rotations[i]; - Service.Configuration.RotationChoices.TryGetValue((uint)group.jobId, out var rotationName); + Service.Config.RotationChoices.TryGetValue((uint)group.jobId, out var rotationName); var rotation = RotationUpdater.GetChooseRotation(group, rotationName); - var canAddButton = Service.ClientState.LocalPlayer != null - && rotation.JobIDs.Contains((ClassJobID)Service.ClientState.LocalPlayer.ClassJob.Id); + var canAddButton = Service.Player != null + && rotation.JobIDs.Contains((ClassJobID)Service.Player.ClassJob.Id); rotation.Display(group.rotations, canAddButton); } @@ -91,11 +92,11 @@ private static void DrawTargetHostileTYpe(ICustomRotation rotation) LocalizationManager.RightLang.Configwindow_Param_TargetToHostileType3, }, 3)) { - Service.Configuration.TargetToHostileTypes[rotation.Job.RowId] = (byte)isAllTargetAsHostile; - Service.Configuration.Save(); + Service.Config.TargetToHostileTypes[rotation.Job.RowId] = (byte)isAllTargetAsHostile; + Service.Config.Save(); } - if (isAllTargetAsHostile != 2 && !Service.Configuration.AutoOffBetweenArea) + if (isAllTargetAsHostile != 2 && !Service.Config.AutoOffBetweenArea) { ImGui.TextColored(ImGuiColors.DPSRed, LocalizationManager.RightLang.Configwindow_Param_NoticeUnexpectedCombat); } @@ -111,7 +112,7 @@ private static void DrawSpecialRoleSettings(JobRole role, ClassJobID job) { DrawDragFloat(job, LocalizationManager.RightLang.Configwindow_Param_HealthForDyingTank, () => ConfigurationHelper.GetHealthForDyingTank(job), - (value) => Service.Configuration.HealthForDyingTanks[job] = value); + (value) => Service.Config.HealthForDyingTanks[job] = value); } } @@ -119,27 +120,27 @@ private static void DrawHealerSettings(ClassJobID job) { DrawDragFloat(job, LocalizationManager.RightLang.Configwindow_Param_HealthAreaAbility, () => ConfigurationHelper.GetHealAreaAbility(job), - (value) => Service.Configuration.HealthAreaAbilities[job] = value); + (value) => Service.Config.HealthAreaAbilities[job] = value); DrawDragFloat(job, LocalizationManager.RightLang.Configwindow_Param_HealthAreaSpell, - () => ConfigurationHelper.GetHealAreafSpell(job), - (value) => Service.Configuration.HealthAreafSpells[job] = value); + () => ConfigurationHelper.GetHealAreaSpell(job), + (value) => Service.Config.HealthAreaSpells[job] = value); DrawDragFloat(job, LocalizationManager.RightLang.Configwindow_Param_HealingOfTimeSubtractArea, () => ConfigurationHelper.GetHealingOfTimeSubtractArea(job), - (value) => Service.Configuration.HealingOfTimeSubtractAreas[job] = value); + (value) => Service.Config.HealingOfTimeSubtractAreas[job] = value); DrawDragFloat(job, LocalizationManager.RightLang.Configwindow_Param_HealthSingleAbility, () => ConfigurationHelper.GetHealSingleAbility(job), - (value) => Service.Configuration.HealthSingleAbilities[job] = value); + (value) => Service.Config.HealthSingleAbilities[job] = value); DrawDragFloat(job, LocalizationManager.RightLang.Configwindow_Param_HealthSingleSpell, () => ConfigurationHelper.GetHealSingleSpell(job), - (value) => Service.Configuration.HealthSingleSpells[job] = value); + (value) => Service.Config.HealthSingleSpells[job] = value); DrawDragFloat(job, LocalizationManager.RightLang.Configwindow_Param_HealingOfTimeSubtractSingle, () => ConfigurationHelper.GetHealingOfTimeSubtractSingle(job), - (value) => Service.Configuration.HealingOfTimeSubtractSingles[job] = value); + (value) => Service.Config.HealingOfTimeSubtractSingles[job] = value); } private static void DrawDragFloat(ClassJobID job, string desc, Func getValue, Action setValue) @@ -153,7 +154,7 @@ private static void DrawDragFloat(ClassJobID job, string desc, Func getVa if (ImGui.DragFloat($"{desc}##{job}{desc}", ref value, speed, 0, 1)) { setValue(value); - Service.Configuration.Save(); + Service.Config.Save(); } } }