diff --git a/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj b/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj index ae9023ba..0b56c529 100644 --- a/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj +++ b/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj @@ -109,6 +109,7 @@ + diff --git a/FFXIVClassic Map Server/PacketProcessor.cs b/FFXIVClassic Map Server/PacketProcessor.cs index 3fdad311..3e0f051c 100644 --- a/FFXIVClassic Map Server/PacketProcessor.cs +++ b/FFXIVClassic Map Server/PacketProcessor.cs @@ -154,6 +154,7 @@ namespace FFXIVClassic_Map_Server case 0x00CC: LockTargetPacket lockTarget = new LockTargetPacket(subpacket.data); session.GetActor().currentLockedTarget = lockTarget.actorID; + // todo: this really needs figuring out.. session.GetActor().isAutoAttackEnabled = lockTarget.otherVal == 0x00000040; break; diff --git a/FFXIVClassic Map Server/WorldManager.cs b/FFXIVClassic Map Server/WorldManager.cs index 52a8a587..bf1f2844 100644 --- a/FFXIVClassic Map Server/WorldManager.cs +++ b/FFXIVClassic Map Server/WorldManager.cs @@ -25,6 +25,7 @@ using System.Diagnostics; using FFXIVClassic_Map_Server.actors.director; using FFXIVClassic_Map_Server.actors.chara.ai; using FFXIVClassic_Map_Server.actors.chara; +using FFXIVClassic_Map_Server.Actors.Chara; namespace FFXIVClassic_Map_Server { @@ -427,7 +428,7 @@ namespace FFXIVClassic_Map_Server { conn.Open(); var query = @" - SELECT bsl.groupId, bsl.positionX, bsl.positionY, bsl.positionZ, bsl.rotation, + SELECT bsl.bnpcId, bsl.groupId, bsl.positionX, bsl.positionY, bsl.positionZ, bsl.rotation, bgr.groupId, bgr.poolId, bgr.actorClassId, bgr.scriptName, bgr.minLevel, bgr.maxLevel, bgr.respawnTime, bgr.hp, bgr.mp, bgr.dropListId, bgr.allegiance, bgr.spawnType, bgr.animationId, bgr.actorState, bgr.privateAreaName, bgr.privateAreaLevel, bgr.zoneId, bpo.poolId, bpo.genusId, bpo.currentJob, bpo.combatSkill, bpo.combatDelay, bpo.combatDmgMult, bpo.aggroType, @@ -439,7 +440,7 @@ namespace FFXIVClassic_Map_Server INNER JOIN server_battlenpc_groups bgr ON bsl.groupId = bgr.groupId INNER JOIN server_battlenpc_pools bpo ON bgr.poolId = bpo.poolId INNER JOIN server_battlenpc_genus bge ON bpo.genusId = bge.genusId - WHERE bgr.zoneId = @zoneId GROUP BY bsl.bnpcIndex; + WHERE bgr.zoneId = @zoneId GROUP BY bsl.bnpcId; "; var count = 0; @@ -457,20 +458,19 @@ namespace FFXIVClassic_Map_Server int actorId = zone.GetActorCount() + 1; // todo: add to private areas, set up immunity, mob linking, - // - load skill/spell/drop lists, set npcWork.hateType, + // - load skill/spell/drop lists, set detection icon, load pool/family/group mods var battleNpc = new BattleNpc(actorId, Server.GetWorldManager().GetActorClass(reader.GetUInt32("actorClassId")), reader.GetString("scriptName"), zone, reader.GetFloat("positionX"), reader.GetFloat("positionY"), reader.GetFloat("positionZ"), reader.GetFloat("rotation"), reader.GetUInt16("actorState"), reader.GetUInt32("animationId"), ""); + battleNpc.SetBattleNpcId(reader.GetUInt32("bnpcId")); battleNpc.neutral = reader.GetByte("aggroType") == 0; battleNpc.SetDetectionType(reader.GetUInt32("detection")); battleNpc.kindredType = (KindredType)reader.GetUInt32("kindredId"); battleNpc.npcSpawnType = (NpcSpawnType)reader.GetUInt32("spawnType"); - // todo: set hateType to appropriate detectionType thing - //battleNpc.npcWork.hateType battleNpc.charaWork.parameterSave.state_mainSkill[0] = reader.GetByte("currentJob"); battleNpc.charaWork.parameterSave.state_mainSkillLevel = (short)Program.Random.Next(reader.GetByte("minLevel"), reader.GetByte("maxLevel")); @@ -527,6 +527,112 @@ namespace FFXIVClassic_Map_Server z.SpawnAllActors(true); } + public void SpawnBattleNpcById(uint id) + { + // todo: this is stupid duplicate code and really needs to die, think of a better way later + using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD))) + { + try + { + conn.Open(); + var query = @" + SELECT bsl.bnpcId, bsl.groupId, bsl.positionX, bsl.positionY, bsl.positionZ, bsl.rotation, + bgr.groupId, bgr.poolId, bgr.actorClassId, bgr.scriptName, bgr.minLevel, bgr.maxLevel, bgr.respawnTime, bgr.hp, bgr.mp, + bgr.dropListId, bgr.allegiance, bgr.spawnType, bgr.animationId, bgr.actorState, bgr.privateAreaName, bgr.privateAreaLevel, bgr.zoneId, + bpo.poolId, bpo.genusId, bpo.currentJob, bpo.combatSkill, bpo.combatDelay, bpo.combatDmgMult, bpo.aggroType, + bpo.immunity, bpo.linkType, bpo.skillListId, bpo.spellListId, + bge.genusId, bge.modelSize, bge.kindredId, bge.detection, bge.hpp, bge.mpp, bge.tpp, bge.str, bge.vit, bge.dex, + bge.int, bge.mnd, bge.pie, bge.att, bge.acc, bge.def, bge.eva, bge.slash, bge.pierce, bge.h2h, bge.blunt, + bge.fire, bge.ice, bge.wind, bge.lightning, bge.earth, bge.water + FROM server_battlenpc_spawn_locations bsl + INNER JOIN server_battlenpc_groups bgr ON bsl.groupId = bgr.groupId + INNER JOIN server_battlenpc_pools bpo ON bgr.poolId = bpo.poolId + INNER JOIN server_battlenpc_genus bge ON bpo.genusId = bge.genusId + WHERE bsl.bnpcId = @bnpcId GROUP BY bsl.bnpcId; + "; + + var count = 0; + + MySqlCommand cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@bnpcId", id); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + var zone = Server.GetWorldManager().GetZone(reader.GetUInt16("zoneId")); + int actorId = zone.GetActorCount() + 1; + var bnpc = zone.GetBattleNpcById(id); + + if (bnpc != null) + { + bnpc.ForceRespawn(); + break; + } + + // todo: add to private areas, set up immunity, mob linking, + // - load skill/spell/drop lists, set detection icon, load pool/family/group mods + + var battleNpc = new BattleNpc(actorId, Server.GetWorldManager().GetActorClass(reader.GetUInt32("actorClassId")), + reader.GetString("scriptName"), zone, reader.GetFloat("positionX"), reader.GetFloat("positionY"), reader.GetFloat("positionZ"), reader.GetFloat("rotation"), + reader.GetUInt16("actorState"), reader.GetUInt32("animationId"), ""); + + battleNpc.SetBattleNpcId(reader.GetUInt32("bnpcId")); + battleNpc.neutral = reader.GetByte("aggroType") == 0; + + battleNpc.SetDetectionType(reader.GetUInt32("detection")); + battleNpc.kindredType = (KindredType)reader.GetUInt32("kindredId"); + battleNpc.npcSpawnType = (NpcSpawnType)reader.GetUInt32("spawnType"); + + battleNpc.charaWork.parameterSave.state_mainSkill[0] = reader.GetByte("currentJob"); + battleNpc.charaWork.parameterSave.state_mainSkillLevel = (short)Program.Random.Next(reader.GetByte("minLevel"), reader.GetByte("maxLevel")); + + battleNpc.allegiance = (CharacterTargetingAllegiance)reader.GetByte("allegiance"); + + // todo: setup private areas and other crap and + // set up rest of stat resists + battleNpc.SetMod((uint)Modifier.Hp, reader.GetUInt32("hp")); + battleNpc.SetMod((uint)Modifier.HpPercent, reader.GetUInt32("hpp")); + battleNpc.SetMod((uint)Modifier.Mp, reader.GetUInt32("mp")); + battleNpc.SetMod((uint)Modifier.MpPercent, reader.GetUInt32("mpp")); + battleNpc.SetMod((uint)Modifier.TpPercent, reader.GetUInt32("tpp")); + + battleNpc.SetMod((uint)Modifier.Strength, reader.GetUInt32("str")); + battleNpc.SetMod((uint)Modifier.Vitality, reader.GetUInt32("vit")); + battleNpc.SetMod((uint)Modifier.Dexterity, reader.GetUInt32("dex")); + battleNpc.SetMod((uint)Modifier.Intelligence, reader.GetUInt32("int")); + battleNpc.SetMod((uint)Modifier.Mind, reader.GetUInt32("mnd")); + battleNpc.SetMod((uint)Modifier.Piety, reader.GetUInt32("pie")); + battleNpc.SetMod((uint)Modifier.Attack, reader.GetUInt32("att")); + battleNpc.SetMod((uint)Modifier.Accuracy, reader.GetUInt32("acc")); + battleNpc.SetMod((uint)Modifier.Defense, reader.GetUInt32("def")); + battleNpc.SetMod((uint)Modifier.Evasion, reader.GetUInt32("eva")); + + + battleNpc.dropListId = reader.GetUInt32("dropListId"); + battleNpc.spellListId = reader.GetUInt32("spellListId"); + battleNpc.skillListId = reader.GetUInt32("skillListId"); + + battleNpc.SetBattleNpcId(reader.GetUInt32("bnpcId")); + //battleNpc.SetMod((uint)Modifier.ResistFire, ) + + zone.AddActorToZone(battleNpc); + count++; + } + } + Program.Log.Info("WorldManager.SpawnBattleNpcById spawned BattleNpc {0}.", id); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } + //Moves the actor to the new zone if exists. No packets are sent nor position changed. Merged zone is removed. public void DoSeamlessZoneChange(Player player, uint destinationZoneId) { diff --git a/FFXIVClassic Map Server/actors/Actor.cs b/FFXIVClassic Map Server/actors/Actor.cs index 3a549e7d..ff589d52 100644 --- a/FFXIVClassic Map Server/actors/Actor.cs +++ b/FFXIVClassic Map Server/actors/Actor.cs @@ -25,9 +25,10 @@ namespace FFXIVClassic_Map_Server.Actors Name = 0x08, Appearance = 0x10, Speed = 0x20, + Work = 0x40, - AllNpc = 0x2F, - AllPlayer = 0x3F + AllNpc = 0x6F, + AllPlayer = 0x9F } class Actor diff --git a/FFXIVClassic Map Server/actors/area/Area.cs b/FFXIVClassic Map Server/actors/area/Area.cs index dac57595..3918f2f6 100644 --- a/FFXIVClassic Map Server/actors/area/Area.cs +++ b/FFXIVClassic Map Server/actors/area/Area.cs @@ -526,6 +526,16 @@ namespace FFXIVClassic_Map_Server.Actors } } + public BattleNpc GetBattleNpcById(uint id) + { + foreach (var bnpc in GetAllActors()) + { + if (bnpc.GetBattleNpcId() == id) + return bnpc; + } + return null; + } + public void DespawnActor(string uniqueId) { RemoveActorFromZone(FindActorInZoneByUniqueID(uniqueId)); diff --git a/FFXIVClassic Map Server/actors/chara/Character.cs b/FFXIVClassic Map Server/actors/chara/Character.cs index 9c046c17..0af7b0be 100644 --- a/FFXIVClassic Map Server/actors/chara/Character.cs +++ b/FFXIVClassic Map Server/actors/chara/Character.cs @@ -159,7 +159,6 @@ namespace FFXIVClassic_Map_Server.Actors ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("charaWork/currentContentGroup", this); propPacketUtil.AddProperty("charaWork.currentContentGroup"); zone.BroadcastPacketsAroundActor(this, propPacketUtil.Done()); - } public List GetActorStatusPackets() @@ -593,6 +592,7 @@ 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); } + target.OnDamageTaken(this, action); // todo: call onAttack/onDamageTaken target.DelHP(action.amount); if (target is BattleNpc) @@ -605,6 +605,9 @@ namespace FFXIVClassic_Map_Server.Actors // damage is handled in script this.DelMP(spell.mpCost); // mpCost can be set in script e.g. if caster has something for free spells + foreach (var action in actions) + zone.FindActorInArea(action.targetId).OnDamageTaken(this, action); + if (target is BattleNpc) ((BattleNpc)target).lastAttacker = this; } @@ -615,6 +618,9 @@ namespace FFXIVClassic_Map_Server.Actors // damage is handled in script this.DelTP(skill.tpCost); + foreach (var action in actions) + zone.FindActorInArea(action.targetId)?.OnDamageTaken(this, action); + if (target is BattleNpc) ((BattleNpc)target).lastAttacker = this; } @@ -623,6 +629,9 @@ namespace FFXIVClassic_Map_Server.Actors { if (target is BattleNpc) ((BattleNpc)target).lastAttacker = this; + + foreach (var action in actions) + zone.FindActorInArea(action.targetId)?.OnDamageTaken(this, action); } public virtual void OnSpawn() @@ -638,6 +647,11 @@ namespace FFXIVClassic_Map_Server.Actors public virtual void OnDespawn() { + } + + public virtual void OnDamageTaken(Character attacker, BattleAction action) + { + } #endregion } diff --git a/FFXIVClassic Map Server/actors/chara/ai/ActionQueue.cs b/FFXIVClassic Map Server/actors/chara/ai/ActionQueue.cs index b69caed8..87112724 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/ActionQueue.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/ActionQueue.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using FFXIVClassic_Map_Server.Actors; using MoonSharp; using MoonSharp.Interpreter; +using FFXIVClassic_Map_Server.lua; namespace FFXIVClassic_Map_Server.actors.chara.ai { @@ -15,8 +16,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai public uint durationMs; public bool checkState; // todo: lua function - Script script; + LuaScript script; } + class ActionQueue { private Character owner; @@ -27,7 +29,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai public ActionQueue(Character owner) { - + this.owner = owner; + actionQueue = new Queue(); + timerQueue = new Queue(); } public void PushAction(Action action) @@ -45,5 +49,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai } + public void CheckAction(DateTime tick) + { + + } } } diff --git a/FFXIVClassic Map Server/actors/chara/ai/controllers/BattleNpcController.cs b/FFXIVClassic Map Server/actors/chara/ai/controllers/BattleNpcController.cs index ca709462..5b8ef878 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/controllers/BattleNpcController.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/controllers/BattleNpcController.cs @@ -363,8 +363,12 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers public override void ChangeTarget(Character target) { owner.target = target; - owner.currentLockedTarget = target != null ? target.actorId : 0xC0000000; - owner.currentTarget = target != null ? target.actorId : 0xC0000000; + owner.currentLockedTarget = target?.actorId ?? 0xC0000000; + owner.currentTarget = target?.actorId ?? 0xC0000000; + + foreach (var player in owner.zone.GetActorsAroundActor(owner, 50)) + player.QueuePacket(owner.GetHateTypePacket(player)); + base.ChangeTarget(target); } } diff --git a/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs b/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs index f0e8df36..5994f140 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs @@ -155,7 +155,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state owner.aiContainer.ChangeTarget(null); return false; } - else if (!owner.aiContainer.GetTargetFind().CanTarget(target, false, true)) + else if (!owner.IsValidTarget(target, ValidTarget.Enemy) || !owner.aiContainer.GetTargetFind().CanTarget(target, false, true)) { return false; } diff --git a/FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs b/FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs index 6aa386fd..610a0e3e 100644 --- a/FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs +++ b/FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs @@ -17,6 +17,7 @@ using FFXIVClassic_Map_Server.packets.send.actor.battle; using FFXIVClassic_Map_Server.actors.chara.ai.utils; using FFXIVClassic_Map_Server.actors.group; using FFXIVClassic_Map_Server.packets.send; +using FFXIVClassic_Map_Server.Actors.Chara; namespace FFXIVClassic_Map_Server.Actors { @@ -56,12 +57,13 @@ namespace FFXIVClassic_Map_Server.Actors private uint despawnTime; private uint respawnTime; private uint spawnDistance; - + private uint bnpcId; public Character lastAttacker; public uint spellListId, skillListId, dropListId; public Dictionary skillList = new Dictionary(); public Dictionary spellList = new Dictionary(); + private Dictionary mobModifiers = new Dictionary(); public BattleNpc(int actorNumber, ActorClass actorClass, string uniqueId, Area spawnedArea, float posX, float posY, float posZ, float rot, ushort actorState, uint animationId, string customDisplayName) @@ -110,10 +112,43 @@ namespace FFXIVClassic_Map_Server.Actors subpackets.Add(CreateSetActorIconPacket()); subpackets.Add(CreateIsZoneingPacket()); subpackets.Add(CreateScriptBindPacket(player)); + subpackets.Add(GetHateTypePacket(player)); } return subpackets; } + public SubPacket GetHateTypePacket(Player player) + { + npcWork.hateType = 1; + + if (player != null) + { + if (aiContainer.IsEngaged()) + { + npcWork.hateType = 2; + } + + if (player.actorId == this.currentLockedTarget) + { + npcWork.hateType = NpcWork.HATE_TYPE_ENGAGED_PARTY; + } + else if (player.currentParty != null) + { + foreach (var memberId in ((Party)player.currentParty).members) + { + if (this.currentLockedTarget == memberId) + { + npcWork.hateType = NpcWork.HATE_TYPE_ENGAGED_PARTY; + break; + } + } + } + } + var propPacketUtil = new ActorPropertyPacketUtil("npcWork", this); + propPacketUtil.AddProperty("npcWork.hateType"); + return propPacketUtil.Done()[0]; + } + public uint GetDetectionType() { return (uint)detectionType; @@ -220,6 +255,23 @@ namespace FFXIVClassic_Map_Server.Actors } } + public void ForceRespawn() + { + base.Spawn(Program.Tick); + + this.isMovingToSpawn = false; + this.ResetMoveSpeeds(); + this.hateContainer.ClearHate(); + zone.BroadcastPacketsAroundActor(this, GetSpawnPackets(null, 0x01)); + zone.BroadcastPacketsAroundActor(this, GetInitPackets()); + charaWork.parameterSave.hp = charaWork.parameterSave.hpMax; + charaWork.parameterSave.mp = charaWork.parameterSave.mpMax; + RecalculateStats(); + + OnSpawn(); + updateFlags |= ActorUpdateFlags.AllNpc; + } + public override void Die(DateTime tick) { if (IsAlive()) @@ -308,6 +360,25 @@ namespace FFXIVClassic_Map_Server.Actors 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 + + if (GetMobMod((uint)MobModifier.AttackScript) != 0) + lua.LuaEngine.CallLuaBattleFunction(this, "onAttack", this, state.GetTarget(), action.amount); + } + + public override void OnCast(State state, BattleAction[] actions, ref BattleAction[] errors) + { + base.OnCast(state, actions, ref errors); + + } + + public override void OnAbility(State state, BattleAction[] actions, ref BattleAction[] errors) + { + base.OnAbility(state, actions, ref errors); + } + + public override void OnWeaponSkill(State state, BattleAction[] actions, ref BattleAction[] errors) + { + base.OnWeaponSkill(state, actions, ref errors); } public override void OnSpawn() @@ -325,5 +396,38 @@ namespace FFXIVClassic_Map_Server.Actors { base.OnDespawn(); } + + public uint GetBattleNpcId() + { + return bnpcId; + } + + public void SetBattleNpcId(uint id) + { + this.bnpcId = id; + } + + + public Int64 GetMobMod(uint mobModId) + { + Int64 res; + if (mobModifiers.TryGetValue((MobModifier)mobModId, out res)) + return res; + return 0; + } + + public void SetMobMod(uint mobModId, Int64 val) + { + if (mobModifiers.ContainsKey((MobModifier)mobModId)) + mobModifiers[(MobModifier)mobModId] = val; + else + mobModifiers.Add((MobModifier)mobModId, val); + } + + public override void OnDamageTaken(Character attacker, BattleAction action) + { + if (GetMobMod((uint)MobModifier.DefendScript) != 0) + lua.LuaEngine.CallLuaBattleFunction(this, "onDamageTaken", this, attacker, action.amount); + } } } diff --git a/FFXIVClassic Map Server/actors/chara/npc/MobModifier.cs b/FFXIVClassic Map Server/actors/chara/npc/MobModifier.cs new file mode 100644 index 00000000..7b2cc67b --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/npc/MobModifier.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Map_Server.actors.chara.npc +{ + enum MobModifier + { + None = 0, + SpawnLeash = 1, // how far can i move before i deaggro target + SightRange = 2, // how close does target need to be for me to detect by sight + SoundRange = 3, // how close does target need to be for me to detect by sound + BuffChance = 4, + HealChance = 5, + SkillUseChance = 6, + LinkRadius = 7, + MagicDelay = 8, + SpecialDelay = 9, + ExpBonus = 10, // + IgnoreSpawnLeash = 11, // pursue target forever + DrawIn = 12, // do i suck people in around me + HpScale = 13, // + Assist = 14, // gotta call the bois + NoMove = 15, // cant move + ShareTarget = 16, // use this actor's id as target id + AttackScript = 17, // call my script's onAttack whenever i attack + DefendScript = 18, // call my script's onDamageTaken whenever i take damage + SpellScript = 19, // call my script's onSpellCast whenever i finish casting + WeaponskillScript = 20, // call my script's onWeaponSkill whenever i finish using a weaponskill + AbilityScript = 21, // call my script's onAbility whenever i finish using an ability + CallForHelp = 22, // actor with this id outside of target's party with this can attack me + FreeForAll = 23, // any actor can attack me + } +} \ No newline at end of file diff --git a/FFXIVClassic Map Server/actors/chara/npc/Npc.cs b/FFXIVClassic Map Server/actors/chara/npc/Npc.cs index 845dda05..8feb3f51 100644 --- a/FFXIVClassic Map Server/actors/chara/npc/Npc.cs +++ b/FFXIVClassic Map Server/actors/chara/npc/Npc.cs @@ -414,6 +414,17 @@ namespace FFXIVClassic_Map_Server.Actors aiContainer.Update(tick); } + public override void PostUpdate(DateTime tick, List packets = null) + { + packets = packets ?? new List(); + + if ((updateFlags & ActorUpdateFlags.Work) != 0) + { + + } + base.PostUpdate(tick, packets); + } + public override void OnSpawn() { base.OnSpawn(); diff --git a/FFXIVClassic Map Server/actors/chara/npc/NpcWork.cs b/FFXIVClassic Map Server/actors/chara/npc/NpcWork.cs index ca21d437..7827f7f5 100644 --- a/FFXIVClassic Map Server/actors/chara/npc/NpcWork.cs +++ b/FFXIVClassic Map Server/actors/chara/npc/NpcWork.cs @@ -2,6 +2,10 @@ { class NpcWork { + public static byte HATE_TYPE_NONE = 0; + public static byte HATE_TYPE_ENGAGED = 2; + public static byte HATE_TYPE_ENGAGED_PARTY = 3; + public ushort pushCommand; public int pushCommandSub; public byte pushCommandPriority; diff --git a/FFXIVClassic Map Server/actors/chara/player/Player.cs b/FFXIVClassic Map Server/actors/chara/player/Player.cs index 2d6686a5..fab60a05 100644 --- a/FFXIVClassic Map Server/actors/chara/player/Player.cs +++ b/FFXIVClassic Map Server/actors/chara/player/Player.cs @@ -25,6 +25,7 @@ using FFXIVClassic_Map_Server.actors.chara.ai.controllers; using FFXIVClassic_Map_Server.packets.send.actor.battle; using FFXIVClassic_Map_Server.actors.chara.ai.utils; using FFXIVClassic_Map_Server.actors.chara.ai.state; +using FFXIVClassic_Map_Server.actors.chara.npc; namespace FFXIVClassic_Map_Server.Actors { @@ -2037,6 +2038,47 @@ namespace FFXIVClassic_Map_Server.Actors SendGameMessage(Server.GetWorldManager().GetActor(), 32549, 0x20); return false; } + + bool partyEngaged = false; + // todo: replace with confrontation status effect? (see how dsp does it) + if (target.aiContainer.IsEngaged()) + { + if (currentParty != null) + { + if (target is BattleNpc) + { + var helpingActorId = ((BattleNpc)target).GetMobMod((uint)MobModifier.CallForHelp); + partyEngaged = this.actorId == helpingActorId || (((BattleNpc)target).GetMobMod((uint)MobModifier.FreeForAll) != 0); + } + + if (!partyEngaged) + { + foreach (var memberId in ((Party)currentParty).members) + { + if (memberId == target.currentLockedTarget) + { + partyEngaged = true; + break; + } + } + } + } + else if (target.currentLockedTarget == actorId) + { + partyEngaged = true; + } + } + else + { + partyEngaged = true; + } + + if (!partyEngaged) + { + // That target is already engaged. + SendGameMessage(Server.GetWorldManager().GetActor(), 32520, 0x20); + return false; + } } if ((validTarget & ValidTarget.Ally) != 0 && target.allegiance != allegiance) diff --git a/data/scripts/commands/weaponskill/fast_blade.lua b/data/scripts/commands/weaponskill/fast_blade.lua new file mode 100644 index 00000000..e44a637d --- /dev/null +++ b/data/scripts/commands/weaponskill/fast_blade.lua @@ -0,0 +1,27 @@ +require("global"); +require("weaponskill"); + +function onSkillPrepare(caster, target, spell) + return 0; +end; + +function onSkillStart(caster, target, spell) + return 0; +end; + +function onSkillFinish(caster, target, spell, action) + local damage = math.random(10, 100); + + -- todo: populate a global script with statuses and modifiers + action.worldMasterTextId = 0x765D; + + -- todo: populate a global script with statuses and modifiers + -- magic.HandleAttackSkill(caster, target, spell, action) + -- action.effectId = bit32.bxor(0x8000000, spell.effectAnimation, 15636); + action.effectId = bit32.bxor(0x8000000, spell.effectAnimation, 15636); + + if target.hateContainer then + target.hateContainer.UpdateHate(caster, damage); + end; + return damage; +end; \ No newline at end of file diff --git a/data/scripts/magic.lua b/data/scripts/magic.lua index 35318f58..a2965fa4 100644 --- a/data/scripts/magic.lua +++ b/data/scripts/magic.lua @@ -7,10 +7,11 @@ magic = }; --[[ - modifier - Modifier.Intelligence, Modifier.Mind (see Modifier.cs) + statId - see BattleTemp.cs + modifierId - Modifier.Intelligence, Modifier.Mind (see Modifier.cs) multiplier - ]] -function magic.HandleHealingMagic(caster, target, spell, action, modifierId, multiplier, baseAmount) +function magic.HandleHealingMagic(caster, target, spell, action, statId, modifierId, multiplier, baseAmount) potency = potency or 1.0; healAmount = baseAmount; @@ -18,19 +19,19 @@ function magic.HandleHealingMagic(caster, target, spell, action, modifierId, mul local mind = caster.GetMod(Modifier.Mind); end; -function magic.HandleAttackMagic(caster, target, spell, action, modifierId, multiplier, baseAmount) +function magic.HandleAttackMagic(caster, target, spell, action, statId, modifierId, multiplier, baseAmount) -- todo: actually handle this damage = baseAmount or math.random(1,10) * 10; return damage; end; -function magic.HandleEvasion(caster, target, spell, action, modifierId) +function magic.HandleEvasion(caster, target, spell, action, statId, modifierId) return false; end; -function magic.HandleStoneskin(caster, target, spell, action, modifierId, damage) +function magic.HandleStoneskin(caster, target, spell, action, statId, modifierId, damage) --[[ if target.statusEffects.HasStatusEffect(StatusEffect.Stoneskin) then -- todo: damage reduction diff --git a/data/scripts/weaponskill.lua b/data/scripts/weaponskill.lua new file mode 100644 index 00000000..84d79cc7 --- /dev/null +++ b/data/scripts/weaponskill.lua @@ -0,0 +1,42 @@ +-- todo: add enums for status effects in global.lua +require("global") + +weaponskill = +{ + +}; + +--[[ + statId - see BattleTemp.cs + modifier - Modifier.Intelligence, Modifier.Mind (see Modifier.cs) + multiplier - + ]] +function weaponskill.HandleHealingSkill(caster, target, spell, action, statId, modifierId, multiplier, baseAmount) + potency = potency or 1.0; + healAmount = baseAmount; + + -- todo: shit based on mnd + local mind = caster.GetMod(Modifier.Mind); +end; + +function weaponskill.HandleAttackSkill(caster, target, spell, action, statId, modifierId, multiplier, baseAmount) + -- todo: actually handle this + damage = baseAmount or math.random(1,10) * 10; + + return damage; +end; + +function weaponskill.HandleEvasion(caster, target, spell, action, statId, modifierId) + + return false; +end; + +function weaponskill.HandleStoneskin(caster, target, spell, action, statId, modifierId, damage) + --[[ + if target.statusEffects.HasStatusEffect(StatusEffect.Stoneskin) then + -- todo: damage reduction + return true; + end; + ]] + return false; +end; \ No newline at end of file diff --git a/sql/server_battlenpc_spawn_locations.sql b/sql/server_battlenpc_spawn_locations.sql index d6f42bf2..91194f28 100644 --- a/sql/server_battlenpc_spawn_locations.sql +++ b/sql/server_battlenpc_spawn_locations.sql @@ -23,14 +23,14 @@ DROP TABLE IF EXISTS `server_battlenpc_spawn_locations`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `server_battlenpc_spawn_locations` ( - `bnpcIndex` int(10) unsigned NOT NULL AUTO_INCREMENT, + `bnpcId` int(10) unsigned NOT NULL AUTO_INCREMENT, `customDisplayName` varchar(32) NOT NULL DEFAULT '', `groupId` int(10) unsigned NOT NULL, `positionX` float NOT NULL, `positionY` float NOT NULL, `positionZ` float NOT NULL, `rotation` float NOT NULL, - PRIMARY KEY (`bnpcIndex`) + PRIMARY KEY (`bnpcId`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; @@ -56,4 +56,4 @@ commit; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2017-09-07 21:54:41 +-- Dump completed on 2017-09-10 2:47:43