From 8e8eddc954efd8552d407ae3569b4bcffa0f811c Mon Sep 17 00:00:00 2001 From: WofWca Date: Tue, 18 Nov 2025 12:34:11 +0400 Subject: [PATCH 01/23] fix: corpses don't absorb shotgun pellets This is a follow-up to ad81db1b27e669c7c59aca8b1c22e6c552d1a2ac (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 b2b172380172cf13bb085d26dbbd39f3a799c976 Mon Sep 17 00:00:00 2001 From: WofWca Date: Sun, 9 Nov 2025 14:07:15 +0400 Subject: [PATCH 02/23] 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 c153d37cc01f7ce621108ba1fb1d658ada128286 Mon Sep 17 00:00:00 2001 From: WofWca Date: Sun, 9 Nov 2025 14:35:21 +0400 Subject: [PATCH 03/23] 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 bed72bf30115a577b645efd25aec29e109f8cd7e Mon Sep 17 00:00:00 2001 From: WofWca Date: Sun, 9 Nov 2025 21:00:17 +0400 Subject: [PATCH 04/23] 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 229bfea6b8eed32974e7ee29748c16265587718b Mon Sep 17 00:00:00 2001 From: WofWca Date: Mon, 2 Feb 2026 17:58:55 +0400 Subject: [PATCH 05/23] 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 2ecadaae45644738b4fd150dacd5e7c02a896e86 Mon Sep 17 00:00:00 2001 From: WofWca Date: Fri, 7 Nov 2025 12:25:32 +0400 Subject: [PATCH 06/23] 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 2b999ab63efa07a92b2a641719ebf6d52249a7a7 Mon Sep 17 00:00:00 2001 From: WofWca Date: Mon, 10 Nov 2025 13:54:51 +0400 Subject: [PATCH 07/23] 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 0c2c19665c83ce6679ce1d9c2923ede01057acdd Mon Sep 17 00:00:00 2001 From: WofWca Date: Mon, 10 Nov 2025 17:55:51 +0400 Subject: [PATCH 08/23] 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 089fb3de325c7a7a761d54f90cdcf8f1d43ad81d Mon Sep 17 00:00:00 2001 From: WofWca Date: Fri, 14 Nov 2025 17:55:52 +0400 Subject: [PATCH 09/23] fix: shotgun not applying knockback on frag --- code/game/g_active.c | 6 ++++++ code/game/g_combat.c | 26 ++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/code/game/g_active.c b/code/game/g_active.c index ad5d2878..d6da2559 100644 --- a/code/game/g_active.c +++ b/code/game/g_active.c @@ -1154,6 +1154,12 @@ void ClientEndFrame( gentity_t *ent ) { client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health... + // This is not present in the original game code, + // see comments about `FL_NO_KNOCKBACK` in `g_combat`. + if ( client->ps.pm_type & PM_DEAD ) { + ent->flags |= FL_NO_KNOCKBACK; + } + G_SetClientSound( ent ); // set the latest info diff --git a/code/game/g_combat.c b/code/game/g_combat.c index 5008feb6..f3936b52 100644 --- a/code/game/g_combat.c +++ b/code/game/g_combat.c @@ -1063,8 +1063,30 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, } if ( targ->health <= 0 ) { - if ( client ) - targ->flags |= FL_NO_KNOCKBACK; + // In the original code we used to set `FL_NO_KNOCKBACK` here. + // However, that made it so that when fragging with the shotgun, + // the dead body does not gain momentum (knockback) + // from the pellets that come after the pellet + // that made the health go below 0. + // That resulted 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. + // + // Now we set the `FL_NO_KNOCKBACK` flag inside of `ClientEndFrame`, + // which is ran after all the pellets of the shotgun shot + // have done their thing, + // i.e. `FL_NO_KNOCKBACK` takes effect only on the next frame. + // + // 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 ) + // targ->flags |= FL_NO_KNOCKBACK; if (targ->health < -999) targ->health = -999; From ad5602e863ccb957813d178cbc2f1830d447d728 Mon Sep 17 00:00:00 2001 From: WofWca Date: Tue, 11 Nov 2025 11:39:56 +0400 Subject: [PATCH 10/23] 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 38e480448861c38730c5b19153e13acf1d36f3d6 Mon Sep 17 00:00:00 2001 From: WofWca Date: Tue, 18 Nov 2025 19:14:07 +0400 Subject: [PATCH 11/23] 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 fcfef56524208a146364e8516bcef1df6da2d70c Mon Sep 17 00:00:00 2001 From: WofWca Date: Wed, 7 Jan 2026 13:01:30 +0400 Subject: [PATCH 12/23] 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 3db539d371fb05e69bf7fe997972c6d273c04937 Mon Sep 17 00:00:00 2001 From: WofWca Date: Mon, 12 Jan 2026 22:49:16 +0400 Subject: [PATCH 13/23] 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 3ad2efe626293d9c2c8fa97b93c09e78646943bd Mon Sep 17 00:00:00 2001 From: WofWca Date: Wed, 14 Jan 2026 22:46:48 +0400 Subject: [PATCH 14/23] 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 8e5636d06bf79e55186faf2c7404511851cfbffd Mon Sep 17 00:00:00 2001 From: WofWca Date: Mon, 19 Jan 2026 21:36:34 +0400 Subject: [PATCH 15/23] 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 bdb2869283f3560d64dc857f142e983aa9fd7506 Mon Sep 17 00:00:00 2001 From: WofWca Date: Sat, 24 Jan 2026 12:59:46 +0400 Subject: [PATCH 16/23] 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 558c50d262a1ca3ea253542c4f9b17cdc13253ea Mon Sep 17 00:00:00 2001 From: WofWca Date: Sat, 24 Jan 2026 16:48:44 +0400 Subject: [PATCH 17/23] 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 | 92 +++++++++++++++++++++++++++++++-------- code/cgame/cg_event.c | 7 ++- code/cgame/cg_local.h | 3 +- code/cgame/cg_localents.c | 2 +- 4 files changed, 81 insertions(+), 23 deletions(-) diff --git a/code/cgame/cg_effects.c b/code/cgame/cg_effects.c index c1aeb1a2..d7b8536a 100644 --- a/code/cgame/cg_effects.c +++ b/code/cgame/cg_effects.c @@ -586,6 +586,51 @@ static void CG_LaunchGib( const vec3_t origin, const vec3_t angles, le->leMarkType = LEMT_BLOOD; } +#define PLAYER_RADIUS 15 +// 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_RADIUS / 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 +642,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_RADIUS; float baseRandomVelocity = cg_gibsExtraRandomVelocity.value; vec3_t playerVelocityScaled; float jump = cg_gibsExtraVerticalVelocity.value; @@ -620,9 +666,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 +685,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 +698,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 +719,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 +736,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 +750,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 +767,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 +781,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 +798,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 +810,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 +827,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 c021e7ec33b30ff70f3641ff8e25811abd170e40 Mon Sep 17 00:00:00 2001 From: WofWca Date: Sat, 24 Jan 2026 16:56:17 +0400 Subject: [PATCH 18/23] 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 d7b8536a..85c2823b 100644 --- a/code/cgame/cg_effects.c +++ b/code/cgame/cg_effects.c @@ -674,9 +674,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 ); @@ -688,14 +685,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 ); @@ -726,7 +724,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; @@ -788,7 +787,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; @@ -817,7 +817,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; @@ -834,7 +835,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 9b92f061498e929d6e76b481948dc28151854fea Mon Sep 17 00:00:00 2001 From: WofWca Date: Thu, 22 Jan 2026 13:55:28 +0400 Subject: [PATCH 19/23] 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 f3936b52..7b68b06d 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 @@ -773,6 +787,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 @@ -803,11 +888,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; } @@ -908,12 +996,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 @@ -1091,6 +1174,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 89e08e97df38b64296be4c598f3bc672b0a0a188 Mon Sep 17 00:00:00 2001 From: WofWca Date: Thu, 29 Jan 2026 22:17:24 +0400 Subject: [PATCH 20/23] 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 3a56190b72e263a2de4fd580116651ab6b10a2fe Mon Sep 17 00:00:00 2001 From: WofWca Date: Mon, 2 Feb 2026 14:41:58 +0400 Subject: [PATCH 21/23] 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 | 64 +++++++++++++++++++++++++++++++------- code/game/g_cvar.h | 1 + 10 files changed, 90 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 85c2823b..7db1b6d6 100644 --- a/code/cgame/cg_effects.c +++ b/code/cgame/cg_effects.c @@ -642,7 +642,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, @@ -656,7 +656,10 @@ void CG_GibPlayer( const vec3_t playerOrigin, const vec3_t playerAngles, // to account for crounching. float playerHeight = 32 - MINS_Z; float playerRadius = PLAYER_RADIUS; - 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 0878102e..95ec9c8b 100644 --- a/code/game/bg_public.h +++ b/code/game/bg_public.h @@ -185,6 +185,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 d6da2559..f92b8db8 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; } @@ -1223,6 +1216,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 7b68b06d..e2909d35 100644 --- a/code/game/g_combat.c +++ b/code/game/g_combat.c @@ -215,17 +215,14 @@ 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 ================== */ 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; } @@ -234,7 +231,8 @@ static float KnockbackToKnockbackSpeed( int knockback ) { GibEntity ================== */ -void GibEntity( gentity_t *self, int killer ) { +void GibEntity( gentity_t *self, int killer, int damageBloodFallback ) { + int eventParm = killer; #ifdef MISSIONPACK gentity_t *ent; int i; @@ -256,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; @@ -276,7 +318,7 @@ void body_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int d return; } - GibEntity( self, 0 ); + GibEntity( self, 0, damage ); } @@ -626,7 +668,7 @@ 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 ); + GibEntity( self, killer, damage ); } else { // normal death static int i; @@ -984,8 +1026,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; 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 ) From eefc0c8e33fc60d600a4f121439417b2c6f591bd Mon Sep 17 00:00:00 2001 From: WofWca Date: Thu, 5 Feb 2026 12:01:40 +0400 Subject: [PATCH 22/23] 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 7db1b6d6..d5f9ef01 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); } } @@ -636,6 +640,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 @@ -643,7 +651,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. @@ -659,6 +667,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; @@ -688,21 +697,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; @@ -711,11 +720,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; } @@ -725,15 +734,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; } @@ -743,11 +752,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; } @@ -757,14 +766,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; } @@ -774,11 +783,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; } @@ -788,15 +797,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; } @@ -804,11 +813,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; } @@ -818,15 +827,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; } @@ -836,14 +845,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; } @@ -863,9 +872,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 @@ -877,55 +886,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 6138028e95ecdbf2c14090bc7483082148c63f62 Mon Sep 17 00:00:00 2001 From: WofWca Date: Thu, 5 Feb 2026 19:39:18 +0400 Subject: [PATCH 23/23] 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;