diff --git a/FFXIVClassic Map Server/Database.cs b/FFXIVClassic Map Server/Database.cs index 1339c727..aee01f0e 100644 --- a/FFXIVClassic Map Server/Database.cs +++ b/FFXIVClassic Map Server/Database.cs @@ -2431,8 +2431,8 @@ namespace FFXIVClassic_Map_Server battleCommand.castTimeMs = reader.GetUInt32("castTime"); battleCommand.maxRecastTimeSeconds = reader.GetUInt32("recastTime"); battleCommand.recastTimeMs = battleCommand.maxRecastTimeSeconds * 1000; - battleCommand.mpCost = reader.GetUInt16("mpCost"); - battleCommand.tpCost = reader.GetUInt16("tpCost"); + battleCommand.mpCost = reader.GetInt16("mpCost"); + battleCommand.tpCost = reader.GetInt16("tpCost"); battleCommand.animationType = reader.GetByte("animationType"); battleCommand.effectAnimation = reader.GetUInt16("effectAnimation"); battleCommand.modelAnimation = reader.GetUInt16("modelAnimation"); diff --git a/FFXIVClassic Map Server/actors/chara/Character.cs b/FFXIVClassic Map Server/actors/chara/Character.cs index 04f927e1..eb40c22e 100644 --- a/FFXIVClassic Map Server/actors/chara/Character.cs +++ b/FFXIVClassic Map Server/actors/chara/Character.cs @@ -1160,8 +1160,8 @@ namespace FFXIVClassic_Map_Server.Actors public List GetPartyMembersInRange(uint range) { TargetFind targetFind = new TargetFind(this); - targetFind.SetAOEType(ValidTarget.PartyMember, TargetFindAOEType.Circle, TargetFindAOETarget.Self, range, 0, 10, 0, 0); - targetFind.FindWithinArea(this, ValidTarget.PartyMember, TargetFindAOETarget.Self); + targetFind.SetAOEType(ValidTarget.Party, TargetFindAOEType.Circle, TargetFindAOETarget.Self, range, 0, 10, 0, 0); + targetFind.FindWithinArea(this, ValidTarget.Party, TargetFindAOETarget.Self); return targetFind.GetTargets(); } } diff --git a/FFXIVClassic Map Server/actors/chara/ai/BattleCommand.cs b/FFXIVClassic Map Server/actors/chara/ai/BattleCommand.cs index fa0cd2e3..7f96bd14 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/BattleCommand.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/BattleCommand.cs @@ -114,8 +114,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai public uint castTimeMs; //cast time in milliseconds public uint recastTimeMs; //recast time in milliseconds public uint maxRecastTimeSeconds; //maximum recast time in seconds - public ushort mpCost; - public ushort tpCost; + public short mpCost; //short in case these casts can have negative cost + public short tpCost; //short because there are certain cases where we want weaponskills to have negative costs (such as Feint) public byte animationType; public ushort effectAnimation; public ushort modelAnimation; @@ -193,10 +193,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai return castTimeMs == 0; } - //Checks whether the skill can be used on the given target - public bool IsValidMainTarget(Character user, Character target) + //Checks whether the skill can be used on the given targets, uses error to return specific text ids for errors + public bool IsValidMainTarget(Character user, Character target, CommandResult error = null) { - targetFind = new TargetFind(user); + targetFind = new TargetFind(user, target); if (aoeType == TargetFindAOEType.Box) { @@ -209,6 +209,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai /* worldMasterTextId + 32511 Target does not exist 32512 cannot be performed on a KO'd target. 32513 can only be performed on a KO'd target. 32514 cannot be performed on yourself. @@ -216,117 +217,112 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai 32516 cannot be performed on a friendly target. 32517 can only be performed on a friendly target. 32518 cannot be performed on an enemy. - 32519 can only be performed on an enemy, - 32556 unable to execute [weaponskill]. Conditions for use are not met. + 32519 can only be performed on an enemy. + 32547 That command cannot be performed on the current target. + 32548 That command cannot be performed on a party member */ - - // cant target dead - if ((mainTarget & (ValidTarget.Corpse | ValidTarget.CorpseOnly)) == 0 && target.IsDead()) + if (target == null) { - // cannot be perfomed on - if (user is Player) - ((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32512, 0x20, (uint)id); + error?.SetTextId(32511); return false; } - //level too high - if (level > user.GetLevel()) + //This skill can't be used on a corpse and target is dead + if ((mainTarget & ValidTarget.Corpse) == 0 && target.IsDead()) { - if (user is Player) - ((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32527, 0x20, (uint)id); - //return false; - } - - //Proc requirement - if (procRequirement != BattleCommandProcRequirement.None && !user.charaWork.battleTemp.timingCommandFlag[(int) procRequirement - 1]) - { - if (user is Player) - ((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32556, 0x20, (uint)id); + error?.SetTextId(32512); return false; } - //costs too much tp - if (CalculateTpCost(user) > user.GetTP()) + //This skill must be used on a corpse and target is alive + if ((mainTarget & ValidTarget.CorpseOnly) != 0 && target.IsAlive()) { - if (user is Player) - ((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32546, 0x20, (uint)id); + error?.SetTextId(32513); return false; } - // todo: calculate cost based on modifiers also (probably in BattleUtils) - if (BattleUtils.CalculateSpellCost(user, target, this) > user.GetMP()) + //This skill can't be used on self and target is self + if ((mainTarget & ValidTarget.Self) == 0 && target == user) { - if (user is Player) - ((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32545, 0x20, (uint)id); + error?.SetTextId(32514); return false; } - // todo: check target requirements - if (requirements != BattleCommandRequirements.None) + //This skill must be used on self and target isn't self + if ((mainTarget & ValidTarget.SelfOnly) != 0 && target != user) { - if (false) - { - // Unable to execute [@SHEET(xtx/command,$E8(1),2)]. Conditions for use are not met. - if (user is Player) - ((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32556, 0x20, (uint)id); - return false; - } + error?.SetTextId(32515); + return false; } - - // todo: i dont care to message for each scenario, just the most common ones.. - if ((mainTarget & ValidTarget.CorpseOnly) != 0) + //This skill can't be used on an ally and target is an ally + if ((mainTarget & ValidTarget.Ally) == 0 && target.allegiance == user.allegiance) { - if (target != null && target.IsAlive()) - { - if (user is Player) - ((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32513, 0x20, (uint)id); - return false; - } + error?.SetTextId(32516); + return false; } - if ((mainTarget & ValidTarget.Enemy) != 0) + //This skill must be used on an ally and target is not an ally + if ((mainTarget & ValidTarget.AllyOnly) != 0 && target.allegiance != user.allegiance) { - if (target == user || target != null && - user.allegiance == target.allegiance) - { - if (user is Player) - ((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32519, 0x20, (uint)id); - return false; - } + error?.SetTextId(32517); + return false; } - if ((mainTarget & ValidTarget.Ally) != 0) + //This skill can't be used on an enemu and target is an enemy + if ((mainTarget & ValidTarget.Enemy) == 0 && target.allegiance != user.allegiance) { - if (target == null || target.allegiance != user.allegiance) - { - if (user is Player) - ((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32517, 0x20, (uint)id); - return false; - } + error?.SetTextId(32518); + return false; } - if ((mainTarget & ValidTarget.PartyMember) != 0) + //This skill must be used on an enemy and target is an ally + if ((mainTarget & ValidTarget.EnemyOnly) != 0 && target.allegiance == user.allegiance) { - if (target == null || target.currentParty != user.currentParty) - { - if (user is Player) - ((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32547, 0x20, (uint)id); - return false; - } + error?.SetTextId(32519); + return false; } - if ((mainTarget & ValidTarget.Player) != 0) + //This skill can't be used on party members and target is a party member + if ((mainTarget & ValidTarget.Party) == 0 && target.currentParty == user.currentParty) { - if (!(target is Player)) - { - if (user is Player) - ((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32517, 0x20, (uint)id); - return false; - } + error?.SetTextId(32548); + return false; } - return true;// targetFind.CanTarget(target, true, true, true); //this will be done later + //This skill must be used on party members and target is not a party member + if ((mainTarget & ValidTarget.PartyOnly) != 0 && target.currentParty != user.currentParty) + { + error?.SetTextId(32547); + return false; + } + + //This skill can't be used on NPCs and target is an npc + if ((mainTarget & ValidTarget.NPC) == 0 && target.isStatic) + { + error?.SetTextId(32547); + return false; + } + + //This skill must be used on NPCs and target is not an npc + if ((mainTarget & ValidTarget.NPCOnly) != 0 && !target.isStatic) + { + error?.SetTextId(32547); + return false; + } + + // todo: why is player always zoning? + // cant target if zoning + if (target is Player && ((Player)target).playerSession.isUpdatesLocked) + { + user.aiContainer.ChangeTarget(null); + return false; + } + + if (target.zone != user.zone) + return false; + + return true; } public ushort CalculateMpCost(Character user) @@ -368,15 +364,15 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai //Calculate TP cost taking into considerating the combo bonus rate for players //Should this set tpCost or should it be called like CalculateMp where it gets calculated each time? //Might cause issues with the delay between starting and finishing a WS - public ushort CalculateTpCost(Character user) + public short CalculateTpCost(Character user) { - ushort tp = tpCost; + short tp = tpCost; //Calculate tp cost if (user is Player) { var player = user as Player; if (player.playerWork.comboNextCommandId[0] == id || player.playerWork.comboNextCommandId[1] == id) - tp = (ushort)Math.Ceiling((float)tpCost * (1 - player.playerWork.comboCostBonusRate)); + tp = (short)Math.Ceiling((float)tpCost * (1 - player.playerWork.comboCostBonusRate)); } return tp; diff --git a/FFXIVClassic Map Server/actors/chara/ai/helpers/TargetFind.cs b/FFXIVClassic Map Server/actors/chara/ai/helpers/TargetFind.cs index 77814d67..5801debe 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/helpers/TargetFind.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/helpers/TargetFind.cs @@ -14,21 +14,24 @@ using FFXIVClassic_Map_Server.packets.send.actor; namespace FFXIVClassic_Map_Server.actors.chara.ai { - // https://github.com/Windower/POLUtils/blob/master/PlayOnline.FFXI/Enums.cs [Flags] public enum ValidTarget : ushort { None = 0x00, - Self = 0x01, - Player = 0x02, - PartyMember = 0x04, - Ally = 0x08, - NPC = 0x10, - Enemy = 0x20, - Unknown = 0x40, - Object = 0x60, - CorpseOnly = 0x80, - Corpse = 0x9D // CorpseOnly + NPC + Ally + Partymember + Self + Self = 0x01, //Can be used on self (if this flag isn't set and target is self, return false) + SelfOnly = 0x02, //Must be used on self (if this flag is set and target isn't self, return false) + Party = 0x4, //Can be used on party members + PartyOnly = 0x8, //Must be used on party members + Ally = 0x10, //Can be used on allies + AllyOnly = 0x20, //Must be used on allies + NPC = 0x40, //Can be used on static NPCs + NPCOnly = 0x60, //Must be used on static NPCs + Enemy = 0x80, //Can be used on enemies + EnemyOnly = 0x100, //Must be used on enemies + Object = 0x200, //Can be used on objects + ObjectOnly = 0x400, //Must be used on objects + Corpse = 0x600, //Can be used on corpses + CorpseOnly = 0x800, //Must be used on corpses } /// Targeting from/to different entity types @@ -70,12 +73,13 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai class TargetFind { private Character owner; - private Character masterTarget; // if target is a pet, this is the owner + private Character mainTarget; //This is the target that the skill is being used on + private Character masterTarget; //If mainTarget is a pet, this is the owner private TargetFindCharacterType findType; private ValidTarget validTarget; private TargetFindAOETarget aoeTarget; private TargetFindAOEType aoeType; - private Vector3 aoeTargetPosition; //This is the center of circle an cone AOEs and the position where line aoes come out + private Vector3 aoeTargetPosition; //This is the center of circle of cone AOEs and the position where line aoes come out. If we have mainTarget this might not be needed? private float aoeTargetRotation; //This is the direction the aoe target is facing private float maxDistance; //Radius for circle and cone AOEs, length for line AOEs private float minDistance; //Minimum distance to that target must be to be able to be hit @@ -86,14 +90,16 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai private float param; private List targets; - public TargetFind(Character owner) + public TargetFind(Character owner, Character mainTarget = null) { - this.owner = owner; Reset(); + this.owner = owner; + this.masterTarget = mainTarget == null ? owner : mainTarget; } public void Reset() { + this.mainTarget = owner; this.findType = TargetFindCharacterType.None; this.validTarget = ValidTarget.Enemy; this.aoeType = TargetFindAOEType.None; @@ -205,11 +211,6 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai //if (targets.Count > 8) //targets.RemoveRange(8, targets.Count - 8); - - //Curaga starts with lowest health players, so the targets are definitely sorted at least for some abilities - //Other aoe abilities might be sorted by distance? - //Protect is random - targets.Sort(delegate (Character a, Character b) { return a.GetHP().CompareTo(b.GetHP()); }); } /// @@ -327,41 +328,57 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai if (target == null || !retarget && targets.Contains(target)) return false; - //This skill can't be used on self and target is self, return false - if ((validTarget & ValidTarget.Self) == 0 && target == owner) + if (target == null) return false; - //This skill can't be used on NPCs and target is an NPC, return false - if ((validTarget & ValidTarget.NPC) == 0 && target.isStatic) - return false; - - //This skill can't be used on corpses and target is dead, return false + //This skill can't be used on a corpse and target is dead if ((validTarget & ValidTarget.Corpse) == 0 && target.IsDead()) return false; - //This skill must be used on Allies and target is not an ally, return false - if ((validTarget & ValidTarget.Ally) != 0 && target.allegiance != owner.allegiance) - return false; - - - //This skill can't be used on players and target is a player, return false - //Do we need a player flag? Ally/Enemy flags probably serve the same purpose - //if ((validTarget & ValidTarget.Player) == 0 && target is Player) - //return false; - - - //This skill must be used on enemies an target is not an enemy - if ((validTarget & ValidTarget.Enemy) != 0 && target.allegiance == owner.allegiance) - return false; - - //This skill must be used on a party member and target is not in owner's party, return false - if ((validTarget & ValidTarget.PartyMember) != 0 && target.currentParty != owner.currentParty) - return false; - - //This skill must be used on a corpse and target is alive, return false + //This skill must be used on a corpse and target is alive if ((validTarget & ValidTarget.CorpseOnly) != 0 && target.IsAlive()) return false; + //This skill can't be used on self and target is self + if ((validTarget & ValidTarget.Self) == 0 && target == owner) + return false; + + //This skill must be used on self and target isn't self + if ((validTarget & ValidTarget.SelfOnly) != 0 && target != owner) + return false; + + //This skill can't be used on an ally and target is an ally + if ((validTarget & ValidTarget.Ally) == 0 && target.allegiance == owner.allegiance) + return false; + + //This skill must be used on an ally and target is not an ally + if ((validTarget & ValidTarget.AllyOnly) != 0 && target.allegiance != owner.allegiance) + return false; + + //This skill can't be used on an enemu and target is an enemy + if ((validTarget & ValidTarget.Enemy) == 0 && target.allegiance != owner.allegiance) + return false; + + //This skill must be used on an enemy and target is an ally + if ((validTarget & ValidTarget.EnemyOnly) != 0 && target.allegiance == owner.allegiance) + return false; + + //This skill can't be used on party members and target is a party member + if ((validTarget & ValidTarget.Party) == 0 && target.currentParty == owner.currentParty) + return false; + + //This skill must be used on party members and target is not a party member + if ((validTarget & ValidTarget.PartyOnly) != 0 && target.currentParty != owner.currentParty) + return false; + + //This skill can't be used on NPCs and target is an npc + if ((validTarget & ValidTarget.NPC) == 0 && target.isStatic) + return false; + + //This skill must be used on NPCs and target is not an npc + if ((validTarget & ValidTarget.NPCOnly) != 0 && !target.isStatic) + return false; + // todo: why is player always zoning? // cant target if zoning if (target is Player && ((Player)target).playerSession.isUpdatesLocked)