/* =========================================================================== Copyright (C) 2015-2019 Project Meteor Dev Team This file is part of Project Meteor Server. Project Meteor Server is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Project Meteor Server is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Project Meteor Server. If not, see . =========================================================================== */ using System; using System.Collections.Generic; using Meteor.Map.Actors; using Meteor.Map.actors.chara.ai.state; using Meteor.Map.actors.chara.ai.controllers; using Meteor.Map.packets.send.actor; // port of ai code in dsp by kjLotus (https://github.com/DarkstarProject/darkstar/blob/master/src/map/ai) namespace Meteor.Map.actors.chara.ai { class AIContainer { private Character owner; private Controller controller; private Stack states; private DateTime latestUpdate; private DateTime prevUpdate; public readonly PathFind pathFind; private TargetFind targetFind; private ActionQueue actionQueue; private DateTime lastActionTime; public AIContainer(Character actor, Controller controller, PathFind pathFind, TargetFind targetFind) { this.owner = actor; this.states = new Stack(); this.controller = controller; this.pathFind = pathFind; this.targetFind = targetFind; latestUpdate = DateTime.Now; prevUpdate = latestUpdate; actionQueue = new ActionQueue(owner); } public void UpdateLastActionTime(uint delay = 0) { lastActionTime = DateTime.Now.AddSeconds(delay); } public DateTime GetLastActionTime() { return lastActionTime; } public void Update(DateTime tick) { prevUpdate = latestUpdate; latestUpdate = tick; // todo: trigger listeners if (controller == null && pathFind != null) { pathFind.FollowPath(); } // todo: action queues if (controller != null && controller.canUpdate) controller.Update(tick); State top; while (states.Count > 0 && (top = states.Peek()).Update(tick)) { if (top == GetCurrentState()) { states.Pop().Cleanup(); } } owner.PostUpdate(tick); } public void CheckCompletedStates() { while (states.Count > 0 && states.Peek().IsCompleted()) { states.Peek().Cleanup(); states.Pop(); } } public void InterruptStates() { while (states.Count > 0 && states.Peek().CanInterrupt()) { states.Peek().SetInterrupted(true); states.Peek().Cleanup(); states.Pop(); } } public void InternalUseItem(Character target, uint slot, uint itemId) { // todo: can allies use items? if (owner is Player) { if (CanChangeState()) { ChangeState(new ItemState((Player)owner, target, (ushort)slot, itemId)); } else { // You cannot use that item now. ((Player)owner).SendGameMessage(Server.GetWorldManager().GetActor(), 32544, 0x20, itemId); } } } public void ClearStates() { while (states.Count > 0) { states.Peek().Cleanup(); states.Pop(); } } public void ChangeController(Controller controller) { this.controller = controller; } public T GetController() where T : Controller { return controller as T; } public TargetFind GetTargetFind() { return targetFind; } public bool CanFollowPath() { return pathFind != null && (GetCurrentState() == null || GetCurrentState().CanChangeState()); } public bool CanChangeState() { return GetCurrentState() == null || states.Peek().CanChangeState(); } public void ChangeTarget(Character target) { if (controller != null) { controller.ChangeTarget(target); } } public void ChangeState(State state) { if (CanChangeState()) { if (states.Count <= 10) { CheckCompletedStates(); states.Push(state); } else { throw new Exception("shit"); } } } public void ForceChangeState(State state) { if (states.Count <= 10) { CheckCompletedStates(); states.Push(state); } else { throw new Exception("force shit"); } } public bool IsCurrentState() where T : State { return GetCurrentState() is T; } public State GetCurrentState() { return states.Count > 0 ? states.Peek() : null; } public DateTime GetLatestUpdate() { return latestUpdate; } public void Reset() { // todo: reset cooldowns and stuff here too? targetFind?.Reset(); pathFind?.Clear(); ClearStates(); InternalDisengage(); } public bool IsSpawned() { return !IsDead(); } public bool IsEngaged() { return owner.currentMainState == SetActorStatePacket.MAIN_STATE_ACTIVE; } public bool IsDead() { return owner.currentMainState == SetActorStatePacket.MAIN_STATE_DEAD || owner.currentMainState == SetActorStatePacket.MAIN_STATE_DEAD2; } public bool IsRoaming() { // todo: check mounted? return owner.currentMainState == SetActorStatePacket.MAIN_STATE_PASSIVE; } public void Engage(Character target) { if (controller != null) controller.Engage(target); else InternalEngage(target); } public void Disengage() { if (controller != null) controller.Disengage(); else 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) controller.Cast(target, spellId); else InternalCast(target, spellId); } public void WeaponSkill(Character target, uint weaponSkillId) { if (controller != null) controller.WeaponSkill(target, weaponSkillId); else InternalWeaponSkill(target, weaponSkillId); } public void MobSkill(Character target, uint mobSkillId) { if (controller != null) controller.MonsterSkill(target, mobSkillId); else InternalMobSkill(target, mobSkillId); } public void UseItem(Character target, uint slot, uint itemId) { if (controller != null) controller.UseItem(target, slot, itemId); } public void InternalChangeTarget(Character target) { // targets are changed in the controller if (IsEngaged() || target == null) { } else { Engage(target); } } public bool InternalEngage(Character target) { if (IsEngaged()) { if (this.owner.target != target) { ChangeTarget(target); return true; } return false; } if (CanChangeState() || (GetCurrentState() != null && GetCurrentState().IsCompleted())) { ForceChangeState(new AttackState(owner, target)); return true; } return false; } public void InternalDisengage() { pathFind?.Clear(); GetTargetFind()?.Reset(); owner.updateFlags |= ActorUpdateFlags.HpTpMp; ChangeTarget(null); if (owner.currentMainState == SetActorStatePacket.MAIN_STATE_ACTIVE) owner.ChangeState(SetActorStatePacket.MAIN_STATE_PASSIVE); ClearStates(); } public void InternalAbility(Character target, uint abilityId) { if (CanChangeState()) { ChangeState(new AbilityState(owner, target, (ushort)abilityId)); } } public void InternalCast(Character target, uint spellId) { if (CanChangeState()) { ChangeState(new MagicState(owner, target, (ushort)spellId)); } } public void InternalWeaponSkill(Character target, uint weaponSkillId) { if (CanChangeState()) { ChangeState(new WeaponSkillState(owner, target, (ushort)weaponSkillId)); } } public void InternalMobSkill(Character target, uint mobSkillId) { if (CanChangeState()) { } } public void InternalDie(DateTime tick, uint fadeoutTimerSeconds) { pathFind?.Clear(); ClearStates(); ForceChangeState(new DeathState(owner, tick, fadeoutTimerSeconds)); } public void InternalDespawn(DateTime tick, uint respawnTimerSeconds) { ClearStates(); Disengage(); ForceChangeState(new DespawnState(owner, respawnTimerSeconds)); } public void InternalRaise(Character target) { // todo: place at target // ForceChangeState(new RaiseState(target)); } } }