2019-06-18 22:55:32 -04:00
|
|
|
|
/*
|
|
|
|
|
===========================================================================
|
|
|
|
|
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;
|
2017-07-08 00:20:55 +01:00
|
|
|
|
using System.Collections.Generic;
|
2019-06-19 00:05:18 -04:00
|
|
|
|
using Meteor.Common;
|
2019-06-19 01:10:15 -04:00
|
|
|
|
using Meteor.Map.Actors;
|
|
|
|
|
using Meteor.Map.packets.send.actor;
|
|
|
|
|
using Meteor.Map.actors.area;
|
|
|
|
|
using Meteor.Map.utils;
|
|
|
|
|
using Meteor.Map.actors.chara.ai.state;
|
|
|
|
|
using Meteor.Map.actors.chara.npc;
|
|
|
|
|
|
|
|
|
|
namespace Meteor.Map.actors.chara.ai.controllers
|
2017-07-08 00:20:55 +01:00
|
|
|
|
{
|
|
|
|
|
class BattleNpcController : Controller
|
|
|
|
|
{
|
2017-09-16 02:50:32 +01:00
|
|
|
|
protected DateTime lastActionTime;
|
|
|
|
|
protected DateTime lastSpellCastTime;
|
|
|
|
|
protected DateTime lastSkillTime;
|
|
|
|
|
protected DateTime lastSpecialSkillTime; // todo: i dont think monsters have "2hr" cooldowns like ffxi
|
|
|
|
|
protected DateTime deaggroTime;
|
|
|
|
|
protected DateTime neutralTime;
|
|
|
|
|
protected DateTime waitTime;
|
2017-07-11 20:49:38 +01:00
|
|
|
|
|
|
|
|
|
private bool firstSpell = true;
|
2017-09-16 02:50:32 +01:00
|
|
|
|
protected DateTime lastRoamUpdate;
|
|
|
|
|
protected DateTime battleStartTime;
|
2017-07-11 20:49:38 +01:00
|
|
|
|
|
2017-09-16 02:50:32 +01:00
|
|
|
|
protected new BattleNpc owner;
|
2017-08-02 23:06:11 +01:00
|
|
|
|
public BattleNpcController(BattleNpc owner) :
|
|
|
|
|
base(owner)
|
2017-07-08 00:20:55 +01:00
|
|
|
|
{
|
|
|
|
|
this.owner = owner;
|
|
|
|
|
this.lastUpdate = DateTime.Now;
|
2017-08-02 23:06:11 +01:00
|
|
|
|
this.waitTime = lastUpdate.AddSeconds(5);
|
2017-07-08 00:20:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Update(DateTime tick)
|
|
|
|
|
{
|
2017-09-05 05:05:25 +01:00
|
|
|
|
lastUpdate = tick;
|
2017-12-10 09:20:42 -06:00
|
|
|
|
if (!owner.IsDead())
|
2017-12-08 00:58:39 -06:00
|
|
|
|
{
|
2017-12-10 09:20:42 -06:00
|
|
|
|
// todo: handle aggro/deaggro and other shit here
|
|
|
|
|
if (!owner.aiContainer.IsEngaged())
|
|
|
|
|
{
|
|
|
|
|
TryAggro(tick);
|
|
|
|
|
}
|
2017-12-08 00:58:39 -06:00
|
|
|
|
|
2017-12-10 09:20:42 -06:00
|
|
|
|
if (owner.aiContainer.IsEngaged())
|
|
|
|
|
{
|
|
|
|
|
DoCombatTick(tick);
|
|
|
|
|
}
|
2018-07-03 04:46:34 -05:00
|
|
|
|
//Only move if owner isn't dead and is either too far away from their spawn point or is meant to roam and isn't in combat
|
|
|
|
|
else if (!owner.IsDead() && (owner.isMovingToSpawn || owner.GetMobMod((uint)MobModifier.Roams) > 0))
|
2017-12-10 09:20:42 -06:00
|
|
|
|
{
|
|
|
|
|
DoRoamTick(tick);
|
|
|
|
|
}
|
2017-08-02 23:06:11 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-07-11 20:49:38 +01:00
|
|
|
|
|
2017-08-02 23:06:11 +01:00
|
|
|
|
public bool TryDeaggro()
|
|
|
|
|
{
|
|
|
|
|
if (owner.hateContainer.GetMostHatedTarget() == null || !owner.aiContainer.GetTargetFind().CanTarget(owner.target as Character))
|
2017-07-11 20:49:38 +01:00
|
|
|
|
{
|
2017-08-02 23:06:11 +01:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else if (!owner.IsCloseToSpawn())
|
|
|
|
|
{
|
|
|
|
|
return true;
|
2017-07-11 20:49:38 +01:00
|
|
|
|
}
|
2017-08-02 23:06:11 +01:00
|
|
|
|
return false;
|
2017-07-08 00:20:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-08 00:58:39 -06:00
|
|
|
|
//If the owner isn't moving to spawn, iterate over nearby enemies and
|
|
|
|
|
//aggro the first one that is within 10 levels and can be detected, then engage
|
|
|
|
|
protected virtual void TryAggro(DateTime tick)
|
|
|
|
|
{
|
|
|
|
|
if (tick >= neutralTime && !owner.isMovingToSpawn)
|
|
|
|
|
{
|
|
|
|
|
if (!owner.neutral && owner.IsAlive())
|
|
|
|
|
{
|
|
|
|
|
foreach (var chara in owner.zone.GetActorsAroundActor<Character>(owner, 50))
|
|
|
|
|
{
|
2018-02-15 13:20:46 -06:00
|
|
|
|
if (chara.allegiance == owner.allegiance)
|
|
|
|
|
continue;
|
2017-12-08 00:58:39 -06:00
|
|
|
|
|
|
|
|
|
if (owner.aiContainer.pathFind.AtPoint() && owner.detectionType != DetectionType.None)
|
|
|
|
|
{
|
|
|
|
|
uint levelDifference = (uint)Math.Abs(owner.GetLevel() - chara.GetLevel());
|
|
|
|
|
|
|
|
|
|
if (levelDifference <= 10 || (owner.detectionType & DetectionType.IgnoreLevelDifference) != 0 && CanAggroTarget(chara))
|
|
|
|
|
{
|
|
|
|
|
owner.hateContainer.AddBaseHate(chara);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (owner.hateContainer.GetHateList().Count > 0)
|
|
|
|
|
{
|
|
|
|
|
Engage(owner.hateContainer.GetMostHatedTarget());
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-12-10 09:20:42 -06:00
|
|
|
|
|
2017-07-08 00:20:55 +01:00
|
|
|
|
public override bool Engage(Character target)
|
|
|
|
|
{
|
2017-07-11 20:49:38 +01:00
|
|
|
|
var canEngage = this.owner.aiContainer.InternalEngage(target);
|
|
|
|
|
if (canEngage)
|
|
|
|
|
{
|
2017-08-31 05:56:43 +01:00
|
|
|
|
//owner.ChangeState(SetActorStatePacket.MAIN_STATE_ACTIVE);
|
|
|
|
|
|
2017-07-11 20:49:38 +01:00
|
|
|
|
// reset casting
|
|
|
|
|
firstSpell = true;
|
2017-08-02 23:06:11 +01:00
|
|
|
|
// todo: find a better place to put this?
|
|
|
|
|
if (owner.GetState() != SetActorStatePacket.MAIN_STATE_ACTIVE)
|
|
|
|
|
owner.ChangeState(SetActorStatePacket.MAIN_STATE_ACTIVE);
|
|
|
|
|
|
|
|
|
|
lastActionTime = DateTime.Now;
|
2017-08-31 05:56:43 +01:00
|
|
|
|
battleStartTime = lastActionTime;
|
2017-07-11 20:49:38 +01:00
|
|
|
|
// todo: adjust cooldowns with modifiers
|
|
|
|
|
}
|
|
|
|
|
return canEngage;
|
2017-07-08 00:20:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-09-16 02:50:32 +01:00
|
|
|
|
protected bool TryEngage(Character target)
|
2017-07-08 00:20:55 +01:00
|
|
|
|
{
|
|
|
|
|
// todo:
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-02 23:06:11 +01:00
|
|
|
|
public override void Disengage()
|
2017-07-08 00:20:55 +01:00
|
|
|
|
{
|
2017-08-02 23:06:11 +01:00
|
|
|
|
var target = owner.target;
|
|
|
|
|
base.Disengage();
|
2017-07-08 00:20:55 +01:00
|
|
|
|
// todo:
|
2017-08-21 00:40:41 +01:00
|
|
|
|
lastActionTime = lastUpdate.AddSeconds(5);
|
2017-08-02 23:06:11 +01:00
|
|
|
|
owner.isMovingToSpawn = true;
|
2017-08-29 01:15:12 +01:00
|
|
|
|
owner.aiContainer.pathFind.SetPathFlags(PathFindFlags.None);
|
|
|
|
|
owner.aiContainer.pathFind.PreparePath(owner.spawnX, owner.spawnY, owner.spawnZ, 1.5f, 10);
|
2017-08-21 00:40:41 +01:00
|
|
|
|
neutralTime = lastActionTime;
|
2017-08-02 23:06:11 +01:00
|
|
|
|
owner.hateContainer.ClearHate();
|
2017-09-05 05:05:25 +01:00
|
|
|
|
lua.LuaEngine.CallLuaBattleFunction(owner, "onDisengage", owner, target, Utils.UnixTimeStampUTC(lastUpdate));
|
2017-07-08 00:20:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Cast(Character target, uint spellId)
|
|
|
|
|
{
|
2017-08-25 03:52:43 +01:00
|
|
|
|
// todo:
|
2018-04-18 16:06:41 -05:00
|
|
|
|
if(owner.aiContainer.CanChangeState())
|
|
|
|
|
owner.aiContainer.InternalCast(target, spellId);
|
2017-07-08 00:20:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Ability(Character target, uint abilityId)
|
|
|
|
|
{
|
2017-08-25 03:52:43 +01:00
|
|
|
|
// todo:
|
2018-04-18 16:06:41 -05:00
|
|
|
|
if (owner.aiContainer.CanChangeState())
|
|
|
|
|
owner.aiContainer.InternalAbility(target, abilityId);
|
2017-07-08 00:20:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void RangedAttack(Character target)
|
|
|
|
|
{
|
2017-08-25 03:52:43 +01:00
|
|
|
|
// todo:
|
2017-07-08 00:20:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-11 20:49:38 +01:00
|
|
|
|
public override void MonsterSkill(Character target, uint mobSkillId)
|
2017-07-08 00:20:55 +01:00
|
|
|
|
{
|
2017-08-25 03:52:43 +01:00
|
|
|
|
// todo:
|
2017-07-08 00:20:55 +01:00
|
|
|
|
}
|
2017-07-11 20:49:38 +01:00
|
|
|
|
|
2017-10-11 14:46:24 +01:00
|
|
|
|
protected virtual void DoRoamTick(DateTime tick, List<Character> contentGroupCharas = null)
|
2017-07-11 20:49:38 +01:00
|
|
|
|
{
|
2017-08-02 23:06:11 +01:00
|
|
|
|
if (tick >= waitTime)
|
|
|
|
|
{
|
2017-09-05 05:05:25 +01:00
|
|
|
|
neutralTime = tick.AddSeconds(5);
|
2017-08-02 23:06:11 +01:00
|
|
|
|
if (owner.aiContainer.pathFind.IsFollowingPath())
|
2017-07-27 22:19:20 +01:00
|
|
|
|
{
|
2017-08-02 23:06:11 +01:00
|
|
|
|
owner.aiContainer.pathFind.FollowPath();
|
|
|
|
|
lastActionTime = tick.AddSeconds(-5);
|
2017-07-27 22:19:20 +01:00
|
|
|
|
}
|
2017-08-02 23:06:11 +01:00
|
|
|
|
else
|
2017-07-27 22:19:20 +01:00
|
|
|
|
{
|
2017-08-02 23:06:11 +01:00
|
|
|
|
if (tick >= lastActionTime)
|
|
|
|
|
{
|
2017-08-29 01:15:12 +01:00
|
|
|
|
|
2017-08-02 23:06:11 +01:00
|
|
|
|
}
|
2017-07-27 22:19:20 +01:00
|
|
|
|
}
|
2017-12-08 00:58:39 -06:00
|
|
|
|
waitTime = tick.AddSeconds(owner.GetMobMod((uint) MobModifier.RoamDelay));
|
2017-08-02 23:06:11 +01:00
|
|
|
|
owner.OnRoam(tick);
|
2017-08-21 00:40:41 +01:00
|
|
|
|
|
2017-10-03 07:32:32 +01:00
|
|
|
|
if (CanMoveForward(0.0f) && !owner.aiContainer.pathFind.IsFollowingPath())
|
2017-08-30 00:14:14 +01:00
|
|
|
|
{
|
|
|
|
|
// will move on next tick
|
|
|
|
|
owner.aiContainer.pathFind.SetPathFlags(PathFindFlags.None);
|
2017-10-03 07:32:32 +01:00
|
|
|
|
owner.aiContainer.pathFind.PathInRange(owner.spawnX, owner.spawnY, owner.spawnZ, 1.5f, 50.0f);
|
2017-08-30 00:14:14 +01:00
|
|
|
|
}
|
2017-12-08 00:58:39 -06:00
|
|
|
|
//lua.LuaEngine.CallLuaBattleFunction(owner, "onRoam", owner, contentGroupCharas);
|
2017-08-29 01:15:12 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-09-12 01:24:02 +01:00
|
|
|
|
if (owner.aiContainer.pathFind.IsFollowingPath() && owner.aiContainer.CanFollowPath())
|
2017-08-21 00:40:41 +01:00
|
|
|
|
{
|
|
|
|
|
owner.aiContainer.pathFind.FollowPath();
|
|
|
|
|
}
|
2017-07-11 20:49:38 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-09-16 02:50:32 +01:00
|
|
|
|
protected virtual void DoCombatTick(DateTime tick, List<Character> contentGroupCharas = null)
|
2017-07-11 20:49:38 +01:00
|
|
|
|
{
|
2017-08-02 23:06:11 +01:00
|
|
|
|
HandleHate();
|
|
|
|
|
// todo: magic/attack/ws cooldowns etc
|
|
|
|
|
if (TryDeaggro())
|
|
|
|
|
{
|
|
|
|
|
Disengage();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-05-27 23:05:20 -07:00
|
|
|
|
owner.SetMod((uint)Modifier.MovementSpeed, 5);
|
2018-04-18 16:06:41 -05:00
|
|
|
|
if ((tick - lastCombatTickScript).TotalSeconds > 3)
|
2017-10-11 19:23:40 +01:00
|
|
|
|
{
|
2018-02-15 13:20:46 -06:00
|
|
|
|
Move();
|
|
|
|
|
//if (owner.aiContainer.CanChangeState())
|
|
|
|
|
//owner.aiContainer.WeaponSkill(owner.zone.FindActorInArea<Character>(owner.target.actorId), 27155);
|
|
|
|
|
//lua.LuaEngine.CallLuaBattleFunction(owner, "onCombatTick", owner, owner.target, Utils.UnixTimeStampUTC(tick), contentGroupCharas);
|
2017-10-11 19:23:40 +01:00
|
|
|
|
lastCombatTickScript = tick;
|
|
|
|
|
}
|
2017-08-02 23:06:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-09-16 02:50:32 +01:00
|
|
|
|
protected virtual void Move()
|
2017-08-02 23:06:11 +01:00
|
|
|
|
{
|
2019-06-01 03:39:46 -07:00
|
|
|
|
if (!owner.aiContainer.CanFollowPath() || owner.statusEffects.HasStatusEffectsByFlag(StatusEffectFlags.PreventMovement))
|
2017-08-02 23:06:11 +01:00
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (owner.aiContainer.pathFind.IsFollowingScriptedPath())
|
|
|
|
|
{
|
|
|
|
|
owner.aiContainer.pathFind.FollowPath();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-03 04:46:34 -05:00
|
|
|
|
var vecToTarget = owner.target.GetPosAsVector3() - owner.GetPosAsVector3();
|
|
|
|
|
vecToTarget /= vecToTarget.Length();
|
|
|
|
|
vecToTarget = (Utils.Distance(owner.GetPosAsVector3(), owner.target.GetPosAsVector3()) - owner.GetAttackRange() + 0.2f) * vecToTarget;
|
|
|
|
|
|
|
|
|
|
var targetPos = vecToTarget + owner.GetPosAsVector3();
|
|
|
|
|
var distance = Utils.Distance(owner.GetPosAsVector3(), owner.target.GetPosAsVector3());
|
|
|
|
|
if (distance > owner.GetAttackRange() - 0.2f)
|
2017-08-02 23:06:11 +01:00
|
|
|
|
{
|
|
|
|
|
if (CanMoveForward(distance))
|
|
|
|
|
{
|
|
|
|
|
if (!owner.aiContainer.pathFind.IsFollowingPath() && distance > 3)
|
|
|
|
|
{
|
|
|
|
|
// pathfind if too far otherwise jump to target
|
2017-08-23 19:31:03 +01:00
|
|
|
|
owner.aiContainer.pathFind.SetPathFlags(PathFindFlags.None);
|
2017-08-22 19:47:54 +01:00
|
|
|
|
owner.aiContainer.pathFind.PreparePath(targetPos, 1.5f, 5);
|
2017-08-02 23:06:11 +01:00
|
|
|
|
}
|
|
|
|
|
owner.aiContainer.pathFind.FollowPath();
|
|
|
|
|
if (!owner.aiContainer.pathFind.IsFollowingPath())
|
|
|
|
|
{
|
2017-08-31 05:56:43 +01:00
|
|
|
|
if (owner.target is Player)
|
2017-08-02 23:06:11 +01:00
|
|
|
|
{
|
2017-08-26 04:08:26 +01:00
|
|
|
|
foreach (var chara in owner.zone.GetActorsAroundActor<Character>(owner, 1))
|
2017-08-02 23:06:11 +01:00
|
|
|
|
{
|
2017-08-26 04:08:26 +01:00
|
|
|
|
if (chara == owner)
|
2017-08-16 17:25:32 +01:00
|
|
|
|
continue;
|
2017-08-26 04:08:26 +01:00
|
|
|
|
|
|
|
|
|
float mobDistance = Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, chara.positionX, chara.positionY, chara.positionZ);
|
2017-08-30 00:14:14 +01:00
|
|
|
|
if (mobDistance < 0.50f && (chara.updateFlags & ActorUpdateFlags.Position) == 0)
|
2017-08-26 04:08:26 +01:00
|
|
|
|
{
|
2017-08-30 00:14:14 +01:00
|
|
|
|
owner.aiContainer.pathFind.PathInRange(targetPos, 1.3f, chara.GetAttackRange());
|
2017-08-26 04:08:26 +01:00
|
|
|
|
break;
|
|
|
|
|
}
|
2017-08-02 23:06:11 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-08-30 00:14:14 +01:00
|
|
|
|
FaceTarget();
|
2017-08-02 23:06:11 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
FaceTarget();
|
|
|
|
|
}
|
2018-02-15 13:20:46 -06:00
|
|
|
|
lastRoamUpdate = DateTime.Now;
|
2017-08-02 23:06:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-09-16 02:50:32 +01:00
|
|
|
|
protected void FaceTarget()
|
2017-08-02 23:06:11 +01:00
|
|
|
|
{
|
|
|
|
|
// todo: check if stunned etc
|
2018-02-15 13:20:46 -06:00
|
|
|
|
if (owner.statusEffects.HasStatusEffectsByFlag(StatusEffectFlags.PreventTurn) )
|
2017-08-02 23:06:11 +01:00
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
owner.LookAt(owner.target);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-16 02:50:32 +01:00
|
|
|
|
protected bool CanMoveForward(float distance)
|
2017-08-02 23:06:11 +01:00
|
|
|
|
{
|
|
|
|
|
// todo: check spawn leash and stuff
|
|
|
|
|
if (!owner.IsCloseToSpawn())
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-08-30 00:14:14 +01:00
|
|
|
|
if (owner.GetSpeed() == 0)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-08-02 23:06:11 +01:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-16 02:50:32 +01:00
|
|
|
|
public virtual bool CanAggroTarget(Character target)
|
2017-08-02 23:06:11 +01:00
|
|
|
|
{
|
2017-09-07 22:02:02 +01:00
|
|
|
|
if (owner.neutral || owner.detectionType == DetectionType.None || owner.IsDead())
|
2017-08-02 23:06:11 +01:00
|
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-16 02:50:32 +01:00
|
|
|
|
public virtual bool CanDetectTarget(Character target, bool forceSight = false)
|
2017-08-02 23:06:11 +01:00
|
|
|
|
{
|
2017-09-03 01:01:19 +01:00
|
|
|
|
if (owner.IsDead())
|
|
|
|
|
return false;
|
|
|
|
|
|
2017-08-23 19:31:03 +01:00
|
|
|
|
// todo: this should probably be changed to only allow detection at end of path?
|
|
|
|
|
if (owner.aiContainer.pathFind.IsFollowingScriptedPath() || owner.aiContainer.pathFind.IsFollowingPath() && !owner.aiContainer.pathFind.AtPoint())
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-02 23:06:11 +01:00
|
|
|
|
// 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;
|
|
|
|
|
|
2017-08-21 00:40:41 +01:00
|
|
|
|
var distance = Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, target.positionX, target.positionY, target.positionZ);
|
2017-08-02 23:06:11 +01:00
|
|
|
|
|
2017-09-07 22:02:02 +01:00
|
|
|
|
bool detectSight = forceSight || (owner.detectionType & DetectionType.Sight) != 0;
|
2017-08-02 23:06:11 +01:00
|
|
|
|
bool hasSneak = false;
|
|
|
|
|
bool hasInvisible = false;
|
|
|
|
|
bool isFacing = owner.IsFacing(target);
|
|
|
|
|
|
2017-09-12 01:24:02 +01:00
|
|
|
|
// use the mobmod sight range before defaulting to 20 yalms
|
|
|
|
|
if (detectSight && !hasInvisible && isFacing && distance < owner.GetMobMod((uint)MobModifier.SightRange))
|
|
|
|
|
return CanSeePoint(target.positionX, target.positionY, target.positionZ);
|
|
|
|
|
|
2017-08-02 23:06:11 +01:00
|
|
|
|
// todo: check line of sight and aggroTypes
|
|
|
|
|
if (distance > 20)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// todo: seems ffxiv doesnt even differentiate between sneak/invis?
|
|
|
|
|
{
|
2019-05-27 23:05:20 -07:00
|
|
|
|
hasSneak = target.GetMod(Modifier.Stealth) > 0;
|
2017-08-02 23:06:11 +01:00
|
|
|
|
hasInvisible = hasSneak;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-09-12 01:24:02 +01:00
|
|
|
|
|
|
|
|
|
if ((owner.detectionType & DetectionType.Sound) != 0 && !hasSneak && distance < owner.GetMobMod((uint)MobModifier.SoundRange))
|
2017-08-02 23:06:11 +01:00
|
|
|
|
return CanSeePoint(target.positionX, target.positionY, target.positionZ);
|
|
|
|
|
|
2017-09-12 01:24:02 +01:00
|
|
|
|
if ((owner.detectionType & DetectionType.Magic) != 0 && target.aiContainer.IsCurrentState<MagicState>())
|
|
|
|
|
return CanSeePoint(target.positionX, target.positionY, target.positionZ);
|
|
|
|
|
|
|
|
|
|
if ((owner.detectionType & DetectionType.LowHp) != 0 && target.GetHPP() < 75)
|
2017-08-30 00:14:14 +01:00
|
|
|
|
return CanSeePoint(target.positionX, target.positionY, target.positionZ);
|
|
|
|
|
|
2017-08-02 23:06:11 +01:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-16 02:50:32 +01:00
|
|
|
|
public virtual bool CanSeePoint(float x, float y, float z)
|
2017-08-02 23:06:11 +01:00
|
|
|
|
{
|
|
|
|
|
return NavmeshUtils.CanSee((Zone)owner.zone, owner.positionX, owner.positionY, owner.positionZ, x, y, z);
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-16 02:50:32 +01:00
|
|
|
|
protected virtual void HandleHate()
|
2017-08-02 23:06:11 +01:00
|
|
|
|
{
|
|
|
|
|
ChangeTarget(owner.hateContainer.GetMostHatedTarget());
|
2017-07-11 20:49:38 +01:00
|
|
|
|
}
|
2017-08-31 05:56:43 +01:00
|
|
|
|
|
|
|
|
|
public override void ChangeTarget(Character target)
|
|
|
|
|
{
|
2018-02-15 13:20:46 -06:00
|
|
|
|
if (target != owner.target)
|
|
|
|
|
{
|
|
|
|
|
owner.target = target;
|
|
|
|
|
owner.currentLockedTarget = target?.actorId ?? Actor.INVALID_ACTORID;
|
|
|
|
|
owner.currentTarget = target?.actorId ?? Actor.INVALID_ACTORID;
|
2017-09-10 03:41:58 +01:00
|
|
|
|
|
2018-02-15 13:20:46 -06:00
|
|
|
|
foreach (var player in owner.zone.GetActorsAroundActor<Player>(owner, 50))
|
|
|
|
|
player.QueuePacket(owner.GetHateTypePacket(player));
|
2017-09-10 03:41:58 +01:00
|
|
|
|
|
2018-02-15 13:20:46 -06:00
|
|
|
|
base.ChangeTarget(target);
|
|
|
|
|
}
|
2017-08-31 05:56:43 +01:00
|
|
|
|
}
|
2017-07-08 00:20:55 +01:00
|
|
|
|
}
|
|
|
|
|
}
|