From c070f5b80e4ed6481498386f85942e19bfc18bd2 Mon Sep 17 00:00:00 2001 From: Tahir Akhlaq Date: Sat, 26 Aug 2017 17:39:28 +0100 Subject: [PATCH] added ion's and showmo's enums - added nullable DateTime param to UnixTimeStampUTC --- FFXIVClassic Common Class Lib/Utils.cs | 8 +- .../actors/chara/Character.cs | 17 ++-- .../actors/chara/ai/AIContainer.cs | 15 +++- .../chara/ai/controllers/PlayerController.cs | 2 +- .../actors/chara/ai/state/AttackState.cs | 4 +- .../actors/chara/ai/state/MagicState.cs | 27 +++++- .../packets/send/Actor/SetActorStatePacket.cs | 2 - .../send/Actor/SetActorSubStatPacket.cs | 13 ++- .../Actor/battle/BattleActionX01Packet.cs | 83 +++++++++++++++++++ data/scripts/commands/Ability.lua | 36 ++++++++ 10 files changed, 188 insertions(+), 19 deletions(-) create mode 100644 data/scripts/commands/Ability.lua diff --git a/FFXIVClassic Common Class Lib/Utils.cs b/FFXIVClassic Common Class Lib/Utils.cs index a4b06fa9..c8ee1b7f 100644 --- a/FFXIVClassic Common Class Lib/Utils.cs +++ b/FFXIVClassic Common Class Lib/Utils.cs @@ -84,10 +84,10 @@ namespace FFXIVClassic.Common return sb.ToString().TrimEnd(Environment.NewLine.ToCharArray()); } - public static uint UnixTimeStampUTC() + public static uint UnixTimeStampUTC(DateTime? time = null) { uint unixTimeStamp; - var currentTime = DateTime.Now; + var currentTime = time ?? DateTime.Now; var zuluTime = currentTime.ToUniversalTime(); var unixEpoch = new DateTime(1970, 1, 1); unixTimeStamp = (uint)zuluTime.Subtract(unixEpoch).TotalSeconds; @@ -95,10 +95,10 @@ namespace FFXIVClassic.Common return unixTimeStamp; } - public static ulong MilisUnixTimeStampUTC() + public static ulong MilisUnixTimeStampUTC(DateTime? time = null) { ulong unixTimeStamp; - var currentTime = DateTime.Now; + var currentTime = time ?? DateTime.Now; var zuluTime = currentTime.ToUniversalTime(); var unixEpoch = new DateTime(1970, 1, 1); unixTimeStamp = (ulong)zuluTime.Subtract(unixEpoch).TotalMilliseconds; diff --git a/FFXIVClassic Map Server/actors/chara/Character.cs b/FFXIVClassic Map Server/actors/chara/Character.cs index 984c2523..3e05d153 100644 --- a/FFXIVClassic Map Server/actors/chara/Character.cs +++ b/FFXIVClassic Map Server/actors/chara/Character.cs @@ -98,7 +98,7 @@ namespace FFXIVClassic_Map_Server.Actors public ushort currentJob; public Character(uint actorID) : base(actorID) - { + { //Init timer array to "notimer" for (int i = 0; i < charaWork.statusShownTime.Length; i++) charaWork.statusShownTime[i] = 0xFFFFFFFF; @@ -109,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, (Program.Random.Next(30,60) * 100)); + SetMod((uint)Modifier.AttackDelay, (Program.Random.Next(30, 60) * 100)); } public SubPacket CreateAppearancePacket() @@ -120,7 +120,7 @@ namespace FFXIVClassic_Map_Server.Actors public SubPacket CreateInitStatusPacket() { - return (SetActorStatusAllPacket.BuildPacket(actorId, charaWork.status)); + return (SetActorStatusAllPacket.BuildPacket(actorId, charaWork.status)); } public SubPacket CreateSetActorIconPacket() @@ -148,7 +148,7 @@ namespace FFXIVClassic_Map_Server.Actors currentContentGroup = group; ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("charaWork/currentContentGroup", this); - propPacketUtil.AddProperty("charaWork.currentContentGroup"); + propPacketUtil.AddProperty("charaWork.currentContentGroup"); zone.BroadcastPacketsAroundActor(this, propPacketUtil.Done()); } @@ -167,7 +167,7 @@ namespace FFXIVClassic_Map_Server.Actors } public void PlayAnimation(uint animId, bool onlySelf = false) - { + { if (onlySelf) { if (this is Player) @@ -236,7 +236,7 @@ namespace FFXIVClassic_Map_Server.Actors if (updateFlags != ActorUpdateFlags.None) { packets = packets ?? new List(); - + if ((updateFlags & ActorUpdateFlags.Appearance) != 0) { packets.Add(new SetActorAppearancePacket(modelId, appearanceIds).BuildPacket(actorId)); @@ -322,6 +322,11 @@ namespace FFXIVClassic_Map_Server.Actors aiContainer.Cast(Server.GetWorldManager().GetActorInWorld(targetId == 0 ? currentTarget : targetId) as Character, spellId); } + public void Ability(uint abilityId, uint targetId = 0) + { + aiContainer.Ability(Server.GetWorldManager().GetActorInWorld(targetId == 0 ? currentTarget : targetId) as Character, abilityId); + } + public void WeaponSkill(uint skillId) { aiContainer.WeaponSkill(Server.GetWorldManager().GetActorInWorld(currentTarget) as Character, skillId); diff --git a/FFXIVClassic Map Server/actors/chara/ai/AIContainer.cs b/FFXIVClassic Map Server/actors/chara/ai/AIContainer.cs index 0883ef51..07a260b0 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/AIContainer.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/AIContainer.cs @@ -113,7 +113,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai public bool CanChangeState() { - return states.Count == 0 || states.Peek().CanInterrupt(); + return GetCurrentState() == null || states.Peek().CanInterrupt(); } public void ChangeTarget(Character target) @@ -217,6 +217,14 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai InternalDisengage(); } + public void Ability(Character target, uint abilityId) + { + if (controller != null) + controller.Ability(target, abilityId); + else + InternalAbility(target, abilityId); + } + public void Cast(Character target, uint spellId) { if (controller != null) @@ -293,6 +301,11 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai ClearStates(); } + public void InternalAbility(Character target, uint abilityId) + { + + } + public void InternalCast(Character target, uint spellId) { ChangeState(new MagicState(owner, target, (ushort)spellId)); diff --git a/FFXIVClassic Map Server/actors/chara/ai/controllers/PlayerController.cs b/FFXIVClassic Map Server/actors/chara/ai/controllers/PlayerController.cs index 83e662ee..350d70df 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/controllers/PlayerController.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/controllers/PlayerController.cs @@ -68,7 +68,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers public override void Ability(Character target, uint abilityId) { - + owner.aiContainer.InternalAbility(target, abilityId); } public override void RangedAttack(Character target) diff --git a/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs b/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs index 3c7d4087..2572db81 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs @@ -18,9 +18,11 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state public AttackState(Character owner, Character target) : base(owner, target) { + this.canInterrupt = true; + this.startTime = DateTime.Now; + owner.ChangeState(SetActorStatePacket.MAIN_STATE_ACTIVE); owner.aiContainer.ChangeTarget(target); - this.startTime = DateTime.Now; attackTime = startTime; owner.aiContainer.pathFind?.Clear(); // todo: should handle everything here instead of on next tick.. diff --git a/FFXIVClassic Map Server/actors/chara/ai/state/MagicState.cs b/FFXIVClassic Map Server/actors/chara/ai/state/MagicState.cs index c7098b5b..ce5f8379 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/state/MagicState.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/state/MagicState.cs @@ -8,6 +8,7 @@ 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; +using FFXIVClassic_Map_Server.utils; namespace FFXIVClassic_Map_Server.actors.chara.ai.state { @@ -56,12 +57,21 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state // todo: check within attack range startPos = owner.GetPosAsVector3(); owner.LookAt(target); + float[] baseCastDuration = { 1.0f, 0.25f }; - foreach (var player in owner.zone.GetActorsAroundActor(owner, 50)) + float spellSpeed = spell.castTimeSeconds; + List packets = new List(); + + // command casting duration + if (owner.currentSubState == SetActorStatePacket.SUB_STATE_PLAYER) { - // 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)); + // todo: modify spellSpeed based on modifiers and stuff + // ((Player)owner).SendStartCastBar(spell.id, Utils.UnixTimeStampUTC(DateTime.Now.AddSeconds(spellSpeed))); + } + // todo: change + + owner.zone.BroadcastPacketsAroundActor(owner, packets); } } @@ -160,5 +170,16 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state { return (Utils.DistanceSquared(owner.GetPosAsVector3(), startPos) > 4.0f); } + + public override void Cleanup() + { + // command casting duration + var packets = new List(); + if (owner.currentSubState == SetActorStatePacket.SUB_STATE_PLAYER) + { + // ((Player)owner).SendStartCastBar(0, 0); + } + owner.zone.BroadcastPacketsAroundActor(owner, packets); + } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/SetActorStatePacket.cs b/FFXIVClassic Map Server/packets/send/Actor/SetActorStatePacket.cs index ddc1fb43..8bab2346 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/SetActorStatePacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/SetActorStatePacket.cs @@ -1,8 +1,6 @@ using FFXIVClassic.Common; using System; -using FFXIVClassic.Common; - namespace FFXIVClassic_Map_Server.packets.send.actor { class SetActorStatePacket diff --git a/FFXIVClassic Map Server/packets/send/Actor/SetActorSubStatPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/SetActorSubStatPacket.cs index 5c8da573..e132a976 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/SetActorSubStatPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/SetActorSubStatPacket.cs @@ -6,10 +6,21 @@ using FFXIVClassic.Common; namespace FFXIVClassic_Map_Server.packets.send.actor { class SetActorSubStatPacket - { + { public const ushort OPCODE = 0x144; public const uint PACKET_SIZE = 0x28; + enum SubStat : int + { + Breakage = 0x00, // (index goes high to low, bitflags) + Chant = 0x01, // [Nibbles: left / right hand = value]) (AKA SubStatObject) + Guard = 0x02, // [left / right hand = true] 0,1,2,3) ||| High byte also defines how many bools to use as flags for byte 0x4. + Waste = 0x03, // (High Nibble) + Mode = 0x04, // ??? + Unknown = 0x05, // ??? + SubStatMotionPack = 0x06, + Unknown2 = 0x07, + } public static SubPacket BuildPacket(uint sourceActorId, byte breakage, int leftChant, int rightChant, int guard, int wasteStat, int statMode, uint idleAnimationId) { byte[] data = new byte[PACKET_SIZE - 0x20]; diff --git a/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX01Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX01Packet.cs index 11c2530e..7943f9b4 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX01Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX01Packet.cs @@ -10,6 +10,89 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle Disengage = 12002, Attack = 22104, } + + //These flags can be stacked and mixed, but the client will prioritize certain flags over others. + [Flags] + public enum HitEffect : uint + { + //Not setting RecoilLv2 or RecoilLv3 results in the weaker RecoilLv1. + //These are the recoil animations that play on the target, ranging from weak to strong. + //The recoil that gets set was likely based on the percentage of HP lost from the attack. + RecoilLv1 = 0, + RecoilLv2 = 1 << 0, + RecoilLv3 = 1 << 1, + + //Setting both recoil flags triggers the "Critical!" pop-up text and hit visual effect. + CriticalHit = RecoilLv2 | RecoilLv3, + + //Hit visual and sound effects when connecting with the target. + //Mixing these flags together will yield different results. + //Each visual likely relates to a specific weapon. + //Ex: HitVisual4 flag alone appears to be the visual and sound effect for hand-to-hand attacks. + HitVisual1 = 1 << 2, + HitVisual2 = 1 << 3, + HitVisual3 = 1 << 4, + HitVisual4 = 1 << 5, + + //An additional visual effect that plays on the target when attacked if: + //The attack is physical and they have the protect buff on. + //The attack is magical and they have the shell buff on. + //Special Note: Shell was removed in later versions of the game. + //Another effect plays when both Protect and Shell flags are activated. + //Not sure what this effect is. + //Random guess: if the attack was a hybrid of both physical and magical and the target had both Protect and Shell buffs applied. + Protect = 1 << 6, + Shell = 1 << 7, + ProtectShellSpecial = Protect | Shell, + + //Unknown = 1 << 8, -- Not sure what this flag does. + + //If only HitEffect1 is set out of the hit effects, the "Evade!" pop-up text triggers along with the evade visual. + //If no hit effects are set, the "Miss!" pop-up is triggered and no hit visual is played. + HitEffect1 = 1 << 9, + HitEffect2 = 1 << 10, //Plays the standard hit visual effect, but with no sound if used alone. + Hit = HitEffect1 | HitEffect2, //A standard hit effect with sound effect. + HitEffect3 = 1 << 11, + HitEffect4 = 1 << 12, + HitEffect5 = 1 << 13, + GustyHitEffect = HitEffect3 | HitEffect2, + GreenTintedHitEffect = HitEffect4 | HitEffect1, + + //Knocks you back away from the attacker. + KnockbackLv1 = HitEffect4 | HitEffect2 | HitEffect1, + KnockbackLv2 = HitEffect4 | HitEffect3, + KnockbackLv3 = HitEffect4 | HitEffect3 | HitEffect1, + KnockbackLv4 = HitEffect4 | HitEffect3 | HitEffect2, + KnockbackLv5 = HitEffect4 | HitEffect3 | HitEffect2 | HitEffect1, + + //Knocks you away from the attacker in a counter-clockwise direction. + KnockbackCounterClockwiseLv1 = HitEffect5, + KnockbackCounterClockwiseLv2 = HitEffect5 | HitEffect1, + + //Knocks you away from the attacker in a clockwise direction. + KnockbackClockwiseLv1 = HitEffect5 | HitEffect2, + KnockbackClockwiseLv2 = HitEffect5 | HitEffect2 | HitEffect1, + + //Completely drags target to the attacker, even across large distances. + DrawIn = HitEffect5 | HitEffect3, + + //An additional visual effect that plays on the target based on according buff. + UnknownShieldEffect = HitEffect5 | HitEffect4, + Stoneskin = HitEffect5 | HitEffect4 | HitEffect1, + + //Unknown = 1 << 14, -- Not sure what this flag does; might be another HitEffect. + + //A special effect when performing appropriate skill combos in succession. + //Ex: Thunder (SkillCombo1 Effect) -> Thundara (SkillCombo2 Effect) -> Thundaga (SkillCombo3 Effect) + //Special Note: SkillCombo4 was never actually used in 1.0 since combos only chained up to 3 times maximum. + SkillCombo1 = 1 << 15, + SkillCombo2 = 1 << 16, + SkillCombo3 = SkillCombo1 | SkillCombo2, + SkillCombo4 = 1 << 17 + + //Flags beyond here are unknown/untested. + } + class BattleActionX01Packet { public const ushort OPCODE = 0x0139; diff --git a/data/scripts/commands/Ability.lua b/data/scripts/commands/Ability.lua new file mode 100644 index 00000000..1ba98a3c --- /dev/null +++ b/data/scripts/commands/Ability.lua @@ -0,0 +1,36 @@ +require ("global") +require ("utils") + +--[[ + +AttackWeaponSkill Script + +Finds the correct weaponskill subscript to fire when a weaponskill actor is activated. + +--]] + +local attackMagicHandlers = { + +} + +function onEventStarted(player, command, triggerName, arg1, arg2, arg3, arg4, targetActor, arg5, arg6, arg7, arg8) + print(command.actorId) + --Are they in active mode? + if (player:GetState() != 2) then + player:SendGameMessage(GetWorldMaster(), 32503, 0x20); + player:endEvent(); + return; + end + + --Does the target exist + target = player:getZone():FindActorInArea(targetActor); + if (target == nil) then + player:SendGameMessage(GetWorldMaster(), 30203, 0x20); + player:endEvent(); + return; + end + + player.Ability(command.actorId, targetActor); + player:endEvent(); + +end \ No newline at end of file