GTG Safe Zone
Place and faction
If the position alone decides, the zone is a free pass for exactly the side it is meant to keep out. The alarm would go off, the defenders could answer – and the intruder could not be hit.
Who is an enemy
The zone has an owner: one or several factions. Whoever is not friendly towards the owner and is hostile to it counts as an enemy. Three outcomes, not two: the same faction or a friendly one is protected, a hostile one is an enemy, and everything in between – neutral – is neither.
protected bool IsEnemy(notnull IEntity entity)
{
FactionAffiliationComponent affiliation = FactionAffiliationComponent.Cast(entity.FindComponent(FactionAffiliationComponent));
if (!affiliation)
return false;
Faction entityFaction = affiliation.GetAffiliatedFaction();
if (!entityFaction)
return false;
FactionManager factionManager = GetGame().GetFactionManager();
if (!factionManager)
return false;
foreach (FactionKey key : m_aOwnerKeys)
{
Faction owner = factionManager.GetFactionByKey(key);
if (!owner)
continue;
if (owner == entityFaction || owner.IsFactionFriendly(entityFaction))
return false; // one friendly owner is enough: protected
if (owner.IsFactionEnemy(entityFaction))
return true;
}
return false; // neutral: neither
}
Protected means inside, and not an enemy, and not forfeited
One function answers the question the damage guard asks. Place first, then faction, then the state described on Shooting from inside. Each step can only take protection away.
static MyAD_Zone FindShieldingZoneOf(IEntity entity)
{
MyAD_Zone zone = FindProtectingZoneOf(entity); // place
if (!zone)
return null;
if (zone.IsEnemy(entity)) // faction
return null;
if (MyAD_Forfeit.HasForfeited(entity)) // behaviour
return null;
return zone;
}
static MyAD_Zone FindProtectingZoneOf(IEntity entity)
{
if (!entity)
return null;
return FindProtectingZone(WorldPos(entity)); // the entity's world position - not GetOrigin()
}
GetOrigin(). A player who sits in a vehicle is a child of that vehicle, and GetOrigin() is then relative to it. The occupants would be tested at coordinates near the world origin – inside the zone, or far outside it, by accident.The owner can follow the base
On a server where bases change hands, a fixed owner is wrong after the first swap, and the zone would then protect the attackers. So a zone with no fixed owner looks for one: the faction that controls a military base or owns a spawn point inside the zone. When the base changes hands, the zone follows; the lookup is repeated on every scan. The lookup uses a world query around the zone's bounding sphere, and the callback keeps what it finds in a member, because its signature takes no extra parameters.
protected FactionKey m_sFound; // written by the query callback
protected void UpdateOwnerFromBases()
{
m_sFound = FactionKey.Empty;
GetGame().GetWorld().QueryEntitiesBySphere(
m_vBoundsCenter, m_fBoundsRadius, OnOwnerCandidate, null, EQueryEntitiesFlags.ALL);
m_sOwner = m_sFound; // empty if nothing was found - see the next box
}
protected bool OnOwnerCandidate(notnull IEntity entity)
{
if (!IsInside(WorldPos(entity)))
return true; // not ours, keep looking
SCR_MilitaryBaseComponent militaryBase = SCR_MilitaryBaseComponent.Cast(entity.FindComponent(SCR_MilitaryBaseComponent));
if (militaryBase && militaryBase.GetFaction())
{
m_sFound = militaryBase.GetFaction().GetFactionKey();
return false; // found one - stop the query
}
SCR_SpawnPoint spawn = SCR_SpawnPoint.Cast(entity);
if (spawn && !spawn.GetFactionKey().IsEmpty())
{
m_sFound = spawn.GetFactionKey();
return false;
}
return true;
}
A neutral zone, on purpose
For a trading post or a meeting place that should be open to everybody, the owner can be switched off: every faction is protected, nobody counts as an enemy, and there is no alarm. It takes priority over any owner that is also entered.