Skip to content

Commit 90b62d7

Browse files
WeylonSantanalodicolopandinocoder
authored
fix: some npc behaviors (#2474)
* fix: some npc behaviors Co-Authored-By: lodicolo <[email protected]> * not cast spell when target is hidden * Introduce CanTarget() --------- Co-authored-by: lodicolo <[email protected]> Co-authored-by: Robbie Lodico <[email protected]>
1 parent d2c9b41 commit 90b62d7

File tree

2 files changed

+76
-54
lines changed

2 files changed

+76
-54
lines changed

Intersect.Server.Core/Entities/Entity.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public abstract partial class Entity : IEntity
4848
public IReadOnlyDictionary<Vital, long> VitalsLookup => _vitals.Select((value, index) => (value, index))
4949
.ToDictionary(t => (Vital)t.index, t => t.value).AsReadOnly();
5050

51-
[NotMapped, JsonIgnore] public Entity Target { get; set; } = null;
51+
[NotMapped, JsonIgnore] public Entity? Target { get; set; }
5252

5353
public Entity() : this(Guid.NewGuid(), Guid.Empty)
5454
{
@@ -318,6 +318,8 @@ public virtual void Dispose()
318318
}
319319
}
320320

321+
public bool HasStatusEffect(SpellEffect spellEffect) => CachedStatuses.Any(s => s.Type == spellEffect);
322+
321323
public virtual void Update(long timeMs)
322324
{
323325
var lockObtained = false;
@@ -1408,6 +1410,24 @@ public virtual int GetWeaponDamage()
14081410

14091411
public virtual bool CanAttack(Entity entity, SpellBase spell) => !IsCasting;
14101412

1413+
public virtual bool CanTarget(Entity? entity)
1414+
{
1415+
if (entity == null)
1416+
{
1417+
// If it's not an entity we can't target it
1418+
return false;
1419+
}
1420+
1421+
if (IsAllyOf(entity))
1422+
{
1423+
// If it's an ally we can always target them
1424+
return true;
1425+
}
1426+
1427+
// If it's not an ally we can't target it if it's stealthed
1428+
return !entity.HasStatusEffect(SpellEffect.Stealth);
1429+
}
1430+
14111431
public virtual void ProcessRegen()
14121432
{
14131433
}
@@ -2922,6 +2942,11 @@ protected Direction DirectionToTarget(Entity en)
29222942
return Dir;
29232943
}
29242944

2945+
if (en.CachedStatuses.Any(status => status.Type == SpellEffect.Stealth))
2946+
{
2947+
return Dir;
2948+
}
2949+
29252950
if (!MapController.TryGet(MapId, out var originMapController) ||
29262951
!MapController.TryGet(en.MapId, out var targetMapController))
29272952
{

Intersect.Server.Core/Entities/Npc.cs

Lines changed: 50 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ public bool TargetHasStealth(Entity target)
190190
}
191191

192192
//Targeting
193-
public void AssignTarget(Entity en)
193+
public void AssignTarget(Entity? en)
194194
{
195195
var oldTarget = Target;
196196

@@ -199,16 +199,7 @@ public void AssignTarget(Entity en)
199199
if (AggroCenterMap != null && pathTarget != null &&
200200
pathTarget.TargetMapId == AggroCenterMap.Id && pathTarget.TargetX == AggroCenterX && pathTarget.TargetY == AggroCenterY)
201201
{
202-
if (en == null)
203-
{
204-
return;
205-
206-
}
207-
else
208-
{
209-
return;
210-
211-
}
202+
return;
212203
}
213204

214205
//Why are we doing all of this logic if we are assigning a target that we already have?
@@ -229,7 +220,7 @@ public void AssignTarget(Entity en)
229220

230221
if (en is Projectile projectile)
231222
{
232-
if (projectile.Owner != this && !TargetHasStealth(projectile))
223+
if (projectile.Owner != this && !projectile.HasStatusEffect(SpellEffect.Stealth))
233224
{
234225
Target = projectile.Owner;
235226
}
@@ -246,21 +237,17 @@ public void AssignTarget(Entity en)
246237
}
247238
}
248239
}
249-
250-
if (en is Player)
240+
else if (en is Player player)
251241
{
252242
//TODO Make sure that the npc can target the player
253-
if (this != en && !TargetHasStealth(en))
243+
if (CanTarget(player))
254244
{
255-
Target = en;
245+
Target = player;
256246
}
257247
}
258-
else
248+
else if (CanTarget(en))
259249
{
260-
if (this != en && !TargetHasStealth(en))
261-
{
262-
Target = en;
263-
}
250+
Target = en;
264251
}
265252
}
266253

@@ -331,8 +318,20 @@ public override bool CanAttack(Entity entity, SpellBase spell)
331318
}
332319
}
333320

334-
if (TargetHasStealth(entity))
321+
if (entity.HasStatusEffect(SpellEffect.Stealth))
335322
{
323+
// if spell is area or projectile, we can attack without knowing the target location
324+
if (spell?.Combat is { TargetType: SpellTargetType.AoE or SpellTargetType.Projectile })
325+
{
326+
return true;
327+
}
328+
329+
// this is for handle aoe when target is single target, we can hit the target if it's in the radius
330+
if (spell?.Combat.TargetType == SpellTargetType.Single && spell.Combat.HitRadius > 0 && InRangeOf(entity, spell.Combat.HitRadius))
331+
{
332+
return true;
333+
}
334+
336335
return false;
337336
}
338337

@@ -669,6 +668,12 @@ private void TryCastSpells()
669668
Log.Warn($"Combat data missing for {spellBase.Id}.");
670669
}
671670

671+
//TODO: try cast spell to find out hidden targets?
672+
// if (target.HasStatusEffect(SpellEffect.Stealth) /* && spellBase.Combat.TargetType != SpellTargetType.AoE*/)
673+
// {
674+
// return;
675+
// }
676+
672677
// Check if we are even allowed to cast this spell.
673678
if (!CanCastSpell(spellBase, target, true, out _))
674679
{
@@ -775,11 +780,12 @@ public override void Update(long timeMs)
775780
{
776781
var curMapLink = MapId;
777782
base.Update(timeMs);
783+
778784
var tempTarget = Target;
779785

780786
foreach (var status in CachedStatuses)
781787
{
782-
if (status.Type == SpellEffect.Stun || status.Type == SpellEffect.Sleep)
788+
if (status.Type is SpellEffect.Stun or SpellEffect.Sleep)
783789
{
784790
return;
785791
}
@@ -794,6 +800,12 @@ public override void Update(long timeMs)
794800
var targetY = 0;
795801
var targetZ = 0;
796802

803+
if (tempTarget != null && (tempTarget.IsDead() || !InRangeOf(tempTarget, Options.MapWidth * 2) || !CanTarget(tempTarget)))
804+
{
805+
_ = TryFindNewTarget(Timing.Global.Milliseconds, tempTarget.Id, !CanTarget(tempTarget));
806+
tempTarget = Target;
807+
}
808+
797809
//TODO Clear Damage Map if out of combat (target is null and combat timer is to the point that regen has started)
798810
if (tempTarget != null && (Options.Instance.NpcOpts.ResetIfCombatTimerExceeded && Timing.Global.Milliseconds > CombatTimer))
799811
{
@@ -803,6 +815,7 @@ public override void Update(long timeMs)
803815
{
804816
PacketSender.SendNpcAggressionToProximity(this);
805817
}
818+
806819
return;
807820
}
808821
}
@@ -836,34 +849,17 @@ public override void Update(long timeMs)
836849
mResetDistance = 0;
837850
}
838851
}
839-
840-
}
841-
842-
if (tempTarget != null && (tempTarget.IsDead() || !InRangeOf(tempTarget, Options.MapWidth * 2)))
843-
{
844-
TryFindNewTarget(Timing.Global.Milliseconds, tempTarget.Id);
845-
tempTarget = Target;
846852
}
847853

848854
//Check if there is a target, if so, run their ass down.
849-
if (tempTarget != null)
855+
if (tempTarget != null && CanTarget(tempTarget))
850856
{
851857
if (!tempTarget.IsDead() && CanAttack(tempTarget, null))
852858
{
853859
targetMap = tempTarget.MapId;
854860
targetX = tempTarget.X;
855861
targetY = tempTarget.Y;
856862
targetZ = tempTarget.Z;
857-
foreach (var targetStatus in tempTarget.CachedStatuses)
858-
{
859-
if (targetStatus.Type == SpellEffect.Stealth)
860-
{
861-
targetMap = Guid.Empty;
862-
targetX = 0;
863-
targetY = 0;
864-
targetZ = 0;
865-
}
866-
}
867863
}
868864
}
869865
else //Find a target if able
@@ -910,7 +906,7 @@ public override void Update(long timeMs)
910906
{
911907
mPathFinder.SetTarget(new PathfinderTarget(targetMap, targetX, targetY, targetZ));
912908

913-
if (tempTarget != Target)
909+
if (tempTarget != null && tempTarget != Target)
914910
{
915911
tempTarget = Target;
916912
}
@@ -1378,11 +1374,11 @@ public bool ShouldAttackPlayerOnSight(Player en)
13781374
return false;
13791375
}
13801376

1381-
public void TryFindNewTarget(long timeMs, Guid avoidId = new Guid(), bool ignoreTimer = false, Entity attackedBy = null)
1377+
public bool TryFindNewTarget(long timeMs, Guid avoidId = new(), bool ignoreTimer = false, Entity attackedBy = null)
13821378
{
13831379
if (!ignoreTimer && FindTargetWaitTime > timeMs)
13841380
{
1385-
return;
1381+
return false;
13861382
}
13871383

13881384
// Are we resetting? If so, do not allow for a new target.
@@ -1392,16 +1388,14 @@ public bool ShouldAttackPlayerOnSight(Player en)
13921388
{
13931389
if (!Options.Instance.NpcOpts.AllowEngagingWhileResetting || attackedBy == null || attackedBy.GetDistanceTo(AggroCenterMap, AggroCenterX, AggroCenterY) > Math.Max(Options.Instance.NpcOpts.ResetRadius, Base.ResetRadius))
13941390
{
1395-
return;
1396-
}
1397-
else
1398-
{
1399-
//We're resetting and just got attacked, and we allow reengagement.. let's stop resetting and fight!
1400-
mPathFinder?.SetTarget(null);
1401-
mResetting = false;
1402-
AssignTarget(attackedBy);
1403-
return;
1391+
return false;
14041392
}
1393+
1394+
//We're resetting and just got attacked, and we allow reengagement.. let's stop resetting and fight!
1395+
mPathFinder?.SetTarget(null);
1396+
mResetting = false;
1397+
AssignTarget(attackedBy);
1398+
return true;
14051399
}
14061400

14071401
var possibleTargets = new List<Entity>();
@@ -1538,9 +1532,12 @@ public bool ShouldAttackPlayerOnSight(Player en)
15381532
{
15391533
CheckForResetLocation(true);
15401534
}
1535+
1536+
AssignTarget(null);
15411537
}
15421538

15431539
FindTargetWaitTime = timeMs + FindTargetDelay;
1540+
return Target != null;
15441541
}
15451542

15461543
public override void ProcessRegen()

0 commit comments

Comments
 (0)