///
/// Owns per-tick combat-state maintenance and acts as a gatekeeper for
/// SetRevengeTarget / SetAttackTarget changes.
///
/// Key improvement over EntityAliveSDX:
/// - Alert-flag clearing reads from the pre-populated NPCFrameCache instead of calling
/// GetAttackOrRevengeTarget a second time inside OnUpdateLive.
///
public class NPCCombatComponent : INPCComponent
{
private EntityAliveSDXV4 _entity;
// -----------------------------------------------------------------------
// INPCComponent
// -----------------------------------------------------------------------
public void Initialize(EntityAliveSDXV4 entity) => _entity = entity;
public void OnUpdateLive(ref NPCFrameCache cache)
{
// If alerted but the cached target is already dead, clear the alert flag.
// Uses the frame-cache value — no extra GetAttackOrRevengeTarget call.
if (_entity.IsAlert
&& cache.AttackOrRevengeTarget != null
&& cache.AttackOrRevengeTarget.IsDead())
{
_entity.bReplicatedAlertFlag = false;
}
}
public void OnDead() { }
public void OnUnload() { }
// -----------------------------------------------------------------------
// Target validation helpers — called from the entity's overridden methods
// -----------------------------------------------------------------------
///
/// Returns true if the proposed revenge target is valid for this NPC.
/// Blocks friendly fire from leader and allied NPCs, and ignores dead targets.
///
public bool ShouldAllowRevengeTarget(EntityAlive proposed)
{
if (proposed == null) return true; // clearing the target is always allowed
if (proposed.IsDead()) return false;
var myLeader = EntityUtilities.GetLeaderOrOwner(_entity.entityId) as EntityAlive;
if (myLeader != null && myLeader.entityId == proposed.entityId)
return false;
return !EntityUtilities.IsAnAlly(_entity.entityId, proposed.entityId);
}
///
/// Returns true if the proposed attack target should be applied.
/// Blocks clearing a living target (some AI tasks reset on stun — we preserve the target).
///
public bool ShouldAllowAttackTarget(EntityAlive proposed)
{
if (proposed == null)
{
// Prevent clearing the target while the current victim is still alive.
return _entity.attackTarget == null || !_entity.attackTarget.IsAlive();
}
return !proposed.IsDead();
}
}