From 0308ab6bd46a10fe721bed2455698f21f7cf5b13 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Fri, 10 Jun 2022 09:24:08 -0400 Subject: [PATCH] Scheduling commands - `schedule_add ` to add a command that runs on a recurring timer. - `schedule_list` to print out all of the scheduled tasks. - NEW: `schedule_clear` to revert the schedule to a blank slate. - `schedule` cvar determines whenever or not to run the scheduled tasks. Unlike HOSTMOD, turning this off will reset the timers of the tasks, instead of freezing them. - I did not implement HOSTMOD's ability to pick from several random command per scheduled task. Would drastically increase the code complexity when you can just use a choose command in your schedule_add for the exact same effect. --- src/d_clisrv.c | 22 +++- src/d_netcmd.c | 297 ++++++++++++++++++++++++++++++++++++++++++++++++- src/d_netcmd.h | 20 ++++ src/k_pwrlv.c | 12 +- src/p_saveg.c | 34 +++++- 5 files changed, 364 insertions(+), 21 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 6ac5ab551..e60a9a549 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -2001,7 +2001,9 @@ static void CL_ConnectToServer(void) wipegamestate = GS_WAITINGPLAYERS; ClearAdminPlayers(); + Schedule_Clear(); K_ClearClientPowerLevels(); + pnumnodes = 1; oldtic = 0; #ifndef NONET @@ -3135,14 +3137,17 @@ void SV_ResetServer(void) for (i = 0; i < MAXPLAYERS; i++) { LUA_InvalidatePlayer(&players[i]); - playeringame[i] = false; - playernode[i] = UINT8_MAX; sprintf(player_names[i], "Player %c", 'A' + i); - adminplayers[i] = -1; // Populate the entire adminplayers array with -1. - K_ClearClientPowerLevels(); - splitscreen_invitations[i] = -1; } + memset(playeringame, false, sizeof playeringame); + memset(playernode, UINT8_MAX, sizeof playernode); + + ClearAdminPlayers(); + Schedule_Clear(); + K_ClearClientPowerLevels(); + + memset(splitscreen_invitations, -1, sizeof splitscreen_invitations); memset(splitscreen_partied, 0, sizeof splitscreen_partied); memset(player_name_changes, 0, sizeof player_name_changes); @@ -3227,6 +3232,7 @@ void D_QuitNetGame(void) D_CloseConnection(); ClearAdminPlayers(); + Schedule_Clear(); K_ClearClientPowerLevels(); DEBFILE("===========================================================================\n" @@ -5230,7 +5236,13 @@ boolean TryRunTics(tic_t realtics) ps_tictime = I_GetPreciseTime(); G_Ticker((gametic % NEWTICRATERATIO) == 0); + if (gametic % TICRATE == 0) + { + Schedule_Run(); + } + ExtraDataTicker(); + gametic++; consistancy[gametic%BACKUPTICS] = Consistancy(); diff --git a/src/d_netcmd.c b/src/d_netcmd.c index da1bbfbe7..f5976997b 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -98,6 +98,8 @@ static void Got_RunSOCcmd(UINT8 **cp, INT32 playernum); static void Got_Teamchange(UINT8 **cp, INT32 playernum); static void Got_Clearscores(UINT8 **cp, INT32 playernum); static void Got_DiscordInfo(UINT8 **cp, INT32 playernum); +static void Got_ScheduleTaskcmd(UINT8 **cp, INT32 playernum); +static void Got_ScheduleClearcmd(UINT8 **cp, INT32 playernum); static void PointLimit_OnChange(void); static void TimeLimit_OnChange(void); @@ -146,6 +148,8 @@ static void KartEncore_OnChange(void); static void KartComeback_OnChange(void); static void KartEliminateLast_OnChange(void); +static void Schedule_OnChange(void); + #ifdef NETGAME_DEVMODE static void Fishcake_OnChange(void); #endif @@ -221,6 +225,10 @@ static void Command_Archivetest_f(void); static void Command_KartGiveItem_f(void); +static void Command_Schedule_Add(void); +static void Command_Schedule_Clear(void); +static void Command_Schedule_List(void); + // ========================================================================= // CLIENT VARIABLES // ========================================================================= @@ -521,6 +529,8 @@ consvar_t cv_perfstats = CVAR_INIT ("perfstats", "Off", 0, perfstats_cons_t, NUL consvar_t cv_director = CVAR_INIT ("director", "Off", 0, CV_OnOff, NULL); +consvar_t cv_schedule = CVAR_INIT ("schedule", "On", CV_NETVAR|CV_CALL, CV_OnOff, Schedule_OnChange); + char timedemo_name[256]; boolean timedemo_csv; char timedemo_csv_id[256]; @@ -536,6 +546,11 @@ UINT8 splitscreen = 0; boolean circuitmap = false; INT32 adminplayers[MAXPLAYERS]; +// Scheduled cvars. +scheduleTask_t **schedule = NULL; +size_t schedule_size = 0; +size_t schedule_len = 0; + /// \warning Keep this up-to-date if you add/remove/rename net text commands const char *netxcmdnames[MAXNETXCMD - 1] = { @@ -575,7 +590,9 @@ const char *netxcmdnames[MAXNETXCMD - 1] = "GIVEITEM", // XD_GIVEITEM "ADDBOT", // XD_ADDBOT "DISCORD", // XD_DISCORD - "PLAYSOUND" // XD_PLAYSOUND + "PLAYSOUND", // XD_PLAYSOUND + "SCHEDULETASK", // XD_SCHEDULETASK + "SCHEDULECLEAR" // XD_SCHEDULECLEAR }; // ========================================================================= @@ -630,6 +647,9 @@ void D_RegisterServerCommands(void) RegisterNetXCmd(XD_GIVEITEM, Got_GiveItemcmd); + RegisterNetXCmd(XD_SCHEDULETASK, Got_ScheduleTaskcmd); + RegisterNetXCmd(XD_SCHEDULECLEAR, Got_ScheduleClearcmd); + // Remote Administration COM_AddCommand("password", Command_Changepassword_f); COM_AddCommand("login", Command_Login_f); // useful in dedicated to kick off remote admin @@ -687,6 +707,10 @@ void D_RegisterServerCommands(void) COM_AddCommand("kartgiveitem", Command_KartGiveItem_f); + COM_AddCommand("schedule_add", Command_Schedule_Add); + COM_AddCommand("schedule_clear", Command_Schedule_Clear); + COM_AddCommand("schedule_list", Command_Schedule_List); + // for master server connection AddMServCommands(); @@ -758,6 +782,8 @@ void D_RegisterServerCommands(void) CV_RegisterVar(&cv_director); + CV_RegisterVar(&cv_schedule); + CV_RegisterVar(&cv_dummyconsvar); #ifdef USE_STUN @@ -3731,9 +3757,7 @@ void SetAdminPlayer(INT32 playernum) void ClearAdminPlayers(void) { - INT32 i; - for (i = 0; i < MAXPLAYERS; i++) - adminplayers[i] = -1; + memset(adminplayers, -1, sizeof(adminplayers)); } void RemoveAdminPlayer(INT32 playernum) @@ -3850,6 +3874,117 @@ static void Got_Removal(UINT8 **cp, INT32 playernum) CONS_Printf(M_GetText("You are no longer a server administrator.\n")); } +void Schedule_Run(void) +{ + size_t i; + + if (schedule_len == 0) + { + // No scheduled tasks to run. + return; + } + + if (!cv_schedule.value) + { + // We don't WANT to run tasks. + return; + } + + for (i = 0; i < schedule_len; i++) + { + scheduleTask_t *task = schedule[i]; + + if (task == NULL) + { + // Shouldn't happen. + break; + } + + if (task->timer > 0) + { + task->timer--; + } + + if (task->timer == 0) + { + // Reset timer + task->timer = task->basetime; + + // Run command for server + if (server) + { + CONS_Printf( + "%d seconds elapsed, running \"" "\x82" "%s" "\x80" "\".\n", + task->basetime, + task->command + ); + + COM_BufAddText(task->command); + COM_BufAddText("\n"); + } + } + } +} + +void Schedule_Insert(scheduleTask_t *addTask) +{ + if (schedule_len >= schedule_size) + { + if (schedule_size == 0) + { + schedule_size = 8; + } + else + { + schedule_size *= 2; + } + + schedule = Z_ReallocAlign( + (void*) schedule, + sizeof(scheduleTask_t*) * schedule_size, + PU_STATIC, + NULL, + sizeof(scheduleTask_t*) * 8 + ); + } + + schedule[schedule_len] = addTask; + schedule_len++; +} + +void Schedule_Add(INT16 basetime, INT16 timeleft, const char *command) +{ + scheduleTask_t *task = (scheduleTask_t*) Z_CallocAlign( + sizeof(scheduleTask_t), + PU_STATIC, + NULL, + sizeof(scheduleTask_t) * 8 + ); + + task->basetime = basetime; + task->timer = timeleft; + task->command = Z_StrDup(command); + + Schedule_Insert(task); +} + +void Schedule_Clear(void) +{ + size_t i; + + for (i = 0; i < schedule_len; i++) + { + scheduleTask_t *task = schedule[i]; + + if (task->command) + Z_Free(task->command); + } + + schedule_len = 0; + schedule_size = 0; + schedule = NULL; +} + static void Command_MotD_f(void) { size_t i, j; @@ -5023,6 +5158,58 @@ static void Got_GiveItemcmd(UINT8 **cp, INT32 playernum) players[playernum].itemamount = amt; } +static void Got_ScheduleTaskcmd(UINT8 **cp, INT32 playernum) +{ + char command[MAXTEXTCMD]; + INT16 seconds; + + seconds = READINT16(*cp); + READSTRING(*cp, command); + + if (playernum != serverplayer && !IsPlayerAdmin(playernum)) + { + CONS_Alert(CONS_WARNING, + M_GetText ("Illegal schedule task received from %s\n"), + player_names[playernum]); + if (server) + SendKick(playernum, KICK_MSG_CON_FAIL); + return; + } + + Schedule_Add(seconds, seconds, (const char *)command); + + if (server || consoleplayer == playernum) + { + CONS_Printf( + "OK! Running \"" "\x82" "%s" "\x80" "\" every " "\x82" "%d" "\x80" " seconds.\n", + command, + seconds + ); + } +} + +static void Got_ScheduleClearcmd(UINT8 **cp, INT32 playernum) +{ + (void)cp; + + if (playernum != serverplayer && !IsPlayerAdmin(playernum)) + { + CONS_Alert(CONS_WARNING, + M_GetText ("Illegal schedule clear received from %s\n"), + player_names[playernum]); + if (server) + SendKick(playernum, KICK_MSG_CON_FAIL); + return; + } + + Schedule_Clear(); + + if (server || consoleplayer == playernum) + { + CONS_Printf("All scheduled tasks have been cleared.\n"); + } +} + /** Prints the number of displayplayers[0]. * * \todo Possibly remove this; it was useful for debugging at one point. @@ -5287,6 +5474,86 @@ static void Command_KartGiveItem_f(void) } } +static void Command_Schedule_Add(void) +{ + UINT8 buf[MAXTEXTCMD]; + UINT8 *buf_p = buf; + + size_t ac; + INT16 seconds; + const char *command; + + if (!(server || IsPlayerAdmin(consoleplayer))) + { + CONS_Printf("Only the server or a remote admin can use this.\n"); + return; + } + + ac = COM_Argc(); + if (ac < 3) + { + CONS_Printf("schedule <...>: runs the specified commands on a recurring timer\n"); + return; + } + + seconds = atoi(COM_Argv(1)); + + if (seconds <= 0) + { + CONS_Printf("Timer must be at least 1 second.\n"); + return; + } + + command = COM_Argv(2); + + WRITEINT16(buf_p, seconds); + WRITESTRING(buf_p, command); + + SendNetXCmd(XD_SCHEDULETASK, buf, buf_p - buf); +} + +static void Command_Schedule_Clear(void) +{ + if (!(server || IsPlayerAdmin(consoleplayer))) + { + CONS_Printf("Only the server or a remote admin can use this.\n"); + return; + } + + SendNetXCmd(XD_SCHEDULECLEAR, NULL, 0); +} + +static void Command_Schedule_List(void) +{ + size_t i; + + if (!(server || IsPlayerAdmin(consoleplayer))) + { + // I set it up in a way that this information could be available + // to everyone, but HOSTMOD has it server/admin-only too, so eh? + CONS_Printf("Only the server or a remote admin can use this.\n"); + return; + } + + if (schedule_len == 0) + { + CONS_Printf("No tasks are scheduled.\n"); + return; + } + + for (i = 0; i < schedule_len; i++) + { + scheduleTask_t *task = schedule[i]; + + CONS_Printf( + "In " "\x82" "%d" "\x80" " second%s: " "\x82" "%s" "\x80" "\n", + task->timer, + (task->timer > 1) ? "s" : "", + task->command + ); + } +} + /** Makes a change to ::cv_forceskin take effect immediately. * * \sa Command_SetForcedSkin_f, cv_forceskin, forcedskin @@ -5912,6 +6179,28 @@ static void KartEliminateLast_OnChange(void) P_CheckRacers(); } +static void Schedule_OnChange(void) +{ + size_t i; + + if (cv_schedule.value) + { + return; + } + + if (schedule_len == 0) + { + return; + } + + // Reset timers when turning off. + for (i = 0; i < schedule_len; i++) + { + scheduleTask_t *task = schedule[i]; + task->timer = task->basetime; + } +} + void Got_DiscordInfo(UINT8 **p, INT32 playernum) { if (playernum != serverplayer /*&& !IsPlayerAdmin(playernum)*/) diff --git a/src/d_netcmd.h b/src/d_netcmd.h index bf8109be5..ff3b0be9c 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -115,6 +115,8 @@ extern consvar_t cv_perfstats; extern consvar_t cv_director; +extern consvar_t cv_schedule; + extern char timedemo_name[256]; extern boolean timedemo_csv; extern char timedemo_csv_id[256]; @@ -159,6 +161,8 @@ typedef enum XD_ADDBOT, // 33 XD_DISCORD, // 34 XD_PLAYSOUND, // 35 + XD_SCHEDULETASK, // 36 + XD_SCHEDULECLEAR, // 37 MAXNETXCMD } netxcmd_t; @@ -227,6 +231,22 @@ void RemoveAdminPlayer(INT32 playernum); void ItemFinder_OnChange(void); void D_SetPassword(const char *pw); +typedef struct +{ + UINT16 basetime; + UINT16 timer; + char *command; +} scheduleTask_t; + +extern scheduleTask_t **schedule; +extern size_t schedule_size; +extern size_t schedule_len; + +void Schedule_Run(void); +void Schedule_Insert(scheduleTask_t *addTask); +void Schedule_Add(INT16 basetime, INT16 timeleft, const char *command); +void Schedule_Clear(void); + // used for the player setup menu UINT8 CanChangeSkin(INT32 playernum); diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index cd3ee1827..b25d26cda 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -60,16 +60,8 @@ SINT8 K_UsingPowerLevels(void) void K_ClearClientPowerLevels(void) { - UINT8 i, j; - for (i = 0; i < MAXPLAYERS; i++) - { - clientPowerAdd[i] = 0; - - for (j = 0; j < PWRLV_NUMTYPES; j++) - { - clientpowerlevels[i][j] = 0; - } - } + memset(clientpowerlevels, 0, sizeof clientpowerlevels); + memset(clientPowerAdd, 0, sizeof clientPowerAdd); } // Adapted from this: http://wiki.tockdom.com/wiki/Player_Rating diff --git a/src/p_saveg.c b/src/p_saveg.c index 81adeca60..1abb6d598 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -4407,7 +4407,7 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride) static void P_NetArchiveMisc(boolean resending) { - INT32 i; + size_t i; WRITEUINT32(save_p, ARCHIVEBLOCK_MISC); @@ -4536,11 +4536,23 @@ static void P_NetArchiveMisc(boolean resending) WRITEUINT8(save_p, 0x2f); else WRITEUINT8(save_p, 0x2e); + + // Only the server uses this, but it + // needs synched for remote admins anyway. + WRITEUINT32(save_p, schedule_len); + for (i = 0; i < schedule_len; i++) + { + scheduleTask_t *task = schedule[i]; + WRITEINT16(save_p, task->basetime); + WRITEINT16(save_p, task->timer); + WRITESTRING(save_p, task->command); + } } static inline boolean P_NetUnArchiveMisc(boolean reloading) { - INT32 i; + size_t i; + size_t numTasks; if (READUINT32(save_p) != ARCHIVEBLOCK_MISC) I_Error("Bad $$$.sav at archive block Misc"); @@ -4684,6 +4696,24 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading) if (READUINT8(save_p) == 0x2f) paused = true; + // Only the server uses this, but it + // needs synched for remote admins anyway. + Schedule_Clear(); + + numTasks = READUINT32(save_p); + for (i = 0; i < numTasks; i++) + { + INT16 basetime; + INT16 timer; + char command[MAXTEXTCMD]; + + basetime = READINT16(save_p); + timer = READINT16(save_p); + READSTRING(save_p, command); + + Schedule_Add(basetime, timer, command); + } + return true; }