Sci-Fi Expansion Pack
Energy shield
18,000 points around every walker. Something always gets through, energy weapons hit it hardest, and it collapses for 10 seconds when it runs dry.
Filter, not a hit zone
The shield is not a collider. It sits inside the walker's damage model, after armour, and counts in the same points as the zones. That makes balancing simple: an RPG hit is ~1,300 points whether it lands on the shield or the leg.
| Damage type | Shield holds | Shield cost |
|---|---|---|
| Energy (laser) | 95 % | x2 per point held |
| Explosive / fragmentation / fire | 85 % | x1 |
| Kinetic (APFSDS, autocannon, minigun) | 75 % | x1 |
| EMP | - | 40 % of maximum, direct |
//! [Server] Returns the damage that still reaches the walker.
float MyShield_Absorb(float damage, BaseDamageContext ctx)
{
if (damage <= 0 || m_fShieldPoints <= 0)
return damage;
m_fLastHit = Now(); // regeneration waits 5 s after the last hit
float hold = HOLD_KINETIC;
float cost = 1.0;
if (s_bEnergyHit) // set by the laser around its damage call
{
hold = HOLD_ENERGY;
cost = COST_ENERGY;
}
else if (IsExplosive(ctx.damageType))
hold = HOLD_EXPLOSIVE;
if (IsPlacedChargeInside(ctx)) // mines / C4 under the walker: no protection
return damage;
float absorbed = Math.Min(damage * hold, m_fShieldPoints / cost);
m_fShieldPoints -= absorbed * cost;
if (m_fShieldPoints <= 0.5)
{
m_fShieldPoints = 0;
m_fDownUntil = Now() + 10; // collapsed
}
SyncShield(); // replicated percent + hit counter (for the flash)
return damage - absorbed;
}
Inside the bubble
A rocket explodes at the walker – inside the bubble – but it flew in from outside. A mine was already inside. The difference is the damage source: anything without a projectile move component that sits inside the bubble ellipsoid gets no protection.
bool IsPlacedChargeInside(BaseDamageContext ctx)
{
IEntity src = ctx.damageSource;
if (!src || src.FindComponent(ProjectileMoveComponent))
return false; // projectile / rocket / grenade: came from outside
vector o = GetOwner().CoordToLocal(src.GetOrigin()) - BUBBLE_CENTRE;
vector r = BUBBLE_RADII; // ellipsoid half axes
float e = (o[0] / r[0]) * (o[0] / r[0]) + (o[1] / r[1]) * (o[1] / r[1]) + (o[2] / r[2]) * (o[2] / r[2]);
return e < 1.0;
}
Beams stop at the bubble
Hitscan beams (the laser) do not create a damage context with a projectile – they trace and apply damage themselves. To make them stop at an enemy shield, the beam is intersected with every standing bubble first: ray against ellipsoid, solved in the walker's local frame scaled to a unit sphere.
//! Distance along the ray to the bubble, or -1.
float RayVsBubble(IEntity walker, vector start, vector dir, float range)
{
vector o = walker.CoordToLocal(start) - BUBBLE_CENTRE;
vector d = walker.VectorToLocal(dir);
vector r = BUBBLE_RADII;
vector os = Vector(o[0] / r[0], o[1] / r[1], o[2] / r[2]);
vector ds = Vector(d[0] / r[0], d[1] / r[1], d[2] / r[2]);
float a = vector.Dot(ds, ds);
float b = 2.0 * vector.Dot(os, ds);
float c = vector.Dot(os, os) - 1.0;
if (c < 0)
return -1; // starts inside (own walker): no interception
float disc = b * b - 4.0 * a * c;
if (disc < 0)
return -1;
float t = (-b - Math.Sqrt(disc)) / (2.0 * a);
if (t < 0 || t > range)
return -1;
return t;
}
The bubble effect
The visible bubble (hexagon grid + haze) is a local child entity spawned on every machine with a display – never replicated. It shows while the shield stands and flashes in the direction of each hit, driven by a replicated hit counter and direction. The dedicated server spawns nothing.