From b2057019c1e7a38f74710705f14076f66036bcbb 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 da1142fa..16805d7f 100644 --- a/include/g_local.h +++ b/include/g_local.h @@ -1271,3 +1271,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 92b4bcfa..70c054ab 100644 --- a/include/progs.h +++ b/include/progs.h @@ -786,6 +786,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 8934ceeb..07feed9d 100644 --- a/src/plats.c +++ b/src/plats.c @@ -338,11 +338,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();