From b1e476c08a9aa88237e282c9d7d73d741a3470a9 Mon Sep 17 00:00:00 2001 From: Oscar Linderholm Date: Wed, 8 Oct 2025 19:47:04 +0200 Subject: [PATCH] Add E1M2 practice mode If practice mode is enabled on E1M2, you can now practice the jump from the moving blocks to mega by retriggering the floor button to restart the block train. --- include/g_local.h | 2 ++ include/progs.h | 1 + src/buttons.c | 7 +++++++ src/maps.c | 5 +++++ src/plats.c | 41 ++++++++++++++++++++++++++++++++++++++++- 5 files changed, 55 insertions(+), 1 deletion(-) diff --git a/include/g_local.h b/include/g_local.h index fa744359..8580ccaf 100644 --- a/include/g_local.h +++ b/include/g_local.h @@ -1296,3 +1296,5 @@ int SpawnShowStatus(void); int SpawnicideStatus(void); void SpawnicideEnable(void); void SpawnicideDisable(void); + +qbool IsE1M2Practice(void); diff --git a/include/progs.h b/include/progs.h index fdf10085..a633601b 100644 --- a/include/progs.h +++ b/include/progs.h @@ -812,6 +812,7 @@ typedef struct gedict_s string_t netname; string_t target; string_t targetname; + string_t firsttarget; string_t message; string_t noise; string_t noise1; diff --git a/src/buttons.c b/src/buttons.c index c7f728c6..8b49446d 100644 --- a/src/buttons.c +++ b/src/buttons.c @@ -81,6 +81,13 @@ void button_fire(void) self->state = STATE_UP; + // Allow the button in the mega room floor to be pressed multiple times + // on E1M2 during practice mode. + if (IsE1M2Practice() && streq(self->model, "*16")) + { + self->wait = 1; + } + SUB_CalcMove(self->pos2, self->speed, button_wait); } diff --git a/src/maps.c b/src/maps.c index 340bbedb..e00b1206 100644 --- a/src/maps.c +++ b/src/maps.c @@ -692,3 +692,8 @@ char* SelectMapInCycle(char *buf, int buf_size) return buf; } + +qbool IsE1M2Practice(void) +{ + return k_practice && streq(mapname, "e1m2"); +} diff --git a/src/plats.c b/src/plats.c index cb60bd7a..9ae4c658 100644 --- a/src/plats.c +++ b/src/plats.c @@ -340,11 +340,50 @@ void train_blocked(void) T_Damage(other, self, self, self->dmg); } +void train_reset(void) +{ + gedict_t *firsttarget; + vec3_t tmpv; + + if (!self->firsttarget) + { + G_bprint(2, "train_reset: no firsttarget stored\n"); + return; + } + + firsttarget = find(world, FOFS(targetname), self->firsttarget); + if (!firsttarget) + { + G_bprint(2, "train_reset: couldn't find first path_corner\n"); + return; + } + + VectorSubtract(firsttarget->s.v.origin, self->s.v.mins, tmpv); + setorigin(self, tmpv[0], tmpv[1], tmpv[2]); + + self->target = self->firsttarget; + self->think = (func_t )funcref_train_find; +} + void train_use(void) { + qbool isE1M2Practice = IsE1M2Practice(); + + if (isE1M2Practice && !self->firsttarget) + { + self->firsttarget = self->target; + } + if (self->think != (func_t) funcref_train_find) { - return; // already activated + if (isE1M2Practice) + { + train_reset(); + } + else + { + return; + } } train_next();