yquake2/rogue

Medic Commander monster spawn lead to state inconsistencies

Closed this issue · 3 comments

While the ability for spawning monsters by other monsters was present in baseq2 the Ground zero addon is the first code that makes excessive use of it. The medic commander monster can not only resurrect dead monster, it can spawn up to 3 new monsters at a time. Spawned monsters are (with decreasing likelihood):

  • Guards
  • Enforcers
  • Gunners
  • Normal Medics (very unlikely)

The higher the skill, the more monsters are spawned. A good place to see this is in action is in rhangar2 at the big cargo lift. In normal gameplay this is working just fine. As soon as savegames are written and loaded there's a small chance (about 1 in every 20 savegame loads) for state inconsistencies to occure:

  • Empty models are standing in the level, even without clip
  • Empty models with clip are standing in the level
  • Invisible monsters are attacking the player

In the first two cases the clients seems to draw and to clip models, that are invisible to the server and game. The empty models are ignored by monsters and they're invisible to other coop clients. In the third case the game and server see a monster that's nonexistent for the client.

The problem seems to occur on savegame loading, even when the same savegame is reloaded over and over again the problem happens only infrequently. My educates guess is, that the savegame loading code doesn't clear the complete state of the monster spawning action. When a monster is spawned in while the savegame is loading some leftovers continue to exist in the client and / or server and game.

Mkay. I wonder if anyone ever really understood that code. There's an endless series of nested function calls, often with cycles that just don't trigger because of strange constrains like "We won't really reenter a level if we're loading a savegame".

When monsters are spawned in the Medic Commander calls medic_finish_spawn() -> CreateGroundMonster() -> CreateMonster() -> G_Spawn(). G_Spawn() selects an slot in g_edicts and puts the new entity into it. g_edict is shared with the server as ge->edicts, at the next server frame SV_BuildClientFrame() writes the entitiy into svs.client_entities and SV_EmitPacketEntities() transferes it to the client. SV_EmitPacketEntities() must set U_REMOVE on no longer existing entities. The client will remove it in CL_ParsePacketEntities() from it's entity list cl_parse_entities.

Theory 1: When a game is reloaded SV_InitGame() calls SV_Shutdown() to force all clients to reconnect. In CL_ParseServerMessage() the client sets the state ca_connecting and reconnects in CL_Reconnect_f() -> CL_Disconnect -> CL_ClearState(). CL_ClearState() is missing something(tm) but I don't know what. Maybe cl_parse_entities needs to be cleared.

Theory 2: For some reason the servers sends an incomplete entity on the first frame. I haven't looked into that.

A savegame for Linux/x86_64 showing the issue can be found here: http://deponie.yamagi.org/quake2/misc/rogue_issue6_save.tar.xz Unfortunately it's existence disproofs most of my theories.

Fixed in 1776ec3.