Systems
Failure & wreck
At 0 % hull the walker does not just turn into a wreck. One side gives way, it collapses, burns and throws sparks – and seven seconds later it goes up in a huge explosion.
Flow
One replicated state, local show
Only two small values travel over the network: a state (0 intact, 1 collapsing, 2 exploded) and a random seed that picks the side that gives way first. Every machine plays the rest from these. A player who joins after the explosion gets state 2 directly and sees the wreck lying – without replaying the sequence.
[RplProp(onRplName: "OnWreckRpl")]
protected int m_iWreck; // 0 intact, 1 collapsing, 2 exploded
[RplProp(onRplName: "OnWreckRpl")]
protected int m_iWreckSeed;
protected int m_iWreckSeen;
void ServerStartWreck()
{
if (m_iWreck != 0)
return;
m_iWreck = 1;
m_iWreckSeed = Math.RandomInt(1, 1000000);
Replication.BumpMe();
OnWreckRpl(); // the server gets no callback for its own change
GetGame().GetCallqueue().CallLater(ServerExplode, 7000, false);
}
protected void OnWreckRpl()
{
if (m_iWreck == m_iWreckSeen)
return;
int before = m_iWreckSeen;
m_iWreckSeen = m_iWreck;
bool instant = before == 0 && m_iWreck == 2; // joined after the explosion
m_Rig.SetWreck(true, m_iWreckSeed, instant);
if (!instant && before == 0 && !System.IsConsoleApp())
StartEffectTimeline(); // sparks, smoke, impact sound, fire
}
The collapse is a pose
No animation clip: the collapse is laid on top of the existing park crouch. First a stagger (one side gives way, a short knee jerk), then a fall that accelerates like a free fall into a deeper, nose-down, rolled pose, ending with a kick into the impact spring. The stabilizer adds a forward slump to the torso and lets the arms hang.
m_fWreckT += dt;
float stagger = Math.Clamp(m_fWreckT / 1.0, 0, 1);
stagger = stagger * stagger * (3 - 2 * stagger); // smoothstep
float u = Math.Clamp((m_fWreckT - 1.0) / 1.1, 0, 1);
float fall = u * u; // accelerating like a free fall
m_fParkBlend = Math.Max(m_fParkBlendAtStart, 0.25 * stagger + 0.75 * fall);
m_fWreckBlend = fall;
m_fWreckRoll = 0.45 * stagger + 0.55 * fall;
// posture = park crouch + wreck: deeper, nose down, rolled to the side that gave way
float pitch = m_fParkPitch * m_fParkBlend + 14 * m_fWreckBlend;
float roll = m_fWreckSide * 11 * m_fWreckRoll;
vector offset = m_vParkOffset * m_fParkBlend + Vector(0, -1.4, 0.6) * m_fWreckBlend;
The big explosion has to reach everyone
The fix is the same path the missiles use: the server sends effect and sound to every player near the walker through that player's own PlayerController (players with the Game Master editor open get it too). The warhead still does the damage on the server.
// server
vector centre = TorsoCentre();
MyWalker_Blast(WARHEAD, centre, lastAttacker); // damage, server only
foreach (SCR_PlayerController pc : PlayersNear(centre, 2000))
pc.MyWalker_SendDetonation(centre, BIG_EFFECT, BIG_SOUND, 4.0); // picture and sound, every client
The crew
Whoever is still aboard dies with the explosion and is thrown out. The seats sit on child turret entities, so walk the children. Remember the last attacker of the hull and use it as instigator – that keeps the kill credit.
protected void KillCrew(IEntity ent, Instigator who, int depth)
{
if (!ent || depth > 6)
return;
SCR_BaseCompartmentManagerComponent seats = SCR_BaseCompartmentManagerComponent.Cast(ent.FindComponent(SCR_BaseCompartmentManagerComponent));
if (seats)
seats.KillOccupants(who, true); // eject the bodies
IEntity child = ent.GetChildren();
while (child)
{
KillCrew(child, who, depth + 1);
child = child.GetSibling();
}
}