From 342e825c732288b082aabd6b14f778ece38a428a Mon Sep 17 00:00:00 2001 From: Maxime Morel Date: Thu, 22 Oct 2026 13:08:39 +0200 Subject: [PATCH] Corpses --- port/src/video.c | 4 ++ src/game/chraction.c | 107 +++++++++++++++++++++++++++++++++ src/game/dyn.c | 5 ++ src/game/explosion.c | 3 + src/game/explosion.h | 9 ++- src/game/initexplosioncasing.c | 6 ++ src/game/initguards.c | 7 +++ 7 files changed, 140 insertions(+), 1 deletion(-) diff --git a/port/src/video.c b/port/src/video.c index 89bd255e..79507257 100644 --- a/port/src/video.c +++ b/port/src/video.c @@ -112,6 +112,8 @@ s32 portSkipIntro = 0; * hook in src/game/bondview2.c currentPlayerSetFadeColour). 0 (default) = * original damage flash. */ s32 portNoHitFlash = 0; +s32 portDecalLimit = 100; /* Game.DecalLimit: bullet holes / blood splats kept per stage (N64: 100) */ +s32 portMaxCorpses = 0; /* Game.MaxCorpses: 0 = bodies fade after dying (N64), -1 = never */ /* v0.4.0 M2 (D373/D379): in-game crosshair on/off + tint * (gunfire.c gunDrawSight, #ifdef PORT). Default keeps the original @@ -561,6 +563,8 @@ PD_CONSTRUCTOR static void videoConfigInit(void) configRegisterFloat("Game.ScreenShakeIntensity", &portScreenShakeScale, 0.0f, 10.0f); configRegisterInt("Game.SkipIntro", &portSkipIntro, 0, 1); configRegisterInt("Game.NoHitFlash", &portNoHitFlash, 0, 1); + configRegisterInt("Game.DecalLimit", &portDecalLimit, 100, 2000); + configRegisterInt("Game.MaxCorpses", &portMaxCorpses, -1, 64); /* -1 = never fade */ configRegisterInt("Game.AIWideView", &cfgAIWideView, 0, 1); /* D468 */ configRegisterInt("Video.CrosshairHide", &portCrosshairHide, 0, 1); /* v0.4.0 M2 (D373) */ configRegisterInt("Video.CrosshairPersistent", &cfgCrosshairPersistent, 0, 1); /* D436 */ diff --git a/src/game/chraction.c b/src/game/chraction.c index 878eba67..c371ad1f 100644 --- a/src/game/chraction.c +++ b/src/game/chraction.c @@ -887,6 +887,92 @@ void chrlvActorFadeAway(ChrRecord *self) } +#ifdef PORT +/* Game.MaxCorpses: 0 = original (every body fades once its death animation + * ends), -1 = bodies never fade unless guard slots run low (spawning needs 3 + * free). Otherwise bodies stay, and the oldest starts its normal fade once + * there are more than this many. A kept body is marked by allowfade == + * CORPSE_KEEP, and reaptimer holds its death order. */ +#define CORPSE_MIN_FREE_SLOTS 4 +#define CORPSE_KEEP (-2) +extern s32 portMaxCorpses; +s32 chrGetNumFree(void); +static s32 s_CorpseSeq = 0; + +static void chrlvActorKeepCorpse(ChrRecord *self) +{ + if (self->actiontype != ACT_DEAD) + { + chrStopFiring(self); + self->actiontype = ACT_DEAD; + self->act_dead.allowfade = CORPSE_KEEP; + self->act_dead.reaptimer = ++s_CorpseSeq; + self->sleep = 0; + } +} + +static void chrlvFadeExcessCorpses(void) +{ + for (;;) + { + s32 i; + s32 count = 0; + s32 fading = 0; + bool excess; + ChrRecord *oldest = NULL; + ChrRecord *oldestoffscreen = NULL; + + for (i = 0; i < g_NumChrSlots; i++) + { + ChrRecord *chr = &g_ChrSlots[i]; + + if (chr->model == NULL || chr->prop == NULL || chr->actiontype != ACT_DEAD) + { + continue; + } + + if (chr->act_dead.allowfade != CORPSE_KEEP) + { + /* already fading: its slot frees up within 1.5 s */ + fading++; + continue; + } + + count++; + + if (oldest == NULL || chr->act_dead.reaptimer < oldest->act_dead.reaptimer) + { + oldest = chr; + } + + if ((chr->prop->flags & PROPFLAG_ONSCREEN) == 0 + && (oldestoffscreen == NULL || chr->act_dead.reaptimer < oldestoffscreen->act_dead.reaptimer)) + { + oldestoffscreen = chr; + } + } + + if (portMaxCorpses > 0) + { + excess = count > portMaxCorpses; + } + else + { + excess = chrGetNumFree() + fading < CORPSE_MIN_FREE_SLOTS; + } + + if (!excess || oldest == NULL) + { + return; + } + + /* Prefer one the player can't see; chrlvTickDead starts the fade */ + (oldestoffscreen ? oldestoffscreen : oldest)->act_dead.allowfade = -1; + } +} +#endif + + /** * chrStepToSide @@ -5331,6 +5417,13 @@ void chrlvTickSurrender(ChrRecord *self) */ void chrlvTickDead(ChrRecord *self) { +#ifdef PORT + if (self->act_dead.allowfade == CORPSE_KEEP) + { + return; + } +#endif + if (self->act_init.padding[0] >= 0) { self->act_init.padding[0] += g_ClockTimer; @@ -5502,6 +5595,13 @@ void chrlvTickDie(ChrRecord *self) return; } +#ifdef PORT + if (portMaxCorpses != 0) + { + chrlvActorKeepCorpse(self); + } + else +#endif chrlvActorFadeAway(self); } @@ -9618,6 +9718,13 @@ void chrlvAllChrTick(void) chrlvActionTick(&g_ActiveChrs[i]); } +#ifdef PORT + if (portMaxCorpses != 0) + { + chrlvFadeExcessCorpses(); + } +#endif + g_SeenBondRecentlyGuardCount = 0; for (i=0; i 100) { + gfxHalf += (portDecalLimit - 100) * 4 * (s32)sizeof(Gfx); + } g_GfxBuffers[0] = mempAllocBytesInBank(gfxHalf * 2, MEMPOOL_STAGE); g_GfxBuffers[1] = (g_GfxBuffers[0] + gfxHalf); g_GfxBuffers[2] = (g_GfxBuffers[1] + gfxHalf); diff --git a/src/game/explosion.c b/src/game/explosion.c index 2dfef75e..fd5d87b4 100644 --- a/src/game/explosion.c +++ b/src/game/explosion.c @@ -74,6 +74,9 @@ struct Scorch *g_ScorchBuffer; * cannonically wallhitlist */ struct BulletImpact *g_BulletImpactBuffer; +#ifdef PORT +s32 g_BulletImpactBufferLen = BULLET_IMPACT_BUFFER_LEN_DEFAULT; +#endif // data //D:80040170 diff --git a/src/game/explosion.h b/src/game/explosion.h index a839e2ad..763fb77f 100644 --- a/src/game/explosion.h +++ b/src/game/explosion.h @@ -13,7 +13,14 @@ #define SMOKE_PARTS_LEN 10 #define SCORCH_BUFFER_LEN 20 #define IMPACT_TYPE_LEN 20 -#define BULLET_IMPACT_BUFFER_LEN 100 +#define BULLET_IMPACT_BUFFER_LEN_DEFAULT 100 +#ifdef PORT +/* Game.DecalLimit, latched when the stage's impact buffer is allocated */ +extern s32 g_BulletImpactBufferLen; +#define BULLET_IMPACT_BUFFER_LEN g_BulletImpactBufferLen +#else +#define BULLET_IMPACT_BUFFER_LEN BULLET_IMPACT_BUFFER_LEN_DEFAULT +#endif #define MAX_FLYING_PARTICLES 200 typedef struct s_smoketype { diff --git a/src/game/initexplosioncasing.c b/src/game/initexplosioncasing.c index 706b1f47..7297e971 100644 --- a/src/game/initexplosioncasing.c +++ b/src/game/initexplosioncasing.c @@ -57,6 +57,12 @@ void alloc_explosion_smoke_casing_scorch_impact_buffers(void) } } +#ifdef PORT + { + extern s32 portDecalLimit; + g_BulletImpactBufferLen = portDecalLimit; + } +#endif osSyncPrintf("Allocating %d bytes for wallhit data\n", BULLET_IMPACT_BUFFER_LEN * sizeof(struct BulletImpact)); g_BulletImpactBuffer = (struct BulletImpact *)mempAllocBytesInBank(BULLET_IMPACT_BUFFER_LEN * sizeof(struct BulletImpact), MEMPOOL_STAGE); diff --git a/src/game/initguards.c b/src/game/initguards.c index e408afe4..7b3d49a1 100644 --- a/src/game/initguards.c +++ b/src/game/initguards.c @@ -28,6 +28,13 @@ void alloc_init_GUARDdata_entries(s32 count) s32 i; g_NumChrSlots = count + 10; //special guards? +#ifdef PORT + { + /* Game.MaxCorpses: kept bodies hold their slots; keep room for spawns */ + extern s32 portMaxCorpses; + g_NumChrSlots += portMaxCorpses < 0 ? 64 : portMaxCorpses; + } +#endif g_ChrSlots = mempAllocBytesInBank(align_addr_zero(g_NumChrSlots * sizeof(ChrRecord)), MEMPOOL_STAGE); for(i = 0; g_NumChrSlots > i; i++)