GTG Safe Zone
Invulnerability
The reliable part of the zone is a few lines in the damage manager: if the entity is protected right now, the hit is dropped. This runs where damage is computed, so no client can argue with it.
A hook, not a switch
The damage manager has an event that is called for every hit before the damage is applied. Its documentation says what the
return value means: true if the damage should be discarded. That is the whole mechanism. It is stateless
– it is evaluated per hit and leaves nothing behind.
class MyAD_ZoneGuard
{
// Should this hit be thrown away?
static bool ShouldDiscard(IEntity victim, EDamageType type, float value, Instigator instigator)
{
// First of all, note who is attacking - see "Shooting from inside". This is the one place every hit on a
// character or a vehicle passes, so it must happen before any of the early returns below.
MyAD_Forfeit.NoteAttacker(instigator, type, value);
if (!victim)
return false;
// Healing runs through the same function as damage. Block it, and nobody can be treated
// in the one place the wounded are brought to.
if (type == EDamageType.HEALING || type == EDamageType.REGENERATION)
return false;
// The engine documents the damage value as possibly negative for healing damage - let that through
// whatever the type says. A negative value cannot hurt anybody.
if (value < 0)
return false;
MyAD_Zone zone = MyAD_Zone.FindShieldingZoneOf(victim);
if (!zone)
return false;
// Bleeding is damage already done that keeps running, not a new attack.
// Let it run unless the zone is set to stop it.
if (type == EDamageType.BLEEDING && !zone.StopsBleeding())
return false;
return true;
}
}
Why not switch damage handling off
The other way to make somebody invulnerable is EnableDamageHandling(false), and it is the way vanilla spawn
protection goes. It is also a switch, one per entity, with exactly one value. Spawn protection writes it. The Game Master's
damage attribute writes it. A zone that writes it as well fights both of them: whoever wrote last wins, and leaving the zone
would switch on damage that somebody else had deliberately switched off.
The hook has no such problem. It never changes the entity. When the hit comes, the zone looks at where the victim is and who it is, and answers for that one hit.
Hook the class that is really used
It looks natural to extend the common base class and be done. But the damage manager of a character overrides the hook
itself and does not call the base class in it. A change to the base is never reached for characters. Extend each class that
matters, one at a time – and always call super when the zone has no opinion.
modded class SCR_CharacterDamageManagerComponent
{
override bool HijackDamageHandling(notnull BaseDamageContext damageContext)
{
if (MyAD_ZoneGuard.ShouldDiscard(GetOwner(), damageContext.damageType, damageContext.damageValue, damageContext.instigator))
return true; // true = discard this hit
return super.HijackDamageHandling(damageContext);
}
}
modded class SCR_VehicleDamageManagerComponent
{
override bool HijackDamageHandling(notnull BaseDamageContext damageContext)
{
if (MyAD_ZoneGuard.ShouldDiscard(GetOwner(), damageContext.damageType, damageContext.damageValue, damageContext.instigator))
return true;
return super.HijackDamageHandling(damageContext);
}
}
Where it runs, and what that means
Damage is computed on the authority. This check is therefore the real enforcement of the zone: a modified client can ignore the action lock, but it cannot get damage past this check. That is also why the invulnerability stays in effect during an alarm – the alarm releases the weapons, not the damage.