From 64f127a22f7661dc7a9953dc458585c3eb5adc0c Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Sun, 17 Dec 2023 01:50:21 +0000 Subject: Cancel fast-forward after the player disconnects Avoids spurious and confusing fast-forward behaviour in the event of resetting a run, then disconnecting early and deciding to load some other map. Also fixes a stupid typo (s/propand/propane/). --- compile | 1 + compile.bat | 1 + gamedata/engine.kv | 8 ++++++ src/gameserver.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/gameserver.h | 28 ++++++++++++++++++++ src/l4dreset.c | 31 +++++++++++++++------- 6 files changed, 135 insertions(+), 10 deletions(-) create mode 100644 src/gameserver.c create mode 100644 src/gameserver.h diff --git a/compile b/compile index f98f565..a08d075 100755 --- a/compile +++ b/compile @@ -69,6 +69,7 @@ src="\ fov.c gamedata.c gameinfo.c + gameserver.c hook.c kv.c kvsys.c diff --git a/compile.bat b/compile.bat index ec420ed..220a4a3 100644 --- a/compile.bat +++ b/compile.bat @@ -76,6 +76,7 @@ setlocal DisableDelayedExpansion :+ fov.c :+ gamedata.c :+ gameinfo.c +:+ gameserver.c :+ hook.c :+ kv.c :+ kvsys.c diff --git a/gamedata/engine.kv b/gamedata/engine.kv index c436eb5..9501221 100644 --- a/gamedata/engine.kv +++ b/gamedata/engine.kv @@ -95,6 +95,14 @@ off_SP_offset { //2013 72 // TODO(compat): not sure about 2013/009 yet pt3 } +// CBaseServer/CGameServer +vtidx_GetSpawnCount { + //OrangeBox "13 + NVDTOR" // not used right now anyway + L4D1 "13 + NVDTOR" + L4D2 "14 + NVDTOR" // GetTimescale() added, pushed it down + // rest untested, add later if/when actually needed for something +} + // IEngineVGuiInternal/CEngineVGui vtidx_VGuiConnect { // note: the actual name is Connect() but that's too generic default "3 + NVDTOR" diff --git a/src/gameserver.c b/src/gameserver.c new file mode 100644 index 0000000..5a76027 --- /dev/null +++ b/src/gameserver.c @@ -0,0 +1,76 @@ +/* + * Copyright © 2023 Michael Smith + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include "con_.h" +#include "errmsg.h" +#include "feature.h" +#include "gamedata.h" +#include "intdefs.h" +#include "mem.h" +#include "x86.h" +#include "vcall.h" +#include "x86util.h" + +FEATURE() +REQUIRE_GAMEDATA(vtidx_GetSpawnCount) + +DECL_VFUNC_DYN(int, GetSpawnCount) + +static void *sv; + +int gameserver_spawncount(void) { return GetSpawnCount(sv); } + +static bool find_sv(con_cmdcb pause_cb) { +#ifdef _WIN32 + // The last thing pause does is call BroadcastPrintf with 4 args including + // `this`, all on the stack since it's varargs. 2 of the args are pushed + // immediately before `this`, so we can just look for 3 back-to-back pushes + // and a call. + const uchar *insns = (const uchar *)pause_cb; + int pushes = 0; + for (const uchar *p = insns; p - insns < 256;) { + if (*p == X86_PUSHIW || *p >= X86_PUSHEAX && *p <= X86_PUSHEDI) { + if (++pushes == 3) { + if (*p != X86_PUSHIW || p[5] != X86_CALL) { + // it'd be super weird to have this many pushes anywhere + // else in the function, so give up here + return false; + } + sv = mem_loadptr(p + 1); + return true; + } + } + else { + pushes = 0; + } + NEXT_INSN(p, "load of sv pointer"); + } +#else +#warning TODO(linux): the usual x86 stuff +#endif + return false; +} + +INIT { + struct con_cmd *pause = con_findcmd("pause"); + if (!find_sv(pause->cb)) { + errmsg_errorx("couldn't find game server object\n"); + return false; + } + return true; +} + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/gameserver.h b/src/gameserver.h new file mode 100644 index 0000000..f27ad5c --- /dev/null +++ b/src/gameserver.h @@ -0,0 +1,28 @@ +/* + * Copyright © 2023 Michael Smith + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef INC_GAMESERVER_H +#define INC_GAMESERVER_H + +/* + * Returns the spawn count / server number which the engine increments every new + * map. Can be used to help keep track of map changes, disconnects and such. + */ +int gameserver_spawncount(void); + +#endif + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/l4dreset.c b/src/l4dreset.c index c228665..479a1c8 100644 --- a/src/l4dreset.c +++ b/src/l4dreset.c @@ -26,6 +26,7 @@ #include "feature.h" #include "gamedata.h" #include "gametype.h" +#include "gameserver.h" #include "hook.h" #include "intdefs.h" #include "l4dmm.h" @@ -42,6 +43,7 @@ FEATURE("Left 4 Dead quick resetting") REQUIRE(ent) REQUIRE(fastfwd) +REQUIRE(gameserver) REQUIRE(l4dmm) REQUIRE_GLOBAL(srvdll) REQUIRE_GAMEDATA(vtidx_GameFrame) // note: for L4D1 only, always defined anyway @@ -119,7 +121,7 @@ static const schar ffsegs[] = { - 9, // No Mercy 4, // Swamp Fever 3, // - seen first propane - -12, // - second propand. Also: Death Toll; Dead Air (L4D1); Dark Carnival + -12, // - second propane. Also: Death Toll; Dead Air (L4D1); Dark Carnival -15, // Blood Harvest (L4D1); The Sacrifice (L4D1) - 8, // Crash Course; Hard Rain -13, // Dead Center; The Parish; Dead Air (L4D2) @@ -148,9 +150,9 @@ static const schar ffsegs[] = { #define FFIDX_STREAM 11 static schar ffidx; -static short ffdelay; +static short ffdelay = 0; static float ffadj = 0; -static bool mapchanging = false; +static int nextmapnum = 0; DEF_CVAR_MINMAX_UNREG(sst_l4d_quickreset_peektime, "Number of seconds to show each relevant item spot during fast-forward", @@ -170,7 +172,7 @@ DEF_CCMD_HERE_UNREG(sst_l4d_quickreset_continue, } HANDLE_EVENT(Tick, bool simulating) { - if (!mapchanging && simulating && ffdelay && !--ffdelay) { + if (!nextmapnum && simulating && ffdelay && !--ffdelay) { schar seg = ffsegs[ffidx]; float halfwin = con_getvarf(sst_l4d_quickreset_peektime) / 2.0f; float t; @@ -193,8 +195,13 @@ typedef void (*VCALLCONV OnGameplayStart_func)(void *this); static OnGameplayStart_func orig_OnGameplayStart; static void VCALLCONV hook_OnGameplayStart(void *this) { orig_OnGameplayStart(this); - if (mapchanging) reset(); // prevent bots walking around. note ffdelay is 45 - mapchanging = false; // resume countdown! + if (nextmapnum) { + // if we changed map more than 1 time, cancel the reset. this'll happen + // if someone prematurely disconnects and then starts a new session. + if (nextmapnum != gameserver_spawncount()) ffdelay = 0; + else reset(); // prevent bots walking around. note ffdelay is stil 45 + } + nextmapnum = 0; // resume countdown if there is one! otherwise do nothing. } // Simply reuse the above for L4D1, since the calling ABI is the exact same! #define UnfreezeTeam_func OnGameplayStart_func @@ -262,13 +269,17 @@ DEF_CCMD_HERE_UNREG(sst_l4d_quickreset, if (cmd->argc == 2 && (!campaign || strcasecmp(campaign, cmd->argv[1]))) { change(cmd->argv[1]); campaign = cmd->argv[1]; - mapchanging = true; + nextmapnum = gameserver_spawncount() + 1; // immediate next changelevel } else { reset(); - // same-map reset is delayed by about a second - save that time here - // also, set mapchanging back to false in case it got stuck somehow - if (!(mapchanging = !l4dmm_firstmap())) fastfwd(0.8, 10); + if (l4dmm_firstmap()) { + fastfwd(0.8, 10); // same-map reset is delayed by about a second + nextmapnum = 0; // reset this just in case it got stuck... somehow? + } + else { + nextmapnum = gameserver_spawncount() + 1; // same as above + } } if (campaign && con_getvari(sst_l4d_quickreset_fastfwd) && (ffidx = getffidx(campaign)) != -1) { -- cgit v1.2.3