fixed auto attack messing up cast anim

- fixed auto attack anim for mobs (<3 u ion)
- added hotbar timer updates (<3 u azia)
- fixed actor block bug
- cleaned up substate retardation
- fixed some targetfind issues
- added despawn state
- added messages for in progress commands
- added fields for aoe target, range, battleAnimation to server_battle_commands table
This commit is contained in:
Tahir Akhlaq 2017-08-31 05:56:43 +01:00
parent 4c7928da78
commit c5cc7c2f00
24 changed files with 550 additions and 330 deletions

View file

@ -2154,8 +2154,8 @@ namespace FFXIVClassic_Map_Server
{
conn.Open();
var query = ("SELECT `id`, name, classJob, lvl, requirements, validTarget, aoeType, numHits, positionBonus, procRequirement, `range`, buffDuration, debuffDuration, " +
"castType, castTime, recastTime, mpCost, tpCost, animationType, effectAnimation, modelAnimation, animationDuration, aoeRange FROM server_battle_commands;");
var query = ("SELECT `id`, name, classJob, lvl, requirements, validTarget, aoeType, aoeRange, aoeTarget, numHits, positionBonus, procRequirement, `range`, buffDuration, debuffDuration, " +
"castType, castTime, recastTime, mpCost, tpCost, animationType, effectAnimation, modelAnimation, animationDuration, battleAnimation FROM server_battle_commands;");
MySqlCommand cmd = new MySqlCommand(query, conn);
@ -2188,7 +2188,9 @@ namespace FFXIVClassic_Map_Server
battleCommand.modelAnimation = reader.GetUInt16("modelAnimation");
battleCommand.animationDurationSeconds = reader.GetUInt16("animationDuration");
battleCommand.aoeRange = reader.GetInt32("aoeRange");
battleCommand.battleAnimation = (uint)((battleCommand.animationType << 24) | (battleCommand.modelAnimation << 12) | (battleCommand.effectAnimation));
battleCommand.aoeTarget = (TargetFindAOETarget)reader.GetByte("aoeTarget");
battleCommand.battleAnimation = reader.GetUInt32("battleAnimation");
battleCommands.Add(id, battleCommand);
}

View file

@ -96,6 +96,7 @@
<Compile Include="actors\chara\ai\BattleCommand.cs" />
<Compile Include="actors\chara\ai\state\AttackState.cs" />
<Compile Include="actors\chara\ai\state\DeathState.cs" />
<Compile Include="actors\chara\ai\state\DespawnState.cs" />
<Compile Include="actors\chara\ai\state\MagicState.cs" />
<Compile Include="actors\chara\ai\state\State.cs" />
<Compile Include="actors\chara\ai\state\WeaponSkillState.cs" />

View file

@ -154,6 +154,8 @@ namespace FFXIVClassic_Map_Server
case 0x00CC:
LockTargetPacket lockTarget = new LockTargetPacket(subpacket.data);
session.GetActor().currentLockedTarget = lockTarget.actorID;
session.GetActor().isAutoAttackEnabled = lockTarget.otherVal == 0x00000040;
break;
//Start Event
case 0x012D:

View file

@ -365,10 +365,10 @@ namespace FFXIVClassic_Map_Server.Actors
public void ChangeState(ushort newState)
{
if (newState != currentMainState)
//if (newState != currentMainState)
{
currentMainState = newState;
updateFlags |= ActorUpdateFlags.State;
updateFlags |= (ActorUpdateFlags.State | ActorUpdateFlags.Position);
}
}
@ -402,7 +402,7 @@ namespace FFXIVClassic_Map_Server.Actors
if (positionUpdates != null && positionUpdates.Count > 0)
{
// push latest for player
var pos = positionUpdates[currentSubState == SetActorStatePacket.SUB_STATE_PLAYER ? positionUpdates.Count - 1 : 0];
var pos = positionUpdates[0];
oldPositionX = positionX;
oldPositionY = positionY;
oldPositionZ = positionZ;
@ -417,8 +417,8 @@ namespace FFXIVClassic_Map_Server.Actors
//Program.Server.GetInstance().mLuaEngine.OnPath(actor, position, positionUpdates)
positionUpdates.Remove(pos);
packets.Add(CreatePositionUpdatePacket());
}
packets.Add(CreatePositionUpdatePacket());
}
if ((updateFlags & ActorUpdateFlags.Speed) != 0)
@ -434,8 +434,6 @@ namespace FFXIVClassic_Map_Server.Actors
if ((updateFlags & ActorUpdateFlags.State) != 0)
{
packets.Add(SetActorStatePacket.BuildPacket(actorId, currentMainState, currentSubState));
if (this is Character)
((Character)this).DoBattleAction(21001, 0x7C000062, new BattleAction(this.actorId, 0, 1, 0, 0, 1)); //Attack Mode
}
updateFlags = ActorUpdateFlags.None;

View file

@ -321,6 +321,11 @@ namespace FFXIVClassic_Map_Server.Actors
}
}
public T FindActorInArea<T>(uint id) where T : Actor
{
return FindActorInArea(id) as T;
}
public Actor FindActorInZoneByUniqueID(string uniqueId)
{
lock (mActorList)

View file

@ -67,6 +67,7 @@ namespace FFXIVClassic_Map_Server.Actors
public bool isStatic = false;
public bool isMovingToSpawn = false;
public bool isAutoAttackEnabled = true;
public uint modelId;
public uint[] appearanceIds = new uint[28];
@ -98,6 +99,8 @@ namespace FFXIVClassic_Map_Server.Actors
protected ushort hpBase, hpMaxBase, mpBase, mpMaxBase, tpBase;
protected BattleTemp baseStats = new BattleTemp();
public ushort currentJob;
public ushort newMainState;
public float spawnX, spawnY, spawnZ;
public Character(uint actorID) : base(actorID)
{
@ -112,6 +115,10 @@ namespace FFXIVClassic_Map_Server.Actors
// todo: base this on equip and shit
SetMod((uint)Modifier.AttackRange, 3);
SetMod((uint)Modifier.AttackDelay, (Program.Random.Next(30, 60) * 100));
spawnX = positionX;
spawnY = positionY;
spawnZ = positionZ;
}
public SubPacket CreateAppearancePacket()
@ -305,7 +312,13 @@ namespace FFXIVClassic_Map_Server.Actors
if ((updateFlags & ActorUpdateFlags.State) != 0)
{
packets.Add(SetActorStatePacket.BuildPacket(actorId, currentMainState, currentSubState));
packets.Add(BattleActionX00Packet.BuildPacket(actorId, 0x72000062, 0));
packets.Add(BattleActionX01Packet.BuildPacket(actorId, 0x7C000062, 21001, new BattleAction(actorId, 0, 1)));
// this is silly, but looks like state change goes full retard unless theyre sent in order
updateFlags &= ~ActorUpdateFlags.State;
//DoBattleAction(21001, 0x7C000062, new BattleAction(this.actorId, 0, 1, 0, 0, 1)); //Attack Mode
}
// todo: should probably add another flag for battleTemp since all this uses reflection
@ -357,26 +370,37 @@ namespace FFXIVClassic_Map_Server.Actors
return (uint)GetMod((uint)Modifier.AttackRange);
}
public bool Engage(uint targid = 0)
public virtual bool Engage(uint targid = 0, ushort newMainState = 0xFFFF)
{
// todo: attack the things
if (targid == 0)
if (newMainState != 0xFFFF)
{
if (currentTarget != 0xC0000000)
targid = currentTarget;
else if (currentLockedTarget != 0xC0000000)
targid = currentLockedTarget;
this.newMainState = newMainState;
}
if (targid != 0)
else if (aiContainer.CanChangeState())
{
aiContainer.Engage(Server.GetWorldManager().GetActorInWorld(targid) as Character);
if (targid == 0)
{
if (currentTarget != 0xC0000000)
targid = currentTarget;
else if (currentLockedTarget != 0xC0000000)
targid = currentLockedTarget;
}
//if (targid != 0)
{
aiContainer.Engage(zone.FindActorInArea<Character>(targid));
}
}
return false;
}
public bool Disengage()
public virtual bool Disengage(ushort newMainState = 0xFFFF)
{
if (aiContainer != null)
if (newMainState != 0xFFFF)
{
this.newMainState = newMainState;
}
else
{
aiContainer.Disengage();
return true;
@ -384,19 +408,22 @@ namespace FFXIVClassic_Map_Server.Actors
return false;
}
public void Cast(uint spellId, uint targetId = 0)
public virtual void Cast(uint spellId, uint targetId = 0)
{
aiContainer.Cast(Server.GetWorldManager().GetActorInWorld(targetId == 0 ? currentTarget : targetId) as Character, spellId);
if (aiContainer.CanChangeState())
aiContainer.Cast(zone.FindActorInArea<Character>(targetId == 0 ? currentTarget : targetId), spellId);
}
public void Ability(uint abilityId, uint targetId = 0)
public virtual void Ability(uint abilityId, uint targetId = 0)
{
aiContainer.Ability(Server.GetWorldManager().GetActorInWorld(targetId == 0 ? currentTarget : targetId) as Character, abilityId);
if (aiContainer.CanChangeState())
aiContainer.Ability(zone.FindActorInArea<Character>(targetId == 0 ? currentTarget : targetId), abilityId);
}
public void WeaponSkill(uint skillId)
public virtual void WeaponSkill(uint skillId, uint targetId = 0)
{
aiContainer.WeaponSkill(Server.GetWorldManager().GetActorInWorld(currentTarget) as Character, skillId);
if (aiContainer.CanChangeState())
aiContainer.WeaponSkill(zone.FindActorInArea<Character>(targetId == 0 ? currentTarget : targetId), skillId);
}
public virtual void Spawn(DateTime tick)
@ -414,9 +441,9 @@ namespace FFXIVClassic_Map_Server.Actors
aiContainer.InternalDie(tick, 10);
}
protected virtual void Despawn(DateTime tick)
public virtual void Despawn(DateTime tick)
{
}
public bool IsDead()
@ -551,20 +578,23 @@ namespace FFXIVClassic_Map_Server.Actors
//var packet = BattleActionX01Packet.BuildPacket(owner.actorId, owner.actorId, target.actorId, (uint)0x19001000, (uint)0x8000604, (ushort)0x765D, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, (byte)0x1);
}
// todo: call onAttack/onDamageTaken
target.DelHP(action.amount);
}
public virtual void OnCast(State state, BattleAction action, ref SubPacket errorPacket)
public virtual void OnCast(State state, BattleAction[] actions, ref BattleAction[] errors)
{
var spell = ((MagicState)state).GetSpell();
this.DelMP(spell.mpCost); // mpCost can be set in script e.g. if caster has something for free spells
}
public virtual void OnWeaponSkill(State state, BattleAction action, ref SubPacket errorPacket)
public virtual void OnWeaponSkill(State state, BattleAction[] actions, ref BattleAction[] errors)
{
var skill = ((WeaponSkillState)state).GetWeaponSkill();
this.DelTP(skill.tpCost);
}
public virtual void OnAbility(State state, BattleAction action, ref SubPacket errorPacket)
public virtual void OnAbility(State state, BattleAction[] actions, ref BattleAction[] errors)
{
}

View file

@ -23,6 +23,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
public readonly PathFind pathFind;
private TargetFind targetFind;
private ActionQueue actionQueue;
private DateTime lastActionTime;
public AIContainer(Character actor, Controller controller, PathFind pathFind, TargetFind targetFind)
{
@ -36,6 +37,16 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
actionQueue = new ActionQueue(owner);
}
public void UpdateLastActionTime()
{
lastActionTime = DateTime.Now;
}
public DateTime GetLastActionTime()
{
return lastActionTime;
}
public void Update(DateTime tick)
{
prevUpdate = latestUpdate;
@ -186,13 +197,13 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
public bool IsEngaged()
{
// todo: check this is legit
return owner.currentMainState == SetActorStatePacket.MAIN_STATE_ACTIVE && owner.target != null;
return owner.currentMainState == SetActorStatePacket.MAIN_STATE_ACTIVE;
}
public bool IsDead()
{
return owner.currentMainState == SetActorStatePacket.MAIN_STATE_DEAD ||
owner.currentMainState == SetActorStatePacket.MAIN_STATE_DEAD2;
owner.currentMainState == SetActorStatePacket.MAIN_STATE_DEAD2 || owner.GetHP() == 0;
}
public bool IsRoaming()
@ -253,10 +264,6 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
{
// todo: use invalid target id
// todo: this is retarded, call entity's changetarget function
owner.target = target;
owner.currentLockedTarget = target != null ? target.actorId : 0xC0000000;
owner.currentTarget = target != null ? target.actorId : 0xC0000000;
if (IsEngaged() || target == null)
{
@ -293,8 +300,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
GetTargetFind()?.Reset();
owner.updateFlags |= ActorUpdateFlags.HpTpMp;
ChangeTarget(null);
owner.ChangeState(SetActorStatePacket.MAIN_STATE_PASSIVE);
ClearStates();
}

View file

@ -53,6 +53,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
public BattleCommandRequirements requirements;
public ValidTarget validTarget;
public TargetFindAOEType aoeType;
public TargetFindAOETarget aoeTarget;
public byte numHits;
public BattleCommandPositionBonus positionBonus;
public BattleCommandProcRequirement procRequirement;
@ -105,11 +106,11 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
if (aoeType == TargetFindAOEType.Box)
{
// todo: read box width from sql
targetFind.SetAOEBox(validTarget, range, aoeRange);
targetFind.SetAOEBox(validTarget, aoeTarget, range, aoeRange);
}
else
{
targetFind.SetAOEType(validTarget, aoeType, range, aoeRange);
targetFind.SetAOEType(validTarget, aoeType, aoeTarget, range, aoeRange);
}
@ -183,8 +184,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
if ((validTarget & ValidTarget.Enemy) != 0)
{
if (target == user || target != null &&
target.currentSubState != (user.currentSubState == SetActorStatePacket.SUB_STATE_MONSTER ?
SetActorStatePacket.SUB_STATE_PLAYER : SetActorStatePacket.SUB_STATE_MONSTER))
user.allegiance == target.allegiance)
{
if (user is Player)
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32519, 0x20, (uint)id);
@ -194,14 +194,14 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
if ((validTarget & (ValidTarget.PartyMember | ValidTarget.Player | ValidTarget.Ally)) != 0)
{
if (target == null || target.currentSubState != user.currentSubState )
if (target == null || target.allegiance != user.allegiance)
{
if (user is Player)
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32516, 0x20, (uint)id);
return false;
}
}
return targetFind.CanTarget(target, true, true);
return targetFind.CanTarget(target, true, true, true);
}
public ushort CalculateCost(uint level)

View file

@ -195,13 +195,13 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
float baseSpeed = owner.GetSpeed();
// todo: get actual speed crap
if (owner.currentSubState != SetActorStatePacket.SUB_STATE_NONE)
if (!(owner is Player))
{
if (owner.currentSubState == SetActorStatePacket.SUB_STATE_MONSTER)
if (owner is BattleNpc)
{
//owner.ChangeSpeed(0.0f, SetActorSpeedPacket.DEFAULT_WALK - 2.0f, SetActorSpeedPacket.DEFAULT_RUN - 2.0f, SetActorSpeedPacket.DEFAULT_ACTIVE - 2.0f);
}
// baseSpeed += ConfigConstants.SPEED_MOD;
// baseSpeed += ConfigConstants.NPC_SPEED_MOD;
}
return baseSpeed;
}

View file

@ -7,6 +7,7 @@ using FFXIVClassic_Map_Server.Actors;
using FFXIVClassic.Common;
using FFXIVClassic_Map_Server.actors.chara.ai;
using FFXIVClassic_Map_Server.actors.chara.ai.controllers;
using FFXIVClassic_Map_Server.actors.group;
using FFXIVClassic_Map_Server.packets.send.actor;
// port of dsp's ai code https://github.com/DarkstarProject/darkstar/blob/master/src/map/ai/
@ -48,9 +49,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
enum TargetFindAOEType : byte
{
None,
/// <summary> Really a cylinder, uses extents parameter in SetAOEType </summary>
/// <summary> Really a cylinder, uses maxDistance parameter in SetAOEType </summary>
Circle,
/// <summary> Create a cone with angle in radians </summary>
/// <summary> Create a cone with param in radians </summary>
Cone,
/// <summary> Box using self/target coords and </summary>
Box
@ -69,14 +70,14 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
class TargetFind
{
private Character owner;
private Character target;
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 extents;
private float angle;
private float maxDistance;
private float param;
private List<Character> targets;
public TargetFind(Character owner)
@ -87,13 +88,13 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
public void Reset()
{
this.target = null;
this.findType = TargetFindCharacterType.None;
this.validTarget = ValidTarget.Enemy;
this.aoeType = TargetFindAOEType.None;
this.aoeTarget = TargetFindAOETarget.Target;
this.targetPosition = null;
this.extents = 0.0f;
this.angle = 0.0f;
this.maxDistance = 0.0f;
this.param = 0.0f;
this.targets = new List<Character>();
}
@ -110,28 +111,29 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
/// <summary>
/// Call this before <see cref="FindWithinArea"/> <para/>
/// </summary>
/// <param name="extents">
/// <param name="maxDistance">
/// <see cref="TargetFindAOEType.Circle"/> - radius of circle <para/>
/// <see cref="TargetFindAOEType.Cone"/> - height of cone <para/>
/// <see cref="TargetFindAOEType.Box"/> - width of box / 2 (todo: set box length not just between user and target)
/// </param>
/// <param name="angle"> Angle in radians of cone </param>
public void SetAOEType(ValidTarget validTarget, TargetFindAOEType aoeType, float extents = -1.0f, float angle = -1.0f)
/// <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)
{
this.validTarget = validTarget;
this.aoeType = aoeType;
this.extents = extents != -1.0f ? extents : 0.0f;
this.angle = angle != -1.0f ? angle : 0.0f;
this.maxDistance = maxDistance != -1.0f ? maxDistance : 0.0f;
this.param = param != -1.0f ? param : 0.0f;
}
public void SetAOEBox(ValidTarget validTarget, float length, float width)
public void SetAOEBox(ValidTarget validTarget, TargetFindAOETarget aoeTarget, float length, float width)
{
this.validTarget = validTarget;
this.aoeType = TargetFindAOEType.Box;
this.aoeTarget = aoeTarget;
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.extents = width;
this.maxDistance = width;
}
/// <summary>
@ -140,7 +142,6 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
public void FindTarget(Character target, ValidTarget flags)
{
validTarget = flags;
this.target = null;
// todo: maybe this should only be set if successfully added?
this.targetPosition = target.GetPosAsVector3();
AddTarget(target, false);
@ -150,21 +151,18 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
/// <para> Call SetAOEType before calling this </para>
/// Find targets within area set by <see cref="SetAOEType"/>
/// </summary>
public void FindWithinArea(Character target, ValidTarget flags)
public void FindWithinArea(Character target, ValidTarget flags, TargetFindAOETarget aoeTarget)
{
validTarget = flags;
// todo: maybe we should keep a snapshot which is only updated on each tick for consistency
// are we creating aoe circles around target or self
if ((aoeType & TargetFindAOEType.Circle) != 0 && validTarget != ValidTarget.Self)
if (aoeTarget == TargetFindAOETarget.Self)
this.targetPosition = owner.GetPosAsVector3();
else
this.targetPosition = target.GetPosAsVector3();
masterTarget = TryGetMasterTarget(target) ?? target;
// todo: should i set this yet or wait til checked if valid target
this.target = target;
// todo: this is stupid
bool withPet = (flags & ValidTarget.Ally) != 0 || masterTarget.allegiance != owner.allegiance;
@ -180,7 +178,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
// todo: handle player parties
if (masterTarget.currentParty != null)
{
if ((validTarget & ValidTarget.Ally) != 0)
if ((validTarget & (ValidTarget.Ally | ValidTarget.PartyMember)) != 0)
AddAllInAlliance(masterTarget, withPet);
else
AddAllInParty(masterTarget, withPet);
@ -229,19 +227,24 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
/// <summary>
/// Find targets within a box using owner's coordinates and target's coordinates as length
/// with corners being `extents` yalms to either side of self and target
/// with corners being `maxDistance` yalms to either side of self and target
/// </summary>
private bool IsWithinBox(Character target, bool withPet)
{
if (aoeTarget == TargetFindAOETarget.Self)
targetPosition = owner.GetPosAsVector3();
else
targetPosition = target.GetPosAsVector3();
var myPos = owner.GetPosAsVector3();
var angle = Vector3.GetAngle(myPos, targetPosition);
// todo: actually check this works..
var myCorner = myPos.NewHorizontalVector(angle, extents);
var myCorner2 = myPos.NewHorizontalVector(angle, -extents);
var myCorner = myPos.NewHorizontalVector(angle, maxDistance);
var myCorner2 = myPos.NewHorizontalVector(angle, -maxDistance);
var targetCorner = targetPosition.NewHorizontalVector(angle, extents);
var targetCorner2 = targetPosition.NewHorizontalVector(angle, -extents);
var targetCorner = targetPosition.NewHorizontalVector(angle, maxDistance);
var targetCorner2 = targetPosition.NewHorizontalVector(angle, -maxDistance);
return target.GetPosAsVector3().IsWithinBox(targetCorner2, myCorner);
}
@ -249,7 +252,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
private bool IsWithinCone(Character target, bool withPet)
{
// todo: make this actual cone
return owner.IsFacing(target, angle) && Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, target.positionX, target.positionY, target.positionZ) < extents;
return owner.IsFacing(target, param) && Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, target.positionX, target.positionY, target.positionZ) < maxDistance;
}
private void AddTarget(Character target, bool withPet)
@ -264,25 +267,20 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
private void AddAllInParty(Character target, bool withPet)
{
// todo:
/*
* foreach (var actor in target.currentParty.GetMembers())
* {
* AddTarget(actor, withPet);
* }
*/
AddTarget(target, withPet);
var party = target.currentParty as Party;
if (party != null)
{
foreach (var actorId in party.members)
{
AddTarget(owner.zone.FindActorInArea(actorId) as Character, withPet);
}
}
}
private void AddAllInAlliance(Character target, bool withPet)
{
// todo:
/*
* foreach (var actor in target.currentParty.GetAllianceMembers())
* {
* AddTarget(actor, withPet);
* }
*/
AddTarget(target, withPet);
AddAllInParty(target, withPet);
}
private void AddAllBattleNpcs(Character target, bool withPet)
@ -321,7 +319,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
}
}
public bool CanTarget(Character target, bool withPet = false, bool retarget = false)
public bool CanTarget(Character target, bool withPet = false, bool retarget = false, bool ignoreAOE = false)
{
// already targeted, dont target again
if (target == null || !retarget && targets.Contains(target))
@ -331,21 +329,18 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
if ((validTarget & ValidTarget.Corpse) == 0 && target.IsDead())
return false;
bool targetingPlayer = target.currentSubState == SetActorStatePacket.SUB_STATE_PLAYER;
if ((validTarget & ValidTarget.Ally) != 0 && target.currentSubState != owner.currentSubState)
if ((validTarget & ValidTarget.Ally) != 0 && target.allegiance != owner.allegiance)
return false;
if ((validTarget & ValidTarget.Enemy) != 0 && target.currentSubState != (owner.currentSubState == SetActorStatePacket.SUB_STATE_MONSTER ?
SetActorStatePacket.SUB_STATE_PLAYER : SetActorStatePacket.SUB_STATE_MONSTER))
if ((validTarget & ValidTarget.Enemy) != 0 && target.allegiance == owner.allegiance)
return false;
if ((validTarget & ValidTarget.NPC) != 0 && target.currentSubState != SetActorStatePacket.SUB_STATE_NONE)
if ((validTarget & ValidTarget.NPC) != 0 && target.isStatic)
return false;
// todo: why is player always zoning?
// cant target if zoning
if (targetingPlayer && ((Player)target).playerSession.isUpdatesLocked)
if (target is Player && ((Player)target).playerSession.isUpdatesLocked)
{
owner.aiContainer.ChangeTarget(null);
return false;
@ -354,25 +349,36 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
if (/*target.isZoning || owner.isZoning || */target.zone != owner.zone)
return false;
if (validTarget == ValidTarget.Self && aoeType != TargetFindAOEType.None && owner != target)
if (validTarget == ValidTarget.Self && aoeType == TargetFindAOEType.None && owner != target)
return false;
// hit everything within zone or within aoe region
if (extents == 255.0f || aoeType == TargetFindAOEType.Circle && target.GetPosAsVector3().IsWithinCircle(targetPosition ?? target.GetPosAsVector3(), extents))
return true;
// this is fuckin retarded, think of a better way l8r
if (!ignoreAOE)
{
// hit everything within zone or within aoe region
if (maxDistance == -1.0f || aoeType == TargetFindAOEType.Circle && !IsWithinCircle(target, maxDistance))
return false;
if (aoeType == TargetFindAOEType.Cone && IsWithinCone(target, withPet))
return true;
if (aoeType == TargetFindAOEType.Cone && !IsWithinCone(target, withPet))
return false;
if (aoeType == TargetFindAOEType.Box && IsWithinBox(target, withPet))
return true;
if (aoeType == TargetFindAOEType.None && targets.Count != 0)
return false;
if (aoeType == TargetFindAOEType.Box && !IsWithinBox(target, withPet))
return false;
if (aoeType == TargetFindAOEType.None && targets.Count != 0)
return false;
}
return true;
}
private bool IsWithinCircle(Character target, float maxDistance)
{
if (this.targetPosition == null)
this.targetPosition = aoeTarget == TargetFindAOETarget.Self ? owner.GetPosAsVector3() : masterTarget.GetPosAsVector3();
return target.GetPosAsVector3().IsWithinCircle(targetPosition, param);
}
private bool IsPlayer(Character target)
{
if (target is Player)
@ -397,11 +403,11 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
private bool IsBattleNpcOwner(Character target)
{
// i know i copied this from dsp but what even
if (owner.currentSubState != SetActorStatePacket.SUB_STATE_PLAYER || target.currentSubState == SetActorStatePacket.SUB_STATE_PLAYER)
if (!(owner is Player) || target is Player)
return true;
// todo: check hate list
if (owner.currentSubState == SetActorStatePacket.SUB_STATE_MONSTER && ((BattleNpc)owner).hateContainer.GetMostHatedTarget() != target)
if (owner is BattleNpc && ((BattleNpc)owner).hateContainer.GetMostHatedTarget() != target)
{
return false;
}
@ -410,7 +416,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
public Character GetValidTarget(Character target, ValidTarget findFlags)
{
if (target == null || target.currentSubState == SetActorStatePacket.SUB_STATE_PLAYER && ((Player)target).playerSession.isUpdatesLocked)
if (target == null || target is Player && ((Player)target).playerSession.isUpdatesLocked)
return null;
if ((findFlags & ValidTarget.Ally) != 0)

View file

@ -23,6 +23,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
private bool firstSpell = true;
private DateTime lastRoamUpdate;
private DateTime battleStartTime;
private new BattleNpc owner;
public BattleNpcController(BattleNpc owner) :
@ -65,6 +66,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
var canEngage = this.owner.aiContainer.InternalEngage(target);
if (canEngage)
{
//owner.ChangeState(SetActorStatePacket.MAIN_STATE_ACTIVE);
// reset casting
firstSpell = true;
// todo: find a better place to put this?
@ -77,6 +80,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
// owner.ResetMoveSpeeds();
owner.moveState = 2;
lastActionTime = DateTime.Now;
battleStartTime = lastActionTime;
// todo: adjust cooldowns with modifiers
}
return canEngage;
@ -100,7 +104,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
neutralTime = lastActionTime;
owner.hateContainer.ClearHate();
owner.moveState = 1;
lua.LuaEngine.CallLuaBattleAction(owner, "onDisengage", owner, target);
lua.LuaEngine.CallLuaBattleAction(owner, "onDisengage", owner, target, Utils.UnixTimeStampUTC(battleStartTime));
}
public override void Cast(Character target, uint spellId)
@ -199,6 +203,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
}
Move();
lua.LuaEngine.CallLuaBattleAction(owner, "onCombatTick", owner, owner.target, Utils.UnixTimeStampUTC());
}
private void Move()
@ -230,7 +235,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
owner.aiContainer.pathFind.FollowPath();
if (!owner.aiContainer.pathFind.IsFollowingPath())
{
if (owner.target.currentSubState == SetActorStatePacket.SUB_STATE_PLAYER)
if (owner.target is Player)
{
foreach (var chara in owner.zone.GetActorsAroundActor<Character>(owner, 1))
{
@ -355,5 +360,13 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
{
ChangeTarget(owner.hateContainer.GetMostHatedTarget());
}
public override void ChangeTarget(Character target)
{
owner.target = target;
owner.currentLockedTarget = target != null ? target.actorId : 0xC0000000;
owner.currentTarget = target != null ? target.actorId : 0xC0000000;
base.ChangeTarget(target);
}
}
}

View file

@ -11,19 +11,35 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
{
class PlayerController : Controller
{
public PlayerController(Character owner) :
private new Player owner;
public PlayerController(Player owner) :
base(owner)
{
this.owner = owner;
this.lastUpdate = DateTime.Now;
}
public override void Update(DateTime tick)
{
// todo: handle player stuff on tick
if (owner.newMainState != owner.currentMainState)
{
//owner.updateFlags = ActorUpdateFlags.Combat;
if (owner.newMainState == SetActorStatePacket.MAIN_STATE_ACTIVE)
{
owner.Engage();
}
else
{
owner.Disengage();
}
owner.currentMainState = (ushort)owner.newMainState;
}
}
public override void ChangeTarget(Character target)
{
owner.target = target;
base.ChangeTarget(target);
}
@ -32,21 +48,12 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
var canEngage = this.owner.aiContainer.InternalEngage(target);
if (canEngage)
{
// todo: find a better place to put this?
if (owner.GetState() != SetActorStatePacket.MAIN_STATE_ACTIVE)
owner.ChangeState(SetActorStatePacket.MAIN_STATE_ACTIVE);
// todo: check speed/is able to move
// todo: too far, path to player if mob, message if player
// todo: actual stat based range
if (Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, target.positionX, target.positionY, target.positionZ) > 10)
if (owner.statusEffects.HasStatusEffect(StatusEffectId.Sleep))
{
{
// todo: out of range error
}
ChangeTarget(target);
// That command cannot be performed.
owner.SendGameMessage(Server.GetWorldManager().GetActor(), 32553, 0x20);
return false;
}
// todo: adjust cooldowns with modifiers

View file

@ -20,7 +20,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
this.startTime = DateTime.Now;
owner.ChangeState(SetActorStatePacket.MAIN_STATE_ACTIVE);
owner.aiContainer.ChangeTarget(target);
ChangeTarget(target);
attackTime = startTime;
owner.aiContainer.pathFind?.Clear();
// todo: should handle everything here instead of on next tick..
@ -42,12 +42,13 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
return true;
}
*/
if (target == null || owner.target != target || owner.target?.actorId != owner.currentLockedTarget)
owner.aiContainer.ChangeTarget(target = owner.zone.FindActorInArea(owner.currentLockedTarget == 0xC0000000 ? owner.currentTarget : owner.currentLockedTarget) as Character);
if ((target == null || owner.target != target || owner.target?.actorId != owner.currentLockedTarget) && owner.isAutoAttackEnabled)
owner.aiContainer.ChangeTarget(target = owner.zone.FindActorInArea<Character>(owner.currentLockedTarget));
if (target == null || target.IsDead())
{
if (owner.currentSubState == SetActorStatePacket.SUB_STATE_MONSTER)
if (owner is BattleNpc)
target = ((BattleNpc)owner).hateContainer.GetMostHatedTarget();
}
else
@ -110,7 +111,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
owner.OnAttack(this, action, ref errorResult);
// handle paralyze/intimidate/sleep/whatever in character thing
owner.DoBattleAction((ushort)BattleActionX01PacketCommand.Attack, 0x19001000, errorResult == null ? action : errorResult);
owner.DoBattleAction((ushort)BattleActionX01PacketCommand.Attack, action.animation, errorResult == null ? action : errorResult);
//this.errorPacket = BattleActionX01Packet.BuildPacket(target.actorId, owner.actorId, target.actorId, 0, effectId, 0, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, 0);
}
@ -140,11 +141,17 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
private bool IsAttackReady()
{
return Program.Tick >= attackTime;
// todo: this enforced delay should really be changed if it's not retail..
return Program.Tick >= attackTime && Program.Tick >= owner.aiContainer.GetLastActionTime().AddSeconds(1);
}
private bool CanAttack()
{
if (!owner.isAutoAttackEnabled)
{
return false;
}
if (target == null)
{
return false;
@ -162,7 +169,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
// 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.currentSubState == SetActorStatePacket.SUB_STATE_PLAYER)
if (owner is Player)
{
((Player)owner).SendGameMessage(Server.GetWorldManager().GetActor(), 32539, 0x20);
}

View file

@ -15,6 +15,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
: base(owner, null)
{
owner.ChangeState(SetActorStatePacket.MAIN_STATE_DEAD);
owner.Disengage();
canInterrupt = false;
startTime = tick;
despawnTime = startTime.AddSeconds(timeToFadeOut);
@ -25,7 +26,14 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
// todo: handle raise etc
if (tick >= despawnTime)
{
owner.Spawn(Program.Tick);
if (owner is BattleNpc)
{
owner.Despawn(tick);
}
else
{
// todo: queue a warp for the player
}
return true;
}
return false;

View file

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FFXIVClassic_Map_Server.Actors;
using FFXIVClassic_Map_Server.packets.send.actor;
namespace FFXIVClassic_Map_Server.actors.chara.ai.state
{
class DespawnState : State
{
private DateTime endTime;
public DespawnState(Character owner, Character target, uint despawnTimeSeconds) :
base(owner, null)
{
startTime = Program.Tick;
endTime = startTime.AddSeconds(despawnTimeSeconds);
}
public override bool Update(DateTime tick)
{
if (tick >= endTime)
{
// todo: send packet to despawn the npc, set it so npc is despawned when requesting spawn packets
owner.zone.BroadcastPacketAroundActor(owner, RemoveActorPacket.BuildPacket(owner.actorId));
owner.QueuePositionUpdate(owner.spawnX, owner.spawnY, owner.spawnZ);
lua.LuaEngine.CallLuaBattleAction(owner, "onDespawn", owner);
return true;
}
return false;
}
}
}

View file

@ -57,14 +57,13 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
float spellSpeed = spell.castTimeSeconds;
// command casting duration
if (owner.currentSubState == SetActorStatePacket.SUB_STATE_PLAYER)
if (owner is Player)
{
// todo: modify spellSpeed based on modifiers and stuff
((Player)owner).SendStartCastbar(spell.id, Utils.UnixTimeStampUTC(DateTime.Now.AddSeconds(spellSpeed)));
owner.SendChant(0xF, 0x0);
owner.DoBattleAction(spell.id, 0x6F000002, new BattleAction(target.actorId, 30128, 1, 0, 1)); //You begin casting (6F000002: BLM, 6F000003: WHM)
((Player)owner).SendStartCastbar(spell.id, Utils.UnixTimeStampUTC(DateTime.Now.AddSeconds(spellSpeed)));
}
owner.SendChant(0xF, 0x0);
owner.DoBattleAction(spell.id, 0x6F000002, new BattleAction(target.actorId, 30128, 1, 0, 1)); //You begin casting (6F000002: BLM, 6F000003: WHM)
}
}
@ -106,7 +105,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
public override void OnComplete()
{
spell.targetFind.FindWithinArea(target, spell.validTarget);
spell.targetFind.FindWithinArea(target, spell.validTarget, spell.aoeTarget);
isCompleted = true;
var targets = spell.targetFind.GetTargets();
@ -119,7 +118,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
actions[i++] = action;
}
owner.SendChant(0, 0);
// todo: this is fuckin stupid, probably only need *one* error packet, not an error for each action
var errors = (BattleAction[])actions.Clone();
owner.OnCast(this, actions, ref errors);
owner.DoBattleAction(spell.id, spell.battleAnimation, actions);
}
@ -138,10 +140,6 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
// todo: actually check proc rate/random chance of whatever effect
effectId = list[0].GetStatusEffectId();
}
// todo: which is actually the swing packet
//this.errorPacket = BattleActionX01Packet.BuildPacket(target.actorId, owner.actorId, target.actorId, 0, effectId, 0, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, 0);
//owner.zone.BroadcastPacketAroundActor(owner, errorPacket);
//errorPacket = null;
interrupt = true;
return;
}
@ -159,7 +157,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
private bool CanCast()
{
return owner.CanCast(target, spell) && !HasMoved();
return owner.CanCast(target, spell) && spell.IsValidTarget(owner, target) && !HasMoved();
}
private bool HasMoved()
@ -169,13 +167,13 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
public override void Cleanup()
{
if (owner.currentSubState == SetActorStatePacket.SUB_STATE_PLAYER)
{
owner.SendChant(0, 0);
if (owner is Player)
{
((Player)owner).SendEndCastbar();
}
// command casting duration
//var packets = new List<SubPacket>();
//owner.zone.BroadcastPacketsAroundActor(owner, packets);
owner.aiContainer.UpdateLastActionTime();
}
public BattleCommand GetSpell()

View file

@ -90,7 +90,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
public override void OnComplete()
{
skill.targetFind.FindWithinArea(target, skill.validTarget);
skill.targetFind.FindWithinArea(target, skill.validTarget, skill.aoeTarget);
isCompleted = true;
var targets = skill.targetFind.GetTargets();
@ -104,10 +104,12 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
// evasion, miss, dodge, etc to be handled in script, calling helpers from scripts/weaponskills.lua
action.amount = (ushort)lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "weaponskill", "onSkillFinish", owner, target, skill, action);
actions[i++] = action;
//packets.Add(BattleActionX01Packet.BuildPacket(chara.actorId, owner.actorId, action.targetId, skill.battleAnimation, action.effectId, action.worldMasterTextId, skill.id, action.amount, action.param));
}
// todo: this is fuckin stupid, probably only need *one* error packet, not an error for each action
var errors = (BattleAction[])actions.Clone();
owner.OnWeaponSkill(this, actions, ref errors);
owner.DoBattleAction(skill.id, 0, actions);
}
@ -126,10 +128,6 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
// todo: actually check proc rate/random chance of whatever effect
effectId = list[0].GetStatusEffectId();
}
// todo: which is actually the swing packet
//this.errorPacket = BattleActionX01Packet.BuildPacket(target.actorId, owner.actorId, target.actorId, 0, effectId, 0, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, 0);
//owner.zone.BroadcastPacketAroundActor(owner, errorPacket);
//errorPacket = null;
interrupt = true;
return;
}
@ -139,12 +137,17 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
private bool CanUse()
{
return owner.CanWeaponSkill(target, skill);
return owner.CanWeaponSkill(target, skill) && skill.IsValidTarget(owner, target);
}
public BattleCommand GetWeaponSkill()
{
return skill;
}
public override void Cleanup()
{
owner.aiContainer.UpdateLastActionTime();
}
}
}

View file

@ -101,7 +101,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
var scaledCost = spell.CalculateCost((uint)caster.charaWork.parameterSave.state_mainSkillLevel);
// todo: calculate cost for mob/player
if (caster.currentSubState == SetActorStatePacket.SUB_STATE_MONSTER)
if (caster is BattleNpc)
{
}

View file

@ -36,7 +36,7 @@ namespace FFXIVClassic_Map_Server.Actors
private uint despawnTime;
private uint spawnDistance;
public float spawnX, spawnY, spawnZ;
public BattleNpc(int actorNumber, ActorClass actorClass, string uniqueId, Area spawnedArea, float posX, float posY, float posZ, float rot,
ushort actorState, uint animationId, string customDisplayName)
: base(actorNumber, actorClass, uniqueId, spawnedArea, posX, posY, posZ, rot, actorState, animationId, customDisplayName)
@ -174,6 +174,13 @@ namespace FFXIVClassic_Map_Server.Actors
}
}
public override void Despawn(DateTime tick)
{
aiContainer.ClearStates();
// todo: probably didnt need to make a new state...
aiContainer.ForceChangeState(new DespawnState(this, null, 0));
}
public void OnRoam(DateTime tick)
{
// todo: move this to battlenpccontroller..
@ -207,6 +214,8 @@ namespace FFXIVClassic_Map_Server.Actors
public override void OnAttack(State state, BattleAction action, ref BattleAction error)
{
base.OnAttack(state, action, ref error);
// todo: move this somewhere else prolly and change based on model/appearance (so maybe in Character.cs instead)
action.animation = 0x11001000; // (temporary) wolf anim
}
}
}

View file

@ -1757,13 +1757,23 @@ namespace FFXIVClassic_Map_Server.Actors
{
// todo: should probably add another flag for battleTemp since all this uses reflection
packets = new List<SubPacket>();
// we only want the latest update for the player
if ((updateFlags & ActorUpdateFlags.Position) != 0)
{
if (positionUpdates.Count > 1)
positionUpdates.RemoveRange(1, positionUpdates.Count - 1);
}
if ((updateFlags & ActorUpdateFlags.HpTpMp) != 0)
{
var propPacketUtil = new ActorPropertyPacketUtil("charaWork.parameterSave", this);
propPacketUtil.AddProperty($"charaWork.parameterSave.hp[{currentJob}]");
propPacketUtil.AddProperty($"charaWork.parameterSave.hpMax[{currentJob}]");
propPacketUtil.AddProperty($"charaWork.parameterSave.state_mainSkill[{currentJob}]");
// todo: should this be using job as index?
propPacketUtil.AddProperty($"charaWork.parameterSave.hp[{0}]");
propPacketUtil.AddProperty($"charaWork.parameterSave.hpMax[{0}]");
propPacketUtil.AddProperty($"charaWork.parameterSave.state_mainSkill[{0}]");
propPacketUtil.AddProperty($"charaWork.parameterSave.state_mainSkillLevel");
packets.AddRange(propPacketUtil.Done());
}
@ -1937,6 +1947,7 @@ namespace FFXIVClassic_Map_Server.Actors
//If the returned value is outside the hotbar, it indicates it wasn't found.
private ushort FindFirstCommandSlotById(uint commandId)
{
commandId |= 0xA0F00000;
ushort firstSlot = (ushort)(charaWork.commandBorder + 30);
for (ushort i = charaWork.commandBorder; i < charaWork.commandBorder + 30; i++)
@ -1950,13 +1961,51 @@ namespace FFXIVClassic_Map_Server.Actors
return firstSlot;
}
/*
public void SendBattleActionX01Packet(uint anim, uint effect, uint text = 0x756D, uint command = 27260, uint param = 0x01, uint idek = 0x01)
private void UpdateHotbarTimer(uint commandId, uint recastTimeSeconds)
{
var packet = BattleActionX01Packet.BuildPacket(actorId, actorId, currentTarget != 0xC0000000 ? currentTarget : currentLockedTarget, (uint)anim, (uint)effect, (ushort)text, (ushort)command, (ushort)param, (byte)idek);
QueuePacket(packet);
}*/
ushort slot = FindFirstCommandSlotById(commandId);
charaWork.parameterSave.commandSlot_recastTime[slot - charaWork.commandBorder] = Utils.UnixTimeStampUTC(DateTime.Now.AddSeconds(recastTimeSeconds));
var slots = new List<ushort>();
slots.Add(slot);
UpdateRecastTimers(slots);
}
private uint GetHotbarTimer(uint commandId)
{
ushort slot = FindFirstCommandSlotById(commandId);
return charaWork.parameterSave.commandSlot_recastTime[slot - charaWork.commandBorder];
}
public override void Cast(uint spellId, uint targetId = 0)
{
if (aiContainer.CanChangeState())
aiContainer.Cast(zone.FindActorInArea<Character>(targetId == 0 ? currentTarget : targetId), spellId);
else if (aiContainer.GetCurrentState() is MagicState)
// You are already casting.
SendGameMessage(Server.GetWorldManager().GetActor(), 32536, 0x20);
else
// Please wait a moment and try again.
SendGameMessage(Server.GetWorldManager().GetActor(), 32535, 0x20);
}
public override void Ability(uint abilityId, uint targetId = 0)
{
if (aiContainer.CanChangeState())
aiContainer.Ability(zone.FindActorInArea<Character>(targetId == 0 ? currentTarget : targetId), abilityId);
else
// Please wait a moment and try again.
SendGameMessage(Server.GetWorldManager().GetActor(), 32535, 0x20);
}
public override void WeaponSkill(uint skillId, uint targetId = 0)
{
if (aiContainer.CanChangeState())
aiContainer.WeaponSkill(zone.FindActorInArea<Character>(targetId == 0 ? currentTarget : targetId), skillId);
else
// Please wait a moment and try again.
SendGameMessage(Server.GetWorldManager().GetActor(), 32535, 0x20);
}
public override bool IsValidTarget(Character target, ValidTarget validTarget)
{
@ -1977,7 +2026,8 @@ namespace FFXIVClassic_Map_Server.Actors
// enemy only
if ((validTarget & ValidTarget.Enemy) != 0)
{
if (target.currentSubState == SetActorStatePacket.SUB_STATE_NONE)
// todo: this seems ambiguous
if (target.isStatic)
{
// That command cannot be performed on the current target.
SendGameMessage(Server.GetWorldManager().GetActor(), 32547, 0x20);
@ -1990,7 +2040,7 @@ namespace FFXIVClassic_Map_Server.Actors
return false;
}
// todo: pvp?
if (target.currentSubState == currentSubState)
if (target.allegiance == allegiance)
{
// That command cannot be performed on an ally.
SendGameMessage(Server.GetWorldManager().GetActor(), 32549, 0x20);
@ -1998,14 +2048,15 @@ namespace FFXIVClassic_Map_Server.Actors
}
}
if ((validTarget & ValidTarget.Ally) != 0 && target.currentSubState != currentSubState)
if ((validTarget & ValidTarget.Ally) != 0 && target.allegiance != allegiance)
{
// That command cannot be performed on the current target.
SendGameMessage(Server.GetWorldManager().GetActor(), 32547, 0x20);
return false;
}
if ((validTarget & ValidTarget.NPC) != 0 && target.currentSubState == SetActorStatePacket.SUB_STATE_NONE)
// todo: isStatic seems ambiguous?
if ((validTarget & ValidTarget.NPC) != 0 && target.isStatic)
return true;
// todo: why is player always zoning?
@ -2022,7 +2073,13 @@ namespace FFXIVClassic_Map_Server.Actors
public override bool CanCast(Character target, BattleCommand spell)
{
// todo: move the ability specific stuff to ability.cs
if (GetHotbarTimer(spell.id) > Utils.UnixTimeStampUTC())
{
// todo: this needs confirming
// Please wait a moment and try again.
SendGameMessage(Server.GetWorldManager().GetActor(), 32535, 0x20, (uint)spell.id);
return false;
}
if (target == null)
{
// Target does not exist.
@ -2046,6 +2103,13 @@ namespace FFXIVClassic_Map_Server.Actors
public override bool CanWeaponSkill(Character target, BattleCommand skill)
{
// todo: see worldmaster ids 32558~32557 for proper ko message and stuff
if (GetHotbarTimer(skill.id) > Utils.UnixTimeStampUTC())
{
// todo: this needs confirming
// Please wait a moment and try again.
SendGameMessage(Server.GetWorldManager().GetActor(), 32535, 0x20, (uint)skill.id);
return false;
}
if (target == null)
{
// Target does not exist.
@ -2069,7 +2133,8 @@ namespace FFXIVClassic_Map_Server.Actors
public override void OnAttack(State state, BattleAction action, ref BattleAction error)
{
base.OnAttack(state, action, ref error);
// todo: switch based on main weap (also probably move this anim assignment somewhere else)
action.animation = 0x19001000;
if (error == null)
{
// melee attack animation
@ -2081,5 +2146,24 @@ namespace FFXIVClassic_Map_Server.Actors
((BattleNpc)target).hateContainer.UpdateHate(this, action.amount);
}
}
public override void OnCast(State state, BattleAction[] actions, ref BattleAction[] errors)
{
// todo: update hotbar timers to skill's recast time (also needs to be done on class change or equip crap)
base.OnCast(state, actions, ref errors);
var spell = ((MagicState)state).GetSpell();
// todo: should just make a thing that updates the one slot cause this is dumb as hell
UpdateHotbarTimer(spell.id, spell.recastTimeSeconds);
}
public override void OnWeaponSkill(State state, BattleAction[] actions, ref BattleAction[] errors)
{
// todo: update hotbar timers to skill's recast time (also needs to be done on class change or equip crap)
base.OnWeaponSkill(state, actions, ref errors);
var skill = ((WeaponSkillState)state).GetWeaponSkill();
// todo: should just make a thing that updates the one slot cause this is dumb as hell
UpdateHotbarTimer(skill.id, skill.recastTimeSeconds);
}
}
}

View file

@ -86,7 +86,7 @@ namespace FFXIVClassic_Map_Server.dataobjects
playerActor.rotation = rot;
playerActor.moveState = moveState;
GetActor().GetZone().UpdateActorPosition(GetActor());
//GetActor().GetZone().UpdateActorPosition(GetActor());
playerActor.QueuePositionUpdate(new Vector3(x,y,z));
}