summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMichael Smith <mikesmiffy128@gmail.com>2023-12-17 01:50:21 +0000
committerMichael Smith <mikesmiffy128@gmail.com>2023-12-17 01:54:50 +0000
commit64f127a22f7661dc7a9953dc458585c3eb5adc0c (patch)
tree9c5d9dc2f84d0b567ac4a9dec0f0edf4d76315b4
parentb71ce15ade8de4ac21a459432ebdd3f94ca5a8dd (diff)
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/).
-rwxr-xr-xcompile1
-rw-r--r--compile.bat1
-rw-r--r--gamedata/engine.kv8
-rw-r--r--src/gameserver.c76
-rw-r--r--src/gameserver.h28
-rw-r--r--src/l4dreset.c31
6 files changed, 135 insertions, 10 deletions
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 <mikesmiffy128@gmail.com>
+ *
+ * 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 <mikesmiffy128@gmail.com>
+ *
+ * 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) {