mirror of
https://bitbucket.org/Ioncannon/project-meteor-server.git
synced 2025-06-10 06:24:38 +02:00
stubbed some more states
- stubbed some ability stuff - moved packet things to loop instead of session only - added mob roaming and aggro - todo: fix target find/detection/pathfinding speed/line of sight/line aoe length etc - todo: see "// todo:" in code
This commit is contained in:
parent
c7b87c0d89
commit
68657e1edc
33 changed files with 1459 additions and 444 deletions
|
@ -3,7 +3,11 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FFXIVClassic.Common;
|
||||
using FFXIVClassic_Map_Server.Actors;
|
||||
using FFXIVClassic_Map_Server.packets.send.actor;
|
||||
using FFXIVClassic_Map_Server.actors.area;
|
||||
using FFXIVClassic_Map_Server.utils;
|
||||
|
||||
namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
|
||||
{
|
||||
|
@ -20,29 +24,39 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
|
|||
private bool firstSpell = true;
|
||||
private DateTime lastRoamScript; // todo: what even is this used as
|
||||
|
||||
public BattleNpcController(Character owner)
|
||||
private new BattleNpc owner;
|
||||
public BattleNpcController(BattleNpc owner) :
|
||||
base(owner)
|
||||
{
|
||||
this.owner = owner;
|
||||
this.lastUpdate = DateTime.Now;
|
||||
this.waitTime = lastUpdate.AddSeconds(5);
|
||||
}
|
||||
|
||||
public override void Update(DateTime tick)
|
||||
{
|
||||
var battleNpc = this.owner as BattleNpc;
|
||||
|
||||
if (battleNpc != null)
|
||||
// todo: handle aggro/deaggro and other shit here
|
||||
if (owner.aiContainer.IsEngaged())
|
||||
{
|
||||
// todo: handle aggro/deaggro and other shit here
|
||||
if (battleNpc.aiContainer.IsEngaged())
|
||||
{
|
||||
DoCombatTick(tick);
|
||||
}
|
||||
else if (!battleNpc.IsDead())
|
||||
{
|
||||
DoRoamTick(tick);
|
||||
}
|
||||
battleNpc.Update(tick);
|
||||
DoCombatTick(tick);
|
||||
}
|
||||
else if (!owner.IsDead())
|
||||
{
|
||||
DoRoamTick(tick);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryDeaggro()
|
||||
{
|
||||
if (owner.hateContainer.GetMostHatedTarget() == null || !owner.aiContainer.GetTargetFind().CanTarget(owner.target as Character))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (!owner.IsCloseToSpawn())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool Engage(Character target)
|
||||
|
@ -53,7 +67,27 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
|
|||
{
|
||||
// reset casting
|
||||
firstSpell = true;
|
||||
// todo: find a better place to put this?
|
||||
if (owner.GetState() != SetActorStatePacket.MAIN_STATE_ACTIVE)
|
||||
owner.ChangeState(SetActorStatePacket.MAIN_STATE_ACTIVE);
|
||||
|
||||
|
||||
// todo: check speed/is able to move
|
||||
// todo: too far, path to player if mob, message if player
|
||||
// owner.ResetMoveSpeeds();
|
||||
owner.moveState = 2;
|
||||
if (owner.currentSubState == SetActorStatePacket.SUB_STATE_MONSTER && owner.moveSpeeds[1] != 0)
|
||||
{
|
||||
// todo: actual stat based range
|
||||
if (Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, target.positionX, target.positionY, target.positionZ) > 10)
|
||||
{
|
||||
owner.aiContainer.pathFind.SetPathFlags(PathFindFlags.None);
|
||||
owner.aiContainer.pathFind.PreparePath(target.positionX, target.positionY, target.positionZ);
|
||||
ChangeTarget(target);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
lastActionTime = DateTime.Now;
|
||||
// todo: adjust cooldowns with modifiers
|
||||
}
|
||||
return canEngage;
|
||||
|
@ -65,10 +99,17 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
|
|||
return true;
|
||||
}
|
||||
|
||||
public override bool Disengage()
|
||||
public override void Disengage()
|
||||
{
|
||||
var target = owner.target;
|
||||
base.Disengage();
|
||||
// todo:
|
||||
return true;
|
||||
lastActionTime = lastUpdate;
|
||||
owner.isMovingToSpawn = true;
|
||||
neutralTime = lastUpdate;
|
||||
owner.hateContainer.ClearHate();
|
||||
owner.moveState = 1;
|
||||
lua.LuaEngine.CallLuaBattleAction(owner, "onDisengage", owner, target);
|
||||
}
|
||||
|
||||
public override void Cast(Character target, uint spellId)
|
||||
|
@ -93,25 +134,185 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
|
|||
|
||||
private void DoRoamTick(DateTime tick)
|
||||
{
|
||||
var battleNpc = owner as BattleNpc;
|
||||
|
||||
if (battleNpc != null)
|
||||
if (owner.hateContainer.GetHateList().Count > 0)
|
||||
{
|
||||
if (battleNpc.hateContainer.GetHateList().Count > 0)
|
||||
Engage(owner.hateContainer.GetMostHatedTarget());
|
||||
return;
|
||||
}
|
||||
//else if (owner.currentLockedTarget != 0)
|
||||
//{
|
||||
// ChangeTarget(Server.GetWorldManager().GetActorInWorld(owner.currentLockedTarget).GetAsCharacter());
|
||||
//}
|
||||
|
||||
if (tick >= waitTime)
|
||||
{
|
||||
// todo: aggro cooldown
|
||||
neutralTime = tick.AddSeconds(5);
|
||||
if (owner.aiContainer.pathFind.IsFollowingPath())
|
||||
{
|
||||
Engage(battleNpc.hateContainer.GetMostHatedTarget());
|
||||
return;
|
||||
owner.aiContainer.pathFind.FollowPath();
|
||||
lastActionTime = tick.AddSeconds(-5);
|
||||
}
|
||||
else if (battleNpc.currentLockedTarget != 0)
|
||||
else
|
||||
{
|
||||
|
||||
if (tick >= lastActionTime)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
// todo:
|
||||
waitTime = tick.AddSeconds(10);
|
||||
owner.OnRoam(tick);
|
||||
}
|
||||
}
|
||||
|
||||
private void DoCombatTick(DateTime tick)
|
||||
{
|
||||
HandleHate();
|
||||
|
||||
// todo: magic/attack/ws cooldowns etc
|
||||
if (TryDeaggro())
|
||||
{
|
||||
Disengage();
|
||||
return;
|
||||
}
|
||||
|
||||
Move();
|
||||
}
|
||||
|
||||
private void Move()
|
||||
{
|
||||
if (!owner.aiContainer.CanFollowPath())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (owner.aiContainer.pathFind.IsFollowingScriptedPath())
|
||||
{
|
||||
owner.aiContainer.pathFind.FollowPath();
|
||||
return;
|
||||
}
|
||||
|
||||
var targetPos = owner.target.GetPosAsVector3();
|
||||
var distance = Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, targetPos.X, targetPos.Y, targetPos.Z);
|
||||
|
||||
if (distance > owner.meleeRange - 0.2f || owner.aiContainer.CanFollowPath())
|
||||
{
|
||||
if (CanMoveForward(distance))
|
||||
{
|
||||
if (!owner.aiContainer.pathFind.IsFollowingPath() && distance > 3)
|
||||
{
|
||||
// pathfind if too far otherwise jump to target
|
||||
owner.aiContainer.pathFind.SetPathFlags(distance > 3 ? PathFindFlags.None : PathFindFlags.IgnoreNav );
|
||||
owner.aiContainer.pathFind.PreparePath(targetPos, 0.7f, 5);
|
||||
}
|
||||
owner.aiContainer.pathFind.FollowPath();
|
||||
if (!owner.aiContainer.pathFind.IsFollowingPath())
|
||||
{
|
||||
if (owner.target.currentSubState == SetActorStatePacket.SUB_STATE_PLAYER)
|
||||
{
|
||||
foreach (var battlenpc in owner.zone.GetActorsAroundActor<BattleNpc>(owner, 1))
|
||||
{
|
||||
battlenpc.aiContainer.pathFind.PathInRange(targetPos, 1.5f, 1.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FaceTarget();
|
||||
}
|
||||
}
|
||||
|
||||
private void FaceTarget()
|
||||
{
|
||||
// todo: check if stunned etc
|
||||
if (owner.statusEffects.HasStatusEffectsByFlag(StatusEffectFlags.PreventAction))
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
owner.LookAt(owner.target);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanMoveForward(float distance)
|
||||
{
|
||||
// todo: check spawn leash and stuff
|
||||
if (!owner.IsCloseToSpawn())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanAggroTarget(Character target)
|
||||
{
|
||||
if (owner.neutral || owner.aggroType == AggroType.None || owner.IsDead())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// todo: can mobs aggro mounted targets?
|
||||
if (target.IsDead() || target.currentMainState == SetActorStatePacket.MAIN_STATE_MOUNTED)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (owner.aiContainer.IsSpawned() && !owner.aiContainer.IsEngaged() && CanDetectTarget(target))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanDetectTarget(Character target, bool forceSight = false)
|
||||
{
|
||||
// todo: handle sight/scent/hp etc
|
||||
if (target.IsDead() || target.currentMainState == SetActorStatePacket.MAIN_STATE_MOUNTED)
|
||||
return false;
|
||||
|
||||
float verticalDistance = Math.Abs(target.positionY - owner.positionY);
|
||||
if (verticalDistance > 8)
|
||||
return false;
|
||||
|
||||
var distance = Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, target.positionX, target.positionY, target.positionZ);
|
||||
|
||||
bool detectSight = forceSight || (owner.aggroType & AggroType.Sight) != 0;
|
||||
bool hasSneak = false;
|
||||
bool hasInvisible = false;
|
||||
bool isFacing = owner.IsFacing(target);
|
||||
|
||||
// todo: check line of sight and aggroTypes
|
||||
if (distance > 20)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// todo: seems ffxiv doesnt even differentiate between sneak/invis?
|
||||
{
|
||||
hasSneak = target.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.Stealth);
|
||||
hasInvisible = hasSneak;
|
||||
}
|
||||
|
||||
if (detectSight && !hasInvisible && owner.IsFacing(target))
|
||||
return CanSeePoint(target.positionX, target.positionY, target.positionZ);
|
||||
|
||||
if ((owner.aggroType & AggroType.LowHp) != 0 && target.GetHPP() < 75)
|
||||
return CanSeePoint(target.positionX, target.positionY, target.positionZ);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanSeePoint(float x, float y, float z)
|
||||
{
|
||||
return NavmeshUtils.CanSee((Zone)owner.zone, owner.positionX, owner.positionY, owner.positionZ, x, y, z);
|
||||
}
|
||||
|
||||
private void HandleHate()
|
||||
{
|
||||
ChangeTarget(owner.hateContainer.GetMostHatedTarget());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue