Breakable surfaces (glass) are broken by each machine's own projectiles
with its own random damage, so a pane could shatter on the host and stay
whole, still blocking, on a client. In co-op the host now sends each break
(and a few again every refresh, for late joiners), and a client breaks the
same surface with its shards.
Destructible scenery and devices (lights, panels) are the map's, at the
same index on every machine, but only damage to units was synced: a
client's shots never damaged them, and what the host broke didn't break
or play its effects on the clients. A client now reports its hits on them
like any other, the host checks and deals them, and every client deals the
host's damage to its own copy, which breaks, plays its effects and is
destroyed as the host's was.
Both are new message kinds that a client without them ignores, so the
network version is unchanged.
Players in big sessions ended up on different BSPs:
- The host told clients about a switch only every half second, and not
reliably, so a client could predict its player for a while in a BSP it
hadn't loaded. A switch is now sent at once, reliably, ahead of the
players' new positions.
- A client still loading reports so: its input carries the BSP it has
loaded. Until it has the host's, the host takes none of its movement
(which falls there with no floor) and none of its loading zones.
- After a switch, by a trigger, a script or a skipped cutscene, no trigger
can switch again until every machine has loaded the new BSP (at most
ten seconds), so no client falls two switches behind.
- A player the host can't place beside whoever crossed, when a big team
runs out of room there, is placed beside another teammate instead of
being left where they were.
Clients only had a kill path for bipeds, so a Ghost, Banshee or Wraith the
host destroyed stayed intact on clients. A vehicle the host's state says
is dead is now destroyed there too, with its explosion.
A loading zone brings the whole team to whoever crosses it. Into a new part
of the level that's what you want, but it let one player drag everyone
back. A switch back to a BSP the team has been in now waits until every
living player is in the trigger or near the player in it.
The scripts make allegiances (ai_allegiance player human) and only the host
runs them, so on clients marines stayed enemies of the players: red reticles
and the wrong team. The host sends its ally and friend tables with the
presentation each tick.
The test compiles main.c's and players.c's death handlers against a fake
world; their network co-op branches call functions it had none of, so it
no longer compiled. Stubbed off, it covers the local game it was written
for, and passes.
- A body the host's unit got back up from (a Flood combat form feigning
death) gets up on the clients too, instead of its ragdoll being dragged
about. The resurrection is one function both use (unit_port_resurrect).
- The scripts' unit_open and unit_close go to the clients, so dropships'
doors aren't left open there.
The host sends each device's position and power when they change (and with
the resent state), and a client puts its device there when the host's is at
rest and they differ, or when they drift apart while it moves. Clients only
had the device groups' values, which aim a device: a door could stop half
open, and an elevator be somewhere else on a client, so its players fell.
- Crossing a BSP loading zone moves every other player to whoever crossed,
as split screen does. Moving only some players left the rest where the
next switch threw them back to a hallway, or under a floor.
- The rescue for a player outside the loaded BSP waits two seconds and
only takes someone falling: a player riding an elevator through its
shaft is left alone.
- AI units' teams go with their states, so friendlies aren't red on
clients' reticles.
- A skipped cutscene's music and dialogue stop on the clients.
- Winning a level returns every machine to the lobby with the next level
set up, instead of leaving them on its last screen.
- Help text carries whether the host shows it, so no prompt is dropped.
Hosting a co-op level from the Map screen (CO-OP CAMPAIGN, a level and its
difficulty, then Server Setup) makes a network game on the campaign map
with no game engine, which is what co-op is. Only the host runs the
scripts and the AI; port/linux/game/network_coop.c sends clients what the
scripts do (cutscenes, camera, fades, HUD, sounds, devices, named objects,
animations), and network_actors.c drives the host's AI units on every
machine. Players spawn beside each other, respawn beside a safe teammate,
and spectate while dead or waiting. Skipping a cutscene is a vote.
Players can join a game in progress at any time.
An antenna's chain is a simulation of the Xbox's own step, an update a tick,
and the port was stepping it once a frame with the frame's own time. Its
springs, its carry and the points' physics then acted against the frames'
movement of it, so how far the chain moved per update depended on the frame
rate, and a vehicle at speed swung it far wider than a tick's step does.
- game.c steps the chain in the tick, beside the effects, which are already
stepped there.
- antenna.c records the shape each tick leaves, relative to the point the
vehicle's marker carries the base by. A frame drawn between two ticks is the
part of the way between those two shapes that it is, carried to where the
marker is now, so the chain stays on the vehicle it hangs from instead of
lagging a tick behind it. The gate that left a chain that had not been drawn
for five updates unstepped counted frames; it counts ticks now, which is
what it counted on the Xbox.
- widgets.c no longer steps the chain from the widget update, which ran once a
frame.
- game_time.c draws the per-frame effects and widgets that hang off objects
(game_frame) with the pose between the last two ticks, the pose the renderer
draws with, so what they place on an object is drawn where they put it.
- objects.c hands markers the matrices the renderer draws with, so an effect,
a particle system or a contrail attached to a moving object follows it
between the ticks too.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Hosting over LAN or the internet, the Map screen's list first offers CO-OP
CAMPAIGN or MULTIPLAYER. CO-OP CAMPAIGN offers CAMPAIGN or CUSTOM CAMPAIGN,
then a level and its difficulty. MULTIPLAYER offers VANILLA MAPS or CUSTOM
MAPS, then the maps, and goes on to the gametypes as before. B steps back
through the choices and leaves the screen from the first. Split screen
starts at VANILLA MAPS / CUSTOM MAPS.
The custom lists stay empty and a co-op level cannot be hosted yet: network
co-op and Custom Edition maps come in their own pull requests, which fill
them in.