-
Notifications
You must be signed in to change notification settings - Fork 2
Shared Game Module
The Shared Game Module contains code and data structures used by both client (clgame) and server (svgame) game modules.
Location: src/baseq2rtxp/sharedgame/
Output: Static library linked into both cgame.dll and game.dll
The sharedgame module provides:
- Player Movement Code: Identical physics on client and server for prediction
- Entity Definitions: Types, flags, events shared between modules
- Game Mode Definitions: Game mode constants and rules
- Animation System: Skeletal animation (SKM) support
- Constants and Enums: Shared data types to ensure consistency
The most important reason is client-side prediction:
// CLIENT SIDE (clgame)
void CLG_PredictMovement() {
// Must use EXACT SAME code as server
Pmove(&pm); // From sharedgame/pmove/
}
// SERVER SIDE (svgame)
void SVG_ClientThink() {
// Same function, same results
Pmove(&pm); // From sharedgame/pmove/
}If client and server used different movement code, prediction would be wrong and constantly corrected.
Without shared code:
// BAD: Duplicated in clgame and svgame
#define EF_ROTATE BIT(1) // In clgame
#define EF_ROTATE BIT(1) // In svgame - must stay in sync!With shared code:
// GOOD: Defined once in sharedgame
#define EF_ROTATE BIT(1) // Used by bothEnsures client and server interpret data identically:
// Shared enum ensures client and server agree
typedef enum {
ET_GENERAL,
ET_PLAYER,
ET_MONSTER
} entity_type_t;sharedgame/
├── pmove/ # Player movement code
│ ├── sg_pmove.cpp # Core movement simulation
│ ├── sg_pmove_air.cpp # Air movement
│ ├── sg_pmove_ground.cpp # Ground movement
│ ├── sg_pmove_water.cpp # Water movement
│ └── sg_pmove.h # Movement interface
├── game_bindings/ # Lua bindings
│ └── sg_lua_bindings.cpp
├── sg_entity_types.h # Entity type constants
├── sg_entity_flags.h # Entity flag constants
├── sg_entity_events.h # Entity event constants
├── sg_tempentity_events.h # Temp entity event constants
├── sg_means_of_death.h # Damage type constants
├── sg_muzzleflashes.h # Muzzle flash constants
├── sg_gamemode.cpp/.h # Game mode system
├── sg_skm.cpp/.h # Skeletal animation
├── sg_skm_rootmotion.cpp/.h # Root motion system
├── sg_usetarget_hints.cpp/.h # UseTarget hint system
├── sg_time.h # Game time utilities
├── sg_shared.h # Shared definitions
└── ...
The most critical shared code - player physics simulation.
Problem Without Shared Movement:
- Client predicts player will move to position A
- Server calculates player moves to position B (slightly different)
- Client must snap to B (jarring/stuttering movement)
Solution:
- Client and server use identical movement code
- Prediction matches server almost perfectly
- Only network latency or packet loss causes corrections
// In sharedgame/pmove/sg_pmove.h
typedef struct {
// Input
pmove_state_t s; // Current state
usercmd_t cmd; // Player input
// Collision functions (provided by caller)
cm_trace_t (*trace)(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end);
int (*pointcontents)(vec3_t point);
// Output
int numtouch; // Number of touched entities
struct edict_s *touchents[MAXTOUCH]; // Entities touched
vec3_t viewangles; // View direction
float viewheight; // Eye height
vec3_t mins, maxs; // Bounding box
int groundentity; // Entity standing on (-1 if none)
int watertype; // Water type (if in water)
int waterlevel; // 0=not in water, 1=feet, 2=waist, 3=head
// Flags
int flags; // PMF_* flags
} pmove_t;
// Main entry point
void Pmove(pmove_t *pm);// clgame/clg_predict.cpp
void CLG_PredictMovement() {
pmove_t pm = {};
// Set up pmove
pm.s = cl.predicted_player.pmove;
pm.cmd = cl.cmd; // Latest input
// Provide collision functions
pm.trace = CL_PMTrace;
pm.pointcontents = CL_PMPointContents;
// Run movement
Pmove(&pm); // From sharedgame
// Use results for rendering
cl.predicted_origin = pm.s.origin;
cl.predicted_velocity = pm.s.velocity;
}// svgame/svg_clients.cpp
void SVG_ClientThink(svg_base_edict_t *ent, usercmd_t *cmd) {
pmove_t pm = {};
// Set up pmove
pm.s = ent->client->ps.pmove;
pm.cmd = *cmd;
// Provide collision functions
pm.trace = SVG_PMTrace;
pm.pointcontents = gi.pointcontents;
// Run movement (SAME CODE as client)
Pmove(&pm); // From sharedgame
// Apply results
ent->client->ps.pmove = pm.s;
ent->s.origin = pm.s.origin;
ent->velocity = pm.s.velocity;
}Key Point: Both client and server call Pmove() with same inputs and get same outputs.
// Simplified pmove structure
void Pmove(pmove_t *pm) {
// 1. Categorize position (ground, air, water)
PM_CategorizePosition(pm);
// 2. Check for jump
if (pm->cmd.upmove && pm->groundentity != -1) {
pm->s.velocity[2] = 270; // Jump velocity
pm->groundentity = -1;
}
// 3. Apply friction
if (pm->groundentity != -1) {
PM_Friction(pm);
}
// 4. Apply acceleration (movement input)
PM_Accelerate(pm);
// 5. Apply gravity
if (pm->groundentity == -1) {
pm->s.velocity[2] -= pm->s.gravity * FRAMETIME;
}
// 6. Move and slide against walls
PM_StepSlideMove(pm);
// 7. Update view height (crouching, stairs)
PM_UpdateViewHeight(pm);
// 8. Detect ground entity
PM_GroundTrace(pm);
}File: src/baseq2rtxp/sharedgame/sg_entity_types.h
typedef enum sg_entity_type_e {
ET_PLAYER = ET_GAME_TYPES,
ET_PLAYER_CORPSE,
ET_MONSTER,
ET_MONSTER_CORPSE,
ET_PUSHER,
ET_ITEM,
ET_GIB,
ET_TARGET_SPEAKER,
ET_PUSH_TRIGGER,
ET_TELEPORT_TRIGGER,
ET_TEMP_ENTITY_EVENT,
ET_MAX_SHAREDGAME_TYPES
} sg_entity_type_t;Usage:
- Server sets:
ent->s.entityType = ET_MONSTER; - Client reads:
if (cent->current.entityType == ET_MONSTER)
See: Entity Types API Reference
File: src/baseq2rtxp/sharedgame/sg_entity_flags.h
#define EF_ROTATE BIT(1) // Rotate (bonus items)
#define EF_GIB BIT(2) // Leave a trail
#define EF_ANIM01 BIT(4) // Cycle frames 0-1 at 2 Hz
#define EF_QUAD BIT(9) // Quad damage glow
#define EF_PENT BIT(10) // Pentagram of protection
// ... etcSee: Entity Flags API Reference
File: src/baseq2rtxp/sharedgame/sg_entity_events.h
typedef enum sg_entity_events_e {
EV_PLAYER_FOOTSTEP = EV_ENGINE_MAX,
EV_OTHER_FOOTSTEP,
EV_FOOTSTEP_LADDER,
EV_WATER_ENTER_FEET,
EV_JUMP_UP,
EV_FALL_SHORT,
EV_WEAPON_PRIMARY_FIRE,
EV_PLAYER_TELEPORT,
EV_ITEM_RESPAWN,
// ... etc
EV_GAME_MAX
} sg_entity_events_t;See: Entity Events API Reference
File: src/baseq2rtxp/sharedgame/sg_tempentity_events.h
typedef enum temp_entity_event_e {
TE_GUNSHOT,
TE_BLOOD,
TE_MOREBLOOD,
TE_SPLASH,
TE_SPARKS,
TE_BULLET_SPARKS,
TE_PLAIN_EXPLOSION,
TE_TELEPORT_EFFECT,
// ... etc
TE_NUM_ENTITY_EVENTS
} temp_entity_event_t;File: src/baseq2rtxp/sharedgame/sg_gamemode.cpp/.h
Game mode definitions shared between modules:
enum class GameMode {
SinglePlayer,
Cooperative,
Deathmatch,
TeamDeathmatch,
CaptureTheFlag
};
// Get current game mode
GameMode SG_GetGameMode();
// Check game mode
bool SG_IsSinglePlayer();
bool SG_IsCooperative();
bool SG_IsDeathmatch();Usage:
Server:
// svgame
if (SG_IsDeathmatch()) {
// Deathmatch-specific rules
RespawnPlayer(player);
}Client:
// clgame
if (SG_IsCooperative()) {
// Show team HUD
DrawTeamStatus();
}Files: src/baseq2rtxp/sharedgame/sg_skm.cpp/.h
Shared skeletal animation support for IQM models:
// Skeletal model structure
typedef struct {
int num_bones;
int num_frames;
skm_bone_t *bones;
skm_frame_t *frames;
} skm_model_t;
// Animation blending
void SG_SKM_BlendAnimations(skm_model_t *model,
int anim1, float weight1,
int anim2, float weight2,
skm_pose_t *out_pose);
// Root motion extraction
void SG_SKM_ExtractRootMotion(skm_model_t *model,
int frame_from, int frame_to,
vec3_t *delta_pos, vec3_t *delta_rot);Usage:
Both client and server can query animation data:
// Get animation length
float duration = SG_SKM_GetAnimationDuration(model, anim_run);
// Blend walk and run animations
SG_SKM_BlendAnimations(model,
anim_walk, 1.0f - runSpeed,
anim_run, runSpeed,
&pose);See: Skeletal Animation, Root Motion
Files: src/baseq2rtxp/sharedgame/sg_skm_rootmotion.cpp/.h
Extracts movement from animations:
// Extract root motion between frames
void SG_ExtractRootMotion(skm_model_t *model,
int frame_a, int frame_b,
vec3_t *delta_position,
quat_t *delta_rotation);
// Apply root motion to entity
void SG_ApplyRootMotion(svg_base_edict_t *ent,
vec3_t delta_position,
quat_t delta_rotation);Benefit: Characters move exactly as animated, not floating or sliding.
Files: src/baseq2rtxp/sharedgame/sg_usetarget_hints.cpp/.h
Defines interaction hints shown to player:
enum class UseTargetHint {
NONE, // Not usable
PRESSABLE, // "Press E"
TOGGLEABLE, // "Toggle"
HOLDABLE, // "Hold E"
PICKUP // "Pick up"
};
// Get hint string
const char *SG_GetUseTargetHintString(UseTargetHint hint);Server: Entities specify their hint:
UseTargetHint svg_func_button_t::GetUseTargetHint() {
return UseTargetHint::PRESSABLE;
}Client: Displays appropriate message:
UseTargetHint hint = GetTargetedEntityHint();
DrawString(screenWidth/2, screenHeight/2 + 20,
SG_GetUseTargetHintString(hint));File: src/baseq2rtxp/sharedgame/sg_means_of_death.h
Damage type constants:
typedef enum means_of_death_e {
MOD_UNKNOWN,
MOD_BLASTER,
MOD_SHOTGUN,
MOD_MACHINEGUN,
MOD_ROCKET,
MOD_GRENADE,
MOD_GRENADE_SPLASH,
MOD_FALLING,
MOD_SUICIDE,
MOD_TELEFRAG,
MOD_WATER,
MOD_SLIME,
MOD_LAVA,
MOD_CRUSH,
MOD_TRIGGER_HURT,
// ... etc
} means_of_death_t;Usage:
Server:
SVG_Damage(target, inflictor, attacker, /*...*/, MOD_ROCKET);Client (death messages):
const char *GetDeathMessage(means_of_death_t mod) {
switch (mod) {
case MOD_ROCKET:
return "was blown up";
case MOD_FALLING:
return "fell to their death";
// ... etc
}
}File: src/baseq2rtxp/sharedgame/sg_time.h
Shared game time type:
class gametime_t {
public:
static gametime_t from_ms(int64_t ms);
static gametime_t from_sec(float sec);
static gametime_t zero();
int64_t count() const; // Milliseconds
float seconds() const; // Seconds
// Comparison operators
bool operator<(gametime_t other) const;
bool operator<=(gametime_t other) const;
// ... etc
};Benefits:
- Type-safe time representation
- Prevents mixing milliseconds and seconds
- Consistent between client and server
✅ Add to sharedgame when:
- Both client and server need the code
- Code must be identical on both sides (e.g., physics)
- Constants/enums need to match exactly
❌ Don't add to sharedgame when:
- Only server needs it (AI, combat logic)
- Only client needs it (HUD, particles)
- Behavior can differ between sides
Sometimes shared code needs slight differences:
// In sharedgame
#ifdef SERVER
// Server-specific variant
void SomeFunction() {
// Use server-specific gi.dprintf
gi.dprintf("Server version\n");
}
#else
// Client-specific variant
void SomeFunction() {
// Use client-specific Com_Printf
Com_Printf("Client version\n");
}
#endifUse sparingly - defeats purpose of shared code!
Ensure consistency:
// Test that client and server get same result
#ifdef _DEBUG
void TestPmove() {
pmove_t pm_client = {...};
pmove_t pm_server = {...};
// Run on both
Pmove(&pm_client);
Pmove(&pm_server);
// Results must match
assert(VectorCompare(pm_client.s.origin, pm_server.s.origin));
assert(VectorCompare(pm_client.s.velocity, pm_server.s.velocity));
}
#endifSharedgame code is compiled into both client and server DLLs:
- Increases total binary size
- Worth it for consistency and reduced bugs
Small shared functions should be inline:
// In sg_time.h
inline gametime_t gametime_t::from_sec(float sec) {
return gametime_t(static_cast<int64_t>(sec * 1000));
}Avoids function call overhead in performance-critical code.
- Server Game Module - Server-side game logic
- Client Game Module - Client-side rendering
- Entity Types API - Entity type constants
- Entity Flags API - Entity flag constants
- Entity Events API - Entity event constants
Key Takeaway: Sharedgame ensures client and server speak the same language, critical for prediction, networking, and consistency.