/*
===========================================================================
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));
}
}
}