Scheduling commands

- `schedule_add <seconds> <command>` 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.
This commit is contained in:
Sally Coolatta 2022-06-10 09:24:08 -04:00 committed by toaster
parent d2a0bdb044
commit 0308ab6bd4
5 changed files with 364 additions and 21 deletions

View file

@ -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();

View file

@ -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 <seconds> <...>: 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)*/)

View file

@ -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);

View file

@ -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

View file

@ -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;
}