Skip to content
CRUSHFORMV1.1.0
GET IT ON FAB

MULTIPLAYERLATE JOINERS

Late joiners

A player who joins a game after a vehicle was damaged does not see the crash, but the damage is still there. The server keeps the state of every body, and a machine that connects later receives that state with its replication. There is no late-joiner setting. The behaviour comes from how each class replicates its own state, so this page goes through the classes one by one.

Dents. The server’s chunks arrive as one replicated array. The joining machine builds its lattice from them. The lattice geometry is worked out on each machine from the same assets and settings, so both machines must use the same profile. A chunk whose index lies outside the lattice, or whose checksum does not match, is counted as bad and the machine keeps the chunk it had. A chunk of another repair generation waits until the generation arrives.

OnDamageChanged fires once the chunks have built the lattice, if the damage is not zero. A joiner does not need a path of its own for it.

Dirt. Dirt is local to each machine and never travels over the network. A joiner’s body starts without dirt and builds its own from the wheels it sees. A wash replicates through the wash count, so a joiner that connects after a wash starts clean as well.

Hinged parts. The server’s list of parts that are off arrives with the generation. A part that the server had off is hidden at once, with no fall, when the joiner first sees the body. On Detached fires there with bHiddenAtOnce true, on the first timer tick after BeginPlay. Its place is also cut out of the body’s collision, so other cars meet the open place. A part that was let swing starts swinging from its closed place, and On Started Swinging fires for it.

Breakable units. Each unit’s level arrives with the body’s generation, and the joiner shows each unit at that level. The first show of a unit fires no level event. Whether a joiner’s existing damage fires one depends on arrival order: if the unit levels arrive before the actor begins play, the settle at begin play fires no event; if they arrive after it, each level change fires one. Recent break events carry the server’s time. An event older than Break Event Max Age Seconds plays no scatter. That setting, 2 seconds by default, is in Project Settings › Plugins › Crushform Breakable › Network. A joiner whose game state has not arrived yet treats every event as stale.

Felled trees. A tree’s state replicates: whether it has yielded or settled, the place and rotation where it settled, and the server time of the last change. A joiner puts a felled tree where it settled. OnYielded, OnSettled and OnRespawned do not fire for a state older than Break Event Max Age Seconds, or before the game state has arrived. OnHit and OnLanded come from local physics, so they are not held back. A tree that respawned while the joiner was away snaps to its rest pose.

Glass. The cracks of each pane replicate. A joiner sees them. A pane’s first replication plays no sound, so a crack that the joiner did not see plays nothing.

Hazards. The moving hazards of the proving ground replicate nothing. Each machine computes their pose from the server’s game time, so a joiner sees the same phase.

Start a listen server, then drive the sedan into a wall, into a tree and into a fence. A tree falls and a fence breaks only when a car hits it, so the test needs driving, not a console command. Then add a player that joins after the damage. There are two ways to do it:

  • In the editor. Open Editor Preferences › General › Experimental › PIE and tick Allow late joining. Set Net Mode to Play As Listen Server and press Play. Once the level runs, press Add Client in the play toolbar. The new client connects to the running server.
  • As a second game instance. Launch it from the engine’s Binaries\Win64 folder with the server’s address: UnrealEditor.exe "<path to your .uproject>" 127.0.0.1:17777 -game -windowed. The port is the Server Port in the Play settings, 17777 by default.

In the joining instance, enter Crushform.Stats. The line of the sedan says client view, with the server’s generation and a chunk count above zero, and its received count rises as the chunks arrive. The dent that the server made is on the joiner’s screen. The line has the same fields as the one in Replication and prediction. A part the server had off is not shown on the joiner’s body at all.

Cause. The joiner counts chunks it cannot use as bad. A chunk is bad when its checksum fails or its index lies outside the joiner’s lattice. Both happen when the two machines build different lattices, for example because they use different profile assets or settings.

Fix. Read the bad count in Crushform.Stats on the joiner. Give both builds the same profile asset and the same settings, and check the count again.

A crack on the joiner’s screen is silent

Section titled “A crack on the joiner’s screen is silent”

Cause. A pane with nothing drawn yet is silent, so a late joiner’s first replication of a pane plays no crack sound. A pane that the replicated levels already break is silent as well, and its break plays Break instead.

Fix. Read the pane’s cracks when the joiner connects, as the tree entry below does. The plugin does not play a sound for a crack the joiner did not see.

A tree that fell before the joiner connected does not fire OnYielded

Section titled “A tree that fell before the joiner connected does not fire OnYielded”

Cause. OnYielded does not fire for a state older than Break Event Max Age Seconds, which is 2 seconds by default, and it does not fire before the game state has arrived.

Fix. Read the tree’s state when the joiner connects, instead of waiting for the event. A larger Break Event Max Age Seconds lets older events fire, but it also lets older break events play their scatter.