BattleCommand changes

New targeting flags for BattleCommand and TargetFind. (This breaks
combat until new BattleCommand data is in)
Changed MP and TP to shorts in BattleCommand to handle cases where they
might go negative. (might not be correct?)
This commit is contained in:
Yogurt 2019-05-29 19:24:58 -07:00
parent 3e58cdbd6c
commit a458608322
4 changed files with 148 additions and 135 deletions

View File

@ -2431,8 +2431,8 @@ namespace FFXIVClassic_Map_Server
battleCommand.castTimeMs = reader.GetUInt32("castTime"); battleCommand.castTimeMs = reader.GetUInt32("castTime");
battleCommand.maxRecastTimeSeconds = reader.GetUInt32("recastTime"); battleCommand.maxRecastTimeSeconds = reader.GetUInt32("recastTime");
battleCommand.recastTimeMs = battleCommand.maxRecastTimeSeconds * 1000; battleCommand.recastTimeMs = battleCommand.maxRecastTimeSeconds * 1000;
battleCommand.mpCost = reader.GetUInt16("mpCost"); battleCommand.mpCost = reader.GetInt16("mpCost");
battleCommand.tpCost = reader.GetUInt16("tpCost"); battleCommand.tpCost = reader.GetInt16("tpCost");
battleCommand.animationType = reader.GetByte("animationType"); battleCommand.animationType = reader.GetByte("animationType");
battleCommand.effectAnimation = reader.GetUInt16("effectAnimation"); battleCommand.effectAnimation = reader.GetUInt16("effectAnimation");
battleCommand.modelAnimation = reader.GetUInt16("modelAnimation"); battleCommand.modelAnimation = reader.GetUInt16("modelAnimation");

View File

@ -1160,8 +1160,8 @@ namespace FFXIVClassic_Map_Server.Actors
public List<Character> GetPartyMembersInRange(uint range) public List<Character> GetPartyMembersInRange(uint range)
{ {
TargetFind targetFind = new TargetFind(this); TargetFind targetFind = new TargetFind(this);
targetFind.SetAOEType(ValidTarget.PartyMember, TargetFindAOEType.Circle, TargetFindAOETarget.Self, range, 0, 10, 0, 0); targetFind.SetAOEType(ValidTarget.Party, TargetFindAOEType.Circle, TargetFindAOETarget.Self, range, 0, 10, 0, 0);
targetFind.FindWithinArea(this, ValidTarget.PartyMember, TargetFindAOETarget.Self); targetFind.FindWithinArea(this, ValidTarget.Party, TargetFindAOETarget.Self);
return targetFind.GetTargets(); return targetFind.GetTargets();
} }
} }

View File

@ -114,8 +114,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
public uint castTimeMs; //cast time in milliseconds public uint castTimeMs; //cast time in milliseconds
public uint recastTimeMs; //recast time in milliseconds public uint recastTimeMs; //recast time in milliseconds
public uint maxRecastTimeSeconds; //maximum recast time in seconds public uint maxRecastTimeSeconds; //maximum recast time in seconds
public ushort mpCost; public short mpCost; //short in case these casts can have negative cost
public ushort tpCost; public short tpCost; //short because there are certain cases where we want weaponskills to have negative costs (such as Feint)
public byte animationType; public byte animationType;
public ushort effectAnimation; public ushort effectAnimation;
public ushort modelAnimation; public ushort modelAnimation;
@ -193,10 +193,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
return castTimeMs == 0; return castTimeMs == 0;
} }
//Checks whether the skill can be used on the given 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) public bool IsValidMainTarget(Character user, Character target, CommandResult error = null)
{ {
targetFind = new TargetFind(user); targetFind = new TargetFind(user, target);
if (aoeType == TargetFindAOEType.Box) if (aoeType == TargetFindAOEType.Box)
{ {
@ -209,6 +209,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
/* /*
worldMasterTextId worldMasterTextId
32511 Target does not exist
32512 cannot be performed on a KO'd target. 32512 cannot be performed on a KO'd target.
32513 can only be performed on a KO'd target. 32513 can only be performed on a KO'd target.
32514 cannot be performed on yourself. 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. 32516 cannot be performed on a friendly target.
32517 can only be performed on a friendly target. 32517 can only be performed on a friendly target.
32518 cannot be performed on an enemy. 32518 cannot be performed on an enemy.
32519 can only be performed on an enemy, 32519 can only be performed on an enemy.
32556 unable to execute [weaponskill]. Conditions for use are not met. 32547 That command cannot be performed on the current target.
32548 That command cannot be performed on a party member
*/ */
if (target == null)
// cant target dead
if ((mainTarget & (ValidTarget.Corpse | ValidTarget.CorpseOnly)) == 0 && target.IsDead())
{ {
// cannot be perfomed on error?.SetTextId(32511);
if (user is Player)
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32512, 0x20, (uint)id);
return false; return false;
} }
//level too high //This skill can't be used on a corpse and target is dead
if (level > user.GetLevel()) if ((mainTarget & ValidTarget.Corpse) == 0 && target.IsDead())
{ {
if (user is Player) error?.SetTextId(32512);
((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);
return false; return false;
} }
//costs too much tp //This skill must be used on a corpse and target is alive
if (CalculateTpCost(user) > user.GetTP()) if ((mainTarget & ValidTarget.CorpseOnly) != 0 && target.IsAlive())
{ {
if (user is Player) error?.SetTextId(32513);
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32546, 0x20, (uint)id);
return false; return false;
} }
// todo: calculate cost based on modifiers also (probably in BattleUtils) //This skill can't be used on self and target is self
if (BattleUtils.CalculateSpellCost(user, target, this) > user.GetMP()) if ((mainTarget & ValidTarget.Self) == 0 && target == user)
{ {
if (user is Player) error?.SetTextId(32514);
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32545, 0x20, (uint)id);
return false; return false;
} }
// todo: check target requirements //This skill must be used on self and target isn't self
if (requirements != BattleCommandRequirements.None) if ((mainTarget & ValidTarget.SelfOnly) != 0 && target != user)
{ {
if (false) error?.SetTextId(32515);
{
// 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; return false;
} }
}
//This skill can't be used on an ally and target is an ally
// todo: i dont care to message for each scenario, just the most common ones.. if ((mainTarget & ValidTarget.Ally) == 0 && target.allegiance == user.allegiance)
if ((mainTarget & ValidTarget.CorpseOnly) != 0)
{ {
if (target != null && target.IsAlive()) error?.SetTextId(32516);
{
if (user is Player)
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32513, 0x20, (uint)id);
return false; 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 && error?.SetTextId(32517);
user.allegiance == target.allegiance)
{
if (user is Player)
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32519, 0x20, (uint)id);
return false; 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) error?.SetTextId(32518);
{
if (user is Player)
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32517, 0x20, (uint)id);
return false; 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) error?.SetTextId(32519);
{
if (user is Player)
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32547, 0x20, (uint)id);
return false; 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)) error?.SetTextId(32548);
{
if (user is Player)
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32517, 0x20, (uint)id);
return false; return false;
} }
//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;
} }
return true;// targetFind.CanTarget(target, true, true, true); //this will be done later //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) 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 //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? //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 //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 //Calculate tp cost
if (user is Player) if (user is Player)
{ {
var player = user as Player; var player = user as Player;
if (player.playerWork.comboNextCommandId[0] == id || player.playerWork.comboNextCommandId[1] == id) 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; return tp;

View File

@ -14,21 +14,24 @@ using FFXIVClassic_Map_Server.packets.send.actor;
namespace FFXIVClassic_Map_Server.actors.chara.ai namespace FFXIVClassic_Map_Server.actors.chara.ai
{ {
// https://github.com/Windower/POLUtils/blob/master/PlayOnline.FFXI/Enums.cs
[Flags] [Flags]
public enum ValidTarget : ushort public enum ValidTarget : ushort
{ {
None = 0x00, None = 0x00,
Self = 0x01, Self = 0x01, //Can be used on self (if this flag isn't set and target is self, return false)
Player = 0x02, SelfOnly = 0x02, //Must be used on self (if this flag is set and target isn't self, return false)
PartyMember = 0x04, Party = 0x4, //Can be used on party members
Ally = 0x08, PartyOnly = 0x8, //Must be used on party members
NPC = 0x10, Ally = 0x10, //Can be used on allies
Enemy = 0x20, AllyOnly = 0x20, //Must be used on allies
Unknown = 0x40, NPC = 0x40, //Can be used on static NPCs
Object = 0x60, NPCOnly = 0x60, //Must be used on static NPCs
CorpseOnly = 0x80, Enemy = 0x80, //Can be used on enemies
Corpse = 0x9D // CorpseOnly + NPC + Ally + Partymember + Self 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
} }
/// <summary> Targeting from/to different entity types </summary> /// <summary> Targeting from/to different entity types </summary>
@ -70,12 +73,13 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
class TargetFind class TargetFind
{ {
private Character owner; 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 TargetFindCharacterType findType;
private ValidTarget validTarget; private ValidTarget validTarget;
private TargetFindAOETarget aoeTarget; private TargetFindAOETarget aoeTarget;
private TargetFindAOEType aoeType; 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 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 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 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 float param;
private List<Character> targets; private List<Character> targets;
public TargetFind(Character owner) public TargetFind(Character owner, Character mainTarget = null)
{ {
this.owner = owner;
Reset(); Reset();
this.owner = owner;
this.masterTarget = mainTarget == null ? owner : mainTarget;
} }
public void Reset() public void Reset()
{ {
this.mainTarget = owner;
this.findType = TargetFindCharacterType.None; this.findType = TargetFindCharacterType.None;
this.validTarget = ValidTarget.Enemy; this.validTarget = ValidTarget.Enemy;
this.aoeType = TargetFindAOEType.None; this.aoeType = TargetFindAOEType.None;
@ -205,11 +211,6 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
//if (targets.Count > 8) //if (targets.Count > 8)
//targets.RemoveRange(8, 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()); });
} }
/// <summary> /// <summary>
@ -327,41 +328,57 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
if (target == null || !retarget && targets.Contains(target)) if (target == null || !retarget && targets.Contains(target))
return false; return false;
//This skill can't be used on self and target is self, return false if (target == null)
if ((validTarget & ValidTarget.Self) == 0 && target == owner)
return false; return false;
//This skill can't be used on NPCs and target is an NPC, return false //This skill can't be used on a corpse and target is dead
if ((validTarget & ValidTarget.NPC) == 0 && target.isStatic)
return false;
//This skill can't be used on corpses and target is dead, return false
if ((validTarget & ValidTarget.Corpse) == 0 && target.IsDead()) if ((validTarget & ValidTarget.Corpse) == 0 && target.IsDead())
return false; return false;
//This skill must be used on Allies and target is not an ally, return false //This skill must be used on a corpse and target is alive
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
if ((validTarget & ValidTarget.CorpseOnly) != 0 && target.IsAlive()) if ((validTarget & ValidTarget.CorpseOnly) != 0 && target.IsAlive())
return false; 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? // todo: why is player always zoning?
// cant target if zoning // cant target if zoning
if (target is Player && ((Player)target).playerSession.isUpdatesLocked) if (target is Player && ((Player)target).playerSession.isUpdatesLocked)