BASEQ3 Runthrough, Server-side

Synopsis

Illustration of an execution path through a Q3 game module (baseq3), from startup through gameplay though shutdown, for dedicated server mode, in outline form.

Motivation: Enhanced understanding of Q3A to assist in modding and total conversions.

Caveats: This document certainly is not a thorough understanding nor explanation of Q3. Information was gleaned by poring through the game source text, with minimal testing. Most of the statements made in this document are based on source comments and gut feelings.

Engine Start

Before the game (modification) even loads, Quake 3 on startup:

  1. searches for/in pak (.pk3) files according to the server cvar fs_game
  2. runs startup configuration (.cfg) files default.cfg and q3config.cfg
  3. initializes the dynamic memory hunk (com_hunkMegs, com_soundMegs, com_zoneMegs)
  4. opens network sockets for listening

Game Start

The most straight-forward way to launch the game is to start a valid map, with the server command map mapname. Quake 3 on game start:

  1. loads the map/level (.bsp) file
  2. looks through the pak (.pk3) files found on startup
  3. loads the game qvm file (qagame.qvm)
  4. compiles the qvm bytecode to native code if vm_game is set to 2
  5. jumps to the QVM procedure linked at code address 0 (i.e. vmMain())
  6. upon returning from the QVM procedure, does some other things (such as network tasks, reading in server commands, complaining with Hitch Warnings, etc.)
  7. jumps to code address 0 again
  8. rinse, lather, repeat.

vmMain

The QVM file lacks a symbol table, so the QVM interpreter/compiler subsystem must make a blind decision on a reasonable entry point into the QVM. Currently, Q3 simply branches to QVM code address 0. While Q3 (the engine) decides to jump to code address 0, the game (mod) expects Q3A to jump into the function named vmMain(). To ensure these two expectations coincide, vmMain() must be the first function compiled before any other function into QVM bytecode. This means the function vmMain() must be the first fully-defined function in the first assembled file. In baseq3, this is vmMain() in g_main.c, which gets assembled first as specified in the file game.q3asm, thus ensuring vmMain() links as code address 0. Note that other variables may be declared/defined before vmMain(), and other functions may be prototyped/declared before vmMain(), but the first function body to be compiled must be vmMain().

The function at code address 0 is the critical function. The associated name can be anything, but for consistency I shall refer to code address 0 as vmMain, as it is in baseq3.

On each server frame (time period determined by cvar sv_fps), the Q3 engine calls vmMain() with a number of arguments. The first argument (command in baseq3) determines what major action to which the game should react. These actions are enumerated in g_public.h, as enum type gameExport_t. Since the arguments to vmMain() are specified by the Q3 engine, and merely reflected in gameExport_t, modifying gameExport_t does not change anything in the game module, except possibly to confuse it badly.

Overview: vmMain command/major game function

A quick overview of the vmMain commands (major game functions) in baseq3:

The call to vmMain() is made by the Q3 engine. The parameters passed to vmMain are marshalled by the Q3 engine. Additions or changes to these major game functions must be implemented in the engine and reflected in the game module (mod). Such ability (to add or change major functions) is limited to developers with access to the Q3 engine (not mod) source. Currently (2002 Aug) this is Id Software itself and its direct licensees (proprietary license).

vmMain(GAME_INIT, levelTime, randomSeed, restart)

When the game module is first loaded on game start, Q3 calls vmMain with major function GAME_INIT, along with three additional arguments:

  1. levelTime - the number of milliseconds the game has been in progress, usually 0 (non-zero for restarts).
  2. randomSeed - a seed value for the random number generator (RNG).
  3. restart - ???, affects bot AI initialization only.

In vmMain(), the code immediately branches to G_InitGame() when vmMain recognizes GAME_INIT.

G_InitGame(levelTime, randomSeed, restart)

  1. Initialize random number generator with randomSeed -- srand(randomSeed).
  2. Register cvars (associates cvars in the Q3 engine with cvar_t variables in game engine) -- G_RegisterCvars().
    1. Goes through all entries in array gameCvarTable[], associating the cvar in the Q3 engine with the cvar_t variable in the game engine (QVM memory).
  3. Initialize banlist -- G_ProcessIPBans().
    1. Reads list of space-separated IP addresses from cvar g_banIPs.
    2. Adds each banned address to ban filter list -- AddIP().
      1. Maximum of 1024 banned addresses.
      2. Converts dotted-quad notation to an internal filter format -- StringToFilter().
      3. Adds filter record to array ipFilters[].
  4. Initialize the in-QVM dynamic memory pool of 256KB (hey, 64KB sent us to the moon!) -- G_InitMemory().
  5. Start logging if so configured.
  6. Initialize world session information -- G_InitWorldSession().
    1. Resets cvar session
  7. Initialize game entities.
  8. Initialize game clients (players).
  9. Tell the Q3 engine about g_entities, for network engine purposes -- trap_LocateGameData().
  10. Initialize body queue (for sinking corpses, etc.) -- InitBodyQue().
    1. Allocates 8 game entities and reserves them for the body queue.
  11. Initialize item registration -- ClearRegisteredItems().
    1. Clears the array itemRegistered[].
    2. Registers machine gun -- RegisterItem().
      1. Sets itemRegistered[10] to true (offset for machine gun weapon item)
    3. Registers gauntlet -- RegisterItem().
      1. Sets itemRegistered[8] to true (offset for gauntlet weapon item)
  12. Spawn map entities -- SpawnEntitiesFromString().
    1. Enable entities spawning with level.spawning = qtrue.
    2. Parse worldspawn variable, which must be the first such entity in the map file -- SP_worldspawn().
      1. Read a spawn string -- G_SpawnString("classname", "", &s).
        1. Retrieve the next spawn string, complain if the spawn string key doesn't match the first argument (i.e. "classname"), otherwise store the value into the third argument. The second argument provides a default value if no value is specified in the map file.
      2. Complain if the string isn't the worldspawn variable, with a fatal error (game stops).
      3. Set ConfigString CS_GAME_VERSION according to mod.
      4. Set ConfigString CS_LEVEL_START_TIME according to the recorded start time.
      5. Set ConfigString CS_MUSIC (map-specific background music) according to spawn entity -- G_SpawnString("music", "", &s).
      6. Set ConfigString CS_MESSAGE (map-specific message) according to spawn entity -- G_SpawnString("message", "", &s).
      7. Set ConfigString CS_MOTD (server MOTD) according to server cvar sv_motd.
      8. Set cvars g_gravity, g_enableDust, and g_enableBreath according to spawn entities.
      9. Record the existence of the worldspawn entity.
      10. Set warmup time as needed, based on cvar g_doWarmup.
    3. Parse spawn variables -- G_ParseSpawnVars().
      1. Expect an opening brace ({), fail miserably if not found -- trap_GetEntityToken().
      2. Read all key/value pairs.
        1. Read a token as a key -- trap_GetEntityToken().
        2. Stop loop if this token is a closing brace (}).
        3. Read a token as a value -- trap_GetEntityToken().
        4. Complain if this token is a closing brace (}).
        5. Complain if there are now more than 64 spawn variables.
        6. Record spawn variable key.
        7. Record spawn variable value.
    4. Spawn entities from spawn variables -- G_SpawnGEntityFromSpawnVars().
      1. Allocate a game entity -- G_Spawn().
      2. Parse the fields for each spawn variable -- G_ParseField().
        1. ???!
        2. Takes key/value pairs and sets fields in game entity using arcane binary doohackery.
      3. Destroy entity according to game type and gametype spawnflags (notsingle, notteam, notfree).
      4. Destroy entity according to spawn variable gametype and active game type.
      5. Adjust origins(?)
      6. Call assocated game spawn function -- G_CallSpawn().
        1. Complain if entity classname is NULL.
        2. Attempt to spawn as an item -- G_SpawnItem().
          1. Set entity field random according to spawn variable random.
          2. Set entity field wait according to spawn variable wait.
          3. Register this item -- RegisterItem().
          4. Check if item is disabled and don't spawn if so -- G_ItemDisabled().
            1. Returns the value of cvar disable_classname. So to disable quad damage would be /set disable_quaddamage 1.
          5. Record global/master/templated item information.
          6. Set think time and function -- FinishSpawningItem().
            1. Makes items fall to floor. Falling is disabled at spawn time to prevent items spawning within other entities.
          7. Make entity bouncy.
          8. Set powerup fields for powerup items (in particular, speed according to spawn variable noglobalsound).
        3. If the entity didn't spawn as an item, attempt to spawn as a normal entity by finding the classname and calling the associated spawn function -- s->spawn(). (XXX: spawn[] and list of SP_*() functions)
        4. Failing that, complain about lack of spawn function.
      7. Destroy object if no such spawn function exists.
  13. Initialize teams information -- G_FindTeams().
    1. Create a linked list for each team composed of all entities belonging to the team. The team master is the lowest numbered entity that is not a slave.
  14. Broadcast item registration information via ConfigString -- SaveRegisteredItems().
    1. Sets ConfigString CS_ITEMS to a sequence of ASCII 1 and 0 according tot he content of array itemRegistered[]. The information is transmitted to all clients for precaching purposes.
  15. Initialize bot AI -- BotAISetup(), BotAILoadMap(), G_InitBots(). (XXX: Aieeee!)
  16. Various other server-side tweaks.

That was just for game initialization.

vmMain(GAME_SHUTDOWN, restart)

When Q3 wants to shut down the game module, Q3 calls vmMain() with major function GAME_SHUTDOWN, and one additional argument:

  1. restart - ???, affects bot AI initialization only.

In vmMain(), the code immediately branches to G_ShutdownGame() when vmMain() recognizes GAME_SHUTDOWN.

For map changes (as after end of round), the game module is shut down then initialized again with the new map (partly to have a clean slate for the new map's entities). Any data within QVM memory is irretrievably destroyed. Any data that needs to survive across map changes, or even map restarts, must be committed to files (disk) or cvars (Q3 engine memory).

G_ShutdownGame(restart)

  1. Closes any logs.
  2. Save session data -- G_WriteSessionData().
    1. Save world session data to cvar session, which is just current gametype.
    2. Save each client session data -- G_WriteClientSessionData().
      1. Save to cvar sessionclientnum the information pertaining to client's team, spectator time, spectator state, client being spectated, wins, losses, and teamLeader.
  3. Shutdown bot AI -- BotAIShutdown().
    1. (XXX: yeah, right...)

vmMain(GAME_CLIENT_CONNECT, clientNum, firstTime, isBot)

When a client connects to the server, Q3 calls vmMain() with major function GAME_CLIENT_CONNECT, with arguments:

  1. clientNum - the assigned client number according to the Q3 engine.
  2. firstTime - zero if a persistent connection from a previous game on the same server, non-zero if a fresh connect to the server.
  3. isBot - non-zero if the connecting player is a bot AI.

In vmMain(), code immediated branches to ClientConnect() when vmMain recognizes GAME_CLIENT_CONNECT.

ClientConnect(clientNum, firstTime, isBot)

  1. Get userinfo string from client -- trap_GetUserinfo().
  2. Check if client's IP address is banned -- G_FilterPacket().
    1. Check client's IP address against each entry in array ipFilters[].
  3. Check for password.
  4. Read/initialize session data -- G_InitSessionData().
    1. Figure out a team to join; autojoin, spectator, predetermined join (same team as last game), waiting-in-line.
  5. Set various flags if bot.
  6. Enact effects of client userinfo -- ClientUserinfoChanged().
    1. Get userinfo from client (again).
    2. Enforce sanity-check on name -- Info_Validate().
    3. Check if local client (non-networked).
    4. Check if client wants item prediction.
    5. Set client name.
    6. Set client team.
    7. Announce any during-play name change (a client that just connected technically had a name-change from nothing to something, but this particular name change isn't announced).
    8. Set max health (influence by handicap value).
    9. Set player model.
    10. Select skin according to team (if applicable).
    11. Set teamtask.
    12. Arrange a subset of userinfo into a ConfigString, stored into a CS_* slot based on client number.
  7. Announce team joining -- BroadcastTeamChange().
    1. Tells everyone in the game about someone joining/changing teams.
  8. Calculate client ranks -- CalculateRanks().
    1. (XXX: lotsa math and sorting...)

vmMain(GAME_CLIENT_THINK, clientNum)

Called (frequency unknown) to handle player input to the game and player movement within the game, with one additional argument:

  1. clientNum - the client number of which to handle input.

In vmMain(), the code immediately branches to ClientThink() when vmMain recognizes GAME_CLIENT_THINK.

ClientThink(clientNum)

  1. Retrieve user input -- trap_GetUsercmd().
  2. Mark the time input was received.
  3. If not a bot, do non-bot client think process -- ClientThink_real().

ClientThink_real(clientNum)

  1. Check if client is still in Connecting phase. Return from function if so.
  2. Record time of retrieval of usercmd (user input).
  3. Basic timestamp sanity check to squelch speedup cheats.
  4. Sanity checks on cvar pmove_msec.
  5. Timestamp checks for cvar pmove_fixed.
  6. Check for intermission -- ClientIntermissionThink().
    1. Disable talk balloons and weapons.
    2. Toggle READY status each time attack button is pressed (changes from low to high).
  7. If client is spectator, do spectator thinking and return -- SpectatorThink().
    1. If not following, do almost-normal movements and some triggering.
    2. Start following or cycle to next spectatee(?) if attack button is pressed.
  8. Check for inactivity timer -- ClientInactivityTimer().
    1. If cvar g_inactivity is unset or 0, set inactivity timer to 1 minute, but don't do any actual inactivity kicking. This gives some leeway to all players during the instant when admin decides to actually set inactivity timer during gameplay (otherwise the time since game start counts as inactivity at that instant).
    2. Otherwise check if client is moving or doing some action, and push forward the inactivity timestamp.
    3. Do not kick local player(s).
    4. Otherwise check if current time surpassed inactivity timestamp and kick.
    5. Otherwise see if time to send 10-second warning.
  9. Clear overhead reward icons if time expired.
  10. Check movement style (noclip, dead, normal).
  11. Inherit gravity value.
  12. Set client speed.
  13. Increase client speed due to any powerups (haste, scout).
  14. Prepare for pmove (predictable movement) calculation.
  15. Check for gauntlet hit for the gauntlet-hit animation if attack button is held with gauntlet, store into pm.gauntletHit -- CheckGauntletAttack().
    1. See if any damageable object is within 32 units of player.
    2. Create blood splatters.
    3. Increase damage due to powerups (Quad, Doubler).
    4. Apply damage to victim.
    5. Return true if gauntlet made a hit, otherwise return false.
  16. Check for gesturing and set animation if needed.
  17. Check for invulnerability powerup (Team Arena only).
  18. Make pmove calculations -- Pmove().
  19. Save results of pmove into client structs.
  20. Smooth out if cvar g_smoothClients is non-zero -- BG_PlayerStateToEntityState().
  21. Snap various vectors (round components to nearest integer).
  22. Execute client events -- ClientEvents().
    1. Retrieve next client event.
    2. Check fall damages (EV_FALL_MEDIUM, EV_FALL_FAR):
      1. Check that fall damage is being applied to a player.
      2. Check if fall damage is disabled (cvar g_dmflags & DF_NO_FALLING (8)).
      3. Set damage amount, 10 for far, 5 for medium.
      4. Set pain debounce time (XXX: what??)
      5. Apply damage to player -- G_Damage().
    3. Check weapon firing (EV_FIRE_WEAPON):
      1. Fire weapon -- FireWeapon().
        1. (XXX: Aieee!)
    4. Check using handheld teleporter (EV_USE_ITEM1):
      1. Drop CTF flag(s).
      2. Select a random spawn point -- SelectSpawnPoint().
      3. Teleport the player to the new point -- TeleportPlayer().
        1. Create appropriate teleport effects at old and new points.
        2. Unlink player from game -- trap_UnlinkEntity().
        3. Place player at new spawn point with some altered properties (speed, angle).
        4. Telefrag.
        5. Update from pmove calculations -- BG_PlayerStateToEntityState().
        6. Link player back into game.
    5. Check using medkit (EV_USE_ITEM2):
      1. Sets health to player's maximum health plus 25; the maximum health may vary according to handicap. The medkit is used up (removed) elsewhere.
    6. Check using kamikaze (Team Arena only, EV_USE_ITEM3):
    7. Check using portal(???) (Team Arena only, EV_USE_ITEM4):
    8. Check using invulnerability (Team Arena only, EV_USE_ITEM5):
    9. Ignore other event types.
  23. Link entity into the world and PVS -- trap_LinkEntity().
  24. Trigger any... triggers -- G_TouchTriggers().
    1. Ensure the triggerer(?) is a living player.
    2. Find all entities reaching into player's space (bounding box), store list into array touch[].
    3. For each touched entity:
      1. Ignore if not a trigger.
      2. If spectating, ignore certain triggers.
      3. If an item, ignore (since item-touching is handled elsewhere) -- BG_PlayerTouchesItem()
      4. Otherwise check that the player really is in contact with this entity -- trap_EntityContact().
      5. If this entity has a touch() function, call it -- hit->touch().
      6. If client has a touch() function, call it -- ent->touch().
    4. Check for jumppad.
  25. Some bot AI checks -- BotTestAAS().
    1. (XXX: foo)
  26. Interact with other objects/entities -- ClientImpacts().
    1. For each entity the player is touching (pm->numtouch, pm->touchents[]):
      1. Run the player's touch() function, if it exists.
      2. Run the other entity's touch() function, if it exists.
  27. Save results of triggers and events.
  28. Swap/latch button actions, helps check for button press/release.
  29. If player is dead, check for respawning, based on button press or cvar g_forcerespawn -- respawn().
    1. Create a corpse in body queue -- CopyToBodyQue().
      1. Selects next spot in queue, overwriting as it goes.
      2. Unlink the body queue entry -- trap_UnlinkEntity().
      3. Copy properties of corpse, unset inapplicable properties (such as powerups).
      4. Set to last frame of death animation, to prevent looped animation.
      5. Set movement properties.
      6. Link corpse into game -- trap_LinkEntity().
    2. Spawn the player -- ClientSpawn().
      1. If a spectator, just set a spawn point for spectators -- SelectSpectatorSpawnPoint().
        1. Spawn at intermission location -- FindIntermissionPoint().
          1. Look for info_player_intermission entity.
          2. Set viewing angles, towards a target if specified.
          3. If intermission entity does not exist, the map creator is a moron:
            1. Select a single-player spawn point as if normally playing, to use as intermission entity -- SelectSpawnPoint().
      2. If a team game, choose spawn point for team games -- SelectCTFSpawnPoint().
        1. Choose a team spawn point -- SelectRandomTeamSpawnPoint().
          1. If just joining the team, look for spawn point named team_CTF_redplayer or team_CTF_blueplayer according to team.
          2. If not just joining (as in having been just fragged and respawned), look for spawn point named team_CTF_redspawn or team_CTF_bluespawn.
          3. Look through each spawn point of the appropriate name:
          4. If the spot would cause a telefrag, choose next spawn point. Otherwise record the location in array spots[].
          5. If a telefrag-less spot doesn't exist, just use first spawn point. Otherwise, choose a random spawn point from spots[].
          6. Return the spawn point information.
        2. If there is no team spawn point, choose a single-player (info_player_deathmatch) spawnpoint -- SelectSpawnPoint().
      3. Otherwise choose spawn point single-player style:
        1. If spawned for the first time, choose a good-looking spot -- SelectInitialSpawnPoint().
          1. Look for a spawn point (info_player_deathmatch) with the initial flag set (0x01).
          2. If no such special spawn point exists, or if the initial spawn point would cause telefrag, choose spawn point the normal way with SelectSpawnPoint().
        2. If not spawned for first time, select a spawn point far from death point -- SelectSpawnPoint().
          1. Just calls SelectRandomFurthestSpawnPoint().
            1. Look through all info_player_deathmatch entities:
              1. Check if telefrag would occur, choose another spot if so -- SpotWouldTelefrag().
                1. Check if any players occupy some of the space the would-be-spawned player would occupy.
              2. Record distance of this spawn point from the avoidPoint (i.e. where player's death occurred) into list_dist and list_spot using [reverse] insertion sort.
              3. If no spawn point can avoid telefrag, choose first spawn point (info_player_deathmatch).
              4. Now that list_dist and list_spot are sorted in decreasing order of distances (furthest first), choose a random spot from the first half of the list.
              5. Return selected spawn point location.
        3. Juggle spawn point according to nobot or nohuman flags for spawn points.
        4. Initialize various client flags, fields, and records.
        5. Provide client with gauntlet and machine gun, with some ammo.
        6. Set spawn-time health.
        7. Move client position to spawn point -- G_SetOrigin().
        8. Set client view angle -- SetClientViewAngle().
          1. Sets angle vectors based on spawn point variables (XXX: which one?).
        9. Telefrag as needed -- G_KillBox().
        10. Link client into game -- trap_LinkEntity().
        11. Bring up the base weapon, the machine gun.
        12. Prevent full-tilt running at spawn time.
        13. Reset inactivity timer.
        14. Set initial animation.
        15. If on intermission, do intermission stuff -- MoveClientToIntermission().
          1. If spectator, stop following -- StopFollowing().
            1. Clears spectator-following fields.
          2. Move to intermission location (set by spawn variable info_player_intermission).
          3. Set movement type to PM_INTERMISSION.
          4. Clear powerups, attributes, flags, sounds, events, etc.
        16. Otherwise, trip triggers at spawn point, then select highest player weapon available (which may not be machine gun) -- G_UseTargets().
        17. Drop player to the floor by gravity -- ClientThink(). No loop as needs to respawn has been cleared by now.
    3. Add teleportation effect at spawn point -- G_TempEntity().
    4. Link client into game (again, just to be sure).
    5. Run end-of-frame stuff -- ClientEndFrame.
    6. Entity state stuff -- BG_PlayerStateToEntityState().
  30. Run once-per-second client actions -- ClientTimerActions().
    1. Check if one-second period has passed.
    2. Increase health due to regeneration powerup, rate is influenced by any excessive health.
    3. Tick down excessive health (health greater than maxHealth) if no regeneration powerup.
    4. Tick down excessive armor (armor greater than maxArmor).
    5. Tick up ammo due to ammo-regen powerup (Team Arena only).

FireWeapon(*entity)

  1. Check for quad damage powerup.
  2. Accumulate accuracy statistics.
  3. Determine firing direction.
  4. Fire according to weapon in use:

TODO: Pmove()

vmMain(GAME_CLIENT_USERINFO_CHANGED, clientNum)

When a client user's info has changed (name, player model, handicap, etc.), the Q3 server is notified and reacts with a call to vmMain() with major function GAME_CLIENT_USERINFO_CHANGED, with one argument:

  1. clientNum - the client number of the client that changed its info.

Immediate branch to ClientUserinfoChanged().

ClientUserinfoChanged(clientNum)

  1. Note client number and client object/entity.
  2. Retrieve client userinfo -- trap_GetUserinfo().
  3. Check for invalid string -- Info_Validate().
  4. Check if local client (key ip) -- Info_ValueForKey().
  5. Check item prediction (key cg_predictItems).
  6. Set name (key name).
  7. Announce if in-game name change.
  8. Set max health according to handicap (key handicap).
  9. Set player model (keys model and hmodel, or team_model and team_hmodel).
  10. If a bot, join team a short time later.
  11. Otherwise just set team (or spec).
  12. Set model skin according to team.
  13. Determine if client wants teamoverlay info (key teamoverlay).
  14. Set team task (key teamtask) and leader.
  15. Set colors (keys color, g_redteam, g_blueteam).
  16. Construct a ConfigString from a subset of userinfo, to broadcast to all clients.

vmMain(GAME_CLIENT_DISCONNECT, clientNum)

When client disconnects (voluntary disconnect, kicked, dropped, etc.). Arguments:

  1. clientNum - the client number of the client just disconnected.

Immediate branch to ClientDisconnect().

ClientDisconnect(clientNum)

  1. Check if kicking a still-unspawned bot -- G_RemoveQueuedBotBegin().
  2. If any spectator is following this client, stop their following -- StopFollowing().
  3. Create teleport-out effect.
  4. Drop any powerups client had -- TossClientItems().
  5. If a tournament game, award remaining player with a win.
  6. Unlink the client -- trap_UnlinkEntity().
  7. Mark ex-client as being disconnected and mark its slot as unused.
  8. Clear associated ConfigString.
  9. Recalculate ranks -- CalculateRanks().
  10. If a bot AI, shutdown AI -- BotAIShutdownClient().

vmMain(GAME_CLIENT_BEGIN, clientNum)

When client is ready to play. This occurs when client has finished connecting and loading, or has switched teams, or has continued in from the previous map. Does not occur on respawns (why should the Q3 engine know about respawns+teleport? The game engine does, though). Arguments:

  1. clientNum - client number.

Immediate branch to ClientBegin().

ClientBegin(clientNum)

  1. Note client number and client entity/record.
  2. Unlink client entity -- trap_UnlinkEntity().
  3. Initialize client game entity -- G_InitGentity().
    1. Clear and initialize some critical game entity fields.
  4. Initialize client fields.
  5. Select a spawn point -- ClientSpawn().
  6. Announce the player joining the game, if not tournament mode.
  7. Recalculate ranks -- CalculateRanks().

vmMain(GAME_CLIENT_COMMAND, clientNum)

When client sends a command to the server. This usually occurs when the client-end doesn't recognize a console command from the user, and so passes off the command to the server in hopes the server understands it. Arguments:

  1. clientNum - client number

Immediate branch to ClientCommand().

ClientCommand(clientNum)

  1. Check client validity.
  2. Retrieve command name from string -- trap_Argv().
  3. Check against known command names:
  4. Unrecognized commands return an error message to the originating client.

vmMain(GAME_RUN_FRAME, levelTime)

Called by Q3 each server frame (period determined by cvar sv_fps, default value of 20 fps (or 50 ms/frame)) to advance all non-player objects. Arguments:

  1. levelTime - the elapsed time on the level as reported by Q3 engine (this is where level.time comes from!).

Immediate branch to G_RunFrame().

G_RunFrame(levelTime)

  1. Check if waiting for level to restart.
  2. Remember previous levelTime.
  3. Store current levelTime.
  4. Calculate the time difference.
  5. Update all cvar changes -- G_UpdateCvars().
  6. Record the current timestamp (not levelTime) -- trap_Milliseconds().
  7. For all allocated game entities:
    1. Ignore unused entity.
    2. Clear out old events.
    3. Free entity slot if no longer needed -- G_FreeEntity().
    4. Unlink entity if needed -- trap_UnlinkEntity().
    5. Ignore temporary entity (they don't think).
    6. Ignore unlinked entity.
    7. If a missile type, run missile think -- G_RunMissile().
      1. Find missile's current position -- BG_EvaluateTrajectory().
      2. Do checks for what the missile will or will not pass through.
      3. See if missile passed through anything noteworthy -- trap_Trace().
      4. Check if missile is stuck in anything, otherwise just keep moving forward.
      5. Link missile into the game (XXX: when was it unlinked?) -- trap_LinkEntity().
      6. If missile collided with something:
        1. Just free up the entity if hit a SURF_NOIMPACT surface (such as sky or a black hole).
        2. Otherwise do missile impact stuff -- G_MissileImpact().
          1. Check for bouncing -- G_BounceMissile()
            1. (vector math for ricochet/bounce effect)
          2. Deal impact damage, if applicable.
          3. Stick proximity mine to victim (Team Arena only)
          4. Latch grappling hook and start pulling, if applicable.
          5. Create any explosion effect -- G_AddEvent().
          6. Snap vectors.
          7. Deal any splash damage -- G_RadiusDamage().
            1. Sanity check on damage radius, create a damage bounding box.
            2. Find entities within damage bounding box and save to array entityList[] -- trap_EntitiesInBox().
            3. For all the entities in entityList[]:
              1. Ignore if entity marked for ignore.
              2. Ignore if entity cannot be damaged.
              3. See if entity is actually within damage radius.
              4. Get distance from explosion center to entity center.
              5. Calculate damage based on this distance.
              6. Deal damage and knockback, while recording accuracy stats.
          8. Link missile into the world (XXX: again?) -- trap_LinkEntity().
      7. If missile hasn't exploded yet, run its think function -- G_RunThink().
    8. If an item type that's bouncing, run item think -- G_RunItem().
      1. Check if item is in free fall.
      2. Check if item has stationary trajectory, if so then just run G_RunThink() and return.
      3. Get current position of item.
      4. See if it hit/landed on anything.
      5. Run think function -- G_RunThink().
      6. If in a nodrop brush, remove entity from game.
      7. Cause item to bounce -- G_BounceItem().
        1. (lots of vector math)
    9. If a mover, run mover think -- G_RunMover().
      1. Check if a team slave, return if so (team captain handles all activities).
      2. If not stationary, run mover -- G_MoverTeam().
        1. Make sure all team members (other movers in a chain) are able to move -- G_MoverPush().
          1. Establish boundaries of mover and entire movement area covered.
          2. Unlink mover temporarily -- trap_UnlinkEntity().
          3. Find entities within mover's movement area and store into list listedEntities[] -- trap_EntitiesInBox().
          4. Move pusher to final position.
          5. Link mover back into the game -- trap_LinkEntity().
          6. For each entity in listedEntities[]:
            1. Try pushing proxmine (Team Arena only).
            2. Ignore entity if not an item nor a player.
            3. Move entities standing on mover, and ensure it doesn't try to occupy the same space as the mover -- G_TestEntityPosition().
            4. Push entity -- G_TryPushingEntity().
            5. Any entites that bob (such as items) get killed if stuck inside a mover position (such as between doors), move on to next pushed object.
            6. Otherwise move entities back, since this is an obstacle.
        2. Restore old position(s) if any mover member can't move.
        3. Run blocked() function for any mover member that couldn't move -- part->blocked().
        4. Otherwise calculate and set new positions.
        5. If a mover member reachd one end of its movement, call its reached() function -- part->reached();.
      3. Run entity-specific think function -- G_RunThunk().
    10. If a client, run client think -- G_RunClient().
      1. Check if a bot AI, return if so.
      2. Record levelTime
      3. Call ClientThink_real().
    11. Otherwise, just run think -- G_RunThink().
      1. Check if there is any thinktime.
      2. Check if levelTime exceeded thinktime, return if not.
      3. Disable nextthink and run entity's think -- ent->think().
  8. Record current timestamp -- trap_Milliseconds().
  9. (Time elapsed during run cycle can be calculated (but isn't)).
  10. Do final fixups on players -- ClientEndFrame().
  11. Check for tournament restart -- CheckTournament().
  12. Check for end of level -- CheckExitRules().
    1. If still intermission time, check that all non-bot clients are READY to leave intermission, return if not ready to exit level/map -- CheckIntermissionExit().
    2. Check for queued intermission (???)
      1. Ignore if queue time not yet exceeded.
      2. Unqueue intermission.
      3. Begin intermission -- BeginIntermission().
        1. Return if intermission already in progress.
        2. For tournament mode, adjust tournament scores -- AdjustTournamentScores().
          1. Update number of wins or losses for the two competitors.
        3. Record the current levelTime as the intermission time.
        4. Find the intermission location -- FindIntermissionPoint().
        5. If single-player game:
          1. Update tournament information -- UpdateTournamentInfo()
            1. Take note of (the only) player's record/slot/entity.
            2. Calculate ranks -- CalculateRanks().
            3. Record postgame data?
            4. Calculate accuracy percentage.
            5. Check for Perfect reward.
            6. Record rewards information.
          2. show the victory podium -- SpawnModelsOnVictoryPads()
            1. Spawn the podium -- SpawnPodium().
              1. Create podium entity.
              2. Place podium in front of intermission view.
            2. Place the top three players in their appropriate places -- SpawnModelOnVictoryPad().
              1. Copy entity properties of the player into a new game entity.
              2. Set entity location relative to podium and intermission viewpoint.
        6. Move all clients to intermission point -- MoveClientToIntermission()
        7. Send current scores to all clients -- SendScoreboardMessageToAllClients().
          1. For each client:
            1. Send scoreboard info -- DeathmatchScoreboardMessage()
              1. For all clients:
                1. Construct string based on client number, frags, ping time, play time, score flags, powerups, rewards, and captures.
              2. Send this huge-ass string, along with blue team score and red team score, to client -- SendServerCommand().
  13. Update team status -- CheckTeamStatus().
    1. Check if time to do team location update.
    2. For all clients:
      1. Ignore clients still connecting.
      2. Determine team.
      3. Store team location information into client record.
    3. For all clients (again):
      1. Ignore clients still connecting.
      2. Determine team.
      3. Transmit team location information to client -- TeamplayInfoMessage().
        1. Return if client has no (doesn't want?) teaminfo.
        2. Extract top eight teammates on player's team.
        3. Sort the eight teammates by client number.
        4. For all clients or the first 32 (TEAM_MAXOVERLAY) (whichever is less):
          1. Ignore if this client is not in use, or is not on the same team.
          2. Store client's health and armor.
          3. Construct a string based on the client's list location (cycle in this particular loop), location, health, armor, current weapon, and active powerup.
          4. Truncate constructed string if too long (8192 bytes).
        5. Transmit the constructed string to the client -- trap_SendServerCommand().
  14. Check global votes -- CheckVote().
    1. If vote string is to be executed and it's time to execute, execute the string as a command -- trap_SendConsoleCommand().
    2. Return if no vote in progress.
    3. Check if vote time expired, report vote having failed if so.
    4. Otherwise check for votes:
      1. If Yes has simple majority, report success and execute vote string within 3 seconds.
      2. If No has simple majority, report failure.
      3. Otherwise keep waiting for a majority one way or another.
    5. Update ConfigString CS_VOTE_TIME accordingly.
  15. Check team votes -- CheckTeamVote().
    1. Determine which team to check (blue or red).
    2. Return if no vote is in progress.
    3. If voting time limit is exceeded, report failure to... everyone(!).
    4. Otherwise:
      1. Check if Yes votes has simple majority.
        1. If the vote was to change leader, set new team leader -- SetLeader().
        2. Otherwise execute vote string as a command -- trap_SendConsoleCommand().
      2. Check if No votes has simple majority; cause vote to fail if so.
      3. Otherwise keep waiting for a majority vote.
    5. Set CS_TEAMVOTE_TIME (+1 for blue) accordingly.
  16. Check on changes in cvars -- CheckCvars().
    1. Do nothing (return) if cvar g_password has not changed.
    2. If cvar g_password is set, and is not none, set g_needpass to 1.
    3. Otherwise, set cvar g_needpass to 0.
  17. Dump entities list if cvar g_listEntity is non-zero.

vmMain(GAME_CONSOLE_COMMAND)

When a console command is entered directly to the server from the server console (admin commands). No arguments. Immediate branch to ConsoleCommand().

ConsoleCommand()

  1. Retrieve command name -- trap_Argv().
  2. Check command:
    1. entitylist
    2. forceteam
    3. game_memory
    4. addbot
    5. botlist
    6. abort_podium
    7. addip
    8. removeip
    9. listip
  3. If command is not found and server is running in dedicated mode (cvar dedicated is 1 or 2), repeat the command line as global chat text from console.

Created 2002.08.26
Updated 2002.09.16 - re-wordings suggested by members of Quake 3 World Modifications Programming Forum (MPF).
Updated 2003.11.10 - corrections sent in by cyrri.
Updated 2011.07.11 - change of contact e-mail.

PhaethonH < PhaethonH@gmail.com >