From 48f2ca8a4e048163be4b23a3df48bb329ce377a8 Mon Sep 17 00:00:00 2001 From: WofWca Date: Tue, 18 Nov 2025 12:34:11 +0400 Subject: [PATCH 01/30] fix: corpses don't absorb shotgun pellets This is a follow-up to ad81db1b27e669c7c59aca8b1c22e6c552d1a2ac ("fix: shotgun not gibbing unless aiming at feet") (https://github.com/ec-/baseq3a/pull/49). This brings the game balance closer to the original game. For discussions, see https://github.com/OpenArena/gamecode/pull/349. --- code/game/g_combat.c | 9 ++++----- code/game/g_weapon.c | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/code/game/g_combat.c b/code/game/g_combat.c index 5a88e713..5008feb6 100644 --- a/code/game/g_combat.c +++ b/code/game/g_combat.c @@ -589,11 +589,10 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int // unless you aim at the feet. // See https://github.com/ioquake/ioq3/issues/794. // - // Note that without this line, when shooting at two players standing - // behind each other, the second target will take less damage, - // because the dead body of the first player will absorb the pellets - // until it gets gibbed (that is, up to 4 pellets, - // see `GIB_HEALTH` and `DEFAULT_SHOTGUN_DAMAGE`). + // Not executing this line makes is so that the corpse + // doesn't get shorter immediately on death + // and instead can still take up other pellets + // from the same shotgun shot. // // The purpose and the effect of this line is not entirely clear. // Maybe it's to transition the player hitbox diff --git a/code/game/g_weapon.c b/code/game/g_weapon.c index 8bd49853..6b40cb3e 100644 --- a/code/game/g_weapon.c +++ b/code/game/g_weapon.c @@ -317,6 +317,27 @@ static qboolean ShotgunPellet( const vec3_t start, const vec3_t end, gentity_t * continue; } #else + + // The below piece of code has been added in + // https://github.com/ec-/baseq3a/pull/60. + // When shooting through a corpse, gib it, + // but don't "absorb" the pellet, i.e. allow to hit a player + // through a corpse. + // This is mostly to compensate for the balance changes + // that are introduced by the removal of the `self->r.maxs[2] = -8;` + // line in `player_die`. + // But it's probably also sensible otherwise that corpses + // affect "more serious" gameplay less. + // See + // - https://github.com/ioquake/ioq3/issues/794 + // - https://github.com/OpenArena/gamecode/pull/349 + if ( traceEnt->client && traceEnt->client->ps.pm_type == PM_DEAD ) { + G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_SHOTGUN ); + passent = traceEnt->s.number; + VectorCopy( tr.endpos, tr_start ); + continue; + } + if ( LogAccuracyHit( traceEnt, ent ) ) { hitClient = qtrue; } From 1718413d284ffdc73a192b8d35e58f260225779e Mon Sep 17 00:00:00 2001 From: WofWca Date: Thu, 6 Nov 2025 12:15:05 +0400 Subject: [PATCH 02/30] refactor: name some magic numbers in `bg_pmove` --- code/game/bg_pmove.c | 18 +++++++++--------- code/game/bg_public.h | 3 +++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/code/game/bg_pmove.c b/code/game/bg_pmove.c index df260874..3ec0c000 100644 --- a/code/game/bg_pmove.c +++ b/code/game/bg_pmove.c @@ -1249,8 +1249,8 @@ static void PM_CheckDuck (void) VectorSet( pm->maxs, 42, 42, 42 ); } else { - VectorSet( pm->mins, -15, -15, MINS_Z ); - VectorSet( pm->maxs, 15, 15, 16 ); + VectorSet( pm->mins, -PLAYER_WIDTH, -PLAYER_WIDTH, MINS_Z ); + VectorSet( pm->maxs, PLAYER_WIDTH, PLAYER_WIDTH, CROUCH_MAXS_Z ); } pm->ps->pm_flags |= PMF_DUCKED; pm->ps->viewheight = CROUCH_VIEWHEIGHT; @@ -1258,11 +1258,11 @@ static void PM_CheckDuck (void) } pm->ps->pm_flags &= ~PMF_INVULEXPAND; - pm->mins[0] = -15; - pm->mins[1] = -15; + pm->mins[0] = -PLAYER_WIDTH; + pm->mins[1] = -PLAYER_WIDTH; - pm->maxs[0] = 15; - pm->maxs[1] = 15; + pm->maxs[0] = PLAYER_WIDTH; + pm->maxs[1] = PLAYER_WIDTH; pm->mins[2] = MINS_Z; @@ -1282,7 +1282,7 @@ static void PM_CheckDuck (void) if (pm->ps->pm_flags & PMF_DUCKED) { // try to stand up - pm->maxs[2] = 32; + pm->maxs[2] = MAXS_Z; pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); if (!trace.allsolid) pm->ps->pm_flags &= ~PMF_DUCKED; @@ -1291,12 +1291,12 @@ static void PM_CheckDuck (void) if (pm->ps->pm_flags & PMF_DUCKED) { - pm->maxs[2] = 16; + pm->maxs[2] = CROUCH_MAXS_Z; pm->ps->viewheight = CROUCH_VIEWHEIGHT; } else { - pm->maxs[2] = 32; + pm->maxs[2] = MAXS_Z; pm->ps->viewheight = DEFAULT_VIEWHEIGHT; } } diff --git a/code/game/bg_public.h b/code/game/bg_public.h index 0878102e..3a521892 100644 --- a/code/game/bg_public.h +++ b/code/game/bg_public.h @@ -31,8 +31,11 @@ #define DROPPED_TIME 30000 // 30 seconds before removing dropped items +#define PLAYER_WIDTH 15 #define MINS_Z -24 +#define MAXS_Z 32 #define DEFAULT_VIEWHEIGHT 26 +#define CROUCH_MAXS_Z 16 #define CROUCH_VIEWHEIGHT 12 #define DEAD_VIEWHEIGHT -16 From 01434e90f6ad890a756b1f435d0c0d66e0d6a8ce Mon Sep 17 00:00:00 2001 From: WofWca Date: Thu, 6 Nov 2025 12:25:12 +0400 Subject: [PATCH 03/30] refactor: `#define DEAD_MAXS_Z -8` From the research conducted in https://github.com/ioquake/ioq3/issues/794 we can probably conclude that the `self->r.maxs[2] = -8;` in `player_die()` is related to `pm->maxs[2] = -8;` in `PM_CheckDuck()`. Let's make this clear with a named constant. --- code/game/bg_pmove.c | 2 +- code/game/bg_public.h | 1 + code/game/g_combat.c | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/code/game/bg_pmove.c b/code/game/bg_pmove.c index 3ec0c000..b2225b4e 100644 --- a/code/game/bg_pmove.c +++ b/code/game/bg_pmove.c @@ -1268,7 +1268,7 @@ static void PM_CheckDuck (void) if (pm->ps->pm_type == PM_DEAD) { - pm->maxs[2] = -8; + pm->maxs[2] = DEAD_MAXS_Z; pm->ps->viewheight = DEAD_VIEWHEIGHT; return; } diff --git a/code/game/bg_public.h b/code/game/bg_public.h index 3a521892..7b83f42f 100644 --- a/code/game/bg_public.h +++ b/code/game/bg_public.h @@ -37,6 +37,7 @@ #define DEFAULT_VIEWHEIGHT 26 #define CROUCH_MAXS_Z 16 #define CROUCH_VIEWHEIGHT 12 +#define DEAD_MAXS_Z -8 #define DEAD_VIEWHEIGHT -16 #define PM_STEP_HEIGHT 18 diff --git a/code/game/g_combat.c b/code/game/g_combat.c index 5008feb6..ff4837d7 100644 --- a/code/game/g_combat.c +++ b/code/game/g_combat.c @@ -600,7 +600,7 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int // But this is already handled in `PM_CheckDuck`, // so maybe it's just leftover code. // - // self->r.maxs[2] = -8; + // self->r.maxs[2] = DEAD_MAXS_Z; // don't allow respawn until the death anim is done // g_forcerespawn may force spawning at some later time From a31e498555fd208c1064817430e537430f0b536f Mon Sep 17 00:00:00 2001 From: WofWca Date: Wed, 18 Feb 2026 17:24:14 +0400 Subject: [PATCH 04/30] fix: `DEAD_MAXS_Z` not getting set immediately Resulting in things like players absorbing more lightning gun shots on death than they should have at low `sv_fps 1`. Closes https://github.com/WofWca/quake3-better-gibs-mod/issues/12. Bug reproduction steps: 1. `\devmap q3dm1; addbot crash; set bot_pause 1;`, `give quad damage; give speed`. 2. `\set sv_fps 1`. 3. `\set timescale 8` for better reproduce-ability. 4. Aim at the Crash's torso (not the feet). 5. Shoot. Sometimes Crash would get gibbed. Which is not the case in the original game. The bug has been introduced recently, in "fix: shotgun not gibbing unless aiming at feet". --- code/game/g_combat.c | 26 ++++++++------------------ code/game/g_local.h | 13 +++++++++++++ code/game/g_weapon.c | 16 +++++++++++++++- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/code/game/g_combat.c b/code/game/g_combat.c index ff4837d7..22a21b0b 100644 --- a/code/game/g_combat.c +++ b/code/game/g_combat.c @@ -583,24 +583,14 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int self->s.loopSound = 0; - // The below line has been commented out in - // https://github.com/ec-/baseq3a/pull/49. - // Executing this line causes a bug where the shotgun doesn't gib - // unless you aim at the feet. - // See https://github.com/ioquake/ioq3/issues/794. - // - // Not executing this line makes is so that the corpse - // doesn't get shorter immediately on death - // and instead can still take up other pellets - // from the same shotgun shot. - // - // The purpose and the effect of this line is not entirely clear. - // Maybe it's to transition the player hitbox - // into the "lying down dead" state, make it shorter. - // But this is already handled in `PM_CheckDuck`, - // so maybe it's just leftover code. - // - // self->r.maxs[2] = DEAD_MAXS_Z; + // `SetDeadHeight` makes the corpse shorter. + // Executing this line unconditionally would cause a bug + // where the shotgun doesn't gib unless you aim at the feet, + // because, since the body is shorter, it would stop getting hit + // by other pellets from the same shot. + if ( !ShouldPostponeDeath( meansOfDeath ) ) { + SetDeadHeight( self ); + } // don't allow respawn until the death anim is done // g_forcerespawn may force spawning at some later time diff --git a/code/game/g_local.h b/code/game/g_local.h index 7c8b9a4f..6bb51eb4 100644 --- a/code/game/g_local.h +++ b/code/game/g_local.h @@ -527,6 +527,19 @@ const char *BuildShaderStateConfig( void ); // qboolean CanDamage (gentity_t *targ, vec3_t origin); void G_Damage (gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_t dir, vec3_t point, int damage, int dflags, int mod); +// The shotgun does `G_Damage` multiple times, per each pellet. +// Normally that would mean that if the target is at 1 HP, +// only one pellet would hit them. +// But that would mean that the shotgun cannot gib. We don't want that, +// so we postpone some of the effects of `G_Damage` until after +// all the pellets have done their thing, in `ShotgunPattern`. +// See +// - https://github.com/ioquake/ioq3/issues/794. +// - https://github.com/ec-/baseq3a/pull/49. +// - https://github.com/WofWca/quake3-better-gibs-mod/issues/12. +// - Also `glcient_s.damage_knockback`. +#define ShouldPostponeDeath( mod ) (mod == MOD_SHOTGUN) +#define SetDeadHeight( ent ) {ent->r.maxs[2] = DEAD_MAXS_Z;} qboolean G_RadiusDamage (vec3_t origin, gentity_t *attacker, float damage, float radius, gentity_t *ignore, int mod); int G_InvulnerabilityEffect( gentity_t *targ, vec3_t dir, vec3_t point, vec3_t impactpoint, vec3_t bouncedir ); void body_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ); diff --git a/code/game/g_weapon.c b/code/game/g_weapon.c index 6b40cb3e..70641486 100644 --- a/code/game/g_weapon.c +++ b/code/game/g_weapon.c @@ -325,7 +325,7 @@ static qboolean ShotgunPellet( const vec3_t start, const vec3_t end, gentity_t * // through a corpse. // This is mostly to compensate for the balance changes // that are introduced by the removal of the `self->r.maxs[2] = -8;` - // line in `player_die`. + // (`SetDeadHeight`) line in `player_die`. // But it's probably also sensible otherwise that corpses // affect "more serious" gameplay less. // See @@ -358,6 +358,7 @@ static void ShotgunPattern( const vec3_t origin, const vec3_t origin2, int seed, vec3_t end; vec3_t forward, right, up; qboolean hitClient = qfalse; + gentity_t *ent2; // derive the right and up vectors from the forward vector, because // the client won't have any other information @@ -381,6 +382,19 @@ static void ShotgunPattern( const vec3_t origin, const vec3_t origin2, int seed, } } + // Do what has been postponed in `G_Damage` due to `ShouldPostponeDeath`. + // assert( ShouldPostponeDeath( MOD_SHOTGUN ) ); + ent2 = &g_entities[0]; + for (i = 0; i < level.num_entities; i++, ent2++) { + if ( !ent2->inuse ) { + continue; + } + + if ( ent2->client && ent2->client->ps.pm_type == PM_DEAD ) { + SetDeadHeight( ent2 ); + } + } + // unlagged G_UndoTimeShiftFor( ent ); } From 18be13b0a1363735ce2b9941bea5c2fead438673 Mon Sep 17 00:00:00 2001 From: WofWca Date: Sun, 9 Nov 2025 14:07:15 +0400 Subject: [PATCH 05/30] refactor: add `cg_oldGibs` CVAR, `CG_GibPlayerOld` This is in preparation to some gibs improvements. --- code/cgame/cg_cvar.h | 1 + code/cgame/cg_effects.c | 76 +++++++++++++++++++++++++++++++++++++++ code/cgame/cg_event.c | 6 +++- code/cgame/cg_local.h | 1 + code/cgame/cg_localents.c | 6 +++- 5 files changed, 88 insertions(+), 2 deletions(-) diff --git a/code/cgame/cg_cvar.h b/code/cgame/cg_cvar.h index 46811af9..f6d96d6a 100644 --- a/code/cgame/cg_cvar.h +++ b/code/cgame/cg_cvar.h @@ -18,6 +18,7 @@ CG_CVAR( cg_fov, "cg_fov", "90", CVAR_ARCHIVE ) CG_CVAR( cg_viewsize, "cg_viewsize", "100", CVAR_ARCHIVE ) CG_CVAR( cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE ) CG_CVAR( cg_gibs, "cg_gibs", "1", CVAR_ARCHIVE ) +CG_CVAR( cg_oldGibs, "cg_oldGibs", "0", CVAR_ARCHIVE ) CG_CVAR( cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE ) CG_CVAR( cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE ) CG_CVAR( cg_drawTimer, "cg_drawTimer", "0", CVAR_ARCHIVE ) diff --git a/code/cgame/cg_effects.c b/code/cgame/cg_effects.c index 115c309f..bfd3ff8f 100644 --- a/code/cgame/cg_effects.c +++ b/code/cgame/cg_effects.c @@ -644,6 +644,82 @@ void CG_GibPlayer( const vec3_t playerOrigin ) { velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; CG_LaunchGib( origin, velocity, cgs.media.gibLeg ); } +void CG_GibPlayerOld( const vec3_t playerOrigin ) { + vec3_t origin, velocity; + + if ( !cg_blood.integer ) { + return; + } + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + if ( rand() & 1 ) { + CG_LaunchGib( origin, velocity, cgs.media.gibSkull ); + } else { + CG_LaunchGib( origin, velocity, cgs.media.gibBrain ); + } + + // allow gibs to be turned off for speed + if ( !cg_gibs.integer ) { + return; + } + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibAbdomen ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibArm ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibChest ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibFist ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibFoot ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibForearm ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibIntestine ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibLeg ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibLeg ); +} /* ================== diff --git a/code/cgame/cg_event.c b/code/cgame/cg_event.c index 67319e7f..badad9a1 100644 --- a/code/cgame/cg_event.c +++ b/code/cgame/cg_event.c @@ -1215,7 +1215,11 @@ void CG_EntityEvent( centity_t *cent, vec3_t position, int entityNum ) { #else trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.gibSound ); #endif - CG_GibPlayer( cent->lerpOrigin ); + if (cg_oldGibs.integer) { + CG_GibPlayerOld( cent->lerpOrigin ); + } else { + CG_GibPlayer( cent->lerpOrigin ); + } break; case EV_STOPLOOPINGSOUND: diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h index 240f6ed1..376e659e 100644 --- a/code/cgame/cg_local.h +++ b/code/cgame/cg_local.h @@ -1407,6 +1407,7 @@ void CG_LightningBoltBeam( vec3_t start, vec3_t end ); void CG_ScorePlum( int client, const vec3_t origin, int score ); void CG_GibPlayer( const vec3_t playerOrigin ); +void CG_GibPlayerOld( const vec3_t playerOrigin ); void CG_BigExplode( vec3_t playerOrigin ); void CG_Bleed( const vec3_t origin, int entityNum ); diff --git a/code/cgame/cg_localents.c b/code/cgame/cg_localents.c index 45535ab7..faf6fdd6 100644 --- a/code/cgame/cg_localents.c +++ b/code/cgame/cg_localents.c @@ -761,7 +761,11 @@ void CG_AddInvulnerabilityJuiced( localEntity_t *le ) { } if ( t > 5000 ) { le->endTime = 0; - CG_GibPlayer( le->refEntity.origin ); + if (cg_oldGibs.integer) { + CG_GibPlayerOld( le->refEntity.origin ); + } else { + CG_GibPlayer( le->refEntity.origin ); + } } else { trap_R_AddRefEntityToScene( &le->refEntity ); From 70e2028729b442f08d8917e71b06715291d35b7d Mon Sep 17 00:00:00 2001 From: WofWca Date: Sun, 9 Nov 2025 14:35:21 +0400 Subject: [PATCH 06/30] improvement: better gib starting positions In the original game, all gibs start flying from one single point (i.e. `playerOrigin`), which is sometimes noticeable. Let's position the gibs closer to where they are supposed to be anatomically. That is, legs go lower, head goes higher. --- README.md | 1 + code/cgame/cg_effects.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/README.md b/README.md index 98a2c9c1..1ff0c708 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Unofficial Quake III Arena gamecode patch * fixed UI mouse sensitivity for high-resolution * fixed not being able to gib after match end (right before showing the scores) * fixed shotgun not gibbing unless aiming at the feet + * improved gibs starting positions * fixed server browser + faster scanning * fixed grappling hook muzzle position visuals * new demo UI (subfolders,filtering,sorting) diff --git a/code/cgame/cg_effects.c b/code/cgame/cg_effects.c index bfd3ff8f..46d4957d 100644 --- a/code/cgame/cg_effects.c +++ b/code/cgame/cg_effects.c @@ -570,12 +570,20 @@ Generated a bunch of gibs launching out from the bodies location #define GIB_JUMP 250 void CG_GibPlayer( const vec3_t playerOrigin ) { vec3_t origin, velocity; + // See `playerMins`, `playerMaxs`. + // TODO we could try to check the actual `mins` and `maxs` + // (do we have them available on the client though?), + // to account for crounching or for the "lying on the ground dead" state. + float playerHeight = 32 - MINS_Z; + float bottom = playerOrigin[2] + MINS_Z; + float playerRadius = 15; if ( !cg_blood.integer ) { return; } VectorCopy( playerOrigin, origin ); + origin[2] = bottom + 0.95 * playerHeight; velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; @@ -591,54 +599,75 @@ void CG_GibPlayer( const vec3_t playerOrigin ) { } VectorCopy( playerOrigin, origin ); + origin[2] = bottom + 0.65 * playerHeight; velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; CG_LaunchGib( origin, velocity, cgs.media.gibAbdomen ); VectorCopy( playerOrigin, origin ); + origin[2] = bottom + 0.78 * playerHeight; + origin[1] += 0.8 * playerRadius; + origin[0] += 0.3 * playerRadius; velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; CG_LaunchGib( origin, velocity, cgs.media.gibArm ); VectorCopy( playerOrigin, origin ); + origin[2] = bottom + 0.80 * playerHeight; velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; CG_LaunchGib( origin, velocity, cgs.media.gibChest ); VectorCopy( playerOrigin, origin ); + origin[2] = bottom + 0.66 * playerHeight; + origin[1] += 0.8 * playerRadius; + origin[0] += 0.2 * playerRadius; velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; CG_LaunchGib( origin, velocity, cgs.media.gibFist ); VectorCopy( playerOrigin, origin ); + origin[2] = bottom + 0.05 * playerHeight; + origin[1] += -0.5 * playerRadius; + origin[0] += -0.5 * playerRadius; velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; CG_LaunchGib( origin, velocity, cgs.media.gibFoot ); VectorCopy( playerOrigin, origin ); + origin[2] = bottom + 0.65 * playerHeight; + origin[1] += -0.6 * playerRadius; + origin[0] += -0.2 * playerRadius; velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; CG_LaunchGib( origin, velocity, cgs.media.gibForearm ); VectorCopy( playerOrigin, origin ); + origin[2] = bottom + 0.57 * playerHeight; velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; CG_LaunchGib( origin, velocity, cgs.media.gibIntestine ); VectorCopy( playerOrigin, origin ); + origin[2] = bottom + 0.42 * playerHeight; + origin[1] += 0.5 * playerRadius; + origin[0] += 0.1 * playerRadius; velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; CG_LaunchGib( origin, velocity, cgs.media.gibLeg ); VectorCopy( playerOrigin, origin ); + origin[2] = bottom + 0.44 * playerHeight; + origin[1] += -0.5 * playerRadius; + origin[0] += -0.2 * playerRadius; velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; From a3b8b815e80afcc1b0b3239eca4b252182c28506 Mon Sep 17 00:00:00 2001 From: WofWca Date: Sun, 9 Nov 2025 21:00:17 +0400 Subject: [PATCH 07/30] improvement: gibs: keep player angles The previous commit added proper initial positions to each individual gib. And now it's time to also make sure that they're facing the right direction. --- code/cgame/cg_effects.c | 88 ++++++++++++++++++++++----------------- code/cgame/cg_event.c | 2 +- code/cgame/cg_local.h | 2 +- code/cgame/cg_localents.c | 6 ++- 4 files changed, 57 insertions(+), 41 deletions(-) diff --git a/code/cgame/cg_effects.c b/code/cgame/cg_effects.c index 46d4957d..f37a0af6 100644 --- a/code/cgame/cg_effects.c +++ b/code/cgame/cg_effects.c @@ -533,7 +533,8 @@ void CG_Bleed( const vec3_t origin, int entityNum ) { CG_LaunchGib ================== */ -static void CG_LaunchGib( const vec3_t origin, const vec3_t velocity, qhandle_t hModel ) { +static void CG_LaunchGib( const vec3_t origin, const vec3_t angles, + const vec3_t velocity, qhandle_t hModel ) { localEntity_t *le; refEntity_t *re; @@ -545,7 +546,7 @@ static void CG_LaunchGib( const vec3_t origin, const vec3_t velocity, qhandle_t le->endTime = le->startTime + 5000 + random() * 3000; VectorCopy( origin, re->origin ); - AxisCopy( axisDefault, re->axis ); + AnglesToAxis( angles, re->axis ); re->hModel = hModel; le->pos.trType = TR_GRAVITY; @@ -568,8 +569,12 @@ Generated a bunch of gibs launching out from the bodies location */ #define GIB_VELOCITY 250 #define GIB_JUMP 250 -void CG_GibPlayer( const vec3_t playerOrigin ) { +void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles ) { vec3_t origin, velocity; + // Generally only the head should have pitch, + // the rest of the body is upright. + vec3_t bodyAngles; + vec3_t forward, right, up; // See `playerMins`, `playerMaxs`. // TODO we could try to check the actual `mins` and `maxs` // (do we have them available on the client though?), @@ -582,15 +587,20 @@ void CG_GibPlayer( const vec3_t playerOrigin ) { return; } + VectorCopy( playerAngles, bodyAngles ); + // This way `up` is always `{0,0,1}`. + bodyAngles[PITCH] = 0; + AngleVectors( bodyAngles, forward, right, up ); + VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.95 * playerHeight; velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; if ( rand() & 1 ) { - CG_LaunchGib( origin, velocity, cgs.media.gibSkull ); + CG_LaunchGib( origin, playerAngles, velocity, cgs.media.gibSkull ); } else { - CG_LaunchGib( origin, velocity, cgs.media.gibBrain ); + CG_LaunchGib( origin, playerAngles, velocity, cgs.media.gibBrain ); } // allow gibs to be turned off for speed @@ -603,91 +613,93 @@ void CG_GibPlayer( const vec3_t playerOrigin ) { velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibAbdomen ); + CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibAbdomen ); VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.78 * playerHeight; - origin[1] += 0.8 * playerRadius; - origin[0] += 0.3 * playerRadius; + VectorMA( origin, 0.8 * playerRadius, right, origin ); + VectorMA( origin, 0.3 * playerRadius, forward, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibArm ); + CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibArm ); VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.80 * playerHeight; velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibChest ); + CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibChest ); VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.66 * playerHeight; - origin[1] += 0.8 * playerRadius; - origin[0] += 0.2 * playerRadius; + VectorMA( origin, 0.8 * playerRadius, right, origin ); + VectorMA( origin, 0.2 * playerRadius, forward, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibFist ); + CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibFist ); VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.05 * playerHeight; - origin[1] += -0.5 * playerRadius; - origin[0] += -0.5 * playerRadius; + VectorMA( origin, -0.5 * playerRadius, right, origin ); + VectorMA( origin, -0.5 * playerRadius, forward, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibFoot ); + CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibFoot ); VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.65 * playerHeight; - origin[1] += -0.6 * playerRadius; - origin[0] += -0.2 * playerRadius; + VectorMA( origin, -0.6 * playerRadius, right, origin ); + VectorMA( origin, -0.2 * playerRadius, forward, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibForearm ); + CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibForearm ); VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.57 * playerHeight; velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibIntestine ); + CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibIntestine ); VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.42 * playerHeight; - origin[1] += 0.5 * playerRadius; - origin[0] += 0.1 * playerRadius; + VectorMA( origin, 0.5 * playerRadius, right, origin ); + VectorMA( origin, 0.1 * playerRadius, forward, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibLeg ); + CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibLeg ); VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.44 * playerHeight; - origin[1] += -0.5 * playerRadius; - origin[0] += -0.2 * playerRadius; + VectorMA( origin, -0.5 * playerRadius, right, origin ); + VectorMA( origin, -0.2 * playerRadius, forward, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibLeg ); + CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibLeg ); } void CG_GibPlayerOld( const vec3_t playerOrigin ) { - vec3_t origin, velocity; + vec3_t origin, angles, velocity; if ( !cg_blood.integer ) { return; } + VectorClear(angles); + VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; if ( rand() & 1 ) { - CG_LaunchGib( origin, velocity, cgs.media.gibSkull ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibSkull ); } else { - CG_LaunchGib( origin, velocity, cgs.media.gibBrain ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibBrain ); } // allow gibs to be turned off for speed @@ -699,55 +711,55 @@ void CG_GibPlayerOld( const vec3_t playerOrigin ) { velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibAbdomen ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibAbdomen ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibArm ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibArm ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibChest ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibChest ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibFist ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibFist ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibFoot ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibFoot ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibForearm ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibForearm ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibIntestine ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibIntestine ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibLeg ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibLeg ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibLeg ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibLeg ); } /* diff --git a/code/cgame/cg_event.c b/code/cgame/cg_event.c index badad9a1..bcfebc1d 100644 --- a/code/cgame/cg_event.c +++ b/code/cgame/cg_event.c @@ -1218,7 +1218,7 @@ void CG_EntityEvent( centity_t *cent, vec3_t position, int entityNum ) { if (cg_oldGibs.integer) { CG_GibPlayerOld( cent->lerpOrigin ); } else { - CG_GibPlayer( cent->lerpOrigin ); + CG_GibPlayer( cent->lerpOrigin, cent->lerpAngles ); } break; diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h index 376e659e..6d2ad9e1 100644 --- a/code/cgame/cg_local.h +++ b/code/cgame/cg_local.h @@ -1406,7 +1406,7 @@ void CG_LightningBoltBeam( vec3_t start, vec3_t end ); #endif void CG_ScorePlum( int client, const vec3_t origin, int score ); -void CG_GibPlayer( const vec3_t playerOrigin ); +void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles ); void CG_GibPlayerOld( const vec3_t playerOrigin ); void CG_BigExplode( vec3_t playerOrigin ); diff --git a/code/cgame/cg_localents.c b/code/cgame/cg_localents.c index faf6fdd6..e4d3df13 100644 --- a/code/cgame/cg_localents.c +++ b/code/cgame/cg_localents.c @@ -764,7 +764,11 @@ void CG_AddInvulnerabilityJuiced( localEntity_t *le ) { if (cg_oldGibs.integer) { CG_GibPlayerOld( le->refEntity.origin ); } else { - CG_GibPlayer( le->refEntity.origin ); + vec3_t angles; + // Angles don't matter much here. + VectorClear( angles ); + + CG_GibPlayer( le->refEntity.origin, angles ); } } else { From b797e8f995c6ad6f53c5523ca37bd2413a66d1ba Mon Sep 17 00:00:00 2001 From: WofWca Date: Mon, 2 Feb 2026 17:58:55 +0400 Subject: [PATCH 08/30] improvement: better gib angles: arm, forearm, legs --- code/cgame/cg_effects.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/code/cgame/cg_effects.c b/code/cgame/cg_effects.c index f37a0af6..bf367169 100644 --- a/code/cgame/cg_effects.c +++ b/code/cgame/cg_effects.c @@ -574,6 +574,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles ) { // Generally only the head should have pitch, // the rest of the body is upright. vec3_t bodyAngles; + vec3_t angles; vec3_t forward, right, up; // See `playerMins`, `playerMaxs`. // TODO we could try to check the actual `mins` and `maxs` @@ -618,11 +619,14 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles ) { VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.78 * playerHeight; VectorMA( origin, 0.8 * playerRadius, right, origin ); - VectorMA( origin, 0.3 * playerRadius, forward, origin ); + VectorMA( origin, -0.3 * playerRadius, forward, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibArm ); + VectorCopy( bodyAngles, angles ); + angles[ROLL] += 70; + angles[PITCH] += 45; + CG_LaunchGib( origin, angles, velocity, cgs.media.gibArm ); VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.80 * playerHeight; @@ -638,7 +642,10 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles ) { velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibFist ); + VectorCopy( bodyAngles, angles ); + angles[PITCH] -= 80; + angles[YAW] += 50; + CG_LaunchGib( origin, angles, velocity, cgs.media.gibFist ); VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.05 * playerHeight; @@ -652,11 +659,14 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles ) { VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.65 * playerHeight; VectorMA( origin, -0.6 * playerRadius, right, origin ); - VectorMA( origin, -0.2 * playerRadius, forward, origin ); + VectorMA( origin, +0.2 * playerRadius, forward, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibForearm ); + VectorCopy( bodyAngles, angles ); + angles[ROLL] -= 90; + angles[PITCH] -= 75; + CG_LaunchGib( origin, angles, velocity, cgs.media.gibForearm ); VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.57 * playerHeight; @@ -672,7 +682,10 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles ) { velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibLeg ); + VectorCopy( bodyAngles, angles ); + angles[ROLL] -= 30; + angles[PITCH] -= 15; + CG_LaunchGib( origin, angles, velocity, cgs.media.gibLeg ); VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.44 * playerHeight; @@ -681,7 +694,9 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles ) { velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibLeg ); + VectorCopy( bodyAngles, angles ); + angles[PITCH] += 15; + CG_LaunchGib( origin, angles, velocity, cgs.media.gibLeg ); } void CG_GibPlayerOld( const vec3_t playerOrigin ) { vec3_t origin, angles, velocity; From d37842675a7446a4841fbfdfe5609fdd9da08913 Mon Sep 17 00:00:00 2001 From: WofWca Date: Fri, 7 Nov 2025 12:25:32 +0400 Subject: [PATCH 09/30] feat: make gibs fly away from damage To be more precise, preserve the player velocity, most importantly, including the velocity they get from knockback. --- README.md | 6 ++- code/cgame/cg_cvar.h | 3 ++ code/cgame/cg_effects.c | 78 +++++++++++++++++++++++---------------- code/cgame/cg_event.c | 2 +- code/cgame/cg_local.h | 3 +- code/cgame/cg_localents.c | 2 +- 6 files changed, 59 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 1ff0c708..b1a8c9d2 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,11 @@ Unofficial Quake III Arena gamecode patch * fixed UI mouse sensitivity for high-resolution * fixed not being able to gib after match end (right before showing the scores) * fixed shotgun not gibbing unless aiming at the feet - * improved gibs starting positions + * improved gibs physics, new CVARs + * `cg_oldGibs` + * `cg_gibsInheritPlayerVelocity` + * `cg_gibsExtraRandomVelocity` + * `cg_gibsExtraVerticalVelocity` * fixed server browser + faster scanning * fixed grappling hook muzzle position visuals * new demo UI (subfolders,filtering,sorting) diff --git a/code/cgame/cg_cvar.h b/code/cgame/cg_cvar.h index f6d96d6a..0272ddd5 100644 --- a/code/cgame/cg_cvar.h +++ b/code/cgame/cg_cvar.h @@ -19,6 +19,9 @@ CG_CVAR( cg_viewsize, "cg_viewsize", "100", CVAR_ARCHIVE ) CG_CVAR( cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE ) CG_CVAR( cg_gibs, "cg_gibs", "1", CVAR_ARCHIVE ) CG_CVAR( cg_oldGibs, "cg_oldGibs", "0", CVAR_ARCHIVE ) +CG_CVAR( cg_gibsInheritPlayerVelocity, "cg_gibsInheritPlayerVelocity", "1.0", CVAR_ARCHIVE ) +CG_CVAR( cg_gibsExtraRandomVelocity, "cg_gibsExtraRandomVelocity", "250", CVAR_ARCHIVE ) +CG_CVAR( cg_gibsExtraVerticalVelocity, "cg_gibsExtraVerticalVelocity", "100", CVAR_ARCHIVE ) CG_CVAR( cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE ) CG_CVAR( cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE ) CG_CVAR( cg_drawTimer, "cg_drawTimer", "0", CVAR_ARCHIVE ) diff --git a/code/cgame/cg_effects.c b/code/cgame/cg_effects.c index bf367169..741b1393 100644 --- a/code/cgame/cg_effects.c +++ b/code/cgame/cg_effects.c @@ -569,7 +569,8 @@ Generated a bunch of gibs launching out from the bodies location */ #define GIB_VELOCITY 250 #define GIB_JUMP 250 -void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles ) { +void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, + const vec3_t playerVelocity ) { vec3_t origin, velocity; // Generally only the head should have pitch, // the rest of the body is upright. @@ -583,6 +584,9 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles ) { float playerHeight = 32 - MINS_Z; float bottom = playerOrigin[2] + MINS_Z; float playerRadius = 15; + float baseRandomVelocity = cg_gibsExtraRandomVelocity.value; + vec3_t playerVelocityScaled; + float jump = cg_gibsExtraVerticalVelocity.value; if ( !cg_blood.integer ) { return; @@ -593,11 +597,14 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles ) { bodyAngles[PITCH] = 0; AngleVectors( bodyAngles, forward, right, up ); + VectorScale( playerVelocity, cg_gibsInheritPlayerVelocity.value, playerVelocityScaled ); + VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.95 * playerHeight; - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + velocity[0] = crandom()*baseRandomVelocity; + velocity[1] = crandom()*baseRandomVelocity; + velocity[2] = jump + crandom()*baseRandomVelocity; + VectorAdd( velocity, playerVelocityScaled, velocity ); if ( rand() & 1 ) { CG_LaunchGib( origin, playerAngles, velocity, cgs.media.gibSkull ); } else { @@ -611,18 +618,20 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles ) { VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.65 * playerHeight; - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + velocity[0] = crandom()*baseRandomVelocity; + velocity[1] = crandom()*baseRandomVelocity; + velocity[2] = jump + crandom()*baseRandomVelocity; + VectorAdd( velocity, playerVelocityScaled, velocity ); CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibAbdomen ); VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.78 * playerHeight; VectorMA( origin, 0.8 * playerRadius, right, origin ); VectorMA( origin, -0.3 * playerRadius, forward, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + velocity[0] = crandom()*baseRandomVelocity; + velocity[1] = crandom()*baseRandomVelocity; + velocity[2] = jump + crandom()*baseRandomVelocity; + VectorAdd( velocity, playerVelocityScaled, velocity ); VectorCopy( bodyAngles, angles ); angles[ROLL] += 70; angles[PITCH] += 45; @@ -630,18 +639,20 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles ) { VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.80 * playerHeight; - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + velocity[0] = crandom()*baseRandomVelocity; + velocity[1] = crandom()*baseRandomVelocity; + velocity[2] = jump + crandom()*baseRandomVelocity; + VectorAdd( velocity, playerVelocityScaled, velocity ); CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibChest ); VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.66 * playerHeight; VectorMA( origin, 0.8 * playerRadius, right, origin ); VectorMA( origin, 0.2 * playerRadius, forward, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + velocity[0] = crandom()*baseRandomVelocity; + velocity[1] = crandom()*baseRandomVelocity; + velocity[2] = jump + crandom()*baseRandomVelocity; + VectorAdd( velocity, playerVelocityScaled, velocity ); VectorCopy( bodyAngles, angles ); angles[PITCH] -= 80; angles[YAW] += 50; @@ -651,18 +662,20 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles ) { origin[2] = bottom + 0.05 * playerHeight; VectorMA( origin, -0.5 * playerRadius, right, origin ); VectorMA( origin, -0.5 * playerRadius, forward, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + velocity[0] = crandom()*baseRandomVelocity; + velocity[1] = crandom()*baseRandomVelocity; + velocity[2] = jump + crandom()*baseRandomVelocity; + VectorAdd( velocity, playerVelocityScaled, velocity ); CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibFoot ); VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.65 * playerHeight; VectorMA( origin, -0.6 * playerRadius, right, origin ); VectorMA( origin, +0.2 * playerRadius, forward, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + velocity[0] = crandom()*baseRandomVelocity; + velocity[1] = crandom()*baseRandomVelocity; + velocity[2] = jump + crandom()*baseRandomVelocity; + VectorAdd( velocity, playerVelocityScaled, velocity ); VectorCopy( bodyAngles, angles ); angles[ROLL] -= 90; angles[PITCH] -= 75; @@ -670,18 +683,20 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles ) { VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.57 * playerHeight; - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + velocity[0] = crandom()*baseRandomVelocity; + velocity[1] = crandom()*baseRandomVelocity; + velocity[2] = jump + crandom()*baseRandomVelocity; + VectorAdd( velocity, playerVelocityScaled, velocity ); CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibIntestine ); VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.42 * playerHeight; VectorMA( origin, 0.5 * playerRadius, right, origin ); VectorMA( origin, 0.1 * playerRadius, forward, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + velocity[0] = crandom()*baseRandomVelocity; + velocity[1] = crandom()*baseRandomVelocity; + velocity[2] = jump + crandom()*baseRandomVelocity; + VectorAdd( velocity, playerVelocityScaled, velocity ); VectorCopy( bodyAngles, angles ); angles[ROLL] -= 30; angles[PITCH] -= 15; @@ -691,9 +706,10 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles ) { origin[2] = bottom + 0.44 * playerHeight; VectorMA( origin, -0.5 * playerRadius, right, origin ); VectorMA( origin, -0.2 * playerRadius, forward, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + velocity[0] = crandom()*baseRandomVelocity; + velocity[1] = crandom()*baseRandomVelocity; + velocity[2] = jump + crandom()*baseRandomVelocity; + VectorAdd( velocity, playerVelocityScaled, velocity ); VectorCopy( bodyAngles, angles ); angles[PITCH] += 15; CG_LaunchGib( origin, angles, velocity, cgs.media.gibLeg ); diff --git a/code/cgame/cg_event.c b/code/cgame/cg_event.c index bcfebc1d..14ed9bd3 100644 --- a/code/cgame/cg_event.c +++ b/code/cgame/cg_event.c @@ -1218,7 +1218,7 @@ void CG_EntityEvent( centity_t *cent, vec3_t position, int entityNum ) { if (cg_oldGibs.integer) { CG_GibPlayerOld( cent->lerpOrigin ); } else { - CG_GibPlayer( cent->lerpOrigin, cent->lerpAngles ); + CG_GibPlayer( cent->lerpOrigin, cent->lerpAngles, es->pos.trDelta ); } break; diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h index 6d2ad9e1..a79d4d98 100644 --- a/code/cgame/cg_local.h +++ b/code/cgame/cg_local.h @@ -1406,7 +1406,8 @@ void CG_LightningBoltBeam( vec3_t start, vec3_t end ); #endif void CG_ScorePlum( int client, const vec3_t origin, int score ); -void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles ); +void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, + const vec3_t playerVelocity ); void CG_GibPlayerOld( const vec3_t playerOrigin ); void CG_BigExplode( vec3_t playerOrigin ); diff --git a/code/cgame/cg_localents.c b/code/cgame/cg_localents.c index e4d3df13..afdf9e9a 100644 --- a/code/cgame/cg_localents.c +++ b/code/cgame/cg_localents.c @@ -768,7 +768,7 @@ void CG_AddInvulnerabilityJuiced( localEntity_t *le ) { // Angles don't matter much here. VectorClear( angles ); - CG_GibPlayer( le->refEntity.origin, angles ); + CG_GibPlayer( le->refEntity.origin, angles, le->pos.trDelta ); } } else { From 2777c6b113ac27b131dbcee98640b5e62d3a3b07 Mon Sep 17 00:00:00 2001 From: WofWca Date: Mon, 10 Nov 2025 13:54:51 +0400 Subject: [PATCH 10/30] improvement: allow multiple bounce marks (gibs) It's more "physically accurate" to decide whether to leave a mark based on velocity. This allows gibs to leave multiple marks, should they keep high velocity after the first impact. --- code/cgame/cg_cvar.h | 2 ++ code/cgame/cg_localents.c | 30 ++++++++++++++++++++---------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/code/cgame/cg_cvar.h b/code/cgame/cg_cvar.h index 0272ddd5..ba7920f0 100644 --- a/code/cgame/cg_cvar.h +++ b/code/cgame/cg_cvar.h @@ -43,6 +43,8 @@ CG_CVAR( cg_crosshairY, "cg_crosshairY", "0", CVAR_ARCHIVE ) CG_CVAR( cg_brassTime, "cg_brassTime", "2500", CVAR_ARCHIVE ) CG_CVAR( cg_simpleItems, "cg_simpleItems", "0", CVAR_ARCHIVE ) CG_CVAR( cg_addMarks, "cg_marks", "1", CVAR_ARCHIVE ) +// Note that ~290 corresponds to a free fall with no bounce from player height. +CG_CVAR( cg_bounceMarksMinImpactSpeed, "cg_bounceMarksMinImpactSpeed", "350", CVAR_ARCHIVE ) CG_CVAR( cg_lagometer, "cg_lagometer", "1", CVAR_ARCHIVE ) CG_CVAR( cg_railTrailTime, "cg_railTrailTime", "400", CVAR_ARCHIVE ) CG_CVAR( cg_railTrailRadius, "cg_railTrailRadius", "0", CVAR_ARCHIVE ) diff --git a/code/cgame/cg_localents.c b/code/cgame/cg_localents.c index afdf9e9a..ccc6adfc 100644 --- a/code/cgame/cg_localents.c +++ b/code/cgame/cg_localents.c @@ -149,9 +149,11 @@ void CG_FragmentBounceMark( localEntity_t *le, trace_t *trace ) { } - // don't allow a fragment to make multiple marks, or they - // pile up while settling - le->leMarkType = LEMT_NONE; + // This is no longer needed, because we now decide whether to leave a mark + // purely based on impact velocity. + // // don't allow a fragment to make multiple marks, or they + // // pile up while settling + // le->leMarkType = LEMT_NONE; } /* @@ -188,9 +190,11 @@ void CG_FragmentBounceSound( localEntity_t *le, trace_t *trace ) { /* ================ CG_ReflectVelocity + +Modifies velocity of `le` and writes the difference to `velocityDifference` ================ */ -void CG_ReflectVelocity( localEntity_t *le, trace_t *trace ) { +void CG_ReflectVelocity( localEntity_t *le, trace_t *trace, vec3_t velocityDifference ) { vec3_t velocity; float dot; int hitTime; @@ -203,6 +207,10 @@ void CG_ReflectVelocity( localEntity_t *le, trace_t *trace ) { VectorScale( le->pos.trDelta, le->bounceFactor, le->pos.trDelta ); + if (velocityDifference) { + VectorSubtract( le->pos.trDelta, velocity, velocityDifference ); + } + VectorCopy( trace->endpos, le->pos.trBase ); le->pos.trTime = cg.time; @@ -223,7 +231,7 @@ CG_AddFragment ================ */ static void CG_AddFragment( localEntity_t *le ) { - vec3_t newOrigin; + vec3_t newOrigin, impactVelocityDiff; trace_t trace; if ( le->pos.trType == TR_STATIONARY ) { @@ -283,15 +291,17 @@ static void CG_AddFragment( localEntity_t *le ) { return; } - // leave a mark - CG_FragmentBounceMark( le, &trace ); + // reflect the velocity on the trace plane + CG_ReflectVelocity( le, &trace, impactVelocityDiff ); + + if ( VectorLengthSquared( impactVelocityDiff ) >= Square( cg_bounceMarksMinImpactSpeed.value ) ) { + // leave a mark + CG_FragmentBounceMark( le, &trace ); + } // do a bouncy sound CG_FragmentBounceSound( le, &trace ); - // reflect the velocity on the trace plane - CG_ReflectVelocity( le, &trace ); - trap_R_AddRefEntityToScene( &le->refEntity ); } From 1f3ffdaf9cbf622331a5a12c0bd44aaa1d32c2ad Mon Sep 17 00:00:00 2001 From: WofWca Date: Mon, 10 Nov 2025 17:55:51 +0400 Subject: [PATCH 11/30] improvement: better gib sound, blood trail Allow playing the gib sound more than once, and keep leaving the blood trail even if the gib already hit something. --- code/cgame/cg_cvar.h | 1 + code/cgame/cg_localents.c | 14 +++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/code/cgame/cg_cvar.h b/code/cgame/cg_cvar.h index ba7920f0..a325fc41 100644 --- a/code/cgame/cg_cvar.h +++ b/code/cgame/cg_cvar.h @@ -45,6 +45,7 @@ CG_CVAR( cg_simpleItems, "cg_simpleItems", "0", CVAR_ARCHIVE ) CG_CVAR( cg_addMarks, "cg_marks", "1", CVAR_ARCHIVE ) // Note that ~290 corresponds to a free fall with no bounce from player height. CG_CVAR( cg_bounceMarksMinImpactSpeed, "cg_bounceMarksMinImpactSpeed", "350", CVAR_ARCHIVE ) +CG_CVAR( cg_bounceSoundMinImpactSpeed, "cg_bounceSoundMinImpactSpeed", "450", CVAR_ARCHIVE ) CG_CVAR( cg_lagometer, "cg_lagometer", "1", CVAR_ARCHIVE ) CG_CVAR( cg_railTrailTime, "cg_railTrailTime", "400", CVAR_ARCHIVE ) CG_CVAR( cg_railTrailRadius, "cg_railTrailRadius", "0", CVAR_ARCHIVE ) diff --git a/code/cgame/cg_localents.c b/code/cgame/cg_localents.c index ccc6adfc..f0361378 100644 --- a/code/cgame/cg_localents.c +++ b/code/cgame/cg_localents.c @@ -181,9 +181,11 @@ void CG_FragmentBounceSound( localEntity_t *le, trace_t *trace ) { } - // don't allow a fragment to make multiple bounce sounds, - // or it gets too noisy as they settle - le->leBounceSoundType = LEBS_NONE; + // This is no longer needed, because we now decide whether to play a sound + // purely based on impact velocity. + // // don't allow a fragment to make multiple bounce sounds, + // // or it gets too noisy as they settle + // le->leBounceSoundType = LEBS_NONE; } @@ -299,8 +301,10 @@ static void CG_AddFragment( localEntity_t *le ) { CG_FragmentBounceMark( le, &trace ); } - // do a bouncy sound - CG_FragmentBounceSound( le, &trace ); + if ( VectorLengthSquared( impactVelocityDiff ) >= Square( cg_bounceSoundMinImpactSpeed.value ) ) { + // do a bouncy sound + CG_FragmentBounceSound( le, &trace ); + } trap_R_AddRefEntityToScene( &le->refEntity ); } From d4205df872961636e0313750a9b283329fe841f4 Mon Sep 17 00:00:00 2001 From: WofWca Date: Fri, 14 Nov 2025 17:55:52 +0400 Subject: [PATCH 12/30] fix: shotgun not applying knockback on frag --- code/game/g_combat.c | 21 +++++++++++++++++++-- code/game/g_local.h | 1 + code/game/g_weapon.c | 1 + 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/code/game/g_combat.c b/code/game/g_combat.c index 22a21b0b..c5fa753a 100644 --- a/code/game/g_combat.c +++ b/code/game/g_combat.c @@ -1053,8 +1053,25 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, } if ( targ->health <= 0 ) { - if ( client ) - targ->flags |= FL_NO_KNOCKBACK; + // Not checking `ShouldPostponeDeath` would cause a bug: + // when fragging with the shotgun, + // the dead body would not gain momentum (knockback) + // from the pellets that come after the pellet + // that made the health go below 0. + // This would result in the dead body not getting pushed + // as far as it should have been, and, most importantly, + // the gibs not getting enough momentum. See + // https://github.com/ec-/baseq3a/pull/53. + // + // Note that if the body gets gibbed, + // then it will still stop absorbing pellets, + // i.e. this fix only adds at most `GIB_HEALTH` worth of knockback. + // + // This issiue is similar to + // https://github.com/ioquake/ioq3/issues/794. + if ( client && !ShouldPostponeDeath( mod ) ) { + SetFlNoKnockback( targ ); + } if (targ->health < -999) targ->health = -999; diff --git a/code/game/g_local.h b/code/game/g_local.h index 6bb51eb4..da1d6d60 100644 --- a/code/game/g_local.h +++ b/code/game/g_local.h @@ -540,6 +540,7 @@ void G_Damage (gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_ // - Also `glcient_s.damage_knockback`. #define ShouldPostponeDeath( mod ) (mod == MOD_SHOTGUN) #define SetDeadHeight( ent ) {ent->r.maxs[2] = DEAD_MAXS_Z;} +#define SetFlNoKnockback( ent ) {ent->flags |= FL_NO_KNOCKBACK;} qboolean G_RadiusDamage (vec3_t origin, gentity_t *attacker, float damage, float radius, gentity_t *ignore, int mod); int G_InvulnerabilityEffect( gentity_t *targ, vec3_t dir, vec3_t point, vec3_t impactpoint, vec3_t bouncedir ); void body_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ); diff --git a/code/game/g_weapon.c b/code/game/g_weapon.c index 70641486..2df5c2ba 100644 --- a/code/game/g_weapon.c +++ b/code/game/g_weapon.c @@ -392,6 +392,7 @@ static void ShotgunPattern( const vec3_t origin, const vec3_t origin2, int seed, if ( ent2->client && ent2->client->ps.pm_type == PM_DEAD ) { SetDeadHeight( ent2 ); + SetFlNoKnockback( ent2 ); } } From fdd115a07ab596a4b4e919b399738aaf2641780a Mon Sep 17 00:00:00 2001 From: WofWca Date: Tue, 11 Nov 2025 11:39:56 +0400 Subject: [PATCH 13/30] feat: `cg_gibsBounceFactor` CVAR --- code/cgame/cg_cvar.h | 1 + code/cgame/cg_effects.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/code/cgame/cg_cvar.h b/code/cgame/cg_cvar.h index a325fc41..e815ab49 100644 --- a/code/cgame/cg_cvar.h +++ b/code/cgame/cg_cvar.h @@ -22,6 +22,7 @@ CG_CVAR( cg_oldGibs, "cg_oldGibs", "0", CVAR_ARCHIVE ) CG_CVAR( cg_gibsInheritPlayerVelocity, "cg_gibsInheritPlayerVelocity", "1.0", CVAR_ARCHIVE ) CG_CVAR( cg_gibsExtraRandomVelocity, "cg_gibsExtraRandomVelocity", "250", CVAR_ARCHIVE ) CG_CVAR( cg_gibsExtraVerticalVelocity, "cg_gibsExtraVerticalVelocity", "100", CVAR_ARCHIVE ) +CG_CVAR( cg_gibsBounceFactor, "cg_gibsBounceFactor", "0.6", CVAR_ARCHIVE ) CG_CVAR( cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE ) CG_CVAR( cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE ) CG_CVAR( cg_drawTimer, "cg_drawTimer", "0", CVAR_ARCHIVE ) diff --git a/code/cgame/cg_effects.c b/code/cgame/cg_effects.c index 741b1393..b2659c68 100644 --- a/code/cgame/cg_effects.c +++ b/code/cgame/cg_effects.c @@ -554,7 +554,7 @@ static void CG_LaunchGib( const vec3_t origin, const vec3_t angles, VectorCopy( velocity, le->pos.trDelta ); le->pos.trTime = cg.time; - le->bounceFactor = 0.6f; + le->bounceFactor = cg_oldGibs.integer ? 0.6f : cg_gibsBounceFactor.value; le->leBounceSoundType = LEBS_BLOOD; le->leMarkType = LEMT_BLOOD; From d119ef80aa3c116198e779241da89c606e137b8d Mon Sep 17 00:00:00 2001 From: WofWca Date: Tue, 18 Nov 2025 19:14:07 +0400 Subject: [PATCH 14/30] improvement: lower gibs bounceFactor This makes the gibs feel more "dead" and less frog-like. --- code/cgame/cg_cvar.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/cgame/cg_cvar.h b/code/cgame/cg_cvar.h index e815ab49..8e91cb0f 100644 --- a/code/cgame/cg_cvar.h +++ b/code/cgame/cg_cvar.h @@ -22,7 +22,7 @@ CG_CVAR( cg_oldGibs, "cg_oldGibs", "0", CVAR_ARCHIVE ) CG_CVAR( cg_gibsInheritPlayerVelocity, "cg_gibsInheritPlayerVelocity", "1.0", CVAR_ARCHIVE ) CG_CVAR( cg_gibsExtraRandomVelocity, "cg_gibsExtraRandomVelocity", "250", CVAR_ARCHIVE ) CG_CVAR( cg_gibsExtraVerticalVelocity, "cg_gibsExtraVerticalVelocity", "100", CVAR_ARCHIVE ) -CG_CVAR( cg_gibsBounceFactor, "cg_gibsBounceFactor", "0.6", CVAR_ARCHIVE ) +CG_CVAR( cg_gibsBounceFactor, "cg_gibsBounceFactor", "0.4", CVAR_ARCHIVE ) CG_CVAR( cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE ) CG_CVAR( cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE ) CG_CVAR( cg_drawTimer, "cg_drawTimer", "0", CVAR_ARCHIVE ) From c3e4bb8b641c923a97391ab436ccb9139ae642e7 Mon Sep 17 00:00:00 2001 From: WofWca Date: Wed, 7 Jan 2026 13:01:30 +0400 Subject: [PATCH 15/30] fix: gibs don't fly inwards --- code/cgame/cg_effects.c | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/code/cgame/cg_effects.c b/code/cgame/cg_effects.c index b2659c68..29f66a96 100644 --- a/code/cgame/cg_effects.c +++ b/code/cgame/cg_effects.c @@ -601,9 +601,15 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.95 * playerHeight; + VectorClear( velocity ); velocity[0] = crandom()*baseRandomVelocity; velocity[1] = crandom()*baseRandomVelocity; - velocity[2] = jump + crandom()*baseRandomVelocity; + // For the skull / brain we want the random velocity + // to never have downwards (inwards) component, + // so we use `random` instead of `crandom`. + // We also do the same for other gibs, + // but for the left / right velocity components. + velocity[2] = jump + random()*baseRandomVelocity; VectorAdd( velocity, playerVelocityScaled, velocity ); if ( rand() & 1 ) { CG_LaunchGib( origin, playerAngles, velocity, cgs.media.gibSkull ); @@ -618,6 +624,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.65 * playerHeight; + VectorClear( velocity ); velocity[0] = crandom()*baseRandomVelocity; velocity[1] = crandom()*baseRandomVelocity; velocity[2] = jump + crandom()*baseRandomVelocity; @@ -628,8 +635,9 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, origin[2] = bottom + 0.78 * playerHeight; VectorMA( origin, 0.8 * playerRadius, right, origin ); VectorMA( origin, -0.3 * playerRadius, forward, origin ); - velocity[0] = crandom()*baseRandomVelocity; - velocity[1] = crandom()*baseRandomVelocity; + VectorClear( velocity ); + VectorMA( velocity, +random()*baseRandomVelocity, right, velocity ); + VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); velocity[2] = jump + crandom()*baseRandomVelocity; VectorAdd( velocity, playerVelocityScaled, velocity ); VectorCopy( bodyAngles, angles ); @@ -639,9 +647,12 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.80 * playerHeight; - velocity[0] = crandom()*baseRandomVelocity; - velocity[1] = crandom()*baseRandomVelocity; - velocity[2] = jump + crandom()*baseRandomVelocity; + VectorClear( velocity ); + // Chest is a more "central" and "heavier" piece, + // so it gets less random velocity. + velocity[0] = 0.5*crandom()*baseRandomVelocity; + velocity[1] = 0.5*crandom()*baseRandomVelocity; + velocity[2] = jump + 0.5*crandom()*baseRandomVelocity; VectorAdd( velocity, playerVelocityScaled, velocity ); CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibChest ); @@ -649,6 +660,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, origin[2] = bottom + 0.66 * playerHeight; VectorMA( origin, 0.8 * playerRadius, right, origin ); VectorMA( origin, 0.2 * playerRadius, forward, origin ); + VectorClear( velocity ); velocity[0] = crandom()*baseRandomVelocity; velocity[1] = crandom()*baseRandomVelocity; velocity[2] = jump + crandom()*baseRandomVelocity; @@ -662,6 +674,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, origin[2] = bottom + 0.05 * playerHeight; VectorMA( origin, -0.5 * playerRadius, right, origin ); VectorMA( origin, -0.5 * playerRadius, forward, origin ); + VectorClear( velocity ); velocity[0] = crandom()*baseRandomVelocity; velocity[1] = crandom()*baseRandomVelocity; velocity[2] = jump + crandom()*baseRandomVelocity; @@ -672,8 +685,9 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, origin[2] = bottom + 0.65 * playerHeight; VectorMA( origin, -0.6 * playerRadius, right, origin ); VectorMA( origin, +0.2 * playerRadius, forward, origin ); - velocity[0] = crandom()*baseRandomVelocity; - velocity[1] = crandom()*baseRandomVelocity; + VectorClear( velocity ); + VectorMA( velocity, -random()*baseRandomVelocity, right, velocity ); + VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); velocity[2] = jump + crandom()*baseRandomVelocity; VectorAdd( velocity, playerVelocityScaled, velocity ); VectorCopy( bodyAngles, angles ); @@ -683,6 +697,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, VectorCopy( playerOrigin, origin ); origin[2] = bottom + 0.57 * playerHeight; + VectorClear( velocity ); velocity[0] = crandom()*baseRandomVelocity; velocity[1] = crandom()*baseRandomVelocity; velocity[2] = jump + crandom()*baseRandomVelocity; @@ -693,8 +708,9 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, origin[2] = bottom + 0.42 * playerHeight; VectorMA( origin, 0.5 * playerRadius, right, origin ); VectorMA( origin, 0.1 * playerRadius, forward, origin ); - velocity[0] = crandom()*baseRandomVelocity; - velocity[1] = crandom()*baseRandomVelocity; + VectorClear( velocity ); + VectorMA( velocity, +random()*baseRandomVelocity, right, velocity ); + VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); velocity[2] = jump + crandom()*baseRandomVelocity; VectorAdd( velocity, playerVelocityScaled, velocity ); VectorCopy( bodyAngles, angles ); @@ -706,8 +722,9 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, origin[2] = bottom + 0.44 * playerHeight; VectorMA( origin, -0.5 * playerRadius, right, origin ); VectorMA( origin, -0.2 * playerRadius, forward, origin ); - velocity[0] = crandom()*baseRandomVelocity; - velocity[1] = crandom()*baseRandomVelocity; + VectorClear( velocity ); + VectorMA( velocity, -random()*baseRandomVelocity, right, velocity ); + VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); velocity[2] = jump + crandom()*baseRandomVelocity; VectorAdd( velocity, playerVelocityScaled, velocity ); VectorCopy( bodyAngles, angles ); From 2f07c00bc8dc5b7aa92e5e62dbbda6f3503f302f Mon Sep 17 00:00:00 2001 From: WofWca Date: Mon, 12 Jan 2026 22:49:16 +0400 Subject: [PATCH 16/30] feat: non-binary `cg_gibs` to control gibs amount --- README.md | 2 + code/cgame/cg_cvar.h | 2 +- code/cgame/cg_effects.c | 282 ++++++++++++++++++++++------------------ 3 files changed, 161 insertions(+), 125 deletions(-) diff --git a/README.md b/README.md index b1a8c9d2..23309b1e 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ Unofficial Quake III Arena gamecode patch * fixed shotgun not gibbing unless aiming at the feet * improved gibs physics, new CVARs * `cg_oldGibs` + * `cg_gibs` is now non-binary: set to 1.3 to launch 3 more pieces of gibs, + or to 0.5 to half the amount of gibs * `cg_gibsInheritPlayerVelocity` * `cg_gibsExtraRandomVelocity` * `cg_gibsExtraVerticalVelocity` diff --git a/code/cgame/cg_cvar.h b/code/cgame/cg_cvar.h index 8e91cb0f..2adbd632 100644 --- a/code/cgame/cg_cvar.h +++ b/code/cgame/cg_cvar.h @@ -17,7 +17,7 @@ CG_CVAR( cg_zoomFov, "cg_zoomfov", "22.5", CVAR_ARCHIVE ) CG_CVAR( cg_fov, "cg_fov", "90", CVAR_ARCHIVE ) CG_CVAR( cg_viewsize, "cg_viewsize", "100", CVAR_ARCHIVE ) CG_CVAR( cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE ) -CG_CVAR( cg_gibs, "cg_gibs", "1", CVAR_ARCHIVE ) +CG_CVAR( cg_gibs, "cg_gibs", "1.0", CVAR_ARCHIVE ) CG_CVAR( cg_oldGibs, "cg_oldGibs", "0", CVAR_ARCHIVE ) CG_CVAR( cg_gibsInheritPlayerVelocity, "cg_gibsInheritPlayerVelocity", "1.0", CVAR_ARCHIVE ) CG_CVAR( cg_gibsExtraRandomVelocity, "cg_gibsExtraRandomVelocity", "250", CVAR_ARCHIVE ) diff --git a/code/cgame/cg_effects.c b/code/cgame/cg_effects.c index 29f66a96..0405c746 100644 --- a/code/cgame/cg_effects.c +++ b/code/cgame/cg_effects.c @@ -567,8 +567,9 @@ CG_GibPlayer Generated a bunch of gibs launching out from the bodies location =================== */ -#define GIB_VELOCITY 250 -#define GIB_JUMP 250 +#define DEFAULT_NUM_GIBS 10 +#define GIB_VELOCITY 250 +#define GIB_JUMP 250 void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, const vec3_t playerVelocity ) { vec3_t origin, velocity; @@ -587,6 +588,8 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, float baseRandomVelocity = cg_gibsExtraRandomVelocity.value; vec3_t playerVelocityScaled; float jump = cg_gibsExtraVerticalVelocity.value; + int numGibs = cg_gibs.value * DEFAULT_NUM_GIBS; + qboolean skullLaunched = qfalse; // launch only one skull. if ( !cg_blood.integer ) { return; @@ -599,137 +602,168 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, VectorScale( playerVelocity, cg_gibsInheritPlayerVelocity.value, playerVelocityScaled ); - VectorCopy( playerOrigin, origin ); - origin[2] = bottom + 0.95 * playerHeight; - VectorClear( velocity ); - velocity[0] = crandom()*baseRandomVelocity; - velocity[1] = crandom()*baseRandomVelocity; - // For the skull / brain we want the random velocity - // to never have downwards (inwards) component, - // so we use `random` instead of `crandom`. - // We also do the same for other gibs, - // but for the left / right velocity components. - velocity[2] = jump + random()*baseRandomVelocity; - VectorAdd( velocity, playerVelocityScaled, velocity ); - if ( rand() & 1 ) { - CG_LaunchGib( origin, playerAngles, velocity, cgs.media.gibSkull ); - } else { - CG_LaunchGib( origin, playerAngles, velocity, cgs.media.gibBrain ); - } - - // allow gibs to be turned off for speed - if ( !cg_gibs.integer ) { - return; - } + do { + // Note that one gib will get launched even if `numGibs == 0`. + // This is in line with the original behavior of `CG_GibPlayer`. + + VectorCopy( playerOrigin, origin ); + origin[2] = bottom + 0.95 * playerHeight; + VectorClear( velocity ); + velocity[0] = crandom()*baseRandomVelocity; + velocity[1] = crandom()*baseRandomVelocity; + // For the skull / brain we want the random velocity + // to never have downwards (inwards) component, + // so we use `random` instead of `crandom`. + // We also do the same for other gibs, + // but for the left / right velocity components. + velocity[2] = jump + random()*baseRandomVelocity; + VectorAdd( velocity, playerVelocityScaled, velocity ); + if ( !skullLaunched && (rand() & 1) ) { + CG_LaunchGib( origin, playerAngles, velocity, cgs.media.gibSkull ); + skullLaunched = qtrue; + } else { + CG_LaunchGib( origin, playerAngles, velocity, cgs.media.gibBrain ); + } + if (--numGibs <= 0) { + return; + } - VectorCopy( playerOrigin, origin ); - origin[2] = bottom + 0.65 * playerHeight; - VectorClear( velocity ); - velocity[0] = crandom()*baseRandomVelocity; - velocity[1] = crandom()*baseRandomVelocity; - velocity[2] = jump + crandom()*baseRandomVelocity; - VectorAdd( velocity, playerVelocityScaled, velocity ); - CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibAbdomen ); + VectorCopy( playerOrigin, origin ); + origin[2] = bottom + 0.65 * playerHeight; + VectorClear( velocity ); + velocity[0] = crandom()*baseRandomVelocity; + velocity[1] = crandom()*baseRandomVelocity; + velocity[2] = jump + crandom()*baseRandomVelocity; + VectorAdd( velocity, playerVelocityScaled, velocity ); + CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibAbdomen ); + if (--numGibs <= 0) { + return; + } - VectorCopy( playerOrigin, origin ); - origin[2] = bottom + 0.78 * playerHeight; - VectorMA( origin, 0.8 * playerRadius, right, origin ); - VectorMA( origin, -0.3 * playerRadius, forward, origin ); - VectorClear( velocity ); - VectorMA( velocity, +random()*baseRandomVelocity, right, velocity ); - VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); - velocity[2] = jump + crandom()*baseRandomVelocity; - VectorAdd( velocity, playerVelocityScaled, velocity ); - VectorCopy( bodyAngles, angles ); - angles[ROLL] += 70; - angles[PITCH] += 45; - CG_LaunchGib( origin, angles, velocity, cgs.media.gibArm ); + VectorCopy( playerOrigin, origin ); + origin[2] = bottom + 0.78 * playerHeight; + VectorMA( origin, 0.8 * playerRadius, right, origin ); + VectorMA( origin, -0.3 * playerRadius, forward, origin ); + VectorClear( velocity ); + VectorMA( velocity, +random()*baseRandomVelocity, right, velocity ); + VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); + velocity[2] = jump + crandom()*baseRandomVelocity; + VectorAdd( velocity, playerVelocityScaled, velocity ); + VectorCopy( bodyAngles, angles ); + angles[ROLL] += 70; + angles[PITCH] += 45; + CG_LaunchGib( origin, angles, velocity, cgs.media.gibArm ); + if (--numGibs <= 0) { + return; + } - VectorCopy( playerOrigin, origin ); - origin[2] = bottom + 0.80 * playerHeight; - VectorClear( velocity ); - // Chest is a more "central" and "heavier" piece, - // so it gets less random velocity. - velocity[0] = 0.5*crandom()*baseRandomVelocity; - velocity[1] = 0.5*crandom()*baseRandomVelocity; - velocity[2] = jump + 0.5*crandom()*baseRandomVelocity; - VectorAdd( velocity, playerVelocityScaled, velocity ); - CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibChest ); + VectorCopy( playerOrigin, origin ); + origin[2] = bottom + 0.80 * playerHeight; + VectorClear( velocity ); + // Chest is a more "central" and "heavier" piece, + // so it gets less random velocity. + velocity[0] = 0.5*crandom()*baseRandomVelocity; + velocity[1] = 0.5*crandom()*baseRandomVelocity; + velocity[2] = jump + 0.5*crandom()*baseRandomVelocity; + VectorAdd( velocity, playerVelocityScaled, velocity ); + CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibChest ); + if (--numGibs <= 0) { + return; + } - VectorCopy( playerOrigin, origin ); - origin[2] = bottom + 0.66 * playerHeight; - VectorMA( origin, 0.8 * playerRadius, right, origin ); - VectorMA( origin, 0.2 * playerRadius, forward, origin ); - VectorClear( velocity ); - velocity[0] = crandom()*baseRandomVelocity; - velocity[1] = crandom()*baseRandomVelocity; - velocity[2] = jump + crandom()*baseRandomVelocity; - VectorAdd( velocity, playerVelocityScaled, velocity ); - VectorCopy( bodyAngles, angles ); - angles[PITCH] -= 80; - angles[YAW] += 50; - CG_LaunchGib( origin, angles, velocity, cgs.media.gibFist ); + VectorCopy( playerOrigin, origin ); + origin[2] = bottom + 0.66 * playerHeight; + VectorMA( origin, 0.8 * playerRadius, right, origin ); + VectorMA( origin, 0.2 * playerRadius, forward, origin ); + VectorClear( velocity ); + velocity[0] = crandom()*baseRandomVelocity; + velocity[1] = crandom()*baseRandomVelocity; + velocity[2] = jump + crandom()*baseRandomVelocity; + VectorAdd( velocity, playerVelocityScaled, velocity ); + VectorCopy( bodyAngles, angles ); + angles[PITCH] -= 80; + angles[YAW] += 50; + CG_LaunchGib( origin, angles, velocity, cgs.media.gibFist ); + if (--numGibs <= 0) { + return; + } - VectorCopy( playerOrigin, origin ); - origin[2] = bottom + 0.05 * playerHeight; - VectorMA( origin, -0.5 * playerRadius, right, origin ); - VectorMA( origin, -0.5 * playerRadius, forward, origin ); - VectorClear( velocity ); - velocity[0] = crandom()*baseRandomVelocity; - velocity[1] = crandom()*baseRandomVelocity; - velocity[2] = jump + crandom()*baseRandomVelocity; - VectorAdd( velocity, playerVelocityScaled, velocity ); - CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibFoot ); + VectorCopy( playerOrigin, origin ); + origin[2] = bottom + 0.05 * playerHeight; + VectorMA( origin, -0.5 * playerRadius, right, origin ); + VectorMA( origin, -0.5 * playerRadius, forward, origin ); + VectorClear( velocity ); + velocity[0] = crandom()*baseRandomVelocity; + velocity[1] = crandom()*baseRandomVelocity; + velocity[2] = jump + crandom()*baseRandomVelocity; + VectorAdd( velocity, playerVelocityScaled, velocity ); + CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibFoot ); + if (--numGibs <= 0) { + return; + } - VectorCopy( playerOrigin, origin ); - origin[2] = bottom + 0.65 * playerHeight; - VectorMA( origin, -0.6 * playerRadius, right, origin ); - VectorMA( origin, +0.2 * playerRadius, forward, origin ); - VectorClear( velocity ); - VectorMA( velocity, -random()*baseRandomVelocity, right, velocity ); - VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); - velocity[2] = jump + crandom()*baseRandomVelocity; - VectorAdd( velocity, playerVelocityScaled, velocity ); - VectorCopy( bodyAngles, angles ); - angles[ROLL] -= 90; - angles[PITCH] -= 75; - CG_LaunchGib( origin, angles, velocity, cgs.media.gibForearm ); + VectorCopy( playerOrigin, origin ); + origin[2] = bottom + 0.65 * playerHeight; + VectorMA( origin, -0.6 * playerRadius, right, origin ); + VectorMA( origin, +0.2 * playerRadius, forward, origin ); + VectorClear( velocity ); + VectorMA( velocity, -random()*baseRandomVelocity, right, velocity ); + VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); + velocity[2] = jump + crandom()*baseRandomVelocity; + VectorAdd( velocity, playerVelocityScaled, velocity ); + VectorCopy( bodyAngles, angles ); + angles[ROLL] -= 90; + angles[PITCH] -= 75; + CG_LaunchGib( origin, angles, velocity, cgs.media.gibForearm ); + if (--numGibs <= 0) { + return; + } - VectorCopy( playerOrigin, origin ); - origin[2] = bottom + 0.57 * playerHeight; - VectorClear( velocity ); - velocity[0] = crandom()*baseRandomVelocity; - velocity[1] = crandom()*baseRandomVelocity; - velocity[2] = jump + crandom()*baseRandomVelocity; - VectorAdd( velocity, playerVelocityScaled, velocity ); - CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibIntestine ); + VectorCopy( playerOrigin, origin ); + origin[2] = bottom + 0.57 * playerHeight; + VectorClear( velocity ); + velocity[0] = crandom()*baseRandomVelocity; + velocity[1] = crandom()*baseRandomVelocity; + velocity[2] = jump + crandom()*baseRandomVelocity; + VectorAdd( velocity, playerVelocityScaled, velocity ); + CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibIntestine ); + if (--numGibs <= 0) { + return; + } - VectorCopy( playerOrigin, origin ); - origin[2] = bottom + 0.42 * playerHeight; - VectorMA( origin, 0.5 * playerRadius, right, origin ); - VectorMA( origin, 0.1 * playerRadius, forward, origin ); - VectorClear( velocity ); - VectorMA( velocity, +random()*baseRandomVelocity, right, velocity ); - VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); - velocity[2] = jump + crandom()*baseRandomVelocity; - VectorAdd( velocity, playerVelocityScaled, velocity ); - VectorCopy( bodyAngles, angles ); - angles[ROLL] -= 30; - angles[PITCH] -= 15; - CG_LaunchGib( origin, angles, velocity, cgs.media.gibLeg ); + VectorCopy( playerOrigin, origin ); + origin[2] = bottom + 0.42 * playerHeight; + VectorMA( origin, 0.5 * playerRadius, right, origin ); + VectorMA( origin, 0.1 * playerRadius, forward, origin ); + VectorClear( velocity ); + VectorMA( velocity, +random()*baseRandomVelocity, right, velocity ); + VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); + velocity[2] = jump + crandom()*baseRandomVelocity; + VectorAdd( velocity, playerVelocityScaled, velocity ); + VectorCopy( bodyAngles, angles ); + angles[ROLL] -= 30; + angles[PITCH] -= 15; + CG_LaunchGib( origin, angles, velocity, cgs.media.gibLeg ); + if (--numGibs <= 0) { + return; + } - VectorCopy( playerOrigin, origin ); - origin[2] = bottom + 0.44 * playerHeight; - VectorMA( origin, -0.5 * playerRadius, right, origin ); - VectorMA( origin, -0.2 * playerRadius, forward, origin ); - VectorClear( velocity ); - VectorMA( velocity, -random()*baseRandomVelocity, right, velocity ); - VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); - velocity[2] = jump + crandom()*baseRandomVelocity; - VectorAdd( velocity, playerVelocityScaled, velocity ); - VectorCopy( bodyAngles, angles ); - angles[PITCH] += 15; - CG_LaunchGib( origin, angles, velocity, cgs.media.gibLeg ); + VectorCopy( playerOrigin, origin ); + origin[2] = bottom + 0.44 * playerHeight; + VectorMA( origin, -0.5 * playerRadius, right, origin ); + VectorMA( origin, -0.2 * playerRadius, forward, origin ); + VectorClear( velocity ); + VectorMA( velocity, -random()*baseRandomVelocity, right, velocity ); + VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); + velocity[2] = jump + crandom()*baseRandomVelocity; + VectorAdd( velocity, playerVelocityScaled, velocity ); + VectorCopy( bodyAngles, angles ); + angles[PITCH] += 15; + CG_LaunchGib( origin, angles, velocity, cgs.media.gibLeg ); + if (--numGibs <= 0) { + return; + } + } while (numGibs > 0); } void CG_GibPlayerOld( const vec3_t playerOrigin ) { vec3_t origin, angles, velocity; From b820d7cb46a7f5298f58daee37a910bb86e73567 Mon Sep 17 00:00:00 2001 From: WofWca Date: Wed, 14 Jan 2026 22:46:48 +0400 Subject: [PATCH 17/30] fix: own gibs not flying away from damage --- code/cgame/cg_event.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/code/cgame/cg_event.c b/code/cgame/cg_event.c index 14ed9bd3..39fc1268 100644 --- a/code/cgame/cg_event.c +++ b/code/cgame/cg_event.c @@ -1218,7 +1218,18 @@ void CG_EntityEvent( centity_t *cent, vec3_t position, int entityNum ) { if (cg_oldGibs.integer) { CG_GibPlayerOld( cent->lerpOrigin ); } else { - CG_GibPlayer( cent->lerpOrigin, cent->lerpAngles, es->pos.trDelta ); + if ( es->number == cg.snap->ps.clientNum ) { + // Apparently at this point `es->pos.trDelta` doesn't yet have + // the knockback from the damage that gibbed us, + // so we have to differentiate between self and non-self, + // and use `cg.predictedPlayerState.velocity` + // if it's ourself. + CG_GibPlayer( cent->lerpOrigin, cent->lerpAngles, + cg.predictedPlayerState.velocity ); + } else { + CG_GibPlayer( cent->lerpOrigin, cent->lerpAngles, + es->pos.trDelta ); + } } break; From a3c476e2502245bce3e5cc746e6ae8073ee1df20 Mon Sep 17 00:00:00 2001 From: WofWca Date: Mon, 19 Jan 2026 21:36:34 +0400 Subject: [PATCH 18/30] feat: make gibs tumble (rotate) --- code/cgame/cg_cvar.h | 1 + code/cgame/cg_effects.c | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/code/cgame/cg_cvar.h b/code/cgame/cg_cvar.h index 2adbd632..e8a4b818 100644 --- a/code/cgame/cg_cvar.h +++ b/code/cgame/cg_cvar.h @@ -23,6 +23,7 @@ CG_CVAR( cg_gibsInheritPlayerVelocity, "cg_gibsInheritPlayerVelocity", "1.0", CV CG_CVAR( cg_gibsExtraRandomVelocity, "cg_gibsExtraRandomVelocity", "250", CVAR_ARCHIVE ) CG_CVAR( cg_gibsExtraVerticalVelocity, "cg_gibsExtraVerticalVelocity", "100", CVAR_ARCHIVE ) CG_CVAR( cg_gibsBounceFactor, "cg_gibsBounceFactor", "0.4", CVAR_ARCHIVE ) +CG_CVAR( cg_gibsRotationFactor, "cg_gibsRotationFactor", "1.0", CVAR_ARCHIVE ) CG_CVAR( cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE ) CG_CVAR( cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE ) CG_CVAR( cg_drawTimer, "cg_drawTimer", "0", CVAR_ARCHIVE ) diff --git a/code/cgame/cg_effects.c b/code/cgame/cg_effects.c index 0405c746..3474eed7 100644 --- a/code/cgame/cg_effects.c +++ b/code/cgame/cg_effects.c @@ -556,6 +556,32 @@ static void CG_LaunchGib( const vec3_t origin, const vec3_t angles, le->bounceFactor = cg_oldGibs.integer ? 0.6f : cg_gibsBounceFactor.value; + if (!cg_oldGibs.integer) { + // `VectorLength` would be more precise, but this is faster + // and good enough for randomness. + float speedIsh = fabs(velocity[0]) + fabs(velocity[1]) + fabs(velocity[2]); + int i; + int mainRotationAxis = rand() % 3; + + le->leFlags = LEF_TUMBLE; + le->angles.trType = TR_LINEAR; + le->angles.trTime = cg.time; + VectorCopy( angles, le->angles.trBase ); + // Just a few degrees of randomness. + le->angles.trBase[PITCH] += rand()&7; + le->angles.trBase[YAW] += rand()&7; + le->angles.trBase[ROLL] += rand()&7; + // TODO the tumble speed should probably depend on damage instead, + // or at least on random velocity. + for ( i = 0; i < 3; i++ ) { + // The numbers are not based on science, but it looks like + // having one axis be bigger than others makes rotation look natural. + float axisMul = mainRotationAxis == i ? 1 : 0.25; + le->angles.trDelta[i] = speedIsh * axisMul * 0.5 * + cg_gibsRotationFactor.value * crandom(); + } + } + le->leBounceSoundType = LEBS_BLOOD; le->leMarkType = LEMT_BLOOD; } From d9e56fb54e5658a4dc367f710b6f2f0da9c86fb5 Mon Sep 17 00:00:00 2001 From: WofWca Date: Sat, 24 Jan 2026 12:59:46 +0400 Subject: [PATCH 19/30] refactor: gibs: use `up` vec, not `origin[2]` This will let us handle non-0 pitch values. --- code/cgame/cg_effects.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/code/cgame/cg_effects.c b/code/cgame/cg_effects.c index 3474eed7..c1aeb1a2 100644 --- a/code/cgame/cg_effects.c +++ b/code/cgame/cg_effects.c @@ -609,7 +609,6 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, // (do we have them available on the client though?), // to account for crounching or for the "lying on the ground dead" state. float playerHeight = 32 - MINS_Z; - float bottom = playerOrigin[2] + MINS_Z; float playerRadius = 15; float baseRandomVelocity = cg_gibsExtraRandomVelocity.value; vec3_t playerVelocityScaled; @@ -633,7 +632,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, // This is in line with the original behavior of `CG_GibPlayer`. VectorCopy( playerOrigin, origin ); - origin[2] = bottom + 0.95 * playerHeight; + VectorMA(origin, MINS_Z + 0.95 * playerHeight, up, origin); VectorClear( velocity ); velocity[0] = crandom()*baseRandomVelocity; velocity[1] = crandom()*baseRandomVelocity; @@ -655,7 +654,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, } VectorCopy( playerOrigin, origin ); - origin[2] = bottom + 0.65 * playerHeight; + VectorMA( origin, MINS_Z + 0.65 * playerHeight, up, origin ); VectorClear( velocity ); velocity[0] = crandom()*baseRandomVelocity; velocity[1] = crandom()*baseRandomVelocity; @@ -667,7 +666,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, } VectorCopy( playerOrigin, origin ); - origin[2] = bottom + 0.78 * playerHeight; + VectorMA( origin, MINS_Z + 0.78 * playerHeight, up, origin ); VectorMA( origin, 0.8 * playerRadius, right, origin ); VectorMA( origin, -0.3 * playerRadius, forward, origin ); VectorClear( velocity ); @@ -684,7 +683,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, } VectorCopy( playerOrigin, origin ); - origin[2] = bottom + 0.80 * playerHeight; + VectorMA( origin, MINS_Z + 0.80 * playerHeight, up, origin ); VectorClear( velocity ); // Chest is a more "central" and "heavier" piece, // so it gets less random velocity. @@ -698,7 +697,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, } VectorCopy( playerOrigin, origin ); - origin[2] = bottom + 0.66 * playerHeight; + VectorMA( origin, MINS_Z + 0.66 * playerHeight, up, origin ); VectorMA( origin, 0.8 * playerRadius, right, origin ); VectorMA( origin, 0.2 * playerRadius, forward, origin ); VectorClear( velocity ); @@ -715,7 +714,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, } VectorCopy( playerOrigin, origin ); - origin[2] = bottom + 0.05 * playerHeight; + VectorMA( origin, MINS_Z + 0.05 * playerHeight, up, origin ); VectorMA( origin, -0.5 * playerRadius, right, origin ); VectorMA( origin, -0.5 * playerRadius, forward, origin ); VectorClear( velocity ); @@ -729,7 +728,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, } VectorCopy( playerOrigin, origin ); - origin[2] = bottom + 0.65 * playerHeight; + VectorMA( origin, MINS_Z + 0.65 * playerHeight, up, origin ); VectorMA( origin, -0.6 * playerRadius, right, origin ); VectorMA( origin, +0.2 * playerRadius, forward, origin ); VectorClear( velocity ); @@ -746,7 +745,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, } VectorCopy( playerOrigin, origin ); - origin[2] = bottom + 0.57 * playerHeight; + VectorMA( origin, MINS_Z + 0.57 * playerHeight, up, origin ); VectorClear( velocity ); velocity[0] = crandom()*baseRandomVelocity; velocity[1] = crandom()*baseRandomVelocity; @@ -758,7 +757,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, } VectorCopy( playerOrigin, origin ); - origin[2] = bottom + 0.42 * playerHeight; + VectorMA( origin, MINS_Z + 0.42 * playerHeight, up, origin ); VectorMA( origin, 0.5 * playerRadius, right, origin ); VectorMA( origin, 0.1 * playerRadius, forward, origin ); VectorClear( velocity ); @@ -775,7 +774,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, } VectorCopy( playerOrigin, origin ); - origin[2] = bottom + 0.44 * playerHeight; + VectorMA( origin, MINS_Z + 0.44 * playerHeight, up, origin ); VectorMA( origin, -0.5 * playerRadius, right, origin ); VectorMA( origin, -0.2 * playerRadius, forward, origin ); VectorClear( velocity ); From 9a8f82f69c9dea61ec558732efc1f7b4086ecf71 Mon Sep 17 00:00:00 2001 From: WofWca Date: Sat, 24 Jan 2026 16:48:44 +0400 Subject: [PATCH 20/30] feat: better gib positions for dead bodies Before this commit we positioned gibs close to where the player model's part are located, but only assuming that the body is in an upright position. That resulted in dead bodies' gibs getting launched from pretty far away from where the model is actually located. This commit fixes that. --- code/cgame/cg_effects.c | 91 +++++++++++++++++++++++++++++++-------- code/cgame/cg_event.c | 7 ++- code/cgame/cg_local.h | 3 +- code/cgame/cg_localents.c | 2 +- 4 files changed, 80 insertions(+), 23 deletions(-) diff --git a/code/cgame/cg_effects.c b/code/cgame/cg_effects.c index c1aeb1a2..70dfbd36 100644 --- a/code/cgame/cg_effects.c +++ b/code/cgame/cg_effects.c @@ -586,6 +586,50 @@ static void CG_LaunchGib( const vec3_t origin, const vec3_t angles, le->leMarkType = LEMT_BLOOD; } +// If it's a dead body playing a death animation, +// gradually transition the body position and angles from upright +// to "lying flat on the ground". +void AdjustPositionIfDeathAnimation( const lerpFrame_t *anim, vec3_t origin, + vec3_t bodyAngles, vec3_t lookDirAngles ) { + // 0 means that the body is fully erect, + // 1 means it's lying flat on the ground. + float deathAnimationProgress = 0; + if ( + // Is this a death / dead animation? + (anim->animationNumber & ~ANIM_TOGGLEBIT) <= BOTH_DEAD3 && + (anim->animationNumber & ~ANIM_TOGGLEBIT) >= BOTH_DEATH1 && + // More sanity checks + anim->animation && + anim->animation->numFrames > 0 + ) { + const int frameOfAnimation = anim->frame - anim->animation->firstFrame; + if ( + frameOfAnimation < 0 || + frameOfAnimation >= anim->animation->numFrames + ) { + // Out of range. This seems to happen + // when we haven't yet managed to start the death animation. + // Maybe we're looking at the wrong things, + // but this works fine. + deathAnimationProgress = 0; + } else { + deathAnimationProgress = + (float)(frameOfAnimation + 1) / anim->animation->numFrames; + } + } + + // TODO fix: with body sinking, gibs get stuck in the floor. + origin[2] += deathAnimationProgress * (MINS_Z + PLAYER_WIDTH / 1.8f); + // From upright to facing up. + // TODO fix: but sometimes the "dead" animation is such that + // the player is facing down. + bodyAngles[PITCH] = 360 - deathAnimationProgress * 90; + lookDirAngles[PITCH] += - deathAnimationProgress * 90; + // Normalize. Doesn't seem to be necessary, but let's do it. + if (lookDirAngles[PITCH] < 0) { + lookDirAngles[PITCH] += 360; + } +} /* =================== CG_GibPlayer @@ -597,19 +641,20 @@ Generated a bunch of gibs launching out from the bodies location #define GIB_VELOCITY 250 #define GIB_JUMP 250 void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, - const vec3_t playerVelocity ) { - vec3_t origin, velocity; + const vec3_t playerVelocity, + const lerpFrame_t *bodyAnimation ) { + vec3_t baseOrigin, origin, velocity; // Generally only the head should have pitch, // the rest of the body is upright. vec3_t bodyAngles; - vec3_t angles; + vec3_t lookDirAngles, angles; vec3_t forward, right, up; // See `playerMins`, `playerMaxs`. // TODO we could try to check the actual `mins` and `maxs` // (do we have them available on the client though?), - // to account for crounching or for the "lying on the ground dead" state. + // to account for crounching. float playerHeight = 32 - MINS_Z; - float playerRadius = 15; + float playerRadius = PLAYER_WIDTH; float baseRandomVelocity = cg_gibsExtraRandomVelocity.value; vec3_t playerVelocityScaled; float jump = cg_gibsExtraVerticalVelocity.value; @@ -620,9 +665,17 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, return; } + VectorCopy( playerOrigin, baseOrigin ); + VectorCopy( playerAngles, lookDirAngles ); VectorCopy( playerAngles, bodyAngles ); - // This way `up` is always `{0,0,1}`. - bodyAngles[PITCH] = 0; + if ( bodyAnimation ) { + AdjustPositionIfDeathAnimation( bodyAnimation, baseOrigin, bodyAngles, lookDirAngles ); + } else { + bodyAngles[PITCH] = 0; + } + // TODO fix: if `AdjustPositionIfDeathAnimation()` ran, + // `forward` could potentially be pointing up, + // so some of our velocity calculations below are not quite right. AngleVectors( bodyAngles, forward, right, up ); VectorScale( playerVelocity, cg_gibsInheritPlayerVelocity.value, playerVelocityScaled ); @@ -631,7 +684,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, // Note that one gib will get launched even if `numGibs == 0`. // This is in line with the original behavior of `CG_GibPlayer`. - VectorCopy( playerOrigin, origin ); + VectorCopy( baseOrigin, origin ); VectorMA(origin, MINS_Z + 0.95 * playerHeight, up, origin); VectorClear( velocity ); velocity[0] = crandom()*baseRandomVelocity; @@ -644,16 +697,16 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, velocity[2] = jump + random()*baseRandomVelocity; VectorAdd( velocity, playerVelocityScaled, velocity ); if ( !skullLaunched && (rand() & 1) ) { - CG_LaunchGib( origin, playerAngles, velocity, cgs.media.gibSkull ); + CG_LaunchGib( origin, lookDirAngles, velocity, cgs.media.gibSkull ); skullLaunched = qtrue; } else { - CG_LaunchGib( origin, playerAngles, velocity, cgs.media.gibBrain ); + CG_LaunchGib( origin, lookDirAngles, velocity, cgs.media.gibBrain ); } if (--numGibs <= 0) { return; } - VectorCopy( playerOrigin, origin ); + VectorCopy( baseOrigin, origin ); VectorMA( origin, MINS_Z + 0.65 * playerHeight, up, origin ); VectorClear( velocity ); velocity[0] = crandom()*baseRandomVelocity; @@ -665,7 +718,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, return; } - VectorCopy( playerOrigin, origin ); + VectorCopy( baseOrigin, origin ); VectorMA( origin, MINS_Z + 0.78 * playerHeight, up, origin ); VectorMA( origin, 0.8 * playerRadius, right, origin ); VectorMA( origin, -0.3 * playerRadius, forward, origin ); @@ -682,7 +735,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, return; } - VectorCopy( playerOrigin, origin ); + VectorCopy( baseOrigin, origin ); VectorMA( origin, MINS_Z + 0.80 * playerHeight, up, origin ); VectorClear( velocity ); // Chest is a more "central" and "heavier" piece, @@ -696,7 +749,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, return; } - VectorCopy( playerOrigin, origin ); + VectorCopy( baseOrigin, origin ); VectorMA( origin, MINS_Z + 0.66 * playerHeight, up, origin ); VectorMA( origin, 0.8 * playerRadius, right, origin ); VectorMA( origin, 0.2 * playerRadius, forward, origin ); @@ -713,7 +766,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, return; } - VectorCopy( playerOrigin, origin ); + VectorCopy( baseOrigin, origin ); VectorMA( origin, MINS_Z + 0.05 * playerHeight, up, origin ); VectorMA( origin, -0.5 * playerRadius, right, origin ); VectorMA( origin, -0.5 * playerRadius, forward, origin ); @@ -727,7 +780,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, return; } - VectorCopy( playerOrigin, origin ); + VectorCopy( baseOrigin, origin ); VectorMA( origin, MINS_Z + 0.65 * playerHeight, up, origin ); VectorMA( origin, -0.6 * playerRadius, right, origin ); VectorMA( origin, +0.2 * playerRadius, forward, origin ); @@ -744,7 +797,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, return; } - VectorCopy( playerOrigin, origin ); + VectorCopy( baseOrigin, origin ); VectorMA( origin, MINS_Z + 0.57 * playerHeight, up, origin ); VectorClear( velocity ); velocity[0] = crandom()*baseRandomVelocity; @@ -756,7 +809,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, return; } - VectorCopy( playerOrigin, origin ); + VectorCopy( baseOrigin, origin ); VectorMA( origin, MINS_Z + 0.42 * playerHeight, up, origin ); VectorMA( origin, 0.5 * playerRadius, right, origin ); VectorMA( origin, 0.1 * playerRadius, forward, origin ); @@ -773,7 +826,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, return; } - VectorCopy( playerOrigin, origin ); + VectorCopy( baseOrigin, origin ); VectorMA( origin, MINS_Z + 0.44 * playerHeight, up, origin ); VectorMA( origin, -0.5 * playerRadius, right, origin ); VectorMA( origin, -0.2 * playerRadius, forward, origin ); diff --git a/code/cgame/cg_event.c b/code/cgame/cg_event.c index 39fc1268..1c574305 100644 --- a/code/cgame/cg_event.c +++ b/code/cgame/cg_event.c @@ -1224,11 +1224,14 @@ void CG_EntityEvent( centity_t *cent, vec3_t position, int entityNum ) { // so we have to differentiate between self and non-self, // and use `cg.predictedPlayerState.velocity` // if it's ourself. + // `cent->pe.torso` also appears to be not good here. CG_GibPlayer( cent->lerpOrigin, cent->lerpAngles, - cg.predictedPlayerState.velocity ); + cg.predictedPlayerState.velocity, + &cg.predictedPlayerEntity.pe.torso ); } else { CG_GibPlayer( cent->lerpOrigin, cent->lerpAngles, - es->pos.trDelta ); + es->pos.trDelta, + ¢->pe.torso ); } } break; diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h index a79d4d98..8fb8e77b 100644 --- a/code/cgame/cg_local.h +++ b/code/cgame/cg_local.h @@ -1407,7 +1407,8 @@ void CG_LightningBoltBeam( vec3_t start, vec3_t end ); void CG_ScorePlum( int client, const vec3_t origin, int score ); void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, - const vec3_t playerVelocity ); + const vec3_t playerVelocity, + const lerpFrame_t *bodyAnimation ); void CG_GibPlayerOld( const vec3_t playerOrigin ); void CG_BigExplode( vec3_t playerOrigin ); diff --git a/code/cgame/cg_localents.c b/code/cgame/cg_localents.c index f0361378..d2b4ce8b 100644 --- a/code/cgame/cg_localents.c +++ b/code/cgame/cg_localents.c @@ -782,7 +782,7 @@ void CG_AddInvulnerabilityJuiced( localEntity_t *le ) { // Angles don't matter much here. VectorClear( angles ); - CG_GibPlayer( le->refEntity.origin, angles, le->pos.trDelta ); + CG_GibPlayer( le->refEntity.origin, angles, le->pos.trDelta, NULL ); } } else { From 5266bc6e9609988e3ad64a688836b14bf9b85ea0 Mon Sep 17 00:00:00 2001 From: WofWca Date: Sat, 24 Jan 2026 16:56:17 +0400 Subject: [PATCH 21/30] fix: better gib velocities for dead bodies --- code/cgame/cg_effects.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/code/cgame/cg_effects.c b/code/cgame/cg_effects.c index 70dfbd36..0029493b 100644 --- a/code/cgame/cg_effects.c +++ b/code/cgame/cg_effects.c @@ -673,9 +673,6 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, } else { bodyAngles[PITCH] = 0; } - // TODO fix: if `AdjustPositionIfDeathAnimation()` ran, - // `forward` could potentially be pointing up, - // so some of our velocity calculations below are not quite right. AngleVectors( bodyAngles, forward, right, up ); VectorScale( playerVelocity, cg_gibsInheritPlayerVelocity.value, playerVelocityScaled ); @@ -687,14 +684,15 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, VectorCopy( baseOrigin, origin ); VectorMA(origin, MINS_Z + 0.95 * playerHeight, up, origin); VectorClear( velocity ); - velocity[0] = crandom()*baseRandomVelocity; - velocity[1] = crandom()*baseRandomVelocity; + VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); + VectorMA( velocity, crandom()*baseRandomVelocity, right, velocity ); // For the skull / brain we want the random velocity // to never have downwards (inwards) component, // so we use `random` instead of `crandom`. // We also do the same for other gibs, // but for the left / right velocity components. - velocity[2] = jump + random()*baseRandomVelocity; + VectorMA( velocity, random()*baseRandomVelocity, up, velocity ); + velocity[2] += jump; VectorAdd( velocity, playerVelocityScaled, velocity ); if ( !skullLaunched && (rand() & 1) ) { CG_LaunchGib( origin, lookDirAngles, velocity, cgs.media.gibSkull ); @@ -725,7 +723,8 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, VectorClear( velocity ); VectorMA( velocity, +random()*baseRandomVelocity, right, velocity ); VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); - velocity[2] = jump + crandom()*baseRandomVelocity; + VectorMA( velocity, crandom()*baseRandomVelocity, up, velocity ); + velocity[2] += jump; VectorAdd( velocity, playerVelocityScaled, velocity ); VectorCopy( bodyAngles, angles ); angles[ROLL] += 70; @@ -787,7 +786,8 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, VectorClear( velocity ); VectorMA( velocity, -random()*baseRandomVelocity, right, velocity ); VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); - velocity[2] = jump + crandom()*baseRandomVelocity; + VectorMA( velocity, crandom()*baseRandomVelocity, up, velocity ); + velocity[2] += jump; VectorAdd( velocity, playerVelocityScaled, velocity ); VectorCopy( bodyAngles, angles ); angles[ROLL] -= 90; @@ -816,7 +816,8 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, VectorClear( velocity ); VectorMA( velocity, +random()*baseRandomVelocity, right, velocity ); VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); - velocity[2] = jump + crandom()*baseRandomVelocity; + VectorMA( velocity, crandom()*baseRandomVelocity, up, velocity ); + velocity[2] += jump; VectorAdd( velocity, playerVelocityScaled, velocity ); VectorCopy( bodyAngles, angles ); angles[ROLL] -= 30; @@ -833,7 +834,8 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, VectorClear( velocity ); VectorMA( velocity, -random()*baseRandomVelocity, right, velocity ); VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); - velocity[2] = jump + crandom()*baseRandomVelocity; + VectorMA( velocity, crandom()*baseRandomVelocity, up, velocity ); + velocity[2] += jump; VectorAdd( velocity, playerVelocityScaled, velocity ); VectorCopy( bodyAngles, angles ); angles[PITCH] += 15; From 5170b60621eafb17bbfc9606100b5161401c4261 Mon Sep 17 00:00:00 2001 From: WofWca Date: Thu, 22 Jan 2026 13:55:28 +0400 Subject: [PATCH 22/30] feat: better gibs direction on missile direct hit Related: - https://github.com/WofWca/quake3-better-gibs-mod/issues/2. --- code/game/g_combat.c | 111 +++++++++++++++++++++++++++++++++++++++--- code/game/g_cvar.h | 13 +++++ code/game/g_missile.c | 5 +- 3 files changed, 122 insertions(+), 7 deletions(-) diff --git a/code/game/g_combat.c b/code/game/g_combat.c index c5fa753a..0891843a 100644 --- a/code/game/g_combat.c +++ b/code/game/g_combat.c @@ -215,6 +215,20 @@ void LookAtKiller( gentity_t *self, gentity_t *inflictor, gentity_t *attacker ) self->client->ps.stats[STAT_DEAD_YAW] = vectoyaw ( dir ); } +/* +================== +KnockbackToKnockbackSpeed +================== +*/ +static float KnockbackToKnockbackSpeed( int knockback ) { + float mass; + + mass = 200; + + return g_knockback.value * (float)knockback / mass; +} + + /* ================== GibEntity @@ -763,6 +777,77 @@ int G_InvulnerabilityEffect( gentity_t *targ, vec3_t dir, vec3_t point, vec3_t i } } #endif + +/* +================ +AdjustKnockbackIfDirectMissileHit + +Adjusts knockback direction from missiles' direct hits so gibs look better. +By default the knockback direction is the direction +in which the missile is flying (see `G_MissileImpact`), +which is not great when the missile hits just the edge of the player's feet. +One would expect that the gibs fly up then. +Which is what this function ensures. + +Assumes that the new `targ->health` is already set. +================ +*/ +static void AdjustKnockbackIfDirectMissileHit( const gentity_t *targ, + const gentity_t *inflictor, const vec3_t dir, const vec3_t point, + int knockback, const vec3_t oldKvel, int dflags, int mod, vec3_t velChange ) +{ + vec3_t dir2; // Direction from the explosion to the player's center. + vec3_t kvel2, finalDir; + + VectorClear( velChange ); + + if (!( + knockback && targ->client && + inflictor && + inflictor->s.eType == ET_MISSILE && + // Make sure it has big splash radius, + // which e.g. is not the case for the nailgun + // (only damages on direct hit) and plasmagun (small explosion radius). + inflictor->splashRadius > 40 && inflictor->splashDamage > 0 && + // But we only handle direct hits here. + !( dflags & DAMAGE_RADIUS ) + // Another way to check for direct hits. + // mod != inflictor->splashMethodOfDeath + )) { + return; + } + + // Note that the missile direction and the direction + // from the explosion to the origin could be quite different, + // so we need to calculate the direction first + // instead of applying velocities right away, + // which would have resulted in less knockback speed. + + // Copy-pasted from `G_RadiusDamage`. + VectorSubtract (targ->r.currentOrigin, point, dir2); + // Set a value lower than the original 24 + // because that more closely corresponds to the position of the chest. + // dir2[2] += 24; + dir2[2] += 20; + if ( VectorNormalize( dir2 ) <= 0.0 ) { + return; + } + + VectorClear( finalDir ); + VectorMA( finalDir, g_gibsMissileDirectionKnockbackWeight.value, dir, finalDir ); + VectorMA( finalDir, (1 - g_gibsMissileDirectionKnockbackWeight.value), dir2, finalDir ); + if ( VectorNormalize( finalDir ) <= 0.0 ) { + // No particular direction, so let's just apply no knockback at all. + VectorScale( oldKvel, -1, velChange ); + return; + } + + // "Cancel" the old knockback. + VectorScale( oldKvel, -1, velChange ); + VectorScale (finalDir, KnockbackToKnockbackSpeed( knockback ), kvel2); + VectorAdd (velChange, kvel2, velChange); +} + /* ============ G_Damage @@ -793,11 +878,14 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, int take; int asave; int knockback; + vec3_t kvel; int max; #ifdef MISSIONPACK vec3_t bouncedir, impactpoint; #endif + VectorClear( kvel ); + if (!targ->takedamage) { return; } @@ -898,12 +986,7 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, // figure momentum add, even if the damage won't be taken if ( knockback && targ->client ) { - vec3_t kvel; - float mass; - - mass = 200; - - VectorScale (dir, g_knockback.value * (float)knockback / mass, kvel); + VectorScale (dir, KnockbackToKnockbackSpeed( knockback ), kvel); VectorAdd (targ->client->ps.velocity, kvel, targ->client->ps.velocity); // set the timer so that the other client can't cancel @@ -1076,6 +1159,22 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, if (targ->health < -999) targ->health = -999; + if ( + // If it's not a gib death, do not apply this adjustment, + // because some might say that it would affect gameplay. + // Namely that dead bodies can e.g. absorb missiles, + // so it _does_ matter where they fly. + // The condition is copy-pasted from `player_die` (partially). + targ->health <= GIB_HEALTH && g_blood.integer && + !g_oldGibs.integer && + g_gibsMissileDirectionKnockbackWeight.value != 1.0 && + targ->client ) { + vec3_t velChange; + AdjustKnockbackIfDirectMissileHit( targ, inflictor, dir, point, + knockback, kvel, dflags, mod, velChange ); + VectorAdd(targ->client->ps.velocity, velChange, targ->client->ps.velocity); + } + targ->enemy = attacker; targ->die (targ, inflictor, attacker, take, mod); return; diff --git a/code/game/g_cvar.h b/code/game/g_cvar.h index c9a8ad9b..24d3d2bb 100644 --- a/code/game/g_cvar.h +++ b/code/game/g_cvar.h @@ -62,6 +62,19 @@ G_CVAR( g_debugDamage, "g_debugDamage", "0", 0, 0, qfalse, qfalse ) G_CVAR( g_debugAlloc, "g_debugAlloc", "0", 0, 0, qfalse, qfalse ) G_CVAR( g_motd, "g_motd", "", 0, 0, qfalse, qfalse ) G_CVAR( g_blood, "com_blood", "1", 0, 0, qfalse, qfalse ) +G_CVAR( g_oldGibs, "g_oldGibs", "0", CVAR_ARCHIVE, 0, qfalse, qfalse ) +// How much the movement direction of a missile affects the knockback direction +// when gibbing, as opposed to the direction from the center of the explosion. +// This makes sure that if the missile hits the player's feet, +// the gibs will fly up, so that there is not a big difference +// between hitting the player's feet and the ground not far +// from the player's feet. +// +// Setting to 1 restores the old behavior. +// +// Note that this affects not just the gibs +// but also the camera velocity of the gibbed player. +G_CVAR( g_gibsMissileDirectionKnockbackWeight, "g_gibsMissileDirectionKnockbackWeight", "0.5", CVAR_ARCHIVE, 0, qfalse, qfalse ) G_CVAR( g_podiumDist, "g_podiumDist", "80", 0, 0, qfalse, qfalse ) G_CVAR( g_podiumDrop, "g_podiumDrop", "70", 0, 0, qfalse, qfalse ) diff --git a/code/game/g_missile.c b/code/game/g_missile.c index 8f3418d2..b4dbd0d0 100644 --- a/code/game/g_missile.c +++ b/code/game/g_missile.c @@ -298,8 +298,11 @@ void G_MissileImpact( gentity_t *ent, trace_t *trace ) { if ( VectorLength( velocity ) == 0 ) { velocity[2] = 1; // stepped on a grenade } + // Originally we used `ent->s.origin` instead of `trace->endpos`, + // but the latter is more accurate. + // We also already use it below for `G_RadiusDamage`. G_Damage (other, ent, &g_entities[ent->r.ownerNum], velocity, - ent->s.origin, ent->damage, + trace->endpos, ent->damage, 0, ent->methodOfDeath); } } From 4553eeca7e35627aa45196feb0666e555108a44a Mon Sep 17 00:00:00 2001 From: WofWca Date: Wed, 18 Feb 2026 21:06:24 +0400 Subject: [PATCH 23/30] refactor: rename `ShouldPostponeDeathOrGib` --- code/game/g_combat.c | 6 +++--- code/game/g_local.h | 2 +- code/game/g_weapon.c | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/code/game/g_combat.c b/code/game/g_combat.c index 0891843a..7cee34ba 100644 --- a/code/game/g_combat.c +++ b/code/game/g_combat.c @@ -602,7 +602,7 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int // where the shotgun doesn't gib unless you aim at the feet, // because, since the body is shorter, it would stop getting hit // by other pellets from the same shot. - if ( !ShouldPostponeDeath( meansOfDeath ) ) { + if ( !ShouldPostponeDeathOrGib( meansOfDeath ) ) { SetDeadHeight( self ); } @@ -1136,7 +1136,7 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, } if ( targ->health <= 0 ) { - // Not checking `ShouldPostponeDeath` would cause a bug: + // Not checking `ShouldPostponeDeathOrGib` would cause a bug: // when fragging with the shotgun, // the dead body would not gain momentum (knockback) // from the pellets that come after the pellet @@ -1152,7 +1152,7 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, // // This issiue is similar to // https://github.com/ioquake/ioq3/issues/794. - if ( client && !ShouldPostponeDeath( mod ) ) { + if ( client && !ShouldPostponeDeathOrGib( mod ) ) { SetFlNoKnockback( targ ); } diff --git a/code/game/g_local.h b/code/game/g_local.h index da1d6d60..c11a1069 100644 --- a/code/game/g_local.h +++ b/code/game/g_local.h @@ -538,7 +538,7 @@ void G_Damage (gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_ // - https://github.com/ec-/baseq3a/pull/49. // - https://github.com/WofWca/quake3-better-gibs-mod/issues/12. // - Also `glcient_s.damage_knockback`. -#define ShouldPostponeDeath( mod ) (mod == MOD_SHOTGUN) +#define ShouldPostponeDeathOrGib( mod ) (mod == MOD_SHOTGUN) #define SetDeadHeight( ent ) {ent->r.maxs[2] = DEAD_MAXS_Z;} #define SetFlNoKnockback( ent ) {ent->flags |= FL_NO_KNOCKBACK;} qboolean G_RadiusDamage (vec3_t origin, gentity_t *attacker, float damage, float radius, gentity_t *ignore, int mod); diff --git a/code/game/g_weapon.c b/code/game/g_weapon.c index 2df5c2ba..e9c04c40 100644 --- a/code/game/g_weapon.c +++ b/code/game/g_weapon.c @@ -382,8 +382,9 @@ static void ShotgunPattern( const vec3_t origin, const vec3_t origin2, int seed, } } - // Do what has been postponed in `G_Damage` due to `ShouldPostponeDeath`. - // assert( ShouldPostponeDeath( MOD_SHOTGUN ) ); + // Do what has been postponed in `G_Damage` + // due to `ShouldPostponeDeathOrGib`. + // assert( ShouldPostponeDeathOrGib( MOD_SHOTGUN ) ); ent2 = &g_entities[0]; for (i = 0; i < level.num_entities; i++, ent2++) { if ( !ent2->inuse ) { From dc962a2c9fff5304e4ffccf2bf6b986c6954a2b8 Mon Sep 17 00:00:00 2001 From: WofWca Date: Wed, 18 Feb 2026 21:29:10 +0400 Subject: [PATCH 24/30] fix: shotgun not applying full knockback on gib Related: - https://github.com/ec-/baseq3a/pull/58. - https://github.com/ioquake/ioq3/issues/7. --- code/game/g_combat.c | 12 ++++++++++-- code/game/g_local.h | 3 +++ code/game/g_weapon.c | 9 +++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/code/game/g_combat.c b/code/game/g_combat.c index 7cee34ba..a200a177 100644 --- a/code/game/g_combat.c +++ b/code/game/g_combat.c @@ -276,7 +276,11 @@ void body_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int d return; } - GibEntity( self, 0 ); + if ( ShouldPostponeDeathOrGib( meansOfDeath ) ) { + self->gibScheduled = qtrue; + } else { + GibEntity( self, 0 ); + } } @@ -616,7 +620,11 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int // never gib in a nodrop if ( (self->health <= GIB_HEALTH && !(contents & CONTENTS_NODROP) && g_blood.integer) || meansOfDeath == MOD_SUICIDE) { // gib death - GibEntity( self, killer ); + if ( ShouldPostponeDeathOrGib( meansOfDeath ) ) { + self->gibScheduled = qtrue; + } else { + GibEntity( self, killer ); + } } else { // normal death static int i; diff --git a/code/game/g_local.h b/code/game/g_local.h index c11a1069..b70041f2 100644 --- a/code/game/g_local.h +++ b/code/game/g_local.h @@ -125,6 +125,8 @@ struct gentity_s { int health; qboolean takedamage; + // Whether to `GibEntity` after applying all the pellets of a shotgun shot. + qboolean gibScheduled; int damage; int splashDamage; // quad will increase this without increasing radius @@ -527,6 +529,7 @@ const char *BuildShaderStateConfig( void ); // qboolean CanDamage (gentity_t *targ, vec3_t origin); void G_Damage (gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_t dir, vec3_t point, int damage, int dflags, int mod); +void GibEntity( gentity_t *self, int killer ); // The shotgun does `G_Damage` multiple times, per each pellet. // Normally that would mean that if the target is at 1 HP, // only one pellet would hit them. diff --git a/code/game/g_weapon.c b/code/game/g_weapon.c index e9c04c40..346af66e 100644 --- a/code/game/g_weapon.c +++ b/code/game/g_weapon.c @@ -395,6 +395,15 @@ static void ShotgunPattern( const vec3_t origin, const vec3_t origin2, int seed, SetDeadHeight( ent2 ); SetFlNoKnockback( ent2 ); } + + if ( ent2->gibScheduled ) { + // Note that this is technically differerent from vanilla, + // because vanilla passes `0` as `eventParm if we are gibbing + // a body from body queue. + int killer = ent->s.number; + GibEntity( ent2, killer ); + } + ent2->gibScheduled = qfalse; } // unlagged From 3c01b28a7d74a5d1c106d4fd95cc3f52e4d8c324 Mon Sep 17 00:00:00 2001 From: WofWca Date: Tue, 17 Feb 2026 19:46:52 +0400 Subject: [PATCH 25/30] fix: shotgun not respecting `MAX_KNOCKBACK` --- code/game/g_combat.c | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/code/game/g_combat.c b/code/game/g_combat.c index a200a177..8a3c31cf 100644 --- a/code/game/g_combat.c +++ b/code/game/g_combat.c @@ -215,6 +215,7 @@ void LookAtKiller( gentity_t *self, gentity_t *inflictor, gentity_t *attacker ) self->client->ps.stats[STAT_DEAD_YAW] = vectoyaw ( dir ); } +#define MAX_KNOCKBACK 200 /* ================== KnockbackToKnockbackSpeed @@ -982,8 +983,8 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, } knockback = damage; - if ( knockback > 200 ) { - knockback = 200; + if ( knockback > MAX_KNOCKBACK ) { + knockback = MAX_KNOCKBACK; } if ( targ->flags & FL_NO_KNOCKBACK ) { knockback = 0; @@ -1182,6 +1183,33 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, knockback, kvel, dflags, mod, velChange ); VectorAdd(targ->client->ps.velocity, velChange, targ->client->ps.velocity); } + // If we already got to max knockback, don't apply any more of it. + // Otherwise one quad shotgun shot can get you 330 knockback, + // resuling in gibs flying too fast, much faster + // than from e.g. a railgun shot. + // It would make sense to do this adjustment always, + // but let's only apply this to shotgun gib deaths, + // to be closer to how the original game works. + // + // Note that we're using `damage_knockback`, which is only cleared + // in `CliendEndFrame` and not immediately after the shot. + // But it's good enough since this is basically only about + // gib and camera speed. + if ( + targ->health <= GIB_HEALTH && g_blood.integer && + !g_oldGibs.integer && + mod == MOD_SHOTGUN && + targ->client && targ->client->damage_knockback > MAX_KNOCKBACK ) { + int excess = targ->client->damage_knockback - MAX_KNOCKBACK; + if ( excess > knockback ) { + // It's excess knockback from this particular shot, + // not total excess knockback. + excess = knockback; + } + VectorMA( targ->client->ps.velocity, + KnockbackToKnockbackSpeed( -excess ), dir, + targ->client->ps.velocity ); + } targ->enemy = attacker; targ->die (targ, inflictor, attacker, take, mod); From d7c4b4ff8e98a6e373d2f7e2a764af079ed4727a Mon Sep 17 00:00:00 2001 From: WofWca Date: Thu, 29 Jan 2026 22:17:24 +0400 Subject: [PATCH 26/30] improvement: increase `MAX_MARK_POLYS` 256 -> 1024 This increases memory usage by 216 kB, but makes blood marks not forcefully disappear as new ones are made. --- code/cgame/cg_local.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h index 8fb8e77b..7fec7c15 100644 --- a/code/cgame/cg_local.h +++ b/code/cgame/cg_local.h @@ -42,7 +42,7 @@ #define MAX_STEP_CHANGE 32 #define MAX_VERTS_ON_POLY 10 -#define MAX_MARK_POLYS 256 +#define MAX_MARK_POLYS 1024 #define STAT_MINUS 10 // num frame for '-' stats digit From d8643ebe03c7e45f3bba9d41493c3bd4be7db352 Mon Sep 17 00:00:00 2001 From: WofWca Date: Mon, 2 Feb 2026 14:41:58 +0400 Subject: [PATCH 27/30] feat: gibs: damage-based random velocity Note that this changes the network protocol. But it's still compatible with vanilla. See new comment in `g_combat.c`. We had to move the code that resets `damage_blood`, `damage_armor`, `damage_knockback` because otherwise they would stop getting reset when the player dies, because `P_DamageFeedback` early-returns when `PM_DEAD`. --- code/cgame/cg_cvar.h | 3 +- code/cgame/cg_effects.c | 7 +++-- code/cgame/cg_event.c | 9 ++++-- code/cgame/cg_local.h | 3 +- code/cgame/cg_localents.c | 5 +++- code/cgame/cg_servercmds.c | 2 ++ code/game/bg_public.h | 7 +++++ code/game/g_active.c | 14 ++++----- code/game/g_combat.c | 59 ++++++++++++++++++++++++++++++++------ code/game/g_cvar.h | 1 + code/game/g_local.h | 2 +- code/game/g_weapon.c | 5 +++- 12 files changed, 92 insertions(+), 25 deletions(-) diff --git a/code/cgame/cg_cvar.h b/code/cgame/cg_cvar.h index e8a4b818..16120f6e 100644 --- a/code/cgame/cg_cvar.h +++ b/code/cgame/cg_cvar.h @@ -20,7 +20,8 @@ CG_CVAR( cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE ) CG_CVAR( cg_gibs, "cg_gibs", "1.0", CVAR_ARCHIVE ) CG_CVAR( cg_oldGibs, "cg_oldGibs", "0", CVAR_ARCHIVE ) CG_CVAR( cg_gibsInheritPlayerVelocity, "cg_gibsInheritPlayerVelocity", "1.0", CVAR_ARCHIVE ) -CG_CVAR( cg_gibsExtraRandomVelocity, "cg_gibsExtraRandomVelocity", "250", CVAR_ARCHIVE ) +CG_CVAR( cg_gibsRandomVelocityFromKnockback, "cg_gibsRandomVelocityFromKnockback", "0.15", CVAR_ARCHIVE ) +CG_CVAR( cg_gibsExtraRandomVelocity, "cg_gibsExtraRandomVelocity", "175", CVAR_ARCHIVE ) CG_CVAR( cg_gibsExtraVerticalVelocity, "cg_gibsExtraVerticalVelocity", "100", CVAR_ARCHIVE ) CG_CVAR( cg_gibsBounceFactor, "cg_gibsBounceFactor", "0.4", CVAR_ARCHIVE ) CG_CVAR( cg_gibsRotationFactor, "cg_gibsRotationFactor", "1.0", CVAR_ARCHIVE ) diff --git a/code/cgame/cg_effects.c b/code/cgame/cg_effects.c index 0029493b..8c22ce6c 100644 --- a/code/cgame/cg_effects.c +++ b/code/cgame/cg_effects.c @@ -641,7 +641,7 @@ Generated a bunch of gibs launching out from the bodies location #define GIB_VELOCITY 250 #define GIB_JUMP 250 void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, - const vec3_t playerVelocity, + const vec3_t playerVelocity, const int knockbackSpeed, const lerpFrame_t *bodyAnimation ) { vec3_t baseOrigin, origin, velocity; // Generally only the head should have pitch, @@ -655,7 +655,10 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, // to account for crounching. float playerHeight = 32 - MINS_Z; float playerRadius = PLAYER_WIDTH; - float baseRandomVelocity = cg_gibsExtraRandomVelocity.value; + float baseRandomVelocity = + cg_gibsExtraRandomVelocity.value + + cg_gibsRandomVelocityFromKnockback.value * knockbackSpeed; + vec3_t playerVelocityScaled; float jump = cg_gibsExtraVerticalVelocity.value; int numGibs = cg_gibs.value * DEFAULT_NUM_GIBS; diff --git a/code/cgame/cg_event.c b/code/cgame/cg_event.c index 1c574305..6f6c9fcb 100644 --- a/code/cgame/cg_event.c +++ b/code/cgame/cg_event.c @@ -1218,6 +1218,11 @@ void CG_EntityEvent( centity_t *cent, vec3_t position, int entityNum ) { if (cg_oldGibs.integer) { CG_GibPlayerOld( cent->lerpOrigin ); } else { + int knockbackSpeed = cgs.g_gibsNewEvGibPlayerParmProtocol == 1 + ? es->eventParm * COMBAT_EV_GIB_PLAYER_ARG_DIVISOR + // Just use the default knockback speed for 100 damage. + : 100 * 1000 / COMBAT_PLAYER_MASS; + if ( es->number == cg.snap->ps.clientNum ) { // Apparently at this point `es->pos.trDelta` doesn't yet have // the knockback from the damage that gibbed us, @@ -1226,11 +1231,11 @@ void CG_EntityEvent( centity_t *cent, vec3_t position, int entityNum ) { // if it's ourself. // `cent->pe.torso` also appears to be not good here. CG_GibPlayer( cent->lerpOrigin, cent->lerpAngles, - cg.predictedPlayerState.velocity, + cg.predictedPlayerState.velocity, knockbackSpeed, &cg.predictedPlayerEntity.pe.torso ); } else { CG_GibPlayer( cent->lerpOrigin, cent->lerpAngles, - es->pos.trDelta, + es->pos.trDelta, knockbackSpeed, ¢->pe.torso ); } } diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h index 7fec7c15..69d8825a 100644 --- a/code/cgame/cg_local.h +++ b/code/cgame/cg_local.h @@ -1136,6 +1136,7 @@ typedef struct { int pmove_msec; qboolean synchronousClients; + int g_gibsNewEvGibPlayerParmProtocol; int ospEnc; qboolean defrag; @@ -1407,7 +1408,7 @@ void CG_LightningBoltBeam( vec3_t start, vec3_t end ); void CG_ScorePlum( int client, const vec3_t origin, int score ); void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, - const vec3_t playerVelocity, + const vec3_t playerVelocity, const int knockbackSpeed, const lerpFrame_t *bodyAnimation ); void CG_GibPlayerOld( const vec3_t playerOrigin ); void CG_BigExplode( vec3_t playerOrigin ); diff --git a/code/cgame/cg_localents.c b/code/cgame/cg_localents.c index d2b4ce8b..50668b13 100644 --- a/code/cgame/cg_localents.c +++ b/code/cgame/cg_localents.c @@ -779,10 +779,13 @@ void CG_AddInvulnerabilityJuiced( localEntity_t *le ) { CG_GibPlayerOld( le->refEntity.origin ); } else { vec3_t angles; + // Just use the default knockback speed for 200 damage. + int knockbackSpeed = 200 * 1000 / COMBAT_PLAYER_MASS; // Angles don't matter much here. VectorClear( angles ); - CG_GibPlayer( le->refEntity.origin, angles, le->pos.trDelta, NULL ); + CG_GibPlayer( le->refEntity.origin, angles, le->pos.trDelta, + knockbackSpeed, NULL ); } } else { diff --git a/code/cgame/cg_servercmds.c b/code/cgame/cg_servercmds.c index d47cfd9b..a40e3a5a 100644 --- a/code/cgame/cg_servercmds.c +++ b/code/cgame/cg_servercmds.c @@ -159,6 +159,8 @@ void CG_ParseSysteminfo( void ) { } cgs.synchronousClients = ( atoi( Info_ValueForKey( info, "g_synchronousClients" ) ) ) ? qtrue : qfalse; + cgs.g_gibsNewEvGibPlayerParmProtocol = + atoi( Info_ValueForKey( info, "g_gibsNewEvGibPlayerParmProtocol" ) ); } diff --git a/code/game/bg_public.h b/code/game/bg_public.h index 7b83f42f..1f1066c7 100644 --- a/code/game/bg_public.h +++ b/code/game/bg_public.h @@ -189,6 +189,13 @@ void Pmove (pmove_t *pmove); //=================================================================================== +#define COMBAT_PLAYER_MASS 200 +// A divisor of knockback speed, to fit it into one byte. +// By dividing by 8 we can represent a speed of up to (255 * 8) = 2040. +// For comparison, with `g_knockback` of 1000 and `MAX_KNOCKBACK` of 200 +// the max knockback speed in most situations is 1000. +#define COMBAT_EV_GIB_PLAYER_ARG_DIVISOR 8 + // player_state->stats[] indexes // NOTE: may not have more than 16 diff --git a/code/game/g_active.c b/code/game/g_active.c index ad5d2878..d4d9bb02 100644 --- a/code/game/g_active.c +++ b/code/game/g_active.c @@ -58,13 +58,6 @@ void P_DamageFeedback( gentity_t *player ) { client->ps.damageCount = count; - - // - // clear totals - // - client->damage_blood = 0; - client->damage_armor = 0; - client->damage_knockback = 0; } @@ -1217,6 +1210,13 @@ void ClientEndFrame( gentity_t *ent ) { client->damage.team = 0; } + // + // clear damage totals + // + client->damage_blood = 0; + client->damage_armor = 0; + client->damage_knockback = 0; + // set the bit for the reachability area the client is currently in // i = trap_AAS_PointReachabilityAreaIndex( ent->client->ps.origin ); // ent->client->areabits[i >> 3] |= 1 << (i & 7); diff --git a/code/game/g_combat.c b/code/game/g_combat.c index 8a3c31cf..72d0db10 100644 --- a/code/game/g_combat.c +++ b/code/game/g_combat.c @@ -222,11 +222,7 @@ KnockbackToKnockbackSpeed ================== */ static float KnockbackToKnockbackSpeed( int knockback ) { - float mass; - - mass = 200; - - return g_knockback.value * (float)knockback / mass; + return g_knockback.value * (float)knockback / COMBAT_PLAYER_MASS; } @@ -235,7 +231,8 @@ static float KnockbackToKnockbackSpeed( int knockback ) { GibEntity ================== */ -void GibEntity( gentity_t *self, int killer ) { +void GibEntity( gentity_t *self, int killer, const int damageBloodFallback ) { + int eventParm = killer; #ifdef MISSIONPACK gentity_t *ent; int i; @@ -257,7 +254,51 @@ void GibEntity( gentity_t *self, int killer ) { } #endif - G_AddEvent( self, EV_GIB_PLAYER, killer ); + + // In vanilla Quake the meaning of the `EV_GIB_PLAYER` eventParm + // is `killer`. + // But it is unused client-side, so it's safe for us to change its meaning + // (i.e. to change the network protocol). + // However, some mods might in fact rely on it, + // so let's have a CVAR to keep the old behavior. + // + // Note that we're not checking `g_oldGibs`, because in itself + // this does not affect behavior: + // we're simply providing the client with the knockback info, + // and whether to use that into is up to `cg_oldGibs`. + if ( g_gibsNewEvGibPlayerParmProtocol.integer == 1 ) { + int damage; + float knockbackSpeed; + + // We prefer actual damage over `client->damage_knockback` + // because `damage_knockback` is sometimes undesirably 0. Namely: + // - when the target is a dead body, with `FL_NO_KNOCKBACK`. + // - when the knockback `dir` is not provided to `G_Damage`, + // such as with crushers. + // + // Most of the time (but not always e.g. with lava) + // "no knockback" means "the player should not be moved + // in any particular direction", + // and not that "their gibs should stay put". + damage = self->client + ? self->client->damage_blood + self->client->damage_armor + : damageBloodFallback; + if ( damage > MAX_KNOCKBACK ) { + damage = MAX_KNOCKBACK; + } + + knockbackSpeed = KnockbackToKnockbackSpeed( damage ); + + // Fit it into one byte. + eventParm = knockbackSpeed / COMBAT_EV_GIB_PLAYER_ARG_DIVISOR; + if (eventParm > 255) { + eventParm = 255; + } + } else { + eventParm = killer; + } + G_AddEvent( self, EV_GIB_PLAYER, eventParm ); + self->takedamage = qfalse; self->s.eType = ET_INVISIBLE; self->r.contents = 0; @@ -280,7 +321,7 @@ void body_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int d if ( ShouldPostponeDeathOrGib( meansOfDeath ) ) { self->gibScheduled = qtrue; } else { - GibEntity( self, 0 ); + GibEntity( self, 0, damage ); } } @@ -624,7 +665,7 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int if ( ShouldPostponeDeathOrGib( meansOfDeath ) ) { self->gibScheduled = qtrue; } else { - GibEntity( self, killer ); + GibEntity( self, killer, damage ); } } else { // normal death diff --git a/code/game/g_cvar.h b/code/game/g_cvar.h index 24d3d2bb..15b6d73e 100644 --- a/code/game/g_cvar.h +++ b/code/game/g_cvar.h @@ -75,6 +75,7 @@ G_CVAR( g_oldGibs, "g_oldGibs", "0", CVAR_ARCHIVE, 0, qfalse, qfalse ) // Note that this affects not just the gibs // but also the camera velocity of the gibbed player. G_CVAR( g_gibsMissileDirectionKnockbackWeight, "g_gibsMissileDirectionKnockbackWeight", "0.5", CVAR_ARCHIVE, 0, qfalse, qfalse ) +G_CVAR( g_gibsNewEvGibPlayerParmProtocol, "g_gibsNewEvGibPlayerParmProtocol", "1", CVAR_SYSTEMINFO | CVAR_ARCHIVE, 0, qfalse, qfalse ) G_CVAR( g_podiumDist, "g_podiumDist", "80", 0, 0, qfalse, qfalse ) G_CVAR( g_podiumDrop, "g_podiumDrop", "70", 0, 0, qfalse, qfalse ) diff --git a/code/game/g_local.h b/code/game/g_local.h index b70041f2..7a4b3cb4 100644 --- a/code/game/g_local.h +++ b/code/game/g_local.h @@ -529,7 +529,7 @@ const char *BuildShaderStateConfig( void ); // qboolean CanDamage (gentity_t *targ, vec3_t origin); void G_Damage (gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_t dir, vec3_t point, int damage, int dflags, int mod); -void GibEntity( gentity_t *self, int killer ); +void GibEntity( gentity_t *self, int killer, const int damageBloodFallback ); // The shotgun does `G_Damage` multiple times, per each pellet. // Normally that would mean that if the target is at 1 HP, // only one pellet would hit them. diff --git a/code/game/g_weapon.c b/code/game/g_weapon.c index 346af66e..1ccf1e84 100644 --- a/code/game/g_weapon.c +++ b/code/game/g_weapon.c @@ -401,7 +401,10 @@ static void ShotgunPattern( const vec3_t origin, const vec3_t origin2, int seed, // because vanilla passes `0` as `eventParm if we are gibbing // a body from body queue. int killer = ent->s.number; - GibEntity( ent2, killer ); + // Just fall back to "half of all the pellets hit". + int damageFallback = DEFAULT_SHOTGUN_DAMAGE * s_quadFactor + * DEFAULT_SHOTGUN_DAMAGE / 2; + GibEntity( ent2, killer, damageFallback ); } ent2->gibScheduled = qfalse; } From 475c0481421c2d2972e441760fb14c75e3e688b1 Mon Sep 17 00:00:00 2001 From: WofWca Date: Thu, 5 Feb 2026 12:01:40 +0400 Subject: [PATCH 28/30] feat: try to make all players see the same gibs --- code/cgame/cg_effects.c | 131 ++++++++++++++++++++------------------ code/cgame/cg_event.c | 18 +++++- code/cgame/cg_local.h | 2 +- code/cgame/cg_localents.c | 10 ++- 4 files changed, 96 insertions(+), 65 deletions(-) diff --git a/code/cgame/cg_effects.c b/code/cgame/cg_effects.c index 8c22ce6c..a6b967ba 100644 --- a/code/cgame/cg_effects.c +++ b/code/cgame/cg_effects.c @@ -531,10 +531,13 @@ void CG_Bleed( const vec3_t origin, int entityNum ) { /* ================== CG_LaunchGib + +`randSeed` has no effect if `cg_oldGibs.integer == 1` ================== */ static void CG_LaunchGib( const vec3_t origin, const vec3_t angles, - const vec3_t velocity, qhandle_t hModel ) { + const vec3_t velocity, qhandle_t hModel, + const int randSeed ) { localEntity_t *le; refEntity_t *re; @@ -561,16 +564,17 @@ static void CG_LaunchGib( const vec3_t origin, const vec3_t angles, // and good enough for randomness. float speedIsh = fabs(velocity[0]) + fabs(velocity[1]) + fabs(velocity[2]); int i; - int mainRotationAxis = rand() % 3; + int seed = randSeed; + int mainRotationAxis = Q_rand(&seed) % 3; le->leFlags = LEF_TUMBLE; le->angles.trType = TR_LINEAR; le->angles.trTime = cg.time; VectorCopy( angles, le->angles.trBase ); // Just a few degrees of randomness. - le->angles.trBase[PITCH] += rand()&7; - le->angles.trBase[YAW] += rand()&7; - le->angles.trBase[ROLL] += rand()&7; + le->angles.trBase[PITCH] += Q_rand(&seed)&7; + le->angles.trBase[YAW] += Q_rand(&seed)&7; + le->angles.trBase[ROLL] += Q_rand(&seed)&7; // TODO the tumble speed should probably depend on damage instead, // or at least on random velocity. for ( i = 0; i < 3; i++ ) { @@ -578,7 +582,7 @@ static void CG_LaunchGib( const vec3_t origin, const vec3_t angles, // having one axis be bigger than others makes rotation look natural. float axisMul = mainRotationAxis == i ? 1 : 0.25; le->angles.trDelta[i] = speedIsh * axisMul * 0.5 * - cg_gibsRotationFactor.value * crandom(); + cg_gibsRotationFactor.value * Q_crandom(&seed); } } @@ -635,6 +639,10 @@ void AdjustPositionIfDeathAnimation( const lerpFrame_t *anim, vec3_t origin, CG_GibPlayer Generated a bunch of gibs launching out from the bodies location + +`randSeed` should be the same for all players, and also preserved +in demo playback, so that players see the same gibs +(as long as they have the same `cg_gibs*` CVAR values). =================== */ #define DEFAULT_NUM_GIBS 10 @@ -642,7 +650,7 @@ Generated a bunch of gibs launching out from the bodies location #define GIB_JUMP 250 void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, const vec3_t playerVelocity, const int knockbackSpeed, - const lerpFrame_t *bodyAnimation ) { + const lerpFrame_t *bodyAnimation, const int randSeed ) { vec3_t baseOrigin, origin, velocity; // Generally only the head should have pitch, // the rest of the body is upright. @@ -658,6 +666,7 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, float baseRandomVelocity = cg_gibsExtraRandomVelocity.value + cg_gibsRandomVelocityFromKnockback.value * knockbackSpeed; + int seed = randSeed; vec3_t playerVelocityScaled; float jump = cg_gibsExtraVerticalVelocity.value; @@ -687,21 +696,21 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, VectorCopy( baseOrigin, origin ); VectorMA(origin, MINS_Z + 0.95 * playerHeight, up, origin); VectorClear( velocity ); - VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); - VectorMA( velocity, crandom()*baseRandomVelocity, right, velocity ); + VectorMA( velocity, Q_crandom(&seed)*baseRandomVelocity, forward, velocity ); + VectorMA( velocity, Q_crandom(&seed)*baseRandomVelocity, right, velocity ); // For the skull / brain we want the random velocity // to never have downwards (inwards) component, - // so we use `random` instead of `crandom`. + // so we use `Q_random` instead of `Q_crandom`. // We also do the same for other gibs, // but for the left / right velocity components. - VectorMA( velocity, random()*baseRandomVelocity, up, velocity ); + VectorMA( velocity, Q_random(&seed)*baseRandomVelocity, up, velocity ); velocity[2] += jump; VectorAdd( velocity, playerVelocityScaled, velocity ); - if ( !skullLaunched && (rand() & 1) ) { - CG_LaunchGib( origin, lookDirAngles, velocity, cgs.media.gibSkull ); + if ( !skullLaunched && (Q_rand(&seed) & 1) ) { + CG_LaunchGib( origin, lookDirAngles, velocity, cgs.media.gibSkull, Q_rand(&seed) ); skullLaunched = qtrue; } else { - CG_LaunchGib( origin, lookDirAngles, velocity, cgs.media.gibBrain ); + CG_LaunchGib( origin, lookDirAngles, velocity, cgs.media.gibBrain, Q_rand(&seed) ); } if (--numGibs <= 0) { return; @@ -710,11 +719,11 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, VectorCopy( baseOrigin, origin ); VectorMA( origin, MINS_Z + 0.65 * playerHeight, up, origin ); VectorClear( velocity ); - velocity[0] = crandom()*baseRandomVelocity; - velocity[1] = crandom()*baseRandomVelocity; - velocity[2] = jump + crandom()*baseRandomVelocity; + velocity[0] = Q_crandom(&seed)*baseRandomVelocity; + velocity[1] = Q_crandom(&seed)*baseRandomVelocity; + velocity[2] = jump + Q_crandom(&seed)*baseRandomVelocity; VectorAdd( velocity, playerVelocityScaled, velocity ); - CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibAbdomen ); + CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibAbdomen, Q_rand(&seed) ); if (--numGibs <= 0) { return; } @@ -724,15 +733,15 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, VectorMA( origin, 0.8 * playerRadius, right, origin ); VectorMA( origin, -0.3 * playerRadius, forward, origin ); VectorClear( velocity ); - VectorMA( velocity, +random()*baseRandomVelocity, right, velocity ); - VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); - VectorMA( velocity, crandom()*baseRandomVelocity, up, velocity ); + VectorMA( velocity, +Q_random(&seed)*baseRandomVelocity, right, velocity ); + VectorMA( velocity, Q_crandom(&seed)*baseRandomVelocity, forward, velocity ); + VectorMA( velocity, Q_crandom(&seed)*baseRandomVelocity, up, velocity ); velocity[2] += jump; VectorAdd( velocity, playerVelocityScaled, velocity ); VectorCopy( bodyAngles, angles ); angles[ROLL] += 70; angles[PITCH] += 45; - CG_LaunchGib( origin, angles, velocity, cgs.media.gibArm ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibArm, Q_rand(&seed) ); if (--numGibs <= 0) { return; } @@ -742,11 +751,11 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, VectorClear( velocity ); // Chest is a more "central" and "heavier" piece, // so it gets less random velocity. - velocity[0] = 0.5*crandom()*baseRandomVelocity; - velocity[1] = 0.5*crandom()*baseRandomVelocity; - velocity[2] = jump + 0.5*crandom()*baseRandomVelocity; + velocity[0] = 0.5*Q_crandom(&seed)*baseRandomVelocity; + velocity[1] = 0.5*Q_crandom(&seed)*baseRandomVelocity; + velocity[2] = jump + 0.5*Q_crandom(&seed)*baseRandomVelocity; VectorAdd( velocity, playerVelocityScaled, velocity ); - CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibChest ); + CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibChest, Q_rand(&seed) ); if (--numGibs <= 0) { return; } @@ -756,14 +765,14 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, VectorMA( origin, 0.8 * playerRadius, right, origin ); VectorMA( origin, 0.2 * playerRadius, forward, origin ); VectorClear( velocity ); - velocity[0] = crandom()*baseRandomVelocity; - velocity[1] = crandom()*baseRandomVelocity; - velocity[2] = jump + crandom()*baseRandomVelocity; + velocity[0] = Q_crandom(&seed)*baseRandomVelocity; + velocity[1] = Q_crandom(&seed)*baseRandomVelocity; + velocity[2] = jump + Q_crandom(&seed)*baseRandomVelocity; VectorAdd( velocity, playerVelocityScaled, velocity ); VectorCopy( bodyAngles, angles ); angles[PITCH] -= 80; angles[YAW] += 50; - CG_LaunchGib( origin, angles, velocity, cgs.media.gibFist ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibFist, Q_rand(&seed) ); if (--numGibs <= 0) { return; } @@ -773,11 +782,11 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, VectorMA( origin, -0.5 * playerRadius, right, origin ); VectorMA( origin, -0.5 * playerRadius, forward, origin ); VectorClear( velocity ); - velocity[0] = crandom()*baseRandomVelocity; - velocity[1] = crandom()*baseRandomVelocity; - velocity[2] = jump + crandom()*baseRandomVelocity; + velocity[0] = Q_crandom(&seed)*baseRandomVelocity; + velocity[1] = Q_crandom(&seed)*baseRandomVelocity; + velocity[2] = jump + Q_crandom(&seed)*baseRandomVelocity; VectorAdd( velocity, playerVelocityScaled, velocity ); - CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibFoot ); + CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibFoot, Q_rand(&seed) ); if (--numGibs <= 0) { return; } @@ -787,15 +796,15 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, VectorMA( origin, -0.6 * playerRadius, right, origin ); VectorMA( origin, +0.2 * playerRadius, forward, origin ); VectorClear( velocity ); - VectorMA( velocity, -random()*baseRandomVelocity, right, velocity ); - VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); - VectorMA( velocity, crandom()*baseRandomVelocity, up, velocity ); + VectorMA( velocity, -Q_random(&seed)*baseRandomVelocity, right, velocity ); + VectorMA( velocity, Q_crandom(&seed)*baseRandomVelocity, forward, velocity ); + VectorMA( velocity, Q_crandom(&seed)*baseRandomVelocity, up, velocity ); velocity[2] += jump; VectorAdd( velocity, playerVelocityScaled, velocity ); VectorCopy( bodyAngles, angles ); angles[ROLL] -= 90; angles[PITCH] -= 75; - CG_LaunchGib( origin, angles, velocity, cgs.media.gibForearm ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibForearm, Q_rand(&seed) ); if (--numGibs <= 0) { return; } @@ -803,11 +812,11 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, VectorCopy( baseOrigin, origin ); VectorMA( origin, MINS_Z + 0.57 * playerHeight, up, origin ); VectorClear( velocity ); - velocity[0] = crandom()*baseRandomVelocity; - velocity[1] = crandom()*baseRandomVelocity; - velocity[2] = jump + crandom()*baseRandomVelocity; + velocity[0] = Q_crandom(&seed)*baseRandomVelocity; + velocity[1] = Q_crandom(&seed)*baseRandomVelocity; + velocity[2] = jump + Q_crandom(&seed)*baseRandomVelocity; VectorAdd( velocity, playerVelocityScaled, velocity ); - CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibIntestine ); + CG_LaunchGib( origin, bodyAngles, velocity, cgs.media.gibIntestine, Q_rand(&seed) ); if (--numGibs <= 0) { return; } @@ -817,15 +826,15 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, VectorMA( origin, 0.5 * playerRadius, right, origin ); VectorMA( origin, 0.1 * playerRadius, forward, origin ); VectorClear( velocity ); - VectorMA( velocity, +random()*baseRandomVelocity, right, velocity ); - VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); - VectorMA( velocity, crandom()*baseRandomVelocity, up, velocity ); + VectorMA( velocity, +Q_random(&seed)*baseRandomVelocity, right, velocity ); + VectorMA( velocity, Q_crandom(&seed)*baseRandomVelocity, forward, velocity ); + VectorMA( velocity, Q_crandom(&seed)*baseRandomVelocity, up, velocity ); velocity[2] += jump; VectorAdd( velocity, playerVelocityScaled, velocity ); VectorCopy( bodyAngles, angles ); angles[ROLL] -= 30; angles[PITCH] -= 15; - CG_LaunchGib( origin, angles, velocity, cgs.media.gibLeg ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibLeg, Q_rand(&seed) ); if (--numGibs <= 0) { return; } @@ -835,14 +844,14 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, VectorMA( origin, -0.5 * playerRadius, right, origin ); VectorMA( origin, -0.2 * playerRadius, forward, origin ); VectorClear( velocity ); - VectorMA( velocity, -random()*baseRandomVelocity, right, velocity ); - VectorMA( velocity, crandom()*baseRandomVelocity, forward, velocity ); - VectorMA( velocity, crandom()*baseRandomVelocity, up, velocity ); + VectorMA( velocity, -Q_random(&seed)*baseRandomVelocity, right, velocity ); + VectorMA( velocity, Q_crandom(&seed)*baseRandomVelocity, forward, velocity ); + VectorMA( velocity, Q_crandom(&seed)*baseRandomVelocity, up, velocity ); velocity[2] += jump; VectorAdd( velocity, playerVelocityScaled, velocity ); VectorCopy( bodyAngles, angles ); angles[PITCH] += 15; - CG_LaunchGib( origin, angles, velocity, cgs.media.gibLeg ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibLeg, Q_rand(&seed) ); if (--numGibs <= 0) { return; } @@ -862,9 +871,9 @@ void CG_GibPlayerOld( const vec3_t playerOrigin ) { velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; if ( rand() & 1 ) { - CG_LaunchGib( origin, angles, velocity, cgs.media.gibSkull ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibSkull, 0 ); } else { - CG_LaunchGib( origin, angles, velocity, cgs.media.gibBrain ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibBrain, 0 ); } // allow gibs to be turned off for speed @@ -876,55 +885,55 @@ void CG_GibPlayerOld( const vec3_t playerOrigin ) { velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, angles, velocity, cgs.media.gibAbdomen ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibAbdomen, 0 ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, angles, velocity, cgs.media.gibArm ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibArm, 0 ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, angles, velocity, cgs.media.gibChest ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibChest, 0 ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, angles, velocity, cgs.media.gibFist ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibFist, 0 ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, angles, velocity, cgs.media.gibFoot ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibFoot, 0 ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, angles, velocity, cgs.media.gibForearm ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibForearm, 0 ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, angles, velocity, cgs.media.gibIntestine ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibIntestine, 0 ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, angles, velocity, cgs.media.gibLeg ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibLeg, 0 ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, angles, velocity, cgs.media.gibLeg ); + CG_LaunchGib( origin, angles, velocity, cgs.media.gibLeg, 0 ); } /* diff --git a/code/cgame/cg_event.c b/code/cgame/cg_event.c index 6f6c9fcb..f3ba06fe 100644 --- a/code/cgame/cg_event.c +++ b/code/cgame/cg_event.c @@ -1223,6 +1223,20 @@ void CG_EntityEvent( centity_t *cent, vec3_t position, int entityNum ) { // Just use the default knockback speed for 100 damage. : 100 * 1000 / COMBAT_PLAYER_MASS; + // TODO fix: things like `origin` and `angles` + // are not in complete sync between clients, + // so this seed is not always the same for all players. + int randSeed = es->number; + randSeed = Q_rand(&randSeed) + es->clientNum; + randSeed = Q_rand(&randSeed) + es->eventParm; + randSeed = Q_rand(&randSeed) + cgs.levelStartTime; + // TODO fix: this varies from client to client. + // So for now we round it to make it in sync ~95% of the time. + randSeed = Q_rand(&randSeed) + cg.snap->serverTime / 2048; + if ( ci ) { + randSeed = Q_rand(&randSeed) + ci->name[0]; + } + if ( es->number == cg.snap->ps.clientNum ) { // Apparently at this point `es->pos.trDelta` doesn't yet have // the knockback from the damage that gibbed us, @@ -1232,11 +1246,11 @@ void CG_EntityEvent( centity_t *cent, vec3_t position, int entityNum ) { // `cent->pe.torso` also appears to be not good here. CG_GibPlayer( cent->lerpOrigin, cent->lerpAngles, cg.predictedPlayerState.velocity, knockbackSpeed, - &cg.predictedPlayerEntity.pe.torso ); + &cg.predictedPlayerEntity.pe.torso, randSeed ); } else { CG_GibPlayer( cent->lerpOrigin, cent->lerpAngles, es->pos.trDelta, knockbackSpeed, - ¢->pe.torso ); + ¢->pe.torso, randSeed ); } } break; diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h index 69d8825a..ed86d554 100644 --- a/code/cgame/cg_local.h +++ b/code/cgame/cg_local.h @@ -1409,7 +1409,7 @@ void CG_ScorePlum( int client, const vec3_t origin, int score ); void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, const vec3_t playerVelocity, const int knockbackSpeed, - const lerpFrame_t *bodyAnimation ); + const lerpFrame_t *bodyAnimation, const int randSeed ); void CG_GibPlayerOld( const vec3_t playerOrigin ); void CG_BigExplode( vec3_t playerOrigin ); diff --git a/code/cgame/cg_localents.c b/code/cgame/cg_localents.c index 50668b13..5e75da43 100644 --- a/code/cgame/cg_localents.c +++ b/code/cgame/cg_localents.c @@ -781,11 +781,19 @@ void CG_AddInvulnerabilityJuiced( localEntity_t *le ) { vec3_t angles; // Just use the default knockback speed for 200 damage. int knockbackSpeed = 200 * 1000 / COMBAT_PLAYER_MASS; + int randSeed; // Angles don't matter much here. VectorClear( angles ); + // Since the player with invulnerability is not moving, + // we expect its position to be the same for all players, + // so we can use it as a seed. + randSeed = le->refEntity.origin[0] * 1024; + randSeed = Q_rand(&randSeed) + le->refEntity.origin[1] * 1024; + randSeed = Q_rand(&randSeed) + le->refEntity.origin[2] * 1024; + randSeed = Q_rand(&randSeed) + cgs.levelStartTime; CG_GibPlayer( le->refEntity.origin, angles, le->pos.trDelta, - knockbackSpeed, NULL ); + knockbackSpeed, NULL, randSeed ); } } else { From 2bd8d6be51383821b38efd5b2b880bcd013957d5 Mon Sep 17 00:00:00 2001 From: WofWca Date: Thu, 5 Feb 2026 19:39:18 +0400 Subject: [PATCH 29/30] fix: gibs: better, more sync, initial angles --- code/cgame/cg_event.c | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/code/cgame/cg_event.c b/code/cgame/cg_event.c index f3ba06fe..5a83f6a6 100644 --- a/code/cgame/cg_event.c +++ b/code/cgame/cg_event.c @@ -1223,6 +1223,12 @@ void CG_EntityEvent( centity_t *cent, vec3_t position, int entityNum ) { // Just use the default knockback speed for 100 damage. : 100 * 1000 / COMBAT_PLAYER_MASS; + lerpFrame_t torsoAnimation = es->number == cg.snap->ps.clientNum + // `cent->pe.torso` appears to be not good for self. + ? cg.predictedPlayerEntity.pe.torso + : cent->pe.torso; + vec3_t torsoAngles; + // TODO fix: things like `origin` and `angles` // are not in complete sync between clients, // so this seed is not always the same for all players. @@ -1237,20 +1243,34 @@ void CG_EntityEvent( centity_t *cent, vec3_t position, int entityNum ) { randSeed = Q_rand(&randSeed) + ci->name[0]; } + // Torso animation angles seem to be in better sync + // between the local state and how others see us, + // and overall are closer to other player's viewangles + // than `cent->lerpAngles`. + // `cent->lerpAngles`, seems to sometimes be pointing + // in a completely different direction than the player's body + // at the time of death. + // Moreover, for non-self pitch seems to be always + // not very far from 0. + // This could be related to `LookAtKiller()`. + // Also see `CG_PlayerAngles`. + torsoAngles[PITCH] = torsoAnimation.pitchAngle; + torsoAngles[YAW] = torsoAnimation.yawAngle; + torsoAngles[ROLL] = 0; + if ( es->number == cg.snap->ps.clientNum ) { // Apparently at this point `es->pos.trDelta` doesn't yet have // the knockback from the damage that gibbed us, // so we have to differentiate between self and non-self, // and use `cg.predictedPlayerState.velocity` // if it's ourself. - // `cent->pe.torso` also appears to be not good here. - CG_GibPlayer( cent->lerpOrigin, cent->lerpAngles, + CG_GibPlayer( cent->lerpOrigin, torsoAngles, cg.predictedPlayerState.velocity, knockbackSpeed, - &cg.predictedPlayerEntity.pe.torso, randSeed ); + &torsoAnimation, randSeed ); } else { - CG_GibPlayer( cent->lerpOrigin, cent->lerpAngles, + CG_GibPlayer( cent->lerpOrigin, torsoAngles, es->pos.trDelta, knockbackSpeed, - ¢->pe.torso, randSeed ); + &torsoAnimation, randSeed ); } } break; From 3c1414c50b1c20ef55a8a59fb33062e44069486d Mon Sep 17 00:00:00 2001 From: WofWca Date: Fri, 20 Feb 2026 14:12:27 +0400 Subject: [PATCH 30/30] feat: make camera fly farther on gib + fix a bug This is, as usual, compatible with vanilla, both servers and clients. The bug that this fixes: https://github.com/WofWca/quake3-better-gibs-mod/issues/3. We also increase the default `cg_gibsExtraVerticalVelocity` because with direct hit weapons the knockback is usually directed a bit towards the ground, because in order to aim at the center of a player your pitch needs to be a little below the horizon. --- code/cgame/cg_cvar.h | 3 ++- code/game/bg_pmove.c | 35 +++++++++++++++++++++++++++++++++-- code/game/bg_public.h | 8 ++++++++ code/game/g_bot.c | 5 +++++ code/game/g_client.c | 3 +++ code/game/g_combat.c | 13 +++++++++++++ code/game/g_local.h | 3 +++ 7 files changed, 67 insertions(+), 3 deletions(-) diff --git a/code/cgame/cg_cvar.h b/code/cgame/cg_cvar.h index 16120f6e..6f136caf 100644 --- a/code/cgame/cg_cvar.h +++ b/code/cgame/cg_cvar.h @@ -22,9 +22,10 @@ CG_CVAR( cg_oldGibs, "cg_oldGibs", "0", CVAR_ARCHIVE ) CG_CVAR( cg_gibsInheritPlayerVelocity, "cg_gibsInheritPlayerVelocity", "1.0", CVAR_ARCHIVE ) CG_CVAR( cg_gibsRandomVelocityFromKnockback, "cg_gibsRandomVelocityFromKnockback", "0.15", CVAR_ARCHIVE ) CG_CVAR( cg_gibsExtraRandomVelocity, "cg_gibsExtraRandomVelocity", "175", CVAR_ARCHIVE ) -CG_CVAR( cg_gibsExtraVerticalVelocity, "cg_gibsExtraVerticalVelocity", "100", CVAR_ARCHIVE ) +CG_CVAR( cg_gibsExtraVerticalVelocity, "cg_gibsExtraVerticalVelocity", "150", CVAR_ARCHIVE ) CG_CVAR( cg_gibsBounceFactor, "cg_gibsBounceFactor", "0.4", CVAR_ARCHIVE ) CG_CVAR( cg_gibsRotationFactor, "cg_gibsRotationFactor", "1.0", CVAR_ARCHIVE ) +CG_CVAR( cg_gibsBetterCameraOnGib, "cg_gibsBetterCameraOnGib", "1", CVAR_USERINFO | CVAR_ARCHIVE ) CG_CVAR( cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE ) CG_CVAR( cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE ) CG_CVAR( cg_drawTimer, "cg_drawTimer", "0", CVAR_ARCHIVE ) diff --git a/code/game/bg_pmove.c b/code/game/bg_pmove.c index b2225b4e..28ee2390 100644 --- a/code/game/bg_pmove.c +++ b/code/game/bg_pmove.c @@ -1268,8 +1268,39 @@ static void PM_CheckDuck (void) if (pm->ps->pm_type == PM_DEAD) { - pm->maxs[2] = DEAD_MAXS_Z; - pm->ps->viewheight = DEAD_VIEWHEIGHT; + // Note that we want to support vanilla clients and servers, + // which do not have this `if` (they always run the `else` branch). + // On the server we check `cg_gibsBetterCameraOnGib` userinfo + // before setting `viewheight = NEW_GIBBED_VIEWHEIGHT`. + // Vanilla servers never set `viewheight` to `NEW_GIBBED_VIEWHEIGHT`, + // so on the client side this check is enough. + if ( pm->ps->viewheight == NEW_GIBBED_VIEWHEIGHT ) { + // Make the camera fly farther away when getting gibbed, + // by making the player's bounding box smaller + // and lifting it off the ground, + // as if the player became just their head. + // + // This also fixes the issue with gibs + // flying parallel to the ground even when knockback direction + // is towards the ground: + // https://github.com/WofWca/quake3-better-gibs-mod/issues/3. + // + // Note that we don't need to change `groundEntityNum` and stuff, + // because there is `PM_GroundTraceMissed()` + // right after `PM_CheckDuck()`. + pm->mins[2] = NEW_GIBBED_MINS_Z; + pm->maxs[2] = NEW_GIBBED_MAXS_Z; + // Also make them slimmer, because they're just a piece of meat now, + // and not an entire body. + pm->mins[0] = -PLAYER_WIDTH * 3 / 4; + pm->mins[1] = -PLAYER_WIDTH * 3 / 4; + pm->maxs[0] = PLAYER_WIDTH * 3 / 4; + pm->maxs[1] = PLAYER_WIDTH * 3 / 4; + } else { + pm->maxs[2] = DEAD_MAXS_Z; + pm->ps->viewheight = DEAD_VIEWHEIGHT; + } + return; } diff --git a/code/game/bg_public.h b/code/game/bg_public.h index 1f1066c7..30a3782e 100644 --- a/code/game/bg_public.h +++ b/code/game/bg_public.h @@ -39,6 +39,14 @@ #define CROUCH_VIEWHEIGHT 12 #define DEAD_MAXS_Z -8 #define DEAD_VIEWHEIGHT -16 +// Make sure to keep `mins[2]` negative so that player origin doesn't sink +// into the ground, to guard against weird behavior +// like sounds not being played (because they're "behind the wall") +// or something. +#define NEW_GIBBED_MINS_Z -2 +#define NEW_GIBBED_HEIGHT_DIFF NEW_GIBBED_MINS_Z - MINS_Z +#define NEW_GIBBED_MAXS_Z DEAD_MAXS_Z + NEW_GIBBED_HEIGHT_DIFF +#define NEW_GIBBED_VIEWHEIGHT DEAD_VIEWHEIGHT + NEW_GIBBED_HEIGHT_DIFF #define PM_STEP_HEIGHT 18 diff --git a/code/game/g_bot.c b/code/game/g_bot.c index 86b82d1c..580a580f 100644 --- a/code/game/g_bot.c +++ b/code/game/g_bot.c @@ -674,6 +674,11 @@ static void G_AddBot( const char *name, float skill, const char *team, int delay Info_SetValueForKey( userinfo, "skill", va( "%1.2f", skill ) ); Info_SetValueForKey( userinfo, "team", team ); + // Settings this to 1 for bots because this also fixes a bug + // https://github.com/WofWca/quake3-better-gibs-mod/issues/3. + // See `bg_pmove.c`. + Info_SetValueForKey( userinfo, "cg_gibsBetterCameraOnGib", "1" ); + bot = &g_entities[ clientNum ]; bot->r.svFlags |= SVF_BOT; bot->inuse = qtrue; diff --git a/code/game/g_client.c b/code/game/g_client.c index da5cbcb1..66fa3156 100644 --- a/code/game/g_client.c +++ b/code/game/g_client.c @@ -624,6 +624,9 @@ qboolean ClientUserinfoChanged( int clientNum ) { client->pers.predictItemPickup = qtrue; } + client->pers.cg_gibsBetterCameraOnGib = + atoi( Info_ValueForKey( userinfo, "cg_gibsBetterCameraOnGib" ) ); + // set name Q_strncpyz( oldname, client->pers.netname, sizeof( oldname ) ); s = Info_ValueForKey( userinfo, "name" ); diff --git a/code/game/g_combat.c b/code/game/g_combat.c index 72d0db10..3be87a2c 100644 --- a/code/game/g_combat.c +++ b/code/game/g_combat.c @@ -302,6 +302,18 @@ void GibEntity( gentity_t *self, int killer, const int damageBloodFallback ) { self->takedamage = qfalse; self->s.eType = ET_INVISIBLE; self->r.contents = 0; + + // See `NEW_GIBBED_VIEWHEIGHT` references in `bg_pmove.c`. + // + // Note that we only do this when gibbed and not on any death, + // because otherwise, due to the change to `mins[2]`, + // the body would immediately visually fall under ground. + if ( self->client && self->client->pers.cg_gibsBetterCameraOnGib & 0x1 + // If we already died, we don't want to change + // the camera position again. + && self->client->deathTime == level.time ) { + self->client->ps.viewheight = NEW_GIBBED_VIEWHEIGHT; + } } /* @@ -488,6 +500,7 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int } #endif self->client->ps.pm_type = PM_DEAD; + self->client->deathTime = level.time; if ( attacker ) { killer = attacker->s.number; diff --git a/code/game/g_local.h b/code/game/g_local.h index 7a4b3cb4..7f4dc24c 100644 --- a/code/game/g_local.h +++ b/code/game/g_local.h @@ -231,6 +231,7 @@ typedef struct { qboolean localClient; // true if "ip" info key is "localhost" qboolean initialSpawn; // the first spawn should be at a cool location qboolean predictItemPickup; // based on cg_predictItems userinfo + int cg_gibsBetterCameraOnGib; char netname[MAX_NETNAME]; int maxHealth; // for handicapping int enterTime; // level.time the client entered the game @@ -304,6 +305,8 @@ struct gclient_s { int lastKillTime; // for multiple kill rewards + int deathTime; // 0 if alive + qboolean fireHeld; // used for hook gentity_t *hook; // grapple hook if out