From c874549c4e2ba9223d7f1c716f9ff3aa84726118 Mon Sep 17 00:00:00 2001 From: "Dr. Jaska" Date: Mon, 27 Jan 2025 16:28:52 +0000 Subject: [PATCH] Added new cl_hooks, menu_cmd isdemo and fixed demoseeking.cfg --- commands.cfg | 1 + demoseeking.cfg | 40 +++++++-------------- gamemodes-client.cfg | 66 ++++++++++++++++++---------------- qcsrc/client/main.qc | 32 ++++++++++++----- qcsrc/client/main.qh | 5 +-- qcsrc/client/view.qc | 20 +++++++++-- qcsrc/menu/command/menu_cmd.qc | 30 ++++++++++++---- 7 files changed, 116 insertions(+), 78 deletions(-) diff --git a/commands.cfg b/commands.cfg index 8ff4e78b8..daf64570b 100644 --- a/commands.cfg +++ b/commands.cfg @@ -130,6 +130,7 @@ alias menu_showsandboxtools "menu_cmd directmenu SandboxTools" alias menu_showquitdialog "menu_cmd directmenu Quit" alias menu_showgamemenudialog "menu_cmd directmenu GameMenu" alias menu_showmonstertools "menu_cmd directmenu MonsterTools" +alias menu_isdemo "menu_cmd isdemo ${* ?}" // command executed before loading a map by the menu // makes sure maxplayers is at least minplayers or minplayers_per_team * 4 or bot_number + 1 diff --git a/demoseeking.cfg b/demoseeking.cfg index 5dbaf86b3..ff4a20a97 100644 --- a/demoseeking.cfg +++ b/demoseeking.cfg @@ -3,12 +3,11 @@ // // Setup: // -// Add "exec input-demoseeking.cfg" and the following cl_hook_gamestart_all -// hook to autoexec.cfg. If you already have a cl_hook_gamestart_all hook, add -// the quoted command to your hook, separated with a semicolon: +// Add either of the following to your autoexec.cfg: +// +// exec input-demoseeking.cfg // // exec demoseeking.cfg -// alias cl_hook_gamestart_all "demoseeking_game_started" // // Usage: // 1. start a demo (ply/playdemo command or menu), and @@ -44,10 +43,6 @@ // // Variables: // -// _demo_is_playing -// Indicates whether a demo is currently playing. Set to 1 when playdemo -// starts a demo. -// Note: the setup above is needed to reset this reliably. // _current_demo_name // The name of the latest played demo. Same as the argument passed to the // last playdemo command. If playdemo fails to start the demo (e.g. when @@ -58,7 +53,8 @@ // // Extras: // -// - The alias playdemo_hook is run when demo playback is started. +// - The alias playdemo_hook is run when playdemo command is called. +// Regardless of its success to load a demo with that name. // - The alias seekdemo_hook_seek_end is run after a seek completed successfully. // - The alias seekdemo_getseektime can be used by scripts to get the current // playback time or seek target time. See its usage below. @@ -71,13 +67,14 @@ set _demoseeking_fast_speed 80 set _demoseeking_speed_factor 10 // state initialization -alias _demoseeking_init_vars "set _demoseeking_vars_loaded 1; set _demo_is_playing 0; set _current_demo_name \"\"; set _demoseeking_is_seeking 0; set _seekdemo_state idle; set _seekdemo_target 0; alias playdemo_hook \"\"; alias seekdemo_hook_seek_end" +alias _demoseeking_init_vars "set _demoseeking_vars_loaded 1; set _seekdemo_demo_is_playing 0; set _current_demo_name \"\"; set _demoseeking_is_seeking 0; set _seekdemo_state idle; set _seekdemo_target 0; alias playdemo_hook \"\"; alias seekdemo_hook_seek_end" alias _demoseeking_init_vars1 "" _demoseeking_init_vars${_demoseeking_vars_loaded ?} -// Hook into game start to reset seekdemo state. This prevents reloading the -// last demo when seekdemo is accidentally called during an actual game. -alias demoseeking_game_started "set _demo_is_playing 0; set _current_demo_name \"\"" +// Hook into "playdemo" command. This alias runs immediately after the actual +// "playdemo" command runs. Needed for seekdemo to restart the demo when +// seeking backwards. +alias playdemo "set _current_demo_name \"$1\"; playdemo_hook \"$1\"" // usage: seekdemo_getseektime // Populates with the current seek target time, if seeking. If not @@ -88,19 +85,6 @@ set _seekdemo_getseektime_rpn_idle time set _seekdemo_getseektime_rpn_starting _seekdemo_target set _seekdemo_getseektime_rpn_seeking _seekdemo_target -// Hook into "playdemo" command. This alias runs immediately after the actual -// "playdemo" command runs. Needed for seekdemo to restart the demo when -// seeking backwards. Runs the playdemo_hook alias when a demo file is being -// loaded. -// --- -// "cl_cmd rpn" fails while a map is loading, leaving the value of -// _demoseeking_playdemo_success untouched, which means the playdemo command -// succeeded. -alias playdemo "set _demoseeking_playdemo_success 1; cl_cmd rpn /_demoseeking_playdemo_success 0 def; _demoseeking_playdemo_check \"$1\"" -alias _demoseeking_playdemo_check "_demoseeking_playdemo_check_$_demoseeking_playdemo_success \"$1\"" -alias _demoseeking_playdemo_check_0 "echo \"playdemo failed\"" -alias _demoseeking_playdemo_check_1 "set _demo_is_playing 1; set _current_demo_name \"$1\"; playdemo_hook" - // Hook into the "defer" command to restore state when "defer clear" is run // while seeking. "defer clear" seems to be run by some csprogs. // --- @@ -128,13 +112,13 @@ alias _demoseeking_restore_seeking "_seekdemo_check_time" // check if seek should start or if target time should be adjusted instead alias seekdemo "_seekdemo_checkstate_$_seekdemo_state ${* q}" // before starting seek, verify demo is playing first -alias _seekdemo_checkstate_idle "set _seekdemo_demo_is_playing 0; cl_cmd rpn /_seekdemo_demo_is_playing _demo_is_playing 0 != def; _seekdemo_checkstart ${* q}" +alias _seekdemo_checkstate_idle "menu_cmd isdemo _seekdemo_demo_is_playing; _seekdemo_checkstart ${* q}" // when already seeking, only update the variable holding the target time alias _seekdemo_checkstate_starting "_seekdemo_checkstate_seeking ${* q}" alias _seekdemo_checkstate_seeking "rpn /_seekdemo_target _seekdemo_target \"$1\" add def" alias _seekdemo_checkstart "_seekdemo_checkstart_$_seekdemo_demo_is_playing ${* q}" -alias _seekdemo_checkstart_0 "echo \"no demo currently playing\"" +alias _seekdemo_checkstart_0 "set _current_demo_name \"\";echo \"no demo currently playing\"" // start new seek alias _seekdemo_checkstart_1 "cl_cmd rpn /_seekdemo_target time \"$1\" add def; set _demoseeking_is_seeking 1; set _seekdemo_state starting; _seekdemo_save_options; _seekdemo_start_seek" diff --git a/gamemodes-client.cfg b/gamemodes-client.cfg index 1648daca5..02aea9cf9 100644 --- a/gamemodes-client.cfg +++ b/gamemodes-client.cfg @@ -14,34 +14,38 @@ alias asay_drop "say_team (%l) dropped %w ; impulse 17" seta cl_matchcount 0 // incremented by cl_hook_gameend and used by playerstats to know when to alias _cl_hook_gamestart "set _cl_hook_gametype $1; _cl_hook_gamestart_stage2" alias _cl_hook_gamestart_stage2 "cl_hook_gamestart_all; cl_hook_gamestart_${_cl_hook_gametype}" -alias cl_hook_gamestart_all -alias cl_hook_gamestart_nop //is only called when CSQC unloads before knowing the gametype, very unlikely -alias cl_hook_gamestart_dm -alias cl_hook_gamestart_tdm -alias cl_hook_gamestart_dom -alias cl_hook_gamestart_ctf -alias cl_hook_gamestart_lms -alias cl_hook_gamestart_ca -alias cl_hook_gamestart_kh -alias cl_hook_gamestart_ons -alias cl_hook_gamestart_as -alias cl_hook_gamestart_rc -alias cl_hook_gamestart_nb -alias cl_hook_gamestart_cts -alias cl_hook_gamestart_ka -alias cl_hook_gamestart_ft -alias cl_hook_gamestart_inv -alias cl_hook_gamestart_duel -alias cl_hook_gamestart_mayhem -alias cl_hook_gamestart_tmayhem -alias cl_hook_gamestart_tka -alias cl_hook_gamestart_surv -alias cl_hook_gameend -alias cl_hook_shutdown -alias cl_hook_activeweapon -// called on client personal best set -// $1 new pb time, -// $2 old pb time, -// $3 how much time the pb improved by -// $4 new rank# on the leaderboard -alias cl_hook_race_pb +alias cl_hook_gamestart_all "" "// this alias is called after loading into a match" +alias cl_hook_gamestart_nop "" "// this alias is only called when CSQC unloads before knowing the gametype, very unlikely" +alias cl_hook_gamestart_dm "" "// this alias is called after loading into deathmatch" +alias cl_hook_gamestart_tdm "" "// this alias is called after loading into team deathmatch" +alias cl_hook_gamestart_dom "" "// this alias is called after loading into domination" +alias cl_hook_gamestart_ctf "" "// this alias is called after loading into capture the flag" +alias cl_hook_gamestart_lms "" "// this alias is called after loading into last man standing" +alias cl_hook_gamestart_ca "" "// this alias is called after loading into clan arena" +alias cl_hook_gamestart_kh "" "// this alias is called after loading into keyhunt" +alias cl_hook_gamestart_ons "" "// this alias is called after loading into onslaught" +alias cl_hook_gamestart_as "" "// this alias is called after loading into assault" +alias cl_hook_gamestart_rc "" "// this alias is called after loading into race" +alias cl_hook_gamestart_nb "" "// this alias is called after loading into nexball" +alias cl_hook_gamestart_cts "" "// this alias is called after loading into complete the stage" +alias cl_hook_gamestart_ka "" "// this alias is called after loading into keepaway" +alias cl_hook_gamestart_ft "" "// this alias is called after loading into freezetag" +alias cl_hook_gamestart_inv "" "// this alias is called after loading into invasion" +alias cl_hook_gamestart_duel "" "// this alias is called after loading into duel" +alias cl_hook_gamestart_mayhem "" "// this alias is called after loading into mayhem" +alias cl_hook_gamestart_tmayhem "" "// this alias is called after loading into team mayhem" +alias cl_hook_gamestart_tka "" "// this alias is called after loading into team keepaway" +alias cl_hook_gamestart_surv "" "// this alias is called after loading into survival" + +alias cl_hook_gameintermission "" "// this alias is called when intermission starts in a match" +alias cl_hook_gameend "" "// this alias is called when intermission starts in a match or client quits from a match before intermission" + +alias cl_hook_shutdown "" "// this alias is called when the CSQC unloads" + +alias cl_hook_demostart "" "// this alias is called when a demo replay starts" +alias cl_hook_demointermission "" "// this alias is called when intermission starts in a demo replay" +alias cl_hook_demoend "" "// this alias is called when a demo replay ends" + +alias cl_hook_activeweapon "" "// this alias is called when switching weapons, with the new weapon's name as the first argument ( $1 )" + +alias cl_hook_race_pb "" "// called on client personal best set, $1 new pb time, $2 old pb time, $3 how much time the pb improved by, $4 new rank# on the leaderboard" diff --git a/qcsrc/client/main.qc b/qcsrc/client/main.qc index 9cf0185ed..8bf013b1a 100644 --- a/qcsrc/client/main.qc +++ b/qcsrc/client/main.qc @@ -179,19 +179,29 @@ void Shutdown() cvar_set("slowmo", cvar_defstring("slowmo")); // reset it back to 'default' - if (!isdemo()) + // if _cl_hook_gamestart wasn't called with an actual gamemode + // before CSQC VM shutdown then call it with nop fallback here + if (!(calledhooks & HOOK_START) && !isdemo()) + localcmd("\n_cl_hook_gamestart nop\n"); + + // fire game or demo end hooks when CSQC VM shuts down + if (!(calledhooks & HOOK_END)) { - if (!(calledhooks & HOOK_START)) - localcmd("\n_cl_hook_gamestart nop\n"); - if (!(calledhooks & HOOK_END)) + // call gameend hook if it hasn't somehow yet fired by intermission starting + if (!isdemo()) { - int gamecount = cvar("cl_matchcount"); localcmd("\ncl_hook_gameend\n"); + // NOTE: using localcmd here to ensure it's executed AFTER cl_hook_gameend // earlier versions of the game abuse the hook to set this cvar + int gamecount = cvar("cl_matchcount"); localcmd(strcat("cl_matchcount ", itos(gamecount + 1), "\n")); //cvar_set("cl_matchcount", itos(gamecount + 1)); } + else + localcmd("\ncl_hook_demoend\n"); + + calledhooks |= HOOK_END; // mark the hook as having fired } localcmd("\ncl_hook_shutdown\n"); @@ -1019,19 +1029,25 @@ void CSQC_Ent_Remove(entity this) void Gamemode_Init() { - if (!isdemo()) + // fire game or demo start hooks here + if (!(calledhooks & HOOK_START)) { - if(!(calledhooks & HOOK_START)) + if (!isdemo()) localcmd("\n_cl_hook_gamestart ", MapInfo_Type_ToString(gametype), "\n"); - calledhooks |= HOOK_START; + else + localcmd("\ncl_hook_demostart\n"); + + calledhooks |= HOOK_START; // mark start hook as fired } } + // CSQC_Parse_StuffCmd : Provides the stuffcmd string in the first parameter that the server provided. To execute standard behavior, simply execute localcmd with the string. void CSQC_Parse_StuffCmd(string strMessage) { if (autocvar_developer_csqcentities) LOG_INFOF("CSQC_Parse_StuffCmd(\"%s\")", strMessage); localcmd(strMessage); } + // CSQC_Parse_Print : Provides the print string in the first parameter that the server provided. To execute standard behavior, simply execute print with the string. void CSQC_Parse_Print(string strMessage) { diff --git a/qcsrc/client/main.qh b/qcsrc/client/main.qh index 8b96b0833..14e6239dd 100644 --- a/qcsrc/client/main.qh +++ b/qcsrc/client/main.qh @@ -157,8 +157,9 @@ float damagepush_speedfactor; //hooks int calledhooks; -const int HOOK_START = 1; -const int HOOK_END = 2; +const int HOOK_START = 1; // VM init +const int HOOK_END = 2; // VM shutdown +const int HOOK_INTERMISSION = 4; // intermission start .float ping, ping_packetloss, ping_movementloss; diff --git a/qcsrc/client/view.qc b/qcsrc/client/view.qc index 19f3c0a2c..070adf13a 100644 --- a/qcsrc/client/view.qc +++ b/qcsrc/client/view.qc @@ -1683,9 +1683,15 @@ void CSQC_UpdateView(entity this, float w, float h, bool notmenu) else if(game_stopped_time && !STAT(GAME_STOPPED)) game_stopped_time = 0; - if(intermission && !isdemo() && !(calledhooks & HOOK_END)) - { - if(calledhooks & HOOK_START) + // fire intermission hooks and gameend hook here + // gameend hook is executed on CSQC VM shutdown if + // the shutdown happens before intermission start + if (intermission + && (calledhooks & HOOK_START) // ensure that we have initiated a gamemode + && !(calledhooks & HOOK_END) // fire only once + && !(calledhooks & HOOK_INTERMISSION)) + { + if(!isdemo()) { int gamecount = cvar("cl_matchcount"); localcmd("\ncl_hook_gameend\n"); @@ -1694,6 +1700,14 @@ void CSQC_UpdateView(entity this, float w, float h, bool notmenu) localcmd(strcat("cl_matchcount ", itos(gamecount + 1), "\n")); //cvar_set("cl_matchcount", itos(gamecount + 1)); calledhooks |= HOOK_END; + + localcmd("\ncl_hook_gameintermission\n"); + calledhooks |= HOOK_INTERMISSION; + } + else + { + localcmd("\ncl_hook_demointermission\n"); + calledhooks |= HOOK_INTERMISSION; } } diff --git a/qcsrc/menu/command/menu_cmd.qc b/qcsrc/menu/command/menu_cmd.qc index 97deef1d4..9c1b9d0c6 100644 --- a/qcsrc/menu/command/menu_cmd.qc +++ b/qcsrc/menu/command/menu_cmd.qc @@ -54,6 +54,8 @@ void GameCommand(string theCommand) LOG_HELP(" 'closemenu' closes the menu window named (or the menu window containing an item named )"); LOG_HELP(" if is not specified it shows the list of available items in the console"); LOG_HELP(" 'dumptree' dumps the state of the menu as a tree to the console"); + LOG_HELP(" 'isdemo' checks if engine is currently running a demo. If given an then 1 / 0 is placed"); + LOG_HELP(" in the cvar with that name. Otherwise without an argument the result is printed to console."); LOG_HELP("\nGeneric commands shared by all programs:"); GenericCommand_macro_help(); @@ -75,21 +77,21 @@ void GameCommand(string theCommand) return; } - string cmd = argv(0); + string argcmd = argv(0); string filter = string_null; bool close_mode = false; - if (cmd == "closemenu") + if (argcmd == "closemenu") { close_mode = true; - cmd = "directmenu"; + argcmd = "directmenu"; } - else if (cmd == "directpanelhudmenu") + else if (argcmd == "directpanelhudmenu") { filter = "HUD"; - cmd = "directmenu"; + argcmd = "directmenu"; } - if (cmd == "directmenu") + if (argcmd == "directmenu") { if (argc == 1) { @@ -201,6 +203,22 @@ void GameCommand(string theCommand) return; } + if (argv(0) == "isdemo") + { + int value = isdemo(); + + if (argv(1) != "") + { + cvar_set(argv(1), ftos(value)); + } + else + { + print(sprintf("%d\n", value)); + } + + return; + } + if(MUTATOR_CALLHOOK(Menu_ConsoleCommand, ss, argc, theCommand)) // handled by a mutator return; -- 2.39.5