using System; using FFXIVClassic.Common; using FFXIVClassic_Map_Server.Actors; using FFXIVClassic_Map_Server.packets.send.actor; using FFXIVClassic_Map_Server.packets.send.actor.battle; namespace FFXIVClassic_Map_Server.actors.chara.ai.state { class AttackState : State { private DateTime attackTime; public AttackState(Character owner, Character target) : base(owner, target) { this.canInterrupt = false; this.startTime = DateTime.Now; owner.ChangeState(SetActorStatePacket.MAIN_STATE_ACTIVE); ChangeTarget(target); attackTime = startTime; owner.aiContainer.pathFind?.Clear(); } public override void OnStart() { } public override bool Update(DateTime tick) { if ((target == null || owner.target != target || owner.target?.actorId != owner.currentLockedTarget) && owner.isAutoAttackEnabled) owner.aiContainer.ChangeTarget(target = owner.zone.FindActorInArea(owner.currentTarget)); if (target == null || target.IsDead()) { if (owner.IsMonster() || owner.IsAlly()) target = ((BattleNpc)owner).hateContainer.GetMostHatedTarget(); } else { if (IsAttackReady()) { if (CanAttack()) { TryInterrupt(); // todo: check weapon delay/haste etc and use that if (!interrupt) { OnComplete(); } else { } SetInterrupted(false); } else { // todo: handle interrupt/paralyze etc } attackTime = DateTime.Now.AddMilliseconds(owner.GetAttackDelayMs()); } } return false; } public override void OnInterrupt() { // todo: send paralyzed/sleep message etc. if (errorResult != null) { owner.zone.BroadcastPacketAroundActor(owner, CommandResultX01Packet.BuildPacket(errorResult.targetId, errorResult.animation, 0x765D, errorResult)); errorResult = null; } } public override void OnComplete() { //BattleAction action = new BattleAction(target.actorId, 0x765D, (uint) HitEffect.Hit, 0, (byte) HitDirection.None); errorResult = null; // todo: implement auto attack damage bonus in Character.OnAttack /* ≪Auto-attack Damage Bonus≫ Class Bonus 1 Bonus 2 Pugilist Intelligence Strength Gladiator Mind Strength Marauder Vitality Strength Archer Dexterity Piety Lancer Piety Strength Conjurer Mind Piety Thaumaturge Mind Piety * The above damage bonus also applies to “Shot” attacks by archers. */ // handle paralyze/intimidate/sleep/whatever in Character.OnAttack // todo: Change this to use a BattleCommand like the other states //List actions = new List(); CommandResultContainer actions = new CommandResultContainer(); //This is all temporary until the skill sheet is finishd and the different auto attacks are added to the database //Some mobs have multiple unique auto attacks that they switch between as well as ranged auto attacks, so we'll need a way to handle that //For now, just use a temporary hardcoded BattleCommand that's the same for everyone. BattleCommand attackCommand = new BattleCommand(22104, "Attack"); attackCommand.range = 5; attackCommand.rangeHeight = 10; attackCommand.worldMasterTextId = 0x765D; attackCommand.mainTarget = (ValidTarget)768; attackCommand.validTarget = (ValidTarget)17152; attackCommand.commandType = CommandType.AutoAttack; attackCommand.numHits = (byte)owner.GetMod(Modifier.HitCount); attackCommand.basePotency = 100; ActionProperty property = (owner.GetMod(Modifier.AttackType) != 0) ? (ActionProperty)owner.GetMod(Modifier.AttackType) : ActionProperty.Slashing; attackCommand.actionProperty = property; attackCommand.actionType = ActionType.Physical; uint anim = (17 << 24 | 1 << 12); if (owner is Player) anim = (25 << 24 | 1 << 12); attackCommand.battleAnimation = anim; if (owner.CanUse(target, attackCommand)) { attackCommand.targetFind.FindWithinArea(target, attackCommand.validTarget, attackCommand.aoeTarget); owner.DoBattleCommand(attackCommand, "autoattack"); } } public override void TryInterrupt() { if (owner.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.PreventAttack)) { // todo: sometimes paralyze can let you attack, calculate proc rate var list = owner.statusEffects.GetStatusEffectsByFlag((uint)StatusEffectFlags.PreventAttack); uint statusId = 0; if (list.Count > 0) { statusId = list[0].GetStatusId(); } interrupt = true; return; } interrupt = !CanAttack(); } private bool IsAttackReady() { // todo: this enforced delay should really be changed if it's not retail.. return Program.Tick >= attackTime && Program.Tick >= owner.aiContainer.GetLastActionTime(); } private bool CanAttack() { if (!owner.isAutoAttackEnabled || target.allegiance == owner.allegiance) { return false; } if (target == null) { return false; } if (!owner.IsFacing(target)) { return false; } // todo: shouldnt need to check if owner is dead since all states would be cleared if (owner.IsDead() || target.IsDead()) { if (owner.IsMonster() || owner.IsAlly()) ((BattleNpc)owner).hateContainer.ClearHate(target); owner.aiContainer.ChangeTarget(null); return false; } else if (!owner.IsValidTarget(target, ValidTarget.Enemy) || !owner.aiContainer.GetTargetFind().CanTarget(target, false, true)) { return false; } // todo: use a mod for melee range else if (Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, target.positionX, target.positionY, target.positionZ) > owner.GetAttackRange()) { if (owner is Player) { //The target is too far away ((Player)owner).SendGameMessage(Server.GetWorldManager().GetActor(), 32537, 0x20); } return false; } return true; } public override void Cleanup() { if (owner.IsDead()) owner.Disengage(); } public override bool CanChangeState() { return true; } } }