diff --git a/FFXIVClassic Map Server/WorldManager.cs b/FFXIVClassic Map Server/WorldManager.cs index 195f05c5..08fa9fe1 100644 --- a/FFXIVClassic Map Server/WorldManager.cs +++ b/FFXIVClassic Map Server/WorldManager.cs @@ -41,7 +41,7 @@ namespace FFXIVClassic_Map_Server private Server mServer; - private const int MILIS_LOOPTIME = 250; + private const int MILIS_LOOPTIME = 333; private Timer mZoneTimer; //Content Groups @@ -1012,7 +1012,8 @@ namespace FFXIVClassic_Map_Server } public void ZoneThreadLoop(Object state) - { + { + // todo: fuck coroutines, seem to be causing it to hang // todo: spawn new thread for each zone on startup lock (zoneList) { diff --git a/FFXIVClassic Map Server/actors/Actor.cs b/FFXIVClassic Map Server/actors/Actor.cs index 0895f0a8..d5827f38 100644 --- a/FFXIVClassic Map Server/actors/Actor.cs +++ b/FFXIVClassic Map Server/actors/Actor.cs @@ -10,6 +10,7 @@ using FFXIVClassic_Map_Server.actors.area; using System.Reflection; using System.ComponentModel; using FFXIVClassic_Map_Server.packets.send.actor.battle; +using FFXIVClassic_Map_Server.packets.send; namespace FFXIVClassic_Map_Server.Actors { @@ -62,7 +63,6 @@ namespace FFXIVClassic_Map_Server.Actors protected DateTime lastUpdate; public Actor target; - public bool hasMoved = false; public bool isAtSpawn = true; public ActorUpdateFlags updateFlags; @@ -416,7 +416,6 @@ namespace FFXIVClassic_Map_Server.Actors //Program.Server.GetInstance().mLuaEngine.OnPath(actor, position, positionUpdates) positionUpdates.Remove(pos); - lastMoveUpdate = DateTime.Now; packets.Add(CreatePositionUpdatePacket()); } } @@ -569,6 +568,7 @@ namespace FFXIVClassic_Map_Server.Actors } } + #region positioning public List GetPos() { List pos = new List(); @@ -614,12 +614,12 @@ namespace FFXIVClassic_Map_Server.Actors } // todo: do this properly - public bool IsFacing(float x, float y) + public bool IsFacing(float x, float z) { var rot1 = this.rotation; var dX = this.positionX - x; - var dY = this.positionY - y; + var dY = this.positionZ - z; var rot2 = Math.Atan2(dY, dX); @@ -632,7 +632,7 @@ namespace FFXIVClassic_Map_Server.Actors { if (actor != null) { - LookAt(actor.positionX, actor.positionY); + LookAt(actor.positionX, actor.positionZ); } else { @@ -640,12 +640,20 @@ namespace FFXIVClassic_Map_Server.Actors } } - public void LookAt(float x, float y) + public void LookAt(Vector3 pos) + { + if (pos != null) + { + LookAt(pos.X, pos.Z); + } + } + + public void LookAt(float x, float z) { var rot1 = this.rotation; var dX = this.positionX - x; - var dY = this.positionY - y; + var dY = this.positionZ - z; var rot2 = Math.Atan2(dY, dX); @@ -658,7 +666,8 @@ namespace FFXIVClassic_Map_Server.Actors public bool IsFacing(float x, float z, float angle = 40.0f) { - return Vector3.GetAngle(positionX, positionZ, x, z) < angle; + angle = (float)(Math.PI * angle / 180); + return Vector3.GetAngle(positionX, positionZ, x, z) <= angle; } // todo: is this legit? @@ -714,6 +723,7 @@ namespace FFXIVClassic_Map_Server.Actors { return FindRandomPoint(positionX, positionY, positionZ, minRadius, maxRadius); } + #endregion public Player GetAsPlayer() { @@ -739,6 +749,14 @@ namespace FFXIVClassic_Map_Server.Actors { return this is Character ? ((Character)this) : null; } + + public SubPacket CreateGameMessagePacket(Actor textIdOwner, ushort textId, byte log, params object[] msgParams) + { + if (msgParams == null || msgParams.Length == 0) + return (GameMessagePacket.BuildPacket(Server.GetWorldManager().GetActor().actorId, textIdOwner.actorId, textId, log)); + else + return (GameMessagePacket.BuildPacket(Server.GetWorldManager().GetActor().actorId, textIdOwner.actorId, textId, log, LuaUtils.CreateLuaParamList(msgParams))); + } } } diff --git a/FFXIVClassic Map Server/actors/chara/Character.cs b/FFXIVClassic Map Server/actors/chara/Character.cs index 5f886131..984c2523 100644 --- a/FFXIVClassic Map Server/actors/chara/Character.cs +++ b/FFXIVClassic Map Server/actors/chara/Character.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; using FFXIVClassic_Map_Server.actors.chara; using FFXIVClassic_Map_Server.packets.send.actor.battle; +using FFXIVClassic_Map_Server.packets.send; namespace FFXIVClassic_Map_Server.Actors { @@ -108,7 +109,7 @@ namespace FFXIVClassic_Map_Server.Actors ResetMoveSpeeds(); // todo: base this on equip and shit SetMod((uint)Modifier.AttackRange, 3); - SetMod((uint)Modifier.AttackDelay, 4200); + SetMod((uint)Modifier.AttackDelay, (Program.Random.Next(30,60) * 100)); } public SubPacket CreateAppearancePacket() @@ -254,12 +255,27 @@ namespace FFXIVClassic_Map_Server.Actors } } + public virtual bool IsValidTarget(Character target, ValidTarget validTarget, ref SubPacket errorPacket) + { + return true; + } + public virtual bool CanAttack() + { + return true; + } + + public virtual bool CanCast(Character target, Ability spell, ref SubPacket errorPacket) { return false; } - public virtual bool CanCast() + public virtual bool CanWeaponSkill(Character target, Ability skill, ref SubPacket errorPacket) + { + return false; + } + + public virtual bool CanUseAbility(Character target, Ability ability, ref SubPacket errorPacket) { return false; } @@ -277,12 +293,16 @@ namespace FFXIVClassic_Map_Server.Actors public bool Engage(uint targid = 0) { // todo: attack the things - targid = targid == 0 ? currentTarget: targid; + if (targid == 0) + { + if (currentTarget != 0xC0000000) + targid = currentTarget; + else if (currentLockedTarget != 0xC0000000) + targid = currentLockedTarget; + } if (targid != 0) { - var targ = Server.GetWorldManager().GetActorInWorld(targid); - if (targ is Character) - aiContainer.Engage((Character)targ); + aiContainer.Engage(Server.GetWorldManager().GetActorInWorld(targid) as Character); } return false; } @@ -297,9 +317,9 @@ namespace FFXIVClassic_Map_Server.Actors return false; } - public void Cast(uint spellId) + public void Cast(uint spellId, uint targetId = 0) { - aiContainer.Cast(Server.GetWorldManager().GetActorInWorld(currentTarget) as Character, spellId); + aiContainer.Cast(Server.GetWorldManager().GetActorInWorld(targetId == 0 ? currentTarget : targetId) as Character, spellId); } public void WeaponSkill(uint skillId) diff --git a/FFXIVClassic Map Server/actors/chara/ai/Ability.cs b/FFXIVClassic Map Server/actors/chara/ai/Ability.cs index e94f10dd..3ba09fa0 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/Ability.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/Ability.cs @@ -5,6 +5,8 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using FFXIVClassic_Map_Server.actors.chara.player; +using FFXIVClassic.Common; +using FFXIVClassic_Map_Server.packets.send.actor; namespace FFXIVClassic_Map_Server.actors.chara.ai { @@ -94,7 +96,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai return castTimeSeconds == 0; } - public bool IsValidTarget(Character user, Character target) + public bool IsValidTarget(Character user, Character target, ref SubPacket errorPacket) { // todo: set box length.. targetFind = new TargetFind(user); @@ -108,7 +110,112 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai { targetFind.SetAOEType(validTarget, aoeType, range, 40); } + + + /* + 32512 cannot be performed on a KO'd target. + 32513 can only be performed on a KO'd target. + 32514 cannot be performed on yourself. + 32515 can only be performed on yourself. + 32516 cannot be performed on a friendly target. + 32517 can only be performed on a friendly target. + 32518 cannot be performed on an enemy. + 32519 can only be performed on an enemy, + */ + + // cant target dead + if ((validTarget & (ValidTarget.Corpse | ValidTarget.CorpseOnly)) == 0 && target.IsDead()) + { + // cannot be perfomed on + errorPacket = user.CreateGameMessagePacket(Server.GetWorldManager().GetActor(), 32512, 0x20, (uint)id); + return false; + } + if (level > user.charaWork.parameterSave.state_mainSkillLevel) + { + errorPacket = user.CreateGameMessagePacket(Server.GetWorldManager().GetActor(), 32527, 0x20, (uint)id); + return false; + } + + if (tpCost > user.GetTP()) + { + errorPacket = user.CreateGameMessagePacket(Server.GetWorldManager().GetActor(), 32546, 0x20, (uint)id); + return false; + } + + // todo: calculate cost based on modifiers also (probably in BattleUtils) + + if (mpCost > 0 && (mpCost = CalculateCost((ushort)user.charaWork.parameterSave.state_mainSkillLevel)) > user.GetMP()) + { + // todo: error message + errorPacket = user.CreateGameMessagePacket(Server.GetWorldManager().GetActor(), 32545, 0x20, (uint)id); + return false; + } + + // todo: check target requirements + if (requirements != AbilityRequirements.None) + { + if (false) + { + // Unable to execute [@SHEET(xtx/command,$E8(1),2)]. Conditions for use are not met. + errorPacket = user.CreateGameMessagePacket(Server.GetWorldManager().GetActor(), 32556, 0x20, (uint)id); + return false; + } + } + + // todo: i dont care to message for each scenario, just the most common ones.. + if ((validTarget & ValidTarget.CorpseOnly) != 0) + { + if (target != null && target.IsAlive()) + { + errorPacket = user.CreateGameMessagePacket(Server.GetWorldManager().GetActor(), 32513, 0x20, (uint)id); + return false; + } + } + + 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)) + { + errorPacket = user.CreateGameMessagePacket(Server.GetWorldManager().GetActor(), 32519, 0x20, (uint)id); + return false; + } + } + + if ((validTarget & (ValidTarget.PartyMember | ValidTarget.Player | ValidTarget.Ally)) != 0) + { + if (target == null || target.currentSubState != user.currentSubState ) + { + errorPacket = user.CreateGameMessagePacket(Server.GetWorldManager().GetActor(), 32516, 0x20, (uint)id); + return false; + } + } return targetFind.CanTarget(target, true, true); } + + public ushort CalculateCost(uint level) + { + // todo: use precalculated costs instead + ushort cost = 0; + if (level <= 10) + cost = (ushort)(100 + level * 10); + else if (level <= 20) + cost = (ushort)(200 + (level - 10) * 20); + else if (level <= 30) + cost = (ushort)(400 + (level - 20) * 40); + else if (level <= 40) + cost = (ushort)(800 + (level - 30) * 70); + else if (level <= 50) + cost = (ushort)(1500 + (level - 40) * 130); + else if (level <= 60) + cost = (ushort)(2800 + (level - 50) * 200); + else if (level <= 70) + cost = (ushort)(4800 + (level - 60) * 320); + else + cost = (ushort)(8000 + (level - 70) * 500); + + return (ushort)Math.Ceiling((cost * ((mpCost == 0 ? tpCost : mpCost) * 0.001))); + } } } diff --git a/FFXIVClassic Map Server/actors/chara/ai/HateContainer.cs b/FFXIVClassic Map Server/actors/chara/ai/HateContainer.cs index b696944f..d5a0904b 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/HateContainer.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/HateContainer.cs @@ -45,11 +45,11 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai public void UpdateHate(Character target, int damage) { - if (HasHateForTarget(target)) - { - //hateList[target].volatileEnmity += (uint)damage; - hateList[target].cumulativeEnmity += (uint)damage; - } + if (!HasHateForTarget(target)) + AddBaseHate(target); + + //hateList[target].volatileEnmity += (uint)damage; + hateList[target].cumulativeEnmity += (uint)damage; } public void ClearHate(Character target = null) diff --git a/FFXIVClassic Map Server/actors/chara/ai/PathFind.cs b/FFXIVClassic Map Server/actors/chara/ai/PathFind.cs index cba1a8fc..a887fc0d 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/PathFind.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/PathFind.cs @@ -151,7 +151,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai float stepDistance = speed / 3; float distanceTo = Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, point.X, point.Y, point.Z); - owner.LookAt(point.X, point.Y); + owner.LookAt(point); if (distanceTo <= distanceFromPoint + stepDistance) { diff --git a/FFXIVClassic Map Server/actors/chara/ai/TargetFind.cs b/FFXIVClassic Map Server/actors/chara/ai/TargetFind.cs index 6a39d57e..8e14b724 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/TargetFind.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/TargetFind.cs @@ -168,6 +168,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai // todo: this is stupid bool withPet = (flags & ValidTarget.Ally) != 0 || masterTarget.allegiance != owner.allegiance; + if (masterTarget != null) + targets.Add(masterTarget); + if (IsPlayer(owner)) { if (masterTarget is Player) @@ -284,8 +287,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai private void AddAllBattleNpcs(Character target, bool withPet) { - // 70 is client render distance so we'll go with that - var actors = owner.zone.GetActorsAroundActor(owner, (int)extents); + var actors = owner.zone.GetActorsAroundActor(owner, 50); // todo: should we look for Characters instead in case player is charmed by BattleNpc foreach (BattleNpc actor in actors) @@ -361,6 +363,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai if (aoeType == TargetFindAOEType.Box && IsWithinBox(target, withPet)) return true; + if (aoeType == TargetFindAOEType.None && targets.Count != 0) + return false; + return true; } diff --git a/FFXIVClassic Map Server/actors/chara/ai/controllers/BattleNpcController.cs b/FFXIVClassic Map Server/actors/chara/ai/controllers/BattleNpcController.cs index 3f0d47e5..a131f181 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/controllers/BattleNpcController.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/controllers/BattleNpcController.cs @@ -217,13 +217,17 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers { if (owner.target.currentSubState == SetActorStatePacket.SUB_STATE_PLAYER) { - foreach (var battlenpc in owner.zone.GetActorsAroundActor(owner, 1)) + foreach (var chara in owner.zone.GetActorsAroundActor(owner, 1)) { - if (battlenpc == owner) + if (chara == owner) continue; - float mobDistance = Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, battlenpc.positionX, battlenpc.positionY, battlenpc.positionZ); - if (mobDistance < 0.25f && (battlenpc.updateFlags & ActorUpdateFlags.Position) == 0) - battlenpc.aiContainer.pathFind.PathInRange(targetPos, 1.3f, 1.8f); + + float mobDistance = Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, chara.positionX, chara.positionY, chara.positionZ); + if (mobDistance < 0.90f && (chara.updateFlags & ActorUpdateFlags.Position) == 0) + { + owner.aiContainer.pathFind.PathInRange(targetPos, 1.3f, 1.8f); + break; + } } } } diff --git a/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs b/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs index 28aa9f9b..3c7d4087 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs @@ -45,7 +45,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state } */ if (owner.target != target || owner.target?.actorId != owner.currentLockedTarget) - owner.aiContainer.ChangeTarget(target = Server.GetWorldManager().GetActorInWorld(owner.currentLockedTarget) as Character); + owner.aiContainer.ChangeTarget(target = Server.GetWorldManager().GetActorInWorld(owner.currentLockedTarget == 0xC0000000 ? owner.currentTarget : owner.currentLockedTarget) as Character); if (target == null || target.IsDead()) { @@ -75,6 +75,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state { // todo: handle interrupt/paralyze etc } + attackTime = DateTime.Now.AddMilliseconds(owner.GetAttackDelayMs()); } } return false; @@ -94,14 +95,14 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state lua.LuaEngine.CallLuaBattleAction(owner, "onAttack", false, owner, target, damage); { - foreach (var player in owner.zone.GetActorsAroundActor(owner, 50)) + var actors = owner.zone.GetActorsAroundActor(owner, 50); + foreach (var player in actors) { var packet = BattleActionX01Packet.BuildPacket(player.actorId, owner.actorId, target.actorId, (uint)0x19001000, (uint)0x8000604, (ushort)0x765D, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, (byte)0x1); player.QueuePacket(packet); } } target.DelHP((short)damage); - attackTime = attackTime.AddMilliseconds(owner.GetAttackDelayMs()); owner.LookAt(target); //this.errorPacket = BattleActionX01Packet.BuildPacket(target.actorId, owner.actorId, target.actorId, 0, effectId, 0, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, 0); } diff --git a/FFXIVClassic Map Server/actors/chara/ai/state/MagicState.cs b/FFXIVClassic Map Server/actors/chara/ai/state/MagicState.cs index 85c6bade..9415ddb6 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/state/MagicState.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/state/MagicState.cs @@ -7,6 +7,8 @@ using FFXIVClassic.Common; using FFXIVClassic_Map_Server.Actors; using FFXIVClassic_Map_Server.packets.send.actor; using FFXIVClassic_Map_Server.packets.send.actor.battle; +using FFXIVClassic_Map_Server.packets.send; + namespace FFXIVClassic_Map_Server.actors.chara.ai.state { class MagicState : State @@ -24,39 +26,19 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state this.spell = Server.GetWorldManager().GetAbility(spellId); var returnCode = lua.LuaEngine.CallLuaAbilityFunction(owner, spell, "spells", "onSpellPrepare", owner, target, spell); - if (spell != null && returnCode == 0) + // todo: check recast + if (owner.CanCast(target, spell, ref errorPacket)) { - // todo: hp/mp shit should be taken care of in scripts, not here // todo: Azia can fix, check the recast time and send error - - if (!spell.IsValidTarget(owner, target)) - { - // todo: error message - interrupt = true; - } - else if ((spell.mpCost = (ushort)Math.Ceiling((8000 + (owner.charaWork.parameterSave.state_mainSkillLevel - 70) * 500) * (spell.mpCost * 0.001))) > owner.GetMP()) - { - // todo: error message - interrupt = true; - } - else if (spell.level > owner.charaWork.parameterSave.state_mainSkillLevel) - { - // todo: error message - } - else if (false /*spell.requirements & */) - { - // todo: error message - } - else - { - OnStart(); - } + OnStart(); } - else + + if (interrupt || errorPacket != null) { - // todo: fuckin retarded. enum log messages somewhere (prolly isnt even right param) - if (owner is Player) - ((Player)owner).SendGameMessage(Server.GetWorldManager().GetActor(), (ushort)(returnCode == -1 ? 32539 : returnCode), 0x20); + if (owner is Player && errorPacket != null) + ((Player)owner).QueuePacket(errorPacket); + + errorPacket = null; interrupt = true; } } @@ -79,7 +61,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state foreach (var player in owner.zone.GetActorsAroundActor(owner, 50)) { // todo: this is retarded, prolly doesnt do what i think its gonna do - player.QueuePacket(BattleActionX01Packet.BuildPacket(player.actorId, owner.actorId, target != null ? target.actorId : 0xC0000000, spell.battleAnimation, spell.effectAnimation, 0, spell.id, 0, (byte)spell.castTimeSeconds)); + //player.QueuePacket(BattleActionX01Packet.BuildPacket(player.actorId, owner.actorId, target != null ? target.actorId : 0xC0000000, spell.battleAnimation, spell.effectAnimation, 0, spell.id, 0, (byte)spell.castTimeSeconds)); } } } @@ -123,24 +105,25 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state spell.targetFind.FindWithinArea(target, spell.validTarget); isCompleted = true; + var targets = spell.targetFind.GetTargets(); + BattleAction[] actions = new BattleAction[targets.Count]; List packets = new List(); - foreach (var chara in spell.targetFind.GetTargets()) + var i = 0; + foreach (var chara in targets) { - // todo: calculate shit, do shit - bool landed = true; - var amount = lua.LuaEngine.CallLuaAbilityFunction(owner, spell, "spells", "onSpellFinish", owner, target, spell); + var action = new BattleAction(); + action.effectId = spell.effectAnimation; + action.param = 1; + action.unknown = 1; + action.targetId = chara.actorId; + action.worldMasterTextId = spell.worldMasterTextId; + action.amount = (ushort)lua.LuaEngine.CallLuaAbilityFunction(owner, spell, "spells", "onSpellFinish", owner, chara, spell, action); + actions[i++] = action; - foreach (var player in owner.zone.GetActorsAroundActor(owner, 50)) - { - player.QueuePacket(BattleActionX01Packet.BuildPacket(player.actorId, owner.actorId, chara.actorId, spell.battleAnimation, spell.effectAnimation, spell.worldMasterTextId, spell.id, (ushort)spell.param, 1)); - } - - if (chara is BattleNpc) - { - ((BattleNpc)chara).hateContainer.UpdateHate(owner, amount); - } + packets.Add(BattleActionX01Packet.BuildPacket(chara.actorId, owner.actorId, action.targetId, spell.battleAnimation, action.effectId, action.worldMasterTextId, spell.id, action.amount, action.param)); } - + //packets.Add(BattleActionX10Packet.BuildPacket(player.actorId, owner.actorId, spell.battleAnimation, spell.id, actions)); + owner.zone.BroadcastPacketsAroundActor(owner, packets); } public override void TryInterrupt() @@ -165,41 +148,18 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state interrupt = true; return; } - - if (Utils.DistanceSquared(owner.GetPosAsVector3(), startPos) > 4.0f) - { - // todo: send interrupt packet - interrupt = true; - return; - } interrupt = !CanCast(); } private bool CanCast() { - if (target == null) - { - return false; - } - // todo: shouldnt need to check if owner is dead since all states would be cleared - if (owner.aiContainer.IsDead() || target.aiContainer.IsDead()) - { - return false; - } - else if (!owner.aiContainer.GetTargetFind().CanTarget(target, false, true)) - { - return false; - } - else if (Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, target.positionX, target.positionY, target.positionZ) > spell.range) - { - if (owner.currentSubState == SetActorStatePacket.SUB_STATE_PLAYER) - { - ((Player)owner).SendGameMessage(Server.GetWorldManager().GetActor(), 32539, 0x20); - } - return false; - } - return true; + return owner.CanCast(target, spell, ref errorPacket) && !HasMoved(); + } + + private bool HasMoved() + { + return (Utils.DistanceSquared(owner.GetPosAsVector3(), startPos) > 4.0f); } } } diff --git a/FFXIVClassic Map Server/actors/chara/ai/state/WeaponSkillState.cs b/FFXIVClassic Map Server/actors/chara/ai/state/WeaponSkillState.cs index 444cd45a..abf1b67e 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/state/WeaponSkillState.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/state/WeaponSkillState.cs @@ -7,6 +7,8 @@ using FFXIVClassic.Common; using FFXIVClassic_Map_Server.Actors; using FFXIVClassic_Map_Server.packets.send.actor; using FFXIVClassic_Map_Server.packets.send.actor.battle; +using FFXIVClassic_Map_Server.packets.send; + namespace FFXIVClassic_Map_Server.actors.chara.ai.state { class WeaponSkillState : State @@ -22,37 +24,19 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state this.skill = Server.GetWorldManager().GetAbility(skillId); var returnCode = lua.LuaEngine.CallLuaAbilityFunction(owner, skill, "weaponskills", "onSkillPrepare", owner, target, skill); - if (skill != null && returnCode == 0) + // todo: check recast + if (owner.CanWeaponSkill(target, skill, ref errorPacket)) { // todo: Azia can fix, check the recast time and send error - - if (!skill.IsValidTarget(owner, target)) - { - // todo: error message - interrupt = true; - } - else if ((skill.tpCost = (ushort)Math.Ceiling((8000 + (owner.charaWork.parameterSave.state_mainSkillLevel - 70) * 500) * (skill.tpCost * 0.001))) > owner.GetTP()) - { - // todo: error message - interrupt = true; - } - else if (skill.level > owner.charaWork.parameterSave.state_mainSkillLevel) - { - // todo: error message - } - else if (false /*skill.requirements & */) - { - // todo: error message - } - else - { - OnStart(); - } + OnStart(); } - else + + if (interrupt || errorPacket != null) { - if (owner is Player) - ((Player)owner).SendGameMessage(Server.GetWorldManager().GetActor(), (ushort)(returnCode == -1 ? 32539 : returnCode), 0x20); + if (owner is Player && errorPacket != null) + ((Player)owner).QueuePacket(errorPacket); + + errorPacket = null; interrupt = true; } } @@ -112,24 +96,26 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state skill.targetFind.FindWithinArea(target, skill.validTarget); isCompleted = true; + var targets = skill.targetFind.GetTargets(); + BattleAction[] actions = new BattleAction[targets.Count]; List packets = new List(); - foreach (var chara in skill.targetFind.GetTargets()) + + var i = 0; + foreach (var chara in targets) { - // todo: calculate shit, do shit - bool landed = true; - var amount = lua.LuaEngine.CallLuaAbilityFunction(owner, skill, "weaponskills", "onSkillFinish", owner, target, skill); + var action = new BattleAction(); + action.effectId = 0; + action.param = 1; + action.unknown = 1; + action.targetId = chara.actorId; + action.worldMasterTextId = skill.worldMasterTextId; + action.amount = (ushort)lua.LuaEngine.CallLuaAbilityFunction(owner, skill, "skills", "onSkillFinish", owner, target, skill, action); + actions[i++] = action; - foreach (var player in owner.zone.GetActorsAroundActor(owner, 50)) - { - player.QueuePacket(BattleActionX01Packet.BuildPacket(player.actorId, owner.actorId, chara.actorId, skill.battleAnimation, skill.effectAnimation, skill.worldMasterTextId, skill.id, (ushort)skill.param, 1)); - } - - if (chara is BattleNpc) - { - ((BattleNpc)chara).hateContainer.UpdateHate(owner, amount); - } + packets.Add(BattleActionX01Packet.BuildPacket(chara.actorId, owner.actorId, action.targetId, skill.battleAnimation, action.effectId, action.worldMasterTextId, skill.id, action.amount, action.param)); } - + //packets.Add(BattleActionX10Packet.BuildPacket(player.actorId, owner.actorId, spell.battleAnimation, spell.id, actions)); + owner.zone.BroadcastPacketsAroundActor(owner, packets); } public override void TryInterrupt() @@ -160,28 +146,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state private bool CanUse() { - if (target == null) - { - return false; - } - // todo: shouldnt need to check if owner is dead since all states would be cleared - if (owner.aiContainer.IsDead() || target.aiContainer.IsDead()) - { - return false; - } - else if (!owner.aiContainer.GetTargetFind().CanTarget(target, false, true)) - { - return false; - } - else if (Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, target.positionX, target.positionY, target.positionZ) > skill.range) - { - if (owner.currentSubState == SetActorStatePacket.SUB_STATE_PLAYER) - { - ((Player)owner).SendGameMessage(Server.GetWorldManager().GetActor(), 32539, 0x20); - } - return false; - } - return true; + return owner.CanWeaponSkill(target, skill, ref errorPacket); } } } diff --git a/FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs b/FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs index 5d77a095..f57acf09 100644 --- a/FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs +++ b/FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs @@ -91,10 +91,38 @@ namespace FFXIVClassic_Map_Server.Actors public override bool CanAttack() { - + // todo: return true; } + public override bool CanCast(Character target, Ability spell, ref SubPacket errorPacket) + { + // todo: + return false; + } + + public override bool CanWeaponSkill(Character target, Ability skill, ref SubPacket errorPacket) + { + // todo: + return false; + } + + public override bool CanUseAbility(Character target, Ability ability, ref SubPacket errorPacket) + { + // todo: + return false; + } + + public uint GetDespawnTime() + { + return despawnTime; + } + + public void SetDespawnTime(uint seconds) + { + despawnTime = seconds; + } + /// // todo: create an action object? public bool OnAttack(AttackState state) { @@ -167,16 +195,6 @@ namespace FFXIVClassic_Map_Server.Actors } } - public uint GetDespawnTime() - { - return despawnTime; - } - - public void SetDespawnTime(uint seconds) - { - despawnTime = seconds; - } - public bool IsCloseToSpawn() { return this.isAtSpawn = Utils.DistanceSquared(positionX, positionY, positionZ, spawnX, spawnY, spawnZ) <= 2500.0f; diff --git a/FFXIVClassic Map Server/actors/chara/player/Player.cs b/FFXIVClassic Map Server/actors/chara/player/Player.cs index f09ea137..5ad0ed5c 100644 --- a/FFXIVClassic Map Server/actors/chara/player/Player.cs +++ b/FFXIVClassic Map Server/actors/chara/player/Player.cs @@ -996,6 +996,7 @@ namespace FFXIVClassic_Map_Server.Actors BroadcastPacket(packet, true); Database.SavePlayerCurrentClass(this); + RecalculateStats(); } public void GraphicChange(int slot, InventoryItem invItem) @@ -1914,5 +1915,107 @@ namespace FFXIVClassic_Map_Server.Actors var packet = BattleActionX01Packet.BuildPacket(actorId, actorId, currentTarget != 0xC0000000 ? currentTarget : currentLockedTarget, (uint)anim, (uint)effect, (ushort)text, (ushort)command, (ushort)param, (byte)idek); QueuePacket(packet); } + + public override bool IsValidTarget(Character target, ValidTarget validTarget, ref SubPacket errorPacket) + { + if (target == null) + { + // Target does not exist. + errorPacket = CreateGameMessagePacket(Server.GetWorldManager().GetActor(), 32511, 0x20); + return false; + } + + // enemy only + if ((validTarget & ValidTarget.Enemy) != 0) + { + if (target.currentSubState == SetActorStatePacket.SUB_STATE_NONE) + { + // That command cannot be performed on the current target. + errorPacket = CreateGameMessagePacket(Server.GetWorldManager().GetActor(), 32547, 0x20); + return false; + } + + if (currentParty != null && target.currentParty == currentParty) + { + // That command cannot be performed on a party member. + errorPacket = CreateGameMessagePacket(Server.GetWorldManager().GetActor(), 32548, 0x20); + return false; + } + // todo: pvp? + if (target.currentSubState == currentSubState) + { + // That command cannot be performed on an ally. + errorPacket = CreateGameMessagePacket(Server.GetWorldManager().GetActor(), 32549, 0x20); + return false; + } + } + + if ((validTarget & ValidTarget.Ally) != 0 && target.currentSubState != currentSubState) + { + // That command cannot be performed on the current target. + errorPacket = CreateGameMessagePacket(Server.GetWorldManager().GetActor(), 32547, 0x20); + return false; + } + + if ((validTarget & ValidTarget.NPC) != 0 && target.currentSubState == SetActorStatePacket.SUB_STATE_NONE) + return true; + + // todo: why is player always zoning? + // cant target if zoning + if (target is Player && ((Player)target).playerSession.isUpdatesLocked) + { + // That command cannot be performed on the current target. + errorPacket = CreateGameMessagePacket(Server.GetWorldManager().GetActor(), 32547, 0x20); + return false; + } + + return true; + } + + public override bool CanCast(Character target, Ability spell, ref SubPacket errorPacket) + { + // todo: move the ability specific stuff to ability.cs + if (target == null) + { + // Target does not exist. + errorPacket = CreateGameMessagePacket(Server.GetWorldManager().GetActor(), 32511, 0x20, (uint)spell.id); + return false; + } + if (Utils.Distance(positionX, positionY, positionZ, target.positionX, target.positionY, target.positionZ) > spell.range) + { + // The target is out of range. + errorPacket = CreateGameMessagePacket(Server.GetWorldManager().GetActor(), 32539, 0x20, spell.id); + return false; + } + if (!IsValidTarget(target, spell.validTarget, ref errorPacket) || !spell.IsValidTarget(this, target, ref errorPacket)) + { + // error packet is set in IsValidTarget + return false; + } + return true; + } + + public override bool CanWeaponSkill(Character target, Ability skill, ref SubPacket errorPacket) + { + // todo: see worldmaster ids 32558~32557 for proper ko message and stuff + if (target == null) + { + // Target does not exist. + errorPacket = CreateGameMessagePacket(Server.GetWorldManager().GetActor(), 32511, 0x20, (uint)skill.id); + return false; + } + if (Utils.Distance(positionX, positionY, positionZ, target.positionX, target.positionY, target.positionZ) > skill.range) + { + // The target is out of range. + errorPacket = CreateGameMessagePacket(Server.GetWorldManager().GetActor(), 32539, 0x20, (uint)skill.id); + return false; + } + if (!IsValidTarget(target, skill.validTarget, ref errorPacket) || !skill.IsValidTarget(this, target, ref errorPacket)) + { + // error packet is set in IsValidTarget + return false; + } + return true; + } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX10Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX10Packet.cs index b09c7834..b1d0056a 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX10Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX10Packet.cs @@ -27,7 +27,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle binWriter.Seek(0x20, SeekOrigin.Begin); binWriter.Write((UInt32) actionList.Length); //Num actions (always 1 for this) binWriter.Write((UInt16)commandId); - binWriter.Write((UInt16)810); //? + binWriter.Write((UInt16)0x810); //? binWriter.Seek(0x20, SeekOrigin.Begin); foreach (BattleAction action in actionList) diff --git a/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX18Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX18Packet.cs index c080a32a..74523980 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX18Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX18Packet.cs @@ -27,7 +27,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle binWriter.Seek(0x20, SeekOrigin.Begin); binWriter.Write((UInt32) actionList.Length); //Num actions (always 1 for this) binWriter.Write((UInt16)commandId); - binWriter.Write((UInt16)810); //? + binWriter.Write((UInt16)0x810); //? binWriter.Seek(0x58, SeekOrigin.Begin); foreach (BattleAction action in actionList) diff --git a/data/scripts/commands/AttackMagic.lua b/data/scripts/commands/AttackMagic.lua index 43a95f5a..2c8f9254 100644 --- a/data/scripts/commands/AttackMagic.lua +++ b/data/scripts/commands/AttackMagic.lua @@ -21,7 +21,7 @@ function onEventStarted(player, command, triggerName, arg1, arg2, arg3, arg4, ta player:endEvent(); return; end - + --Does the target exist target = player:getZone():FindActorInArea(targetActor); if (target == nil) then @@ -30,7 +30,7 @@ function onEventStarted(player, command, triggerName, arg1, arg2, arg3, arg4, ta return; end - player.Cast(command.actorId); + player.Cast(command.actorId, targetActor); player:endEvent(); end \ No newline at end of file diff --git a/data/scripts/commands/AttackWeaponSkill.lua b/data/scripts/commands/AttackWeaponSkill.lua index 8cebd1bb..b1e1f66a 100644 --- a/data/scripts/commands/AttackWeaponSkill.lua +++ b/data/scripts/commands/AttackWeaponSkill.lua @@ -26,7 +26,10 @@ function onEventStarted(player, command, triggerName, arg1, arg2, arg3, arg4, ta return; end - player.WeaponSkill(command.actorId); - player:endEvent(); + if not player.aiContainer.IsEngaged() then + player.Engage(targetActor); + end; + player.WeaponSkill(command.actorId, targetActor); + player:endEvent(); end \ No newline at end of file diff --git a/data/scripts/commands/gm/effect.lua b/data/scripts/commands/gm/effect.lua index 95157072..99826e2c 100644 --- a/data/scripts/commands/gm/effect.lua +++ b/data/scripts/commands/gm/effect.lua @@ -19,9 +19,9 @@ function onTrigger(player, argc, effectId, magnitude, tick, duration) player.DelHP(500); effectId = tonumber(effectId) or 223180; - magnitude = tonumber(magnitude) or 300; + magnitude = tonumber(magnitude) or 700; tick = tonumber(tick) or 3; - duration = tonumber(duration) or 60; + duration = tonumber(duration) or 360; while player.statusEffects.HasStatusEffect(effectId) do player.statusEffects.RemoveStatusEffect(effectId); diff --git a/data/scripts/effects/regen.lua b/data/scripts/effects/regen.lua index b3b1f463..7b460604 100644 --- a/data/scripts/effects/regen.lua +++ b/data/scripts/effects/regen.lua @@ -6,25 +6,22 @@ function onGain(target, effect) messageId = MESSAGE_TYPE_SYSTEM_ERROR; sender = "regen"; - target.SendMessage(messageId, sender, "dicks"); end; function onTick(target, effect) messageId = MESSAGE_TYPE_SYSTEM_ERROR; sender = "regen"; + -- todo: actual regen effect thing local ability = GetWorldManager().GetAbility(27346); local anim = bit32.bxor(bit32.lshift(ability.animationType, 24), bit32.lshift(tonumber(1), 12) , 101); local addHp = effect.GetMagnitude(); target.AddHP(addHp); - target.SendBattleActionX01Packet(anim, 101, 0, 0, addHp); - target.SendMessage(messageId, sender, string.format("ate %u dicks", addHp)); + -- target.SendBattleActionX01Packet(anim, 101, 0, 0, addHp); end; function onLose(target, effect) messageId = MESSAGE_TYPE_SYSTEM_ERROR; sender = "regen"; - - target.SendMessage(messageId, sender, "dicks gon"); end; \ No newline at end of file diff --git a/data/scripts/spells/thunder.lua b/data/scripts/spells/thunder.lua new file mode 100644 index 00000000..1418061a --- /dev/null +++ b/data/scripts/spells/thunder.lua @@ -0,0 +1,18 @@ +function onSpellPrepare(caster, target, spell) + return 0; +end; + +function onSpellStart(caster, target, spell) + return 0; +end; + +function onSpellFinish(caster, target, spell, action) + local damage = math.random(10, 100); + print("fuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuckkk") + + if target.hateContainer then + target.hateContainer.AddBaseHate(caster); + target.hateContainer.UpdateHate(caster, damage); + end; + return damage; +end; \ No newline at end of file