diff --git a/FFXIVClassic Map Server/actors/chara/ai/AIContainer.cs b/FFXIVClassic Map Server/actors/chara/ai/AIContainer.cs index a0dba867..a7e3bce7 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/AIContainer.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/AIContainer.cs @@ -204,7 +204,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai public void MobSkill(Character target, uint mobSkillId) { if (controller != null) - controller.MobSkill(target, mobSkillId); + controller.MonsterSkill(target, mobSkillId); else InternalMobSkill(target, mobSkillId); } diff --git a/FFXIVClassic Map Server/actors/chara/ai/controllers/BattleNpcController.cs b/FFXIVClassic Map Server/actors/chara/ai/controllers/BattleNpcController.cs index 1fe68e2c..35a64016 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/controllers/BattleNpcController.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/controllers/BattleNpcController.cs @@ -9,6 +9,17 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers { class BattleNpcController : Controller { + private DateTime lastActionTime; + private DateTime lastSpellCastTime; + private DateTime lastSkillTime; + private DateTime lastSpecialSkillTime; // todo: i dont think monsters have "2hr" cooldowns like ffxi + private DateTime deaggroTime; + private DateTime neutralTime; + private DateTime waitTime; + + private bool firstSpell = true; + private DateTime lastRoamScript; // todo: what even is this used as + public BattleNpcController(Character owner) { this.owner = owner; @@ -17,15 +28,35 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers public override void Update(DateTime tick) { - // todo: handle aggro/deaggro and other shit here - ((BattleNpc)this.owner).statusEffects.Update(tick); + var battleNpc = this.owner as BattleNpc; + + if (battleNpc != null) + { + // todo: handle aggro/deaggro and other shit here + if (battleNpc.aiContainer.IsEngaged()) + { + DoCombatTick(tick); + } + else if (!battleNpc.IsDead()) + { + DoRoamTick(tick); + } + battleNpc.Update(tick); + } } public override bool Engage(Character target) { // todo: check distance, last swing time, status effects - this.owner.aiContainer.InternalEngage(target); - return true; + var canEngage = this.owner.aiContainer.InternalEngage(target); + if (canEngage) + { + // reset casting + firstSpell = true; + + // todo: adjust cooldowns with modifiers + } + return canEngage; } private bool TryEngage(Character target) @@ -55,9 +86,19 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers } - public override void MobSkill(Character target, uint mobSkillId) + public override void MonsterSkill(Character target, uint mobSkillId) { } + + private void DoRoamTick(DateTime tick) + { + + } + + private void DoCombatTick(DateTime tick) + { + + } } } diff --git a/FFXIVClassic Map Server/actors/chara/ai/controllers/Controller.cs b/FFXIVClassic Map Server/actors/chara/ai/controllers/Controller.cs index 50566f15..9ad00f8b 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/controllers/Controller.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/controllers/Controller.cs @@ -24,7 +24,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers public abstract bool Disengage(); public abstract void Cast(Character target, uint spellId); public virtual void WeaponSkill(Character target, uint weaponSkillId) { } - public virtual void MobSkill(Character target, uint mobSkillId) { } + public virtual void MonsterSkill(Character target, uint mobSkillId) { } public abstract void Ability(Character target, uint abilityId); public abstract void RangedAttack(Character target); public virtual void Spawn() { } diff --git a/FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs b/FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs index 8adaa964..7f031c22 100644 --- a/FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs +++ b/FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs @@ -13,18 +13,41 @@ using FFXIVClassic_Map_Server.packets.send.actor; namespace FFXIVClassic_Map_Server.Actors { + [Flags] + enum AggroType + { + None, + Sight, + Scent, + LowHp, + IgnoreLevelDifference + } + class BattleNpc : Npc { public HateContainer hateContainer; + public AggroType aggroType; 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) { this.aiContainer = new AIContainer(this, new BattleNpcController(this), new PathFind(this), new TargetFind(this)); + this.currentSubState = SetActorStatePacket.SUB_STATE_MONSTER; + //this.currentMainState = SetActorStatePacket.MAIN_STATE_ACTIVE; + + //charaWork.property[2] = 1; + //npcWork.hateType = 1; + this.hateContainer = new HateContainer(this); this.allegiance = CharacterTargetingAllegiance.BattleNpcs; } + + public override void Update(DateTime tick) + { + // todo: + this.statusEffects.Update(tick); + } } } diff --git a/FFXIVClassic Map Server/actors/chara/npc/Npc.cs b/FFXIVClassic Map Server/actors/chara/npc/Npc.cs index 29583814..ba5cb429 100644 --- a/FFXIVClassic Map Server/actors/chara/npc/Npc.cs +++ b/FFXIVClassic Map Server/actors/chara/npc/Npc.cs @@ -125,6 +125,7 @@ namespace FFXIVClassic_Map_Server.Actors this.instance = instance; GenerateActorName((int)actorNumber); + this.aiContainer = new AIContainer(this, null, new PathFind(this), new TargetFind(null)); } public SubPacket CreateAddActorPacket() @@ -393,7 +394,7 @@ namespace FFXIVClassic_Map_Server.Actors zone.DespawnActor(this); } - public void Update(DateTime tick) + public override void Update(DateTime tick) { var deltaTime = (tick - aiContainer.GetLatestUpdate()).Milliseconds; LuaEngine.GetInstance().CallLuaFunction(null, this, "onUpdate", true, deltaTime); diff --git a/FFXIVClassic Map Server/lua/LuaEngine.cs b/FFXIVClassic Map Server/lua/LuaEngine.cs index 1843e12b..94d4fc8f 100644 --- a/FFXIVClassic Map Server/lua/LuaEngine.cs +++ b/FFXIVClassic Map Server/lua/LuaEngine.cs @@ -129,6 +129,40 @@ namespace FFXIVClassic_Map_Server.lua player.EndEvent(); } + /// + /// // todo: this is dumb, should probably make a function for each action with different default return values + /// or just make generic function and pass default value as first arg after functionName + /// + public static void CallLuaMonsterAction(Character actor, string functionName, params object[] args) + { + // todo: should we call this for players too? + if (actor is BattleNpc) + { + // todo: check this is correct + string path = $"./scripts/unique/{actor.zone.zoneName}/Monster/{actor.customDisplayName}.lua"; + + // dont wanna throw an error if file doesnt exist + if (File.Exists(path)) + { + var script = LoadGlobals(); + try + { + script.DoFile(path); + } + catch (Exception e) + { + Program.Log.Error($"LuaEngine.CallLuaMonsterAction [{functionName}] {e.Message}"); + } + DynValue res = new DynValue(); + + if (!script.Globals.Get(functionName).IsNil()) + { + res = script.Call(script.Globals.Get(functionName)); + } + } + } + } + private static string GetScriptPath(Actor target) { if (target is Player) diff --git a/FFXIVClassic World Server/Server.cs b/FFXIVClassic World Server/Server.cs index 964abb35..62e4edfc 100644 --- a/FFXIVClassic World Server/Server.cs +++ b/FFXIVClassic World Server/Server.cs @@ -169,7 +169,7 @@ namespace FFXIVClassic_World_Server { uint sessionId = subpacket.header.targetId; Session session = GetSession(sessionId); - subpacket.DebugPrintSubPacket(); + //subpacket.DebugPrintSubPacket(); if (subpacket.gameMessage.opcode >= 0x1000) { //subpacket.DebugPrintSubPacket();