mirror of
https://bitbucket.org/Ioncannon/project-meteor-server.git
synced 2025-06-10 14:34:32 +02:00
AoE rewrite and bug fixes
Rewrote aoe checks for cone and line aoes and added minimum distance values Added height checks for commands Fixed combo effects repeating for every target hit by AoE attacks Fixed teleport sometimes not raising (I think) Fixed gear checks in some command scripts
This commit is contained in:
parent
8c5375f609
commit
cf30eef39e
34 changed files with 483 additions and 330 deletions
|
@ -2258,7 +2258,7 @@ namespace FFXIVClassic_Map_Server
|
|||
int count = 0;
|
||||
conn.Open();
|
||||
|
||||
var query = ("SELECT `id`, name, classJob, lvl, requirements, mainTarget, validTarget, aoeType, aoeRange, aoeTarget, basePotency, numHits, positionBonus, procRequirement, `range`, statusId, statusDuration, statusChance, " +
|
||||
var query = ("SELECT `id`, name, classJob, lvl, requirements, mainTarget, validTarget, aoeType, aoeRange, aoeMinRange, aoeConeAngle, aoeRotateAngle, aoeTarget, basePotency, numHits, positionBonus, procRequirement, `range`, minRange, rangeHeight, rangeWidth, statusId, statusDuration, statusChance, " +
|
||||
"castType, castTime, recastTime, mpCost, tpCost, animationType, effectAnimation, modelAnimation, animationDuration, battleAnimation, validUser, comboId1, comboId2, comboStep, accuracyMod, worldMasterTextId, commandType, actionType, actionProperty FROM server_battle_commands;");
|
||||
|
||||
MySqlCommand cmd = new MySqlCommand(query, conn);
|
||||
|
@ -2281,7 +2281,10 @@ namespace FFXIVClassic_Map_Server
|
|||
battleCommand.numHits = reader.GetByte("numHits");
|
||||
battleCommand.positionBonus = (BattleCommandPositionBonus)reader.GetByte("positionBonus");
|
||||
battleCommand.procRequirement = (BattleCommandProcRequirement)reader.GetByte("procRequirement");
|
||||
battleCommand.range = reader.GetInt32("range");
|
||||
battleCommand.range = reader.GetFloat("range");
|
||||
battleCommand.minRange = reader.GetFloat("minRange");
|
||||
battleCommand.rangeHeight = reader.GetInt32("rangeHeight");
|
||||
battleCommand.rangeWidth = reader.GetInt32("rangeWidth");
|
||||
battleCommand.statusId = reader.GetUInt32("statusId");
|
||||
battleCommand.statusDuration = reader.GetUInt32("statusDuration");
|
||||
battleCommand.statusChance = reader.GetFloat("statusChance");
|
||||
|
@ -2295,7 +2298,10 @@ namespace FFXIVClassic_Map_Server
|
|||
battleCommand.effectAnimation = reader.GetUInt16("effectAnimation");
|
||||
battleCommand.modelAnimation = reader.GetUInt16("modelAnimation");
|
||||
battleCommand.animationDurationSeconds = reader.GetUInt16("animationDuration");
|
||||
battleCommand.aoeRange = reader.GetInt32("aoeRange");
|
||||
battleCommand.aoeRange = reader.GetFloat("aoeRange");
|
||||
battleCommand.aoeMinRange = reader.GetFloat("aoeMinRange");
|
||||
battleCommand.aoeConeAngle = reader.GetFloat("aoeConeAngle");
|
||||
battleCommand.aoeRotateAngle = reader.GetFloat("aoeRotateAngle");
|
||||
battleCommand.aoeTarget = (TargetFindAOETarget)reader.GetByte("aoeTarget");
|
||||
|
||||
battleCommand.battleAnimation = reader.GetUInt32("battleAnimation");
|
||||
|
|
|
@ -657,9 +657,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
|
||||
var dX = this.positionX - x;
|
||||
var dY = this.positionZ - z;
|
||||
|
||||
var rot2 = Math.Atan2(dY, dX);
|
||||
|
||||
var dRot = Math.PI - rot2 + Math.PI / 2;
|
||||
|
||||
// pending move, dont need to unset it
|
||||
|
@ -668,10 +666,11 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
}
|
||||
|
||||
// todo: is this legit?
|
||||
public bool IsFacing(float x, float z, float angle = 40.0f)
|
||||
public bool IsFacing(float x, float z, float angle = 90.0f)
|
||||
{
|
||||
angle = (float)(Math.PI * angle / 180);
|
||||
return Math.Abs(Vector3.GetAngle(positionX, positionZ, x, z) - rotation) < angle;
|
||||
var a = Vector3.GetAngle(positionX, positionZ, x, z);
|
||||
return new Vector3(x, 0, z).IsWithinCone(GetPosAsVector3(), rotation, angle);
|
||||
}
|
||||
|
||||
public bool IsFacing(Actor target, float angle = 40.0f)
|
||||
|
|
|
@ -828,7 +828,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
//TP gained on an attack is usually 100 * delay.
|
||||
//Store TP seems to add .1% per point.
|
||||
double weaponDelay = GetMod(Modifier.AttackDelay) / 1000.0;
|
||||
var storeTPPercent = 1 + (GetMod(Modifier.StoreTP) * 0.1);
|
||||
var storeTPPercent = 1 + (GetMod(Modifier.StoreTP) * 0.001);
|
||||
AddTP((int)(weaponDelay * 100 * storeTPPercent));
|
||||
}
|
||||
}
|
||||
|
@ -1106,7 +1106,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
}
|
||||
|
||||
//Now that we know if we hit the target we can check if the combo continues
|
||||
if (this is Player player && command.commandType == CommandType.WeaponSkill)
|
||||
if (this is Player player)
|
||||
if (command.isCombo && hitTarget)
|
||||
player.SetCombos(command.comboNextCommandId);
|
||||
else
|
||||
|
@ -1122,7 +1122,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
public List<Character> GetPartyMembersInRange(uint range)
|
||||
{
|
||||
TargetFind targetFind = new TargetFind(this);
|
||||
targetFind.SetAOEType(ValidTarget.PartyMember, TargetFindAOEType.Circle, TargetFindAOETarget.Self, range);
|
||||
targetFind.SetAOEType(ValidTarget.PartyMember, TargetFindAOEType.Circle, TargetFindAOETarget.Self, range, 0, 10, 0, 0);
|
||||
targetFind.FindWithinArea(this, ValidTarget.PartyMember, TargetFindAOETarget.Self);
|
||||
return targetFind.GetTargets();
|
||||
}
|
||||
|
|
|
@ -104,7 +104,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
public byte numHits; //amount of hits in the skill
|
||||
public BattleCommandPositionBonus positionBonus; //bonus for front/flank/rear
|
||||
public BattleCommandProcRequirement procRequirement;//if the skill requires a block/parry/evade before using
|
||||
public int range; //max distance to use skill
|
||||
public float range; //maximum distance to target to be able to use this skill
|
||||
public float minRange; //Minimum distance to target to be able to use this skill
|
||||
|
||||
public uint statusId; //id of statuseffect that the skill might inflict
|
||||
public uint statusDuration; //duration of statuseffect in milliseconds
|
||||
|
@ -121,7 +122,12 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
public ushort animationDurationSeconds;
|
||||
public uint battleAnimation;
|
||||
public ushort worldMasterTextId;
|
||||
public int aoeRange; //size of aoe effect. (how will this work for box aoes?)
|
||||
public float aoeRange; //Radius for circle and cone aoes, length for box aoes
|
||||
public float aoeMinRange; //Minimum range of aoe effect for things like Lunar Dynamo or Arrow Helix
|
||||
public float aoeConeAngle; //Angle of aoe cones
|
||||
public float aoeRotateAngle; //Amount aoes are rotated about the target position (usually the user's position)
|
||||
public float rangeHeight; //Total height a skill can be used against target above or below user
|
||||
public float rangeWidth; //Width of box aoes
|
||||
public int[] comboNextCommandId = new int[2]; //next two skills in a combo
|
||||
public short comboStep; //Where in a combo string this skill is
|
||||
public CommandType commandType;
|
||||
|
@ -130,12 +136,13 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
|
||||
|
||||
public byte statusTier; //tier of status to put on target
|
||||
public ulong statusMagnitude = 0; //magnitude of status to put on target
|
||||
public double statusMagnitude = 0; //magnitude of status to put on target
|
||||
public ushort basePotency; //damage variable
|
||||
public float enmityModifier; //multiples by damage done to get final enmity
|
||||
public float accuracyModifier; //modifies accuracy
|
||||
public float bonusCritRate; //extra crit rate
|
||||
public bool isCombo;
|
||||
public bool comboEffectAdded = false; //If the combo effect is added to multiple hiteffects it plays multiple times, so this keeps track of that
|
||||
public bool isRanged = false;
|
||||
|
||||
public bool actionCrit; //Whether any actions were critical hits, used for Excruciate
|
||||
|
@ -193,11 +200,11 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
|
||||
if (aoeType == TargetFindAOEType.Box)
|
||||
{
|
||||
targetFind.SetAOEBox(validTarget, aoeTarget, range, aoeRange);
|
||||
targetFind.SetAOEBox(validTarget, aoeTarget, aoeRange, rangeWidth, aoeRotateAngle);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetFind.SetAOEType(validTarget, aoeType, aoeTarget, range, aoeRange);
|
||||
targetFind.SetAOEType(validTarget, aoeType, aoeTarget, aoeRange, aoeMinRange, rangeHeight, aoeRotateAngle, aoeConeAngle);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -127,7 +127,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
return AddStatusEffect(se, owner);
|
||||
}
|
||||
|
||||
public bool AddStatusEffect(uint id, byte tier, UInt64 magnitude)
|
||||
public bool AddStatusEffect(uint id, byte tier, double magnitude)
|
||||
{
|
||||
var se = Server.GetWorldManager().GetStatusEffect(id);
|
||||
|
||||
|
@ -137,7 +137,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
return AddStatusEffect(se, owner);
|
||||
}
|
||||
|
||||
public bool AddStatusEffect(uint id, byte tier, UInt64 magnitude, uint duration, int tickMs = 3000)
|
||||
public bool AddStatusEffect(uint id, byte tier, double magnitude, uint duration, int tickMs = 3000)
|
||||
{
|
||||
var se = Server.GetWorldManager().GetStatusEffect(id);
|
||||
if (se != null)
|
||||
|
|
|
@ -240,7 +240,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
|
|||
|
||||
var targetPos = new Vector3(owner.target.positionX, owner.target.positionY, owner.target.positionZ);
|
||||
var distance = Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, targetPos.X, targetPos.Y, targetPos.Z);
|
||||
if (distance > owner.GetAttackRange() - 0.2f || owner.aiContainer.CanFollowPath())
|
||||
if (distance > owner.GetAttackRange() - 0.2f && owner.aiContainer.CanFollowPath())
|
||||
{
|
||||
if (CanMoveForward(distance))
|
||||
{
|
||||
|
|
|
@ -70,13 +70,19 @@ 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 masterTarget; // if target is a pet, this is the owner
|
||||
private TargetFindCharacterType findType;
|
||||
private ValidTarget validTarget;
|
||||
private TargetFindAOETarget aoeTarget;
|
||||
private TargetFindAOEType aoeType;
|
||||
private Vector3 targetPosition;
|
||||
private float maxDistance;
|
||||
private Vector3 aoeTargetPosition; //This is the center of circle an cone AOEs and the position where line aoes come out
|
||||
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
|
||||
private float width; //Width of line AOEs
|
||||
private float height; //All AoEs are boxes or cylinders. Height is usually 10y regardless of maxDistance, but some commands have different values. Height is total height, so targets can be at most half this distance away on Y axis
|
||||
private float aoeRotateAngle; //This is the angle that cones and line aoes are rotated about aoeTargetPosition for skills that come out of a side other than the front
|
||||
private float coneAngle; //The angle of the cone itself in Pi Radians
|
||||
private float param;
|
||||
private List<Character> targets;
|
||||
|
||||
|
@ -92,8 +98,14 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
this.validTarget = ValidTarget.Enemy;
|
||||
this.aoeType = TargetFindAOEType.None;
|
||||
this.aoeTarget = TargetFindAOETarget.Target;
|
||||
this.targetPosition = null;
|
||||
this.aoeTargetPosition = null;
|
||||
this.aoeTargetRotation = 0;
|
||||
this.maxDistance = 0.0f;
|
||||
this.minDistance = 0.0f;
|
||||
this.width = 0.0f;
|
||||
this.height = 0.0f;
|
||||
this.aoeRotateAngle = 0.0f;
|
||||
this.coneAngle = 0.0f;
|
||||
this.param = 0.0f;
|
||||
this.targets = new List<Character>();
|
||||
}
|
||||
|
@ -117,12 +129,16 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
/// <see cref="TargetFindAOEType.Box"/> - width of box / 2 (todo: set box length not just between user and target)
|
||||
/// </param>
|
||||
/// <param name="param"> param in degrees of cone (todo: probably use radians and forget converting at runtime) </param>
|
||||
public void SetAOEType(ValidTarget validTarget, TargetFindAOEType aoeType, TargetFindAOETarget aoeTarget, float maxDistance = -1.0f, float param = -1.0f)
|
||||
public void SetAOEType(ValidTarget validTarget, TargetFindAOEType aoeType, TargetFindAOETarget aoeTarget, float maxDistance, float minDistance, float height, float aoeRotate, float coneAngle, float param = 0.0f)
|
||||
{
|
||||
this.validTarget = validTarget;
|
||||
this.aoeType = aoeType;
|
||||
this.maxDistance = maxDistance != -1.0f ? maxDistance : 0.0f;
|
||||
this.param = param != -1.0f ? param : 0.0f;
|
||||
this.maxDistance = maxDistance;
|
||||
this.minDistance = minDistance;
|
||||
this.param = param;
|
||||
this.height = height;
|
||||
this.aoeRotateAngle = aoeRotate;
|
||||
this.coneAngle = coneAngle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -132,15 +148,16 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
/// <param name="aoeTarget"></param>
|
||||
/// <param name="length"></param>
|
||||
/// <param name="width"></param>
|
||||
public void SetAOEBox(ValidTarget validTarget, TargetFindAOETarget aoeTarget, float length, float width)
|
||||
public void SetAOEBox(ValidTarget validTarget, TargetFindAOETarget aoeTarget, float length, float width, float aoeRotateAngle)
|
||||
{
|
||||
this.validTarget = validTarget;
|
||||
this.aoeType = TargetFindAOEType.Box;
|
||||
this.aoeTarget = aoeTarget;
|
||||
this.aoeRotateAngle = aoeRotateAngle;
|
||||
float x = owner.positionX - (float)Math.Cos(owner.rotation + (float)(Math.PI / 2)) * (length);
|
||||
float z = owner.positionZ + (float)Math.Sin(owner.rotation + (float)(Math.PI / 2)) * (length);
|
||||
this.targetPosition = new Vector3(x, owner.positionY, z);
|
||||
this.maxDistance = width;
|
||||
this.maxDistance = length;
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -163,9 +180,15 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
validTarget = flags;
|
||||
// are we creating aoe circles around target or self
|
||||
if (aoeTarget == TargetFindAOETarget.Self)
|
||||
this.targetPosition = owner.GetPosAsVector3();
|
||||
{
|
||||
this.aoeTargetPosition = owner.GetPosAsVector3();
|
||||
this.aoeTargetRotation = owner.rotation + (float) (aoeRotateAngle * Math.PI);
|
||||
}
|
||||
else
|
||||
this.targetPosition = target.GetPosAsVector3();
|
||||
{
|
||||
this.aoeTargetPosition = target.GetPosAsVector3();
|
||||
this.aoeTargetRotation = target.rotation + (float) (aoeRotateAngle * Math.PI);
|
||||
}
|
||||
|
||||
masterTarget = TryGetMasterTarget(target) ?? target;
|
||||
|
||||
|
@ -179,66 +202,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
{
|
||||
AddAllInRange(target, withPet);
|
||||
}
|
||||
/*
|
||||
if (aoeType != TargetFindAOEType.None)
|
||||
{
|
||||
if (IsPlayer(owner))
|
||||
{
|
||||
if (masterTarget is Player)
|
||||
{
|
||||
findType = TargetFindCharacterType.PlayerToPlayer;
|
||||
|
||||
if (masterTarget.currentParty != null)
|
||||
{
|
||||
if ((validTarget & (ValidTarget.Ally | ValidTarget.PartyMember)) != 0)
|
||||
AddAllInAlliance(masterTarget, withPet);
|
||||
else
|
||||
AddAllInParty(masterTarget, withPet);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddTarget(masterTarget, withPet);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
findType = TargetFindCharacterType.PlayerToBattleNpc;
|
||||
AddAllBattleNpcs(masterTarget, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// todo: this needs checking..
|
||||
if (masterTarget is Player || owner.allegiance == CharacterTargetingAllegiance.Player)
|
||||
findType = TargetFindCharacterType.BattleNpcToPlayer;
|
||||
else
|
||||
findType = TargetFindCharacterType.BattleNpcToBattleNpc;
|
||||
|
||||
// todo: configurable pet aoe buff
|
||||
if (findType == TargetFindCharacterType.BattleNpcToBattleNpc && TryGetMasterTarget(target) != null)
|
||||
withPet = true;
|
||||
|
||||
// todo: does ffxiv have call for help flag?
|
||||
//if ((findFlags & ValidTarget.HitAll) != 0)
|
||||
//{
|
||||
// AddAllInZone(masterTarget, withPet);
|
||||
//}
|
||||
|
||||
AddAllInAlliance(target, withPet);
|
||||
|
||||
if (findType == TargetFindCharacterType.BattleNpcToPlayer)
|
||||
{
|
||||
if (owner.allegiance == CharacterTargetingAllegiance.Player)
|
||||
AddAllInZone(masterTarget, withPet);
|
||||
else
|
||||
AddAllInHateList();
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
if (targets.Count > 8)
|
||||
targets.RemoveRange(8, targets.Count - 8);
|
||||
//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?
|
||||
|
@ -252,28 +218,33 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
/// </summary>
|
||||
private bool IsWithinBox(Character target, bool withPet)
|
||||
{
|
||||
if (aoeTarget == TargetFindAOETarget.Self)
|
||||
targetPosition = owner.GetPosAsVector3();
|
||||
else
|
||||
targetPosition = target.GetPosAsVector3();
|
||||
Vector3 vec = target.GetPosAsVector3() - aoeTargetPosition;
|
||||
Vector3 relativePos = new Vector3();
|
||||
|
||||
var myPos = owner.GetPosAsVector3();
|
||||
var angle = Vector3.GetAngle(myPos, targetPosition);
|
||||
//Get target's position relative to owner's position where owner's front is facing positive z axis
|
||||
relativePos.X = (float)(vec.X * Math.Cos(aoeTargetRotation) - vec.Z * Math.Sin(aoeTargetRotation));
|
||||
relativePos.Z = (float)(vec.X * Math.Sin(aoeTargetRotation) + vec.Z * Math.Cos(aoeTargetRotation));
|
||||
|
||||
// todo: actually check this works..
|
||||
var myCorner = myPos.NewHorizontalVector(angle, maxDistance);
|
||||
var myCorner2 = myPos.NewHorizontalVector(angle, -maxDistance);
|
||||
float halfHeight = height / 2;
|
||||
float halfWidth = width / 2;
|
||||
|
||||
var targetCorner = targetPosition.NewHorizontalVector(angle, maxDistance);
|
||||
var targetCorner2 = targetPosition.NewHorizontalVector(angle, -maxDistance);
|
||||
Vector3 closeBottomLeft = new Vector3(-halfWidth, -halfHeight, minDistance);
|
||||
Vector3 farTopRight = new Vector3(halfWidth, halfHeight, maxDistance);
|
||||
|
||||
return target.GetPosAsVector3().IsWithinBox(targetCorner2, myCorner);
|
||||
return relativePos.IsWithinBox(closeBottomLeft, farTopRight);
|
||||
}
|
||||
|
||||
private bool IsWithinCone(Character target, bool withPet)
|
||||
{
|
||||
// todo: make this actual cone
|
||||
return owner.IsFacing(target, param) && Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, target.positionX, target.positionY, target.positionZ) < maxDistance;
|
||||
double distance = Utils.XZDistance(aoeTargetPosition, target.GetPosAsVector3());
|
||||
|
||||
//Make sure target is within the correct range first
|
||||
if (!IsWithinCircle(target))
|
||||
return false;
|
||||
|
||||
//This might not be 100% right or the most optimal way to do this
|
||||
//Get between taget's position and our position
|
||||
return target.GetPosAsVector3().IsWithinCone(aoeTargetPosition, aoeTargetRotation, coneAngle);
|
||||
}
|
||||
|
||||
private void AddTarget(Character target, bool withPet)
|
||||
|
@ -364,10 +335,15 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
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())
|
||||
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)
|
||||
|
@ -404,7 +380,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
if (!ignoreAOE)
|
||||
{
|
||||
// hit everything within zone or within aoe region
|
||||
if (param == -1.0f || aoeType == TargetFindAOEType.Circle && !IsWithinCircle(target, param))
|
||||
if (param == -1.0f || aoeType == TargetFindAOEType.Circle && !IsWithinCircle(target))
|
||||
return false;
|
||||
|
||||
if (aoeType == TargetFindAOEType.Cone && !IsWithinCone(target, withPet))
|
||||
|
@ -419,16 +395,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
return true;
|
||||
}
|
||||
|
||||
private bool IsWithinCircle(Character target, float maxDistance)
|
||||
private bool IsWithinCircle(Character target)
|
||||
{
|
||||
// todo: make y diff modifiable?
|
||||
|
||||
//if (Math.Abs(owner.positionX - target.positionY) > 6.0f)
|
||||
// return false;
|
||||
|
||||
if (this.targetPosition == null)
|
||||
this.targetPosition = aoeTarget == TargetFindAOETarget.Self ? owner.GetPosAsVector3() : masterTarget.GetPosAsVector3();
|
||||
return target.GetPosAsVector3().IsWithinCircle(targetPosition, maxDistance);
|
||||
//Check if XZ is in circle and that y difference isn't larger than half height
|
||||
return target.GetPosAsVector3().IsWithinCircle(aoeTargetPosition, maxDistance, minDistance) && Math.Abs(owner.positionY - target.positionY) <= (height / 2);
|
||||
}
|
||||
|
||||
private bool IsPlayer(Character target)
|
||||
|
|
|
@ -47,7 +47,20 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
}
|
||||
else
|
||||
{
|
||||
//owner.LookAt(target);
|
||||
if (!skill.IsInstantCast())
|
||||
{
|
||||
float castTime = skill.castTimeMs;
|
||||
|
||||
// command casting duration
|
||||
if (owner is Player)
|
||||
{
|
||||
// todo: modify spellSpeed based on modifiers and stuff
|
||||
((Player)owner).SendStartCastbar(skill.id, Utils.UnixTimeStampUTC(DateTime.Now.AddMilliseconds(castTime)));
|
||||
}
|
||||
owner.SendChant(0xf, 0x0);
|
||||
//You ready [skill] (6F000002: BLM, 6F000003: WHM, 0x6F000008: BRD)
|
||||
owner.DoBattleAction(skill.id, (uint)0x6F000000 | skill.castType, new BattleAction(target.actorId, 30126, 1, 0, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,7 +79,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
// todo: check weapon delay/haste etc and use that
|
||||
var actualCastTime = skill.castTimeMs;
|
||||
|
||||
if ((tick - startTime).Milliseconds >= skill.castTimeMs)
|
||||
if ((tick - startTime).TotalMilliseconds >= skill.castTimeMs)
|
||||
{
|
||||
OnComplete();
|
||||
return true;
|
||||
|
@ -88,6 +101,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
|
||||
public override void OnComplete()
|
||||
{
|
||||
owner.LookAt(target);
|
||||
bool hitTarget = false;
|
||||
|
||||
skill.targetFind.FindWithinArea(target, skill.validTarget, skill.aoeTarget);
|
||||
|
@ -130,6 +144,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
|
||||
public override void Cleanup()
|
||||
{
|
||||
owner.SendChant(0, 0);
|
||||
owner.aiContainer.UpdateLastActionTime(skill.animationDurationSeconds);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,7 +104,6 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
|
||||
//List<BattleAction> actions = new List<BattleAction>();
|
||||
BattleActionContainer actions = new BattleActionContainer();
|
||||
target.SetMod((uint) Modifier.MinimumHpLock, 0);
|
||||
|
||||
var i = 0;
|
||||
for (int hitNum = 0; hitNum < 1 /* owner.GetMod((uint) Modifier.HitCount)*/; hitNum++)
|
||||
|
@ -123,7 +122,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
BattleAction error = null;// new BattleAction(0, null, 0, 0);
|
||||
//owner.DoActions(null, actions.GetList(), ref error);
|
||||
//owner.OnAttack(this, actions[0], ref errorResult);
|
||||
owner.DoBattleAction(22104, 0x19001000, actions.GetList());
|
||||
var anim = (uint)(17 << 24 | 1 << 12);
|
||||
owner.DoBattleAction(22104, anim, actions.GetList());
|
||||
}
|
||||
|
||||
public override void TryInterrupt()
|
||||
|
@ -161,6 +161,11 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
{
|
||||
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())
|
||||
{
|
||||
|
@ -179,7 +184,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
{
|
||||
if (owner is Player)
|
||||
{
|
||||
((Player)owner).SendGameMessage(Server.GetWorldManager().GetActor(), 32539, 0x20);
|
||||
//The target is too far away
|
||||
((Player)owner).SendGameMessage(Server.GetWorldManager().GetActor(), 32537, 0x20);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -61,18 +61,21 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
// todo: check within attack range
|
||||
float[] baseCastDuration = { 1.0f, 0.25f };
|
||||
|
||||
//There are no positional spells, so just check onCombo, need to check first because certain spells change aoe type/accuracy
|
||||
//If owner is a player and the spell being used is part of the current combo
|
||||
if (owner is Player p && p.GetClass() == spell.job)
|
||||
{
|
||||
if (spell.comboStep == 1 || ((p.playerWork.comboNextCommandId[0] == spell.id || p.playerWork.comboNextCommandId[1] == spell.id)))
|
||||
{
|
||||
lua.LuaEngine.CallLuaBattleCommandFunction(owner, spell, "magic", "onCombo", owner, target, spell);
|
||||
spell.isCombo = true;
|
||||
}
|
||||
}
|
||||
|
||||
//Check combo stuff here because combos can impact spell cast times
|
||||
|
||||
float spellSpeed = spell.castTimeMs;
|
||||
|
||||
//There are no positional spells, so just check onCombo, need to check first because certain spells change aoe type/accuracy
|
||||
//If owner is a player and the spell being used is part of the current combo
|
||||
if (spell.comboStep == 1 || ((owner is Player p) && (p.playerWork.comboNextCommandId[0] == spell.id || p.playerWork.comboNextCommandId[1] == spell.id)))
|
||||
{
|
||||
lua.LuaEngine.CallLuaBattleCommandFunction(owner, spell, "magic", "onCombo", owner, target, spell);
|
||||
spell.isCombo = true;
|
||||
}
|
||||
|
||||
if (!spell.IsInstantCast())
|
||||
{
|
||||
// command casting duration
|
||||
|
|
|
@ -46,7 +46,6 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
}
|
||||
else
|
||||
{
|
||||
owner.LookAt(target);
|
||||
hitDirection = owner.GetHitDirection(target);
|
||||
|
||||
//Do positionals and combo effects first because these can influence accuracy and amount of targets/numhits, which influence the rest of the steps
|
||||
|
@ -76,6 +75,21 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
skill.isCombo = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!skill.IsInstantCast())
|
||||
{
|
||||
float castTime = skill.castTimeMs;
|
||||
|
||||
// command casting duration
|
||||
if (owner is Player)
|
||||
{
|
||||
// todo: modify spellSpeed based on modifiers and stuff
|
||||
((Player)owner).SendStartCastbar(skill.id, Utils.UnixTimeStampUTC(DateTime.Now.AddMilliseconds(castTime)));
|
||||
}
|
||||
owner.SendChant(0xf, 0x0);
|
||||
//You ready [skill] (6F000002: BLM, 6F000003: WHM, 0x6F000008: BRD)
|
||||
owner.DoBattleAction(skill.id, (uint)0x6F000000 | skill.castType, new BattleAction(target.actorId, 30126, 1, 0, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +109,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
// todo: check weapon delay/haste etc and use that
|
||||
var actualCastTime = skill.castTimeMs;
|
||||
|
||||
if ((tick - startTime).Milliseconds >= skill.castTimeMs)
|
||||
if ((tick - startTime).TotalMilliseconds >= skill.castTimeMs)
|
||||
{
|
||||
OnComplete();
|
||||
return true;
|
||||
|
@ -117,6 +131,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
|
||||
public override void OnComplete()
|
||||
{
|
||||
owner.LookAt(target);
|
||||
skill.targetFind.FindWithinArea(target, skill.validTarget, skill.aoeTarget);
|
||||
isCompleted = true;
|
||||
|
||||
|
|
|
@ -542,8 +542,11 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||
hitEffect |= HitTypeEffects[hitType];
|
||||
|
||||
//For combos that land, add the combo effect
|
||||
if (skill != null && skill.isCombo && hitType > HitType.Evade && hitType != HitType.Evade)
|
||||
if (skill != null && skill.isCombo && hitType > HitType.Evade && hitType != HitType.Evade && !skill.comboEffectAdded)
|
||||
{
|
||||
hitEffect |= (HitEffect)(skill.comboStep << 15);
|
||||
skill.comboEffectAdded = true;
|
||||
}
|
||||
|
||||
//if attack hit the target, take into account protective status effects
|
||||
if (hitType >= HitType.Parry)
|
||||
|
@ -581,8 +584,11 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||
|
||||
hitEffect |= HitTypeEffects[hitType];
|
||||
|
||||
if (skill != null && skill.isCombo)
|
||||
if (skill != null && skill.isCombo && !skill.comboEffectAdded)
|
||||
{
|
||||
hitEffect |= (HitEffect)(skill.comboStep << 15);
|
||||
skill.comboEffectAdded = true;
|
||||
}
|
||||
|
||||
//if attack hit the target, take into account protective status effects
|
||||
if (hitType >= HitType.Block)
|
||||
|
|
|
@ -2124,6 +2124,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
|
||||
public override bool CanCast(Character target, BattleCommand spell)
|
||||
{
|
||||
//Might want to do these with a BattleAction instead to be consistent with the rest of command stuff
|
||||
if (GetHotbarTimer(spell.id) > Utils.UnixTimeStampUTC())
|
||||
{
|
||||
// todo: this needs confirming
|
||||
|
@ -2137,12 +2138,30 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
SendGameMessage(Server.GetWorldManager().GetActor(), 32511, 0x20, (uint)spell.id);
|
||||
return false;
|
||||
}
|
||||
if (Utils.Distance(positionX, positionY, positionZ, target.positionX, target.positionY, target.positionZ) > spell.range)
|
||||
if (Utils.XZDistance(positionX, positionZ, target.positionX, target.positionZ) > spell.range)
|
||||
{
|
||||
// The target is out of range.
|
||||
// The target is too far away.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32539, 0x20, (uint)spell.id);
|
||||
return false;
|
||||
}
|
||||
if (Utils.XZDistance(positionX, positionZ, target.positionX, target.positionZ) < spell.minRange)
|
||||
{
|
||||
// The target is too close.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32538, 0x20, (uint)spell.id);
|
||||
return false;
|
||||
}
|
||||
if (target.positionY - positionY > (spell.rangeHeight / 2))
|
||||
{
|
||||
// The target is too far above you.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32540, 0x20, (uint)spell.id);
|
||||
return false;
|
||||
}
|
||||
if (positionY - target.positionY > (spell.rangeHeight / 2))
|
||||
{
|
||||
// The target is too far below you.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32541, 0x20, (uint)spell.id);
|
||||
return false;
|
||||
}
|
||||
if (!IsValidTarget(target, spell.mainTarget) || !spell.IsValidMainTarget(this, target))
|
||||
{
|
||||
// error packet is set in IsValidTarget
|
||||
|
@ -2169,13 +2188,38 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
return false;
|
||||
}
|
||||
|
||||
if (Utils.Distance(positionX, positionY, positionZ, target.positionX, target.positionY, target.positionZ) > skill.range)
|
||||
//Original game checked height difference before horizontal distance
|
||||
if (target.positionY - positionY > (skill.rangeHeight / 2))
|
||||
{
|
||||
// The target is out of range.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32539, 0x20, (uint)skill.id);
|
||||
// The target is too far above you.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32540, 0x20, (uint)skill.id);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (positionY - target.positionY > (skill.rangeHeight / 2))
|
||||
{
|
||||
// The target is too far below you.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32541, 0x20, (uint)skill.id);
|
||||
return false;
|
||||
}
|
||||
|
||||
var targetDist = Utils.XZDistance(positionX, positionZ, target.positionX, target.positionZ);
|
||||
|
||||
if (targetDist > skill.range)
|
||||
{
|
||||
// The target is out of range.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32537, 0x20, (uint)skill.id);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetDist < skill.minRange)
|
||||
{
|
||||
// The target is too close.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32538, 0x20, (uint)skill.id);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!IsValidTarget(target, skill.validTarget) || !skill.IsValidMainTarget(this, target))
|
||||
{
|
||||
// error packet is set in IsValidTarget
|
||||
|
@ -2379,7 +2423,8 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
//If we're starting or continuing a combo chain, add the status effect and combo cost bonus
|
||||
if (comboIds[0] != 0)
|
||||
{
|
||||
StatusEffect comboEffect = new StatusEffect(this, (uint) StatusEffectId.Combo, 1, 0, 13);
|
||||
StatusEffect comboEffect = new StatusEffect(this, Server.GetWorldManager().GetStatusEffect((uint) StatusEffectId.Combo));
|
||||
comboEffect.SetDuration(13);
|
||||
comboEffect.SetOverwritable(1);
|
||||
statusEffects.AddStatusEffect(comboEffect, this, true);
|
||||
playerWork.comboCostBonusRate = 1;
|
||||
|
@ -2469,5 +2514,12 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasItemEquippedInSlot(uint itemId, ushort slot)
|
||||
{
|
||||
var equippedItem = equipment.GetItemAtSlot(slot);
|
||||
|
||||
return equippedItem != null && equippedItem.itemId == itemId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,8 +32,8 @@ namespace FFXIVClassic_Map_Server.packets.send.actor
|
|||
binWriter.Write((byte)breakage);
|
||||
binWriter.Write((byte)(((leftChant & 0xF) << 4) | (rightChant & 0xF)));
|
||||
binWriter.Write((byte)(guard & 0xF));
|
||||
binWriter.Write((byte)((wasteStat & 0xF) << 4));
|
||||
binWriter.Write((byte)(statMode & 0xF));
|
||||
binWriter.Write((byte)(wasteStat));
|
||||
binWriter.Write((byte)(statMode));
|
||||
binWriter.Write((byte)0);
|
||||
binWriter.Write((UInt16)(idleAnimationId&0xFFFF));
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue