mirror of
https://bitbucket.org/Ioncannon/project-meteor-server.git
synced 2025-04-02 19:42:05 -04:00
401 lines
11 KiB
C#
401 lines
11 KiB
C#
/*
|
|
===========================================================================
|
|
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 <https:www.gnu.org/licenses/>.
|
|
===========================================================================
|
|
*/
|
|
|
|
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<State> 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<State>();
|
|
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<T>() 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<T>() 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));
|
|
}
|
|
}
|
|
}
|