diff --git a/src/console.c b/src/console.c index 7cbcb85a9..80174692e 100644 --- a/src/console.c +++ b/src/console.c @@ -34,6 +34,7 @@ #include "k_menu.h" #include "filesrch.h" #include "m_misc.h" +#include "m_random.h" #ifdef _WINDOWS #include "win32/win_main.h" @@ -243,6 +244,81 @@ static void CONS_Bind_f(void) bindtable[key] = Z_StrDup(COM_Argv(2)); } +static void CONS_Choose_f(void) +{ + size_t na = COM_Argc(); + + if (na < 2) + { + CONS_Printf(M_GetText("choose [] [] [...]: Picks a command at random\n")); + return; + } + + COM_BufAddText(COM_Argv(M_RandomKey(na - 1) + 1)); + COM_BufAddText("\n"); +} + +static void CONS_ChooseWeighted_f(void) +{ + size_t na = COM_Argc(); + size_t i, cmd; + const char *commands[40]; + INT32 weights[40]; + INT32 totalWeight = 0; + INT32 roll; + + if (na < 3) + { + CONS_Printf(M_GetText("chooseweighted [ ] [ ] [...]: Picks a command with weighted randomization\n")); + return; + } + + memset(weights, 0, sizeof(weights)); + + i = 1; + cmd = 0; + while (i < na) + { + commands[cmd] = COM_Argv(i); + + i++; + if (i >= na) + { + break; + } + + weights[cmd] = atoi(COM_Argv(i)); + totalWeight += weights[cmd]; + + i++; + cmd++; + } + + if (cmd == 0 || totalWeight <= 0) + { + return; + } + + roll = M_RandomRange(1, totalWeight); + + for (i = 0; i < cmd; i++) + { + if (roll <= weights[i]) + { + if (commands[i] == NULL) + { + break; + } + + COM_BufAddText(commands[i]); + COM_BufAddText("\n"); + break; + } + + roll -= weights[i]; + } +} + //====================================================================== // CONSOLE SETUP //====================================================================== @@ -445,6 +521,8 @@ void CON_Init(void) CV_RegisterVar(&cons_backpic); CV_RegisterVar(&cons_backcolor); COM_AddCommand("bind", CONS_Bind_f); + COM_AddCommand("choose", CONS_Choose_f); + COM_AddCommand("chooseweighted", CONS_ChooseWeighted_f); } else { diff --git a/src/d_clisrv.c b/src/d_clisrv.c index f8934c2a8..a7193327d 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -122,7 +122,7 @@ SINT8 nodetoplayer4[MAXNETNODES]; // say the numplayer for this node if any (spl UINT8 playerpernode[MAXNETNODES]; // used specialy for splitscreen boolean nodeingame[MAXNETNODES]; // set false as nodes leave game -tic_t servermaxping = 800; // server's max ping. Defaults to 800 +tic_t servermaxping = 20; // server's max delay, in frames. Defaults to 20 static tic_t nettics[MAXNETNODES]; // what tic the client have received static tic_t supposedtics[MAXNETNODES]; // nettics prevision for smaller packet static UINT8 nodewaiting[MAXNETNODES]; @@ -189,6 +189,8 @@ consvar_t cv_playbackspeed = CVAR_INIT ("playbackspeed", "1", 0, playbackspeed_c consvar_t cv_httpsource = CVAR_INIT ("http_source", "", CV_SAVE, NULL, NULL); +consvar_t cv_kicktime = CVAR_INIT ("kicktime", "10", CV_SAVE, CV_Unsigned, NULL); + static inline void *G_DcpyTiccmd(void* dest, const ticcmd_t* src, const size_t n) { const size_t d = n / sizeof(ticcmd_t); @@ -1351,7 +1353,7 @@ static void SendAskInfo(INT32 node) if (node != 0 && node != BROADCASTADDR && cv_rendezvousserver.string[0]) { - I_NetRequestHolePunch(); + I_NetRequestHolePunch(node); } asktime = I_GetTime(); @@ -2031,7 +2033,10 @@ static void CL_ConnectToServer(void) wipegamestate = GS_WAITINGPLAYERS; ClearAdminPlayers(); + Schedule_Clear(); + Automate_Clear(); K_ClearClientPowerLevels(); + pnumnodes = 1; oldtic = 0; #ifndef NONET @@ -2095,57 +2100,83 @@ static void CL_ConnectToServer(void) } #ifndef NONET -typedef struct banreason_s -{ - char *reason; - struct banreason_s *prev; //-1 - struct banreason_s *next; //+1 -} banreason_t; - -static banreason_t *reasontail = NULL; //last entry, use prev -static banreason_t *reasonhead = NULL; //1st entry, use next - static void Command_ShowBan(void) //Print out ban list { size_t i; - const char *address, *mask; - banreason_t *reasonlist = reasonhead; + const char *address, *mask, *reason, *username; + time_t unbanTime = NO_BAN_TIME; + const time_t curTime = time(NULL); if (I_GetBanAddress) CONS_Printf(M_GetText("Ban List:\n")); else return; - for (i = 0;(address = I_GetBanAddress(i)) != NULL;i++) + for (i = 0; (address = I_GetBanAddress(i)) != NULL; i++) { + unbanTime = NO_BAN_TIME; + if (I_GetUnbanTime) + unbanTime = I_GetUnbanTime(i); + + if (unbanTime != NO_BAN_TIME && curTime >= unbanTime) + continue; + + CONS_Printf("%s: ", sizeu1(i+1)); + + if (I_GetBanUsername && (username = I_GetBanUsername(i)) != NULL) + CONS_Printf("%s - ", username); + if (!I_GetBanMask || (mask = I_GetBanMask(i)) == NULL) - CONS_Printf("%s: %s ", sizeu1(i+1), address); + CONS_Printf("%s", address); else - CONS_Printf("%s: %s/%s ", sizeu1(i+1), address, mask); + CONS_Printf("%s/%s", address, mask); - if (reasonlist && reasonlist->reason) - CONS_Printf("(%s)\n", reasonlist->reason); - else - CONS_Printf("\n"); + if (I_GetBanReason && (reason = I_GetBanReason(i)) != NULL) + CONS_Printf(" - %s", reason); - if (reasonlist) reasonlist = reasonlist->next; + if (unbanTime != NO_BAN_TIME) + { + // these are fudged a little to match what a joiner sees + int minutes = ((unbanTime - curTime) + 30) / 60; + int hours = (minutes + 1) / 60; + int days = (hours + 1) / 24; + if (days) + CONS_Printf(" (%d day%s)", days, days > 1 ? "s" : ""); + else if (hours) + CONS_Printf(" (%d hour%s)", hours, hours > 1 ? "s" : ""); + else if (minutes) + CONS_Printf(" (%d minute%s)", minutes, minutes > 1 ? "s" : ""); + else + CONS_Printf(" (<1 minute)"); + } + + CONS_Printf("\n"); } if (i == 0 && !address) CONS_Printf(M_GetText("(empty)\n")); } +static boolean bansLoaded = false; +// If you're a community contributor looking to improve how bans are written, please +// offer your changes back to our Git repository. Kart Krew reserve the right to +// utilise format numbers in use by community builds for different layouts. +#define BANFORMAT 1 + void D_SaveBan(void) { FILE *f; size_t i; - banreason_t *reasonlist = reasonhead; const char *address, *mask; + const char *username, *reason; + const time_t curTime = time(NULL); + time_t unbanTime = NO_BAN_TIME; const char *path = va("%s"PATHSEP"%s", srb2home, "ban.txt"); - if (!reasonhead) + if (bansLoaded != true) { - remove(path); + // You didn't even get to ATTEMPT to load bans.txt. + // Don't immediately save nothing over it. return; } @@ -2157,78 +2188,79 @@ void D_SaveBan(void) return; } - for (i = 0;(address = I_GetBanAddress(i)) != NULL;i++) + // Add header. + fprintf(f, "BANFORMAT %d\n", BANFORMAT); + + for (i = 0; (address = I_GetBanAddress(i)) != NULL; i++) { + if (I_GetUnbanTime) + { + unbanTime = I_GetUnbanTime(i); + } + else + { + unbanTime = NO_BAN_TIME; + } + + if (unbanTime != NO_BAN_TIME && curTime >= unbanTime) + { + // This one has served their sentence. + // We don't need to save them in the file anymore. + continue; + } + + mask = NULL; if (!I_GetBanMask || (mask = I_GetBanMask(i)) == NULL) - fprintf(f, "%s 0", address); + fprintf(f, "%s/0", address); else - fprintf(f, "%s %s", address, mask); + fprintf(f, "%s/%s", address, mask); - if (reasonlist && reasonlist->reason) - fprintf(f, " %s\n", reasonlist->reason); + // TODO: it'd be nice to convert this to an actual date-time, + // so it'd be easier to edit outside of the game. + fprintf(f, " %ld", (long)unbanTime); + + username = NULL; + if (I_GetBanUsername && (username = I_GetBanUsername(i)) != NULL) + fprintf(f, " \"%s\"", username); else - fprintf(f, " %s\n", "NA"); + fprintf(f, " \"%s\"", "Direct IP ban"); - if (reasonlist) reasonlist = reasonlist->next; + reason = NULL; + if (I_GetBanReason && (reason = I_GetBanReason(i)) != NULL) + fprintf(f, " \"%s\"\n", reason); + else + fprintf(f, " \"%s\"\n", "No reason given"); } fclose(f); } -static void Ban_Add(const char *reason) -{ - banreason_t *reasonlist = malloc(sizeof(*reasonlist)); - - if (!reasonlist) - return; - if (!reason) - reason = "NA"; - - reasonlist->next = NULL; - reasonlist->reason = Z_StrDup(reason); - if ((reasonlist->prev = reasontail) == NULL) - reasonhead = reasonlist; - else - reasontail->next = reasonlist; - reasontail = reasonlist; -} - -static void Ban_Clear(void) -{ - banreason_t *temp; - - I_ClearBans(); - - reasontail = NULL; - - while (reasonhead) - { - temp = reasonhead->next; - Z_Free(reasonhead->reason); - free(reasonhead); - reasonhead = temp; - } -} - static void Command_ClearBans(void) { if (!I_ClearBans) return; - Ban_Clear(); + I_ClearBans(); D_SaveBan(); } -static void Ban_Load_File(boolean warning) +void D_LoadBan(boolean warning) { FILE *f; - size_t i; - const char *address, *mask; + size_t i, j; + char *address, *mask; + char *username, *reason; + time_t unbanTime = NO_BAN_TIME; char buffer[MAX_WADPATH]; + UINT8 banmode = 0; + boolean malformed = false; if (!I_ClearBans) return; + // We at least attempted loading bans.txt + bansLoaded = true; + f = fopen(va("%s"PATHSEP"%s", srb2home, "ban.txt"), "r"); if (!f) @@ -2238,24 +2270,115 @@ static void Ban_Load_File(boolean warning) return; } - Ban_Clear(); + I_ClearBans(); - for (i=0; fgets(buffer, (int)sizeof(buffer), f); i++) + for (i = 0; fgets(buffer, (int)sizeof(buffer), f); i++) { - address = strtok(buffer, " \t\r\n"); + address = strtok(buffer, " /\t\r\n"); mask = strtok(NULL, " \t\r\n"); - I_SetBanAddress(address, mask); + if (i == 0 && !strncmp(address, "BANFORMAT", 9)) + { + if (mask) + { + banmode = atoi(mask); + } + switch (banmode) + { + case BANFORMAT: // currently supported format + //case 0: -- permitted only when BANFORMAT string not present + break; + default: + { + fclose(f); + CONS_Alert(CONS_WARNING, "Could not load unknown ban.txt for ban list (BANFORMAT %s, expected %d)\n", mask, BANFORMAT); + return; + } + } + continue; + } - Ban_Add(strtok(NULL, "\r\n")); + if (I_SetBanAddress(address, mask) == false) // invalid IP input? + { + CONS_Alert(CONS_WARNING, "\"%s/%s\" is not a valid IP address, discarding...\n", address, mask); + continue; + } + + // One-way legacy format conversion -- the game will crash otherwise + if (banmode == 0) + { + unbanTime = NO_BAN_TIME; + username = NULL; // not guaranteed to be accurate, but only sane substitute + reason = strtok(NULL, "\r\n"); + if (reason && reason[0] == 'N' && reason[1] == 'A' && reason[2] == '\0') + { + reason = NULL; + } + } + else + { + reason = strtok(NULL, " \"\t\r\n"); + if (reason) + { + unbanTime = atoi(reason); + reason = NULL; + } + else + { + unbanTime = NO_BAN_TIME; + malformed = true; + } + + username = strtok(NULL, "\"\t\r\n"); // go until next " + if (!username) + { + malformed = true; + } + + strtok(NULL, "\"\t\r\n"); // remove first " + reason = strtok(NULL, "\"\r\n"); // go until next " + if (!reason) + { + malformed = true; + } + } + + // Enforce MAX_REASONLENGTH. + if (reason) + { + j = 0; + while (reason[j] != '\0') + { + if ((j++) < MAX_REASONLENGTH) + continue; + reason[j] = '\0'; + break; + } + } + + if (I_SetUnbanTime) + I_SetUnbanTime(unbanTime); + + if (I_SetBanUsername) + I_SetBanUsername(username); + + if (I_SetBanReason) + I_SetBanReason(reason); + } + + if (malformed) + { + CONS_Alert(CONS_WARNING, "One or more lines of ban.txt are malformed. The game can correct for this, but some data may be lost.\n"); } fclose(f); } +#undef BANFORMAT + static void Command_ReloadBan(void) //recheck ban.txt { - Ban_Load_File(true); + D_LoadBan(true); } static void Command_connect(void) @@ -2400,6 +2523,8 @@ void CL_ClearPlayer(INT32 playernum) splitscreen_original_party_size[playernum] = 0; memset(&players[playernum], 0, sizeof (player_t)); + + RemoveAdminPlayer(playernum); // don't stay admin after you're gone } // @@ -2457,11 +2582,6 @@ void CL_RemovePlayer(INT32 playernum, kickreason_t reason) player_name_changes[playernum] = 0; - if (IsPlayerAdmin(playernum)) - { - RemoveAdminPlayer(playernum); // don't stay admin after you're gone - } - LUA_InvalidatePlayer(&players[playernum]); K_CheckBumpers(); @@ -2605,7 +2725,7 @@ static void Command_Ban(void) { if (COM_Argc() < 2) { - CONS_Printf(M_GetText("Ban : ban and kick a player\n")); + CONS_Printf(M_GetText("ban : ban and kick a player\n")); return; } @@ -2620,83 +2740,95 @@ static void Command_Ban(void) UINT8 buf[3 + MAX_REASONLENGTH]; UINT8 *p = buf; const SINT8 pn = nametonum(COM_Argv(1)); - const INT32 node = playernode[(INT32)pn]; if (pn == -1 || pn == 0) return; WRITEUINT8(p, pn); - if (server && I_Ban && !I_Ban(node)) // only the server is allowed to do this right now + if (COM_Argc() == 2) { - CONS_Alert(CONS_WARNING, M_GetText("Too many bans! Geez, that's a lot of people you're excluding...\n")); - WRITEUINT8(p, KICK_MSG_GO_AWAY); + WRITEUINT8(p, KICK_MSG_BANNED); SendNetXCmd(XD_KICK, &buf, 2); } else { - if (server) // only the server is allowed to do this right now + size_t i, j = COM_Argc(); + char message[MAX_REASONLENGTH]; + + //Steal from the motd code so you don't have to put the reason in quotes. + strlcpy(message, COM_Argv(2), sizeof message); + for (i = 3; i < j; i++) { - Ban_Add(COM_Argv(2)); - D_SaveBan(); // save the ban list + strlcat(message, " ", sizeof message); + strlcat(message, COM_Argv(i), sizeof message); } - if (COM_Argc() == 2) - { - WRITEUINT8(p, KICK_MSG_BANNED); - SendNetXCmd(XD_KICK, &buf, 2); - } - else - { - size_t i, j = COM_Argc(); - char message[MAX_REASONLENGTH]; - - //Steal from the motd code so you don't have to put the reason in quotes. - strlcpy(message, COM_Argv(2), sizeof message); - for (i = 3; i < j; i++) - { - strlcat(message, " ", sizeof message); - strlcat(message, COM_Argv(i), sizeof message); - } - - WRITEUINT8(p, KICK_MSG_CUSTOM_BAN); - WRITESTRINGN(p, message, MAX_REASONLENGTH); - SendNetXCmd(XD_KICK, &buf, p - buf); - } + WRITEUINT8(p, KICK_MSG_CUSTOM_BAN); + WRITESTRINGN(p, message, MAX_REASONLENGTH); + SendNetXCmd(XD_KICK, &buf, p - buf); } } else CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n")); - } static void Command_BanIP(void) { - if (COM_Argc() < 2) + size_t ac = COM_Argc(); + + if (ac < 2) { - CONS_Printf(M_GetText("banip : ban an ip address\n")); + CONS_Printf(M_GetText("banip []: ban an ip address\n")); return; } if (server) // Only the server can use this, otherwise does nothing. { - const char *address = (COM_Argv(1)); - const char *reason; + char *addressInput = Z_StrDup(COM_Argv(1)); - if (COM_Argc() == 2) - reason = NULL; - else + const char *address = NULL; + const char *mask = NULL; + + const char *reason = NULL; + + address = strtok(addressInput, "/"); + mask = strtok(NULL, ""); + + if (ac > 2) + { reason = COM_Argv(2); + } - - if (I_SetBanAddress && I_SetBanAddress(address, NULL)) + if (I_SetBanAddress && I_SetBanAddress(address, mask)) { if (reason) - CONS_Printf("Banned IP address %s for: %s\n", address, reason); + { + CONS_Printf( + "Banned IP address %s%s for: %s\n", + address, + (mask && (strlen(mask) > 0)) ? va("/%s", mask) : "", + reason + ); + } else - CONS_Printf("Banned IP address %s\n", address); + { + CONS_Printf( + "Banned IP address %s%s\n", + address, + (mask && (strlen(mask) > 0)) ? va("/%s", mask) : "" + ); + } + + if (I_SetUnbanTime) + I_SetUnbanTime(NO_BAN_TIME); + + if (I_SetBanUsername) + I_SetBanUsername(NULL); + + if (I_SetBanReason) + I_SetBanReason(reason); - Ban_Add(reason); D_SaveBan(); } else @@ -2774,6 +2906,7 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) char buf[3 + MAX_REASONLENGTH]; char *reason = buf; kickreason_t kickreason = KR_KICK; + UINT32 banMinutes = 0; pnum = READUINT8(*p); msg = READUINT8(*p); @@ -2832,18 +2965,49 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) msg = KICK_MSG_CON_FAIL; } + if (msg == KICK_MSG_CUSTOM_BAN || msg == KICK_MSG_CUSTOM_KICK) + { + READSTRINGN(*p, reason, MAX_REASONLENGTH+1); + } + //CONS_Printf("\x82%s ", player_names[pnum]); - // If a verified admin banned someone, the server needs to know about it. - // If the playernum isn't zero (the server) then the server needs to record the ban. - if (server && playernum && (msg == KICK_MSG_BANNED || msg == KICK_MSG_CUSTOM_BAN)) + // Save bans here. Used to be split between here and the actual command, depending on + // whenever the server did it or a remote admin did it, but it's simply more convenient + // to keep it all in one place. + if (server) { - if (I_Ban && !I_Ban(playernode[(INT32)pnum])) - CONS_Alert(CONS_WARNING, M_GetText("Too many bans! Geez, that's a lot of people you're excluding...\n")); -#ifndef NONET - else - Ban_Add(reason); -#endif + if (msg == KICK_MSG_GO_AWAY || msg == KICK_MSG_CUSTOM_KICK) + { + // Kick as a temporary ban. + banMinutes = cv_kicktime.value; + } + + if (msg == KICK_MSG_BANNED || msg == KICK_MSG_CUSTOM_BAN || banMinutes) + { + if (I_Ban && !I_Ban(playernode[(INT32)pnum])) + { + CONS_Alert(CONS_WARNING, M_GetText("Ban failed. Invalid node?\n")); + } + else + { + if (I_SetBanUsername) + I_SetBanUsername(player_names[pnum]); + + if (I_SetBanReason) + I_SetBanReason(reason); + + if (I_SetUnbanTime) + { + if (banMinutes) + I_SetUnbanTime(time(NULL) + (banMinutes * 60)); + else + I_SetUnbanTime(NO_BAN_TIME); + } + + D_SaveBan(); + } + } } if (msg == KICK_MSG_PLAYER_QUIT) @@ -2858,7 +3022,7 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) kickreason = KR_KICK; break; case KICK_MSG_PING_HIGH: - HU_AddChatText(va("\x82*%s left the game (Broke ping limit)", player_names[pnum]), false); + HU_AddChatText(va("\x82*%s left the game (Broke delay limit)", player_names[pnum]), false); kickreason = KR_PINGLIMIT; break; case KICK_MSG_CON_FAIL: @@ -2912,12 +3076,10 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) kickreason = KR_BAN; break; case KICK_MSG_CUSTOM_KICK: - READSTRINGN(*p, reason, MAX_REASONLENGTH+1); HU_AddChatText(va("\x82*%s has been kicked (%s)", player_names[pnum], reason), false); kickreason = KR_KICK; break; case KICK_MSG_CUSTOM_BAN: - READSTRINGN(*p, reason, MAX_REASONLENGTH+1); HU_AddChatText(va("\x82*%s has been banned (%s)", player_names[pnum], reason), false); kickreason = KR_BAN; break; @@ -2940,23 +3102,25 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) if (playernode[pnum] == playernode[consoleplayer]) { - LUA_HookBool(false, HOOK(GameQuit)); #ifdef DUMPCONSISTENCY if (msg == KICK_MSG_CON_FAIL) SV_SavedGame(); #endif + LUA_HookBool(false, HOOK(GameQuit)); //Lua hooks handled differently now + D_QuitNetGame(); CL_Reset(); D_StartTitle(); + if (msg == KICK_MSG_CON_FAIL) M_StartMessage(M_GetText("Server closed connection\n(Synch failure)\nPress ESC\n"), NULL, MM_NOTHING); else if (msg == KICK_MSG_PING_HIGH) - M_StartMessage(M_GetText("Server closed connection\n(Broke ping limit)\nPress ESC\n"), NULL, MM_NOTHING); + M_StartMessage(M_GetText("Server closed connection\n(Broke delay limit)\nPress ESC\n"), NULL, MM_NOTHING); else if (msg == KICK_MSG_BANNED) M_StartMessage(M_GetText("You have been banned by the server\n\nPress ESC\n"), NULL, MM_NOTHING); else if (msg == KICK_MSG_CUSTOM_KICK) - M_StartMessage(va(M_GetText("You have been kicked\n(%s)\nPress ESC\n"), reason), NULL, MM_NOTHING); + M_StartMessage(va(M_GetText("You have been kicked\n(%s)\n\nPress ESC\n"), reason), NULL, MM_NOTHING); else if (msg == KICK_MSG_CUSTOM_BAN) - M_StartMessage(va(M_GetText("You have been banned\n(%s)\nPress ESC\n"), reason), NULL, MM_NOTHING); + M_StartMessage(va(M_GetText("You have been banned\n(%s)\n\nPress ESC\n"), reason), NULL, MM_NOTHING); else M_StartMessage(M_GetText("You have been kicked by the server\n\nPress ESC\n"), NULL, MM_NOTHING); } @@ -3184,7 +3348,7 @@ void D_ClientServerInit(void) #ifdef DUMPCONSISTENCY CV_RegisterVar(&cv_dumpconsistency); #endif - Ban_Load_File(false); + D_LoadBan(false); #endif gametic = 0; @@ -3214,6 +3378,9 @@ static void ResetNode(INT32 node) sendingsavegame[node] = false; resendingsavegame[node] = false; savegameresendcooldown[node] = 0; + + bannednode[node].banid = SIZE_MAX; + bannednode[node].timeleft = NO_BAN_TIME; } void SV_ResetServer(void) @@ -3236,14 +3403,18 @@ 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(); + Automate_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); @@ -3328,6 +3499,8 @@ void D_QuitNetGame(void) D_CloseConnection(); ClearAdminPlayers(); + Schedule_Clear(); + Automate_Clear(); K_ClearClientPowerLevels(); DEBFILE("===========================================================================\n" @@ -3791,32 +3964,87 @@ static void HandleConnect(SINT8 node) // It's too much effort to legimately fix right now. Just prevent it from reaching that state. UINT8 maxplayers = min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxconnections.value); - if (bannednode && bannednode[node]) - SV_SendRefuse(node, M_GetText("You have been banned\nfrom the server.")); + if (bannednode && bannednode[node].banid != SIZE_MAX) + { + const char *reason = NULL; + + // Get the reason... + if (!I_GetBanReason || (reason = I_GetBanReason(bannednode[node].banid)) == NULL) + reason = "No reason given"; + + if (bannednode[node].timeleft != NO_BAN_TIME) + { + // these are fudged a little to allow it to sink in for impatient rejoiners + int minutes = (bannednode[node].timeleft + 30) / 60; + int hours = (minutes + 1) / 60; + int days = (hours + 1) / 24; + + if (days) + { + SV_SendRefuse(node, va("K|%s\n(Time remaining: %d day%s)", reason, days, days > 1 ? "s" : "")); + } + else if (hours) + { + SV_SendRefuse(node, va("K|%s\n(Time remaining: %d hour%s)", reason, hours, hours > 1 ? "s" : "")); + } + else if (minutes) + { + SV_SendRefuse(node, va("K|%s\n(Time remaining: %d minute%s)", reason, minutes, minutes > 1 ? "s" : "")); + } + else + { + SV_SendRefuse(node, va("K|%s\n(Time remaining: <1 minute)", reason)); + } + } + else + { + SV_SendRefuse(node, va("B|%s", reason)); + } + } else if (netbuffer->u.clientcfg._255 != 255 || netbuffer->u.clientcfg.packetversion != PACKETVERSION) + { SV_SendRefuse(node, "Incompatible packet formats."); + } else if (strncmp(netbuffer->u.clientcfg.application, SRB2APPLICATION, sizeof netbuffer->u.clientcfg.application)) + { SV_SendRefuse(node, "Different Ring Racers modifications\nare not compatible."); + } else if (netbuffer->u.clientcfg.version != VERSION || netbuffer->u.clientcfg.subversion != SUBVERSION) + { SV_SendRefuse(node, va(M_GetText("Different Ring Racers versions cannot\nplay a netgame!\n(server version %d.%d)"), VERSION, SUBVERSION)); + } else if (!cv_allownewplayer.value && node) + { SV_SendRefuse(node, M_GetText("The server is not accepting\njoins for the moment.")); + } else if (D_NumPlayers() >= maxplayers) + { SV_SendRefuse(node, va(M_GetText("Maximum players reached: %d"), maxplayers)); + } else if (netgame && netbuffer->u.clientcfg.localplayers > MAXSPLITSCREENPLAYERS) // Hacked client? + { SV_SendRefuse(node, M_GetText("Too many players from\nthis node.")); + } else if (netgame && D_NumPlayers() + netbuffer->u.clientcfg.localplayers > maxplayers) + { SV_SendRefuse(node, va(M_GetText("Number of local players\nwould exceed maximum: %d"), maxplayers)); + } else if (netgame && !netbuffer->u.clientcfg.localplayers) // Stealth join? + { SV_SendRefuse(node, M_GetText("No players from\nthis node.")); + } else if (luafiletransfers) + { SV_SendRefuse(node, M_GetText("The server is broadcasting a file\nrequested by a Lua script.\nPlease wait a bit and then\ntry rejoining.")); + } else if (netgame && joindelay > 2 * (tic_t)cv_joindelay.value * TICRATE) + { SV_SendRefuse(node, va(M_GetText("Too many people are connecting.\nPlease wait %d seconds and then\ntry rejoining."), (joindelay - 2 * cv_joindelay.value * TICRATE) / TICRATE)); + } else { #ifndef NONET @@ -4090,13 +4318,22 @@ static void HandlePacketFromAwayNode(SINT8 node) break; } - M_StartMessage(va(M_GetText("Server refuses connection\n\nReason:\n%s"), - reason), NULL, MM_NOTHING); - D_QuitNetGame(); CL_Reset(); D_StartTitle(); + if (reason[1] == '|') + { + M_StartMessage(va("You have been %sfrom the server\n\nReason:\n%s", + (reason[0] == 'B') ? "banned\n" : "temporarily\nkicked ", + reason+2), NULL, MM_NOTHING); + } + else + { + M_StartMessage(va(M_GetText("Server refuses connection\n\nReason:\n%s"), + reason), NULL, MM_NOTHING); + } + free(reason); // Will be reset by caller. Signals refusal. @@ -5294,7 +5531,18 @@ boolean TryRunTics(tic_t realtics) ps_tictime = I_GetPreciseTime(); G_Ticker((gametic % NEWTICRATERATIO) == 0); + if (gametic % TICRATE == 0) + { + Schedule_Run(); + + if (cv_livestudioaudience.value) + { + LiveStudioAudience(); + } + } + ExtraDataTicker(); + gametic++; consistancy[gametic%BACKUPTICS] = Consistancy(); @@ -5424,7 +5672,7 @@ static void UpdatePingTable(void) if (! server_lagless && playernode[i] > 0 && !players[i].spectator) { lag = GetLag(playernode[i]); - realpingtable[i] += (INT32)(lag * (1000.00f/TICRATE)); + realpingtable[i] += lag; if (! fastest || lag < fastest) fastest = lag; @@ -5432,7 +5680,7 @@ static void UpdatePingTable(void) else { // TicsToMilliseconds can't handle pings over 1000ms lol - realpingtable[i] += (INT32)(GetLag(playernode[i]) * (1000.00f/TICRATE)); + realpingtable[i] += GetLag(playernode[i]); } } } @@ -5450,7 +5698,7 @@ static void UpdatePingTable(void) else lag = GetLag(0); - lag = ( realpingtable[0] + G_TicsToMilliseconds(lag) ); + lag = ( realpingtable[0] + lag ); switch (playerpernode[0]) { diff --git a/src/d_clisrv.h b/src/d_clisrv.h index e10e51626..72ce18a5c 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -394,6 +394,7 @@ extern INT32 mapchangepending; extern doomdata_t *netbuffer; extern consvar_t cv_stunserver; extern consvar_t cv_httpsource; +extern consvar_t cv_kicktime; extern consvar_t cv_showjoinaddress; extern consvar_t cv_playbackspeed; diff --git a/src/d_net.c b/src/d_net.c index 1c81fe3f3..201920f73 100644 --- a/src/d_net.c +++ b/src/d_net.c @@ -75,7 +75,7 @@ boolean (*I_NetCanGet)(void) = NULL; void (*I_NetCloseSocket)(void) = NULL; void (*I_NetFreeNodenum)(INT32 nodenum) = NULL; SINT8 (*I_NetMakeNodewPort)(const char *address, const char* port) = NULL; -void (*I_NetRequestHolePunch)(void) = NULL; +void (*I_NetRequestHolePunch)(INT32 node) = NULL; void (*I_NetRegisterHolePunch)(void) = NULL; boolean (*I_NetOpenSocket)(void) = NULL; boolean (*I_Ban) (INT32 node) = NULL; @@ -83,8 +83,14 @@ void (*I_ClearBans)(void) = NULL; const char *(*I_GetNodeAddress) (INT32 node) = NULL; const char *(*I_GetBanAddress) (size_t ban) = NULL; const char *(*I_GetBanMask) (size_t ban) = NULL; +const char *(*I_GetBanUsername) (size_t ban) = NULL; +const char *(*I_GetBanReason) (size_t ban) = NULL; +time_t (*I_GetUnbanTime) (size_t ban) = NULL; boolean (*I_SetBanAddress) (const char *address, const char *mask) = NULL; -boolean *bannednode = NULL; +boolean (*I_SetBanUsername) (const char *username) = NULL; +boolean (*I_SetBanReason) (const char *reason) = NULL; +boolean (*I_SetUnbanTime) (time_t timestamp) = NULL; +bannednode_t *bannednode = NULL; // network stats @@ -1389,6 +1395,7 @@ struct pingcell { INT32 num; INT32 ms; + INT32 f; }; static int pingcellcmp(const void *va, const void *vb) @@ -1412,6 +1419,7 @@ void Command_Ping_f(void) INT32 pingc; int name_width = 0; + int f_width = 0; int ms_width = 0; int n; @@ -1419,21 +1427,35 @@ void Command_Ping_f(void) pingc = 0; for (i = 1; i < MAXPLAYERS; ++i) - if (playeringame[i]) { - n = strlen(player_names[i]); - if (n > name_width) - name_width = n; + if (playeringame[i]) + { + INT32 ms; - n = playerpingtable[i]; - if (n > ms_width) - ms_width = n; + n = strlen(player_names[i]); + if (n > name_width) + name_width = n; - pingv[pingc].num = i; - pingv[pingc].ms = playerpingtable[i]; - pingc++; + n = playerpingtable[i]; + if (n > f_width) + f_width = n; + + ms = (INT32)(playerpingtable[i] * (1000.00f / TICRATE)); + n = ms; + if (n > ms_width) + ms_width = n; + + pingv[pingc].num = i; + pingv[pingc].f = playerpingtable[i]; + pingv[pingc].ms = ms; + pingc++; + } } + if (f_width < 10) f_width = 1; + else if (f_width < 100) f_width = 2; + else f_width = 3; + if (ms_width < 10) ms_width = 1; else if (ms_width < 100) ms_width = 2; else ms_width = 3; @@ -1442,15 +1464,16 @@ void Command_Ping_f(void) for (i = 0; i < pingc; ++i) { - CONS_Printf("%02d : %-*s %*d ms\n", + CONS_Printf("%02d : %-*s %*d frames (%*d ms)\n", pingv[i].num, name_width, player_names[pingv[i].num], + f_width, pingv[i].f, ms_width, pingv[i].ms); } if (!server && playeringame[consoleplayer]) { - CONS_Printf("\nYour ping is %d ms\n", playerpingtable[consoleplayer]); + CONS_Printf("\nYour ping is %d frames (%d ms)\n", playerpingtable[consoleplayer], (INT32)(playerpingtable[i] * (1000.00f / TICRATE))); } } diff --git a/src/d_net.h b/src/d_net.h index 7f50706ee..9586cecb9 100644 --- a/src/d_net.h +++ b/src/d_net.h @@ -55,6 +55,7 @@ boolean HGetPacket(void); void D_SetDoomcom(void); #ifndef NONET void D_SaveBan(void); +void D_LoadBan(boolean warning); #endif boolean D_CheckNetGame(void); void D_CloseConnection(void); diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 63fe71f0a..62bc4ee29 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -99,6 +99,9 @@ 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 Got_Automatecmd(UINT8 **cp, INT32 playernum); static void PointLimit_OnChange(void); static void TimeLimit_OnChange(void); @@ -147,6 +150,9 @@ static void KartEncore_OnChange(void); static void KartComeback_OnChange(void); static void KartEliminateLast_OnChange(void); +static void Schedule_OnChange(void); +static void LiveStudioAudience_OnChange(void); + #ifdef NETGAME_DEVMODE static void Fishcake_OnChange(void); #endif @@ -157,6 +163,8 @@ static void Command_Stopdemo_f(void); static void Command_StartMovie_f(void); static void Command_StopMovie_f(void); static void Command_Map_f(void); +static void Command_RandomMap(void); +static void Command_RestartLevel(void); static void Command_ResetCamera_f(void); static void Command_View_f (void); @@ -220,6 +228,12 @@ 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); + +static void Command_Automate_Set(void); + // ========================================================================= // CLIENT VARIABLES // ========================================================================= @@ -508,17 +522,20 @@ static CV_PossibleValue_t nettimeout_cons_t[] = {{TICRATE/7, "MIN"}, {60*TICRATE consvar_t cv_nettimeout = CVAR_INIT ("nettimeout", "105", CV_CALL|CV_SAVE, nettimeout_cons_t, NetTimeout_OnChange); //static CV_PossibleValue_t jointimeout_cons_t[] = {{5*TICRATE, "MIN"}, {60*TICRATE, "MAX"}, {0, NULL}}; consvar_t cv_jointimeout = CVAR_INIT ("jointimeout", "105", CV_CALL|CV_SAVE, nettimeout_cons_t, JoinTimeout_OnChange); -consvar_t cv_maxping = CVAR_INIT ("maxping", "800", CV_SAVE, CV_Unsigned, NULL); +consvar_t cv_maxping = CVAR_INIT ("maxdelay", "20", CV_SAVE, CV_Unsigned, NULL); consvar_t cv_lagless = CVAR_INIT ("lagless", "Off", CV_SAVE|CV_NETVAR|CV_CALL, CV_OnOff, Lagless_OnChange); static CV_PossibleValue_t pingtimeout_cons_t[] = {{8, "MIN"}, {120, "MAX"}, {0, NULL}}; -consvar_t cv_pingtimeout = CVAR_INIT ("pingtimeout", "10", CV_SAVE|CV_NETVAR, pingtimeout_cons_t, NULL); +consvar_t cv_pingtimeout = CVAR_INIT ("maxdelaytimeout", "10", CV_SAVE|CV_NETVAR, pingtimeout_cons_t, NULL); // show your ping on the HUD next to framerate. Defaults to warning only (shows up if your ping is > maxping) static CV_PossibleValue_t showping_cons_t[] = {{0, "Off"}, {1, "Always"}, {2, "Warning"}, {0, NULL}}; consvar_t cv_showping = CVAR_INIT ("showping", "Always", CV_SAVE, showping_cons_t, NULL); +static CV_PossibleValue_t pingmeasurement_cons_t[] = {{0, "Frames"}, {1, "Milliseconds"}, {0, NULL}}; +consvar_t cv_pingmeasurement = CVAR_INIT ("pingmeasurement", "Frames", CV_SAVE, pingmeasurement_cons_t, NULL); + consvar_t cv_showviewpointtext = CVAR_INIT ("showviewpointtext", "On", CV_SAVE, CV_OnOff, NULL); // Intermission time Tails 04-19-2002 @@ -547,6 +564,16 @@ 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); + +consvar_t cv_automate = CVAR_INIT ("automate", "On", CV_NETVAR, CV_OnOff, NULL); + +#ifdef DEVELOP +consvar_t cv_livestudioaudience = CVAR_INIT ("livestudioaudience", "On", CV_NETVAR|CV_CALL, CV_OnOff, LiveStudioAudience_OnChange); +#else +consvar_t cv_livestudioaudience = CVAR_INIT ("livestudioaudience", "Off", CV_NETVAR|CV_CALL, CV_OnOff, LiveStudioAudience_OnChange); +#endif + char timedemo_name[256]; boolean timedemo_csv; char timedemo_csv_id[256]; @@ -562,44 +589,66 @@ UINT8 splitscreen = 0; boolean circuitmap = false; INT32 adminplayers[MAXPLAYERS]; +// Scheduled commands. +scheduleTask_t **schedule = NULL; +size_t schedule_size = 0; +size_t schedule_len = 0; + +// Automation commands +char *automate_commands[AEV__MAX]; + +const char *automate_names[AEV__MAX] = +{ + "RoundStart", // AEV_ROUNDSTART + "IntermissionStart", // AEV_INTERMISSIONSTART + "VoteStart" // AEV_VOTESTART +}; + +static UINT32 livestudioaudience_timer = 90; + /// \warning Keep this up-to-date if you add/remove/rename net text commands const char *netxcmdnames[MAXNETXCMD - 1] = { - "NAMEANDCOLOR", - "WEAPONPREF", - "KICK", - "NETVAR", - "SAY", - "MAP", - "EXITLEVEL", - "ADDFILE", - "PAUSE", - "ADDPLAYER", - "TEAMCHANGE", - "CLEARSCORES", - "VERIFIED", - "RANDOMSEED", - "RUNSOC", - "REQADDFILE", - "SETMOTD", - "RESPAWN", - "DEMOTED", - "LUACMD", - "LUAVAR", - "LUAFILE", + "NAMEANDCOLOR", // XD_NAMEANDCOLOR + "WEAPONPREF", // XD_WEAPONPREF + "KICK", // XD_KICK + "NETVAR", // XD_NETVAR + "SAY", // XD_SAY + "MAP", // XD_MAP + "EXITLEVEL", // XD_EXITLEVEL + "ADDFILE", // XD_ADDFILE + "PAUSE", // XD_PAUSE + "ADDPLAYER", // XD_ADDPLAYER + "TEAMCHANGE", // XD_TEAMCHANGE + "CLEARSCORES", // XD_CLEARSCORES + "VERIFIED", // XD_VERIFIED + "RANDOMSEED", // XD_RANDOMSEED + "RUNSOC", // XD_RUNSOC + "REQADDFILE", // XD_REQADDFILE + "SETMOTD", // XD_SETMOTD + "RESPAWN", // XD_RESPAWN + "DEMOTED", // XD_DEMOTED + "LUACMD", // XD_LUACMD + "LUAVAR", // XD_LUAVAR + "LUAFILE", // XD_LUAFILE // SRB2Kart - "SETUPVOTE", - "MODIFYVOTE", - "PICKVOTE", - "REMOVEPLAYER", - "POWERLEVEL", - "PARTYINVITE", - "ACCEPTPARTYINVITE", - "LEAVEPARTY", - "CANCELPARTYINVITE", - "GIVEITEM", - "ADDBOT" + "SETUPVOTE", // XD_SETUPVOTE + "MODIFYVOTE", // XD_MODIFYVOTE + "PICKVOTE", // XD_PICKVOTE + "REMOVEPLAYER", // XD_REMOVEPLAYER + "POWERLEVEL", // XD_POWERLEVEL + "PARTYINVITE", // XD_PARTYINVITE + "ACCEPTPARTYINVITE", // XD_ACCEPTPARTYINVITE + "LEAVEPARTY", // XD_LEAVEPARTY + "CANCELPARTYINVITE", // XD_CANCELPARTYINVITE + "GIVEITEM", // XD_GIVEITEM + "ADDBOT", // XD_ADDBOT + "DISCORD", // XD_DISCORD + "PLAYSOUND", // XD_PLAYSOUND + "SCHEDULETASK", // XD_SCHEDULETASK + "SCHEDULECLEAR", // XD_SCHEDULECLEAR + "AUTOMATE" // XD_AUTOMATE }; // ========================================================================= @@ -648,6 +697,10 @@ void D_RegisterServerCommands(void) RegisterNetXCmd(XD_GIVEITEM, Got_GiveItemcmd); + RegisterNetXCmd(XD_SCHEDULETASK, Got_ScheduleTaskcmd); + RegisterNetXCmd(XD_SCHEDULECLEAR, Got_ScheduleClearcmd); + RegisterNetXCmd(XD_AUTOMATE, Got_Automatecmd); + // Remote Administration COM_AddCommand("password", Command_Changepassword_f); COM_AddCommand("login", Command_Login_f); // useful in dedicated to kick off remote admin @@ -665,6 +718,8 @@ void D_RegisterServerCommands(void) RegisterNetXCmd(XD_CLEARSCORES, Got_Clearscores); COM_AddCommand("clearscores", Command_Clearscores_f); COM_AddCommand("map", Command_Map_f); + COM_AddCommand("randommap", Command_RandomMap); + COM_AddCommand("restartlevel", Command_RestartLevel); COM_AddCommand("exitgame", Command_ExitGame_f); COM_AddCommand("retry", Command_Retry_f); @@ -703,6 +758,12 @@ 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); + + COM_AddCommand("automate_set", Command_Automate_Set); + // for master server connection AddMServCommands(); @@ -762,17 +823,22 @@ void D_RegisterServerCommands(void) COM_AddCommand("ping", Command_Ping_f); CV_RegisterVar(&cv_nettimeout); CV_RegisterVar(&cv_jointimeout); - + CV_RegisterVar(&cv_kicktime); CV_RegisterVar(&cv_skipmapcheck); CV_RegisterVar(&cv_sleep); CV_RegisterVar(&cv_maxping); CV_RegisterVar(&cv_lagless); CV_RegisterVar(&cv_pingtimeout); CV_RegisterVar(&cv_showping); + CV_RegisterVar(&cv_pingmeasurement); CV_RegisterVar(&cv_showviewpointtext); CV_RegisterVar(&cv_director); + CV_RegisterVar(&cv_schedule); + CV_RegisterVar(&cv_automate); + CV_RegisterVar(&cv_livestudioaudience); + CV_RegisterVar(&cv_dummyconsvar); #ifdef USE_STUN @@ -963,6 +1029,11 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_consolechat); CV_RegisterVar(&cv_chatnotifications); CV_RegisterVar(&cv_chatbacktint); + + CV_RegisterVar(&cv_shoutname); + CV_RegisterVar(&cv_shoutcolor); + CV_RegisterVar(&cv_autoshout); + CV_RegisterVar(&cv_songcredits); CV_RegisterVar(&cv_tutorialprompt); CV_RegisterVar(&cv_showfocuslost); @@ -1079,6 +1150,15 @@ void D_RegisterClientCommands(void) * \sa CleanupPlayerName, SetPlayerName, Got_NameAndColor * \author Graue */ + +static boolean AllowedPlayerNameChar(char ch) +{ + if (!isprint(ch) || ch == ';' || ch == '"' || (UINT8)(ch) >= 0x80) + return false; + + return true; +} + boolean EnsurePlayerNameIsGood(char *name, INT32 playernum) { INT32 ix; @@ -1100,7 +1180,7 @@ boolean EnsurePlayerNameIsGood(char *name, INT32 playernum) // Also, anything over 0x80 is disallowed too, since compilers love to // differ on whether they're printable characters or not. for (ix = 0; name[ix] != '\0'; ix++) - if (!isprint(name[ix]) || name[ix] == ';' || (UINT8)(name[ix]) >= 0x80) + if (!AllowedPlayerNameChar(name[ix])) return false; // Check if a player is currently using the name, case-insensitively. @@ -1191,8 +1271,7 @@ void CleanupPlayerName(INT32 playernum, const char *newname) do { - /* from EnsurePlayerNameIsGood */ - if (!isprint(*p) || *p == ';' || (UINT8)*p >= 0x80) + if (!AllowedPlayerNameChar(*p)) break; } while (*++p) ; @@ -2973,6 +3052,67 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) #endif } +static void Command_RandomMap(void) +{ + INT32 oldmapnum; + INT32 newmapnum; + INT32 newgametype; + boolean newencoremode; + boolean newresetplayers; + + if (client && !IsPlayerAdmin(consoleplayer)) + { + CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n")); + return; + } + + // TODO: Handle singleplayer conditions. + // The existing ones are way too annoyingly complicated and "anti-cheat" for my tastes. + + if (Playing()) + { + newgametype = gametype; + newencoremode = encoremode; + newresetplayers = false; + + if (gamestate == GS_LEVEL) + { + oldmapnum = gamemap-1; + } + else + { + oldmapnum = prevmap; + } + } + else + { + newgametype = cv_dummygametype.value; // Changed from cv_newgametype to match newmenus + newencoremode = false; + newresetplayers = true; + oldmapnum = -1; + } + + newmapnum = G_RandMap(G_TOLFlag(newgametype), oldmapnum, 0, 0, false, NULL) + 1; + D_MapChange(newmapnum, newgametype, newencoremode, newresetplayers, 0, false, false); +} + +static void Command_RestartLevel(void) +{ + if (client && !IsPlayerAdmin(consoleplayer)) + { + CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n")); + return; + } + + if (!Playing()) + { + CONS_Printf(M_GetText("You must be in a game to use this.\n")); + return; + } + + D_MapChange(gamemap, gametype, encoremode, false, 0, false, false); +} + static void Command_Pause(void) { UINT8 buf[2]; @@ -3689,9 +3829,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) @@ -3808,6 +3946,218 @@ 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; + } + + if (K_CanChangeRules() == false) + { + // Don't engage in automation while in a restricted context. + 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; +} + +void Automate_Set(automateEvents_t type, const char *command) +{ + if (!server) + { + // Since there's no list command or anything for this, + // we don't need this code to run for anyone but the server. + return; + } + + if (automate_commands[type] != NULL) + { + // Free the old command. + Z_Free(automate_commands[type]); + } + + if (command == NULL || strlen(command) == 0) + { + // Remove the command. + automate_commands[type] = NULL; + } + else + { + // New command. + automate_commands[type] = Z_StrDup(command); + } +} + +void Automate_Run(automateEvents_t type) +{ + if (!server) + { + // Only the server should be doing this. + return; + } + + if (K_CanChangeRules() == false) + { + // Don't engage in automation while in a restricted context. + return; + } + +#ifdef PARANOIA + if (type >= AEV__MAX) + { + // Shouldn't happen. + I_Error("Attempted to run invalid automation type."); + return; + } +#endif + + if (!cv_automate.value) + { + // We don't want to run automation. + return; + } + + if (automate_commands[type] == NULL) + { + // No command to run. + return; + } + + CONS_Printf( + "Running %s automate command \"" "\x82" "%s" "\x80" "\"...\n", + automate_names[type], + automate_commands[type] + ); + + COM_BufAddText(automate_commands[type]); + COM_BufAddText("\n"); +} + +void Automate_Clear(void) +{ + size_t i; + + for (i = 0; i < AEV__MAX; i++) + { + Automate_Set(i, NULL); + } +} + +void LiveStudioAudience(void) +{ + if (livestudioaudience_timer == 0) + { + S_StartSound(NULL, sfx_mbv91); + livestudioaudience_timer = 90; + } + else + { + livestudioaudience_timer--; + } +} + static void Command_MotD_f(void) { size_t i, j; @@ -5002,6 +5352,101 @@ 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"); + } +} + +static void Got_Automatecmd(UINT8 **cp, INT32 playernum) +{ + UINT8 eventID; + char command[MAXTEXTCMD]; + + eventID = READUINT8(*cp); + READSTRING(*cp, command); + + if ( + (playernum != serverplayer && !IsPlayerAdmin(playernum)) + || (eventID >= AEV__MAX) + ) + { + CONS_Alert(CONS_WARNING, + M_GetText ("Illegal automate received from %s\n"), + player_names[playernum]); + if (server) + SendKick(playernum, KICK_MSG_CON_FAIL); + return; + } + + Automate_Set(eventID, command); + + if (server || consoleplayer == playernum) + { + if (command[0] == '\0') + { + CONS_Printf( + "Removed the %s automate command.\n", + automate_names[eventID] + ); + } + else + { + CONS_Printf( + "Set the %s automate command to \"" "\x82" "%s" "\x80" "\".\n", + automate_names[eventID], + command + ); + } + } +} + /** Prints the number of displayplayers[0]. * * \todo Possibly remove this; it was useful for debugging at one point. @@ -5266,6 +5711,135 @@ 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 + ); + } +} + +static void Command_Automate_Set(void) +{ + UINT8 buf[MAXTEXTCMD]; + UINT8 *buf_p = buf; + + size_t ac; + + const char *event; + size_t eventID; + + 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("automate_set : sets the command to run each time a event triggers\n"); + return; + } + + event = COM_Argv(1); + + for (eventID = 0; eventID < AEV__MAX; eventID++) + { + if (strcasecmp(event, automate_names[eventID]) == 0) + { + break; + } + } + + if (eventID == AEV__MAX) + { + CONS_Printf("Unknown event type \"%s\".\n", event); + return; + } + + command = COM_Argv(2); + + WRITEUINT8(buf_p, eventID); + WRITESTRING(buf_p, command); + + SendNetXCmd(XD_AUTOMATE, buf, buf_p - buf); +} + /** Makes a change to ::cv_forceskin take effect immediately. * * \sa Command_SetForcedSkin_f, cv_forceskin, forcedskin @@ -5884,6 +6458,33 @@ 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; + } +} + +static void LiveStudioAudience_OnChange(void) +{ + livestudioaudience_timer = 90; +} + 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 a7050b762..7125e3b27 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -118,6 +118,7 @@ extern consvar_t cv_maxping; extern consvar_t cv_lagless; extern consvar_t cv_pingtimeout; extern consvar_t cv_showping; +extern consvar_t cv_pingmeasurement; extern consvar_t cv_showviewpointtext; extern consvar_t cv_skipmapcheck; @@ -128,6 +129,10 @@ extern consvar_t cv_perfstats; extern consvar_t cv_director; +extern consvar_t cv_schedule; + +extern consvar_t cv_livestudioaudience; + extern char timedemo_name[256]; extern boolean timedemo_csv; extern char timedemo_csv_id[256]; @@ -171,6 +176,10 @@ typedef enum XD_GIVEITEM, // 32 XD_ADDBOT, // 33 XD_DISCORD, // 34 + XD_PLAYSOUND, // 35 + XD_SCHEDULETASK, // 36 + XD_SCHEDULECLEAR, // 37 + XD_AUTOMATE, // 38 MAXNETXCMD } netxcmd_t; @@ -239,6 +248,36 @@ 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); + +typedef enum +{ + AEV_ROUNDSTART, + AEV_INTERMISSIONSTART, + AEV_VOTESTART, + AEV__MAX +} automateEvents_t; + +void Automate_Run(automateEvents_t type); +void Automate_Set(automateEvents_t type, const char *command); +void Automate_Clear(void); + +void LiveStudioAudience(void); + // used for the player setup menu UINT8 CanChangeSkin(INT32 playernum); diff --git a/src/doomstat.h b/src/doomstat.h index 66e21ccfa..95baf7ce4 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -796,7 +796,6 @@ extern consvar_t cv_forceskin; // force clients to use the server's skin extern consvar_t cv_downloading; // allow clients to downloading WADs. extern consvar_t cv_nettimeout; // SRB2Kart: Advanced server options menu extern consvar_t cv_jointimeout; -extern consvar_t cv_maxping; extern ticcmd_t netcmds[BACKUPTICS][MAXPLAYERS]; extern INT32 serverplayer; extern INT32 adminplayers[MAXPLAYERS]; diff --git a/src/g_game.c b/src/g_game.c index 61013cf6c..4d98256f1 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -379,6 +379,36 @@ consvar_t cv_chatbacktint = CVAR_INIT ("chatbacktint", "On", CV_SAVE, CV_OnOff, static CV_PossibleValue_t consolechat_cons_t[] = {{0, "Window"}, {1, "Console"}, {2, "Window (Hidden)"}, {0, NULL}}; consvar_t cv_consolechat = CVAR_INIT ("chatmode", "Window", CV_SAVE, consolechat_cons_t, NULL); +// Shout settings +// The relevant ones are CV_NETVAR because too lazy to send them any other way +consvar_t cv_shoutname = CVAR_INIT ("shout_name", "SERVER", CV_NETVAR, NULL, NULL); + +static CV_PossibleValue_t shoutcolor_cons_t[] = +{ + {-1, "Player color"}, + {0, "White"}, + {1, "Yellow"}, + {2, "Purple"}, + {3, "Green"}, + {4, "Blue"}, + {5, "Red"}, + {6, "Gray"}, + {7, "Orange"}, + {8, "Sky-blue"}, + {9, "Gold"}, + {10, "Lavender"}, + {11, "Aqua-green"}, + {12, "Magenta"}, + {13, "Pink"}, + {14, "Brown"}, + {15, "Tan"}, + {0, NULL} +}; +consvar_t cv_shoutcolor = CVAR_INIT ("shout_color", "Red", CV_NETVAR, shoutcolor_cons_t, NULL); + +// If on and you're an admin, your messages will automatically become shouts. +consvar_t cv_autoshout = CVAR_INIT ("autoshout", "Off", CV_NETVAR, CV_OnOff, NULL); + // Pause game upon window losing focus consvar_t cv_pauseifunfocused = CVAR_INIT ("pauseifunfocused", "Yes", CV_SAVE, CV_YesNo, NULL); @@ -1255,6 +1285,7 @@ static void weaponPrefChange4(void) // void G_DoLoadLevel(boolean resetplayer) { + boolean doAutomate = false; INT32 i; // Make sure objectplace is OFF when you first start the level! @@ -1287,6 +1318,10 @@ void G_DoLoadLevel(boolean resetplayer) else titlemapinaction = TITLEMAP_OFF; + // Doing this matches HOSTMOD behavior. + // Is that desired? IDK + doAutomate = (gamestate != GS_LEVEL); + G_SetGamestate(GS_LEVEL); if (wipegamestate == GS_MENU) M_ClearMenus(true); @@ -1325,6 +1360,11 @@ void G_DoLoadLevel(boolean resetplayer) CON_ClearHUD(); server_lagless = cv_lagless.value; + + if (doAutomate == true) + { + Automate_Run(AEV_ROUNDSTART); + } } // diff --git a/src/g_game.h b/src/g_game.h index 93be97096..4341a36c3 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -48,6 +48,7 @@ extern boolean promptactive; extern consvar_t cv_tutorialprompt; extern consvar_t cv_chatwidth, cv_chatnotifications, cv_chatheight, cv_chattime, cv_consolechat, cv_chatbacktint, cv_chatspamprotection; +extern consvar_t cv_shoutname, cv_shoutcolor, cv_autoshout; extern consvar_t cv_songcredits; extern consvar_t cv_pauseifunfocused; diff --git a/src/g_input.c b/src/g_input.c index ead8cab27..9fbf89bf0 100644 --- a/src/g_input.c +++ b/src/g_input.c @@ -499,6 +499,9 @@ INT32 G_KeyStringtoNum(const char *keystr) { UINT32 j; + if (!keystr[0]) + return 0; + if (!keystr[1] && keystr[0] > ' ' && keystr[0] <= 'z') return keystr[0]; diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h index 5ad7de244..1ba8164a3 100644 --- a/src/hardware/hw_defs.h +++ b/src/hardware/hw_defs.h @@ -133,6 +133,7 @@ typedef struct // Predefined shader types enum { + SHADER_NONE = -1, SHADER_DEFAULT = 0, SHADER_FLOOR, @@ -235,7 +236,8 @@ enum EPolyFlags PF_RemoveYWrap = 0x00010000, // Forces clamp texture on Y PF_ForceWrapX = 0x00020000, // Forces repeat texture on X PF_ForceWrapY = 0x00040000, // Forces repeat texture on Y - PF_Ripple = 0x00100000 // Water ripple effect. The current backend doesn't use it for anything. + PF_Ripple = 0x00100000, // Water ripple effect. The current backend doesn't use it for anything. + PF_WireFrame = 0x00200000, // Draws vertices as lines instead of triangles }; diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h index 5b3f4654a..c13da6889 100644 --- a/src/hardware/hw_glob.h +++ b/src/hardware/hw_glob.h @@ -83,6 +83,7 @@ typedef struct gl_vissprite_s boolean flip, vflip; boolean precip; // Tails 08-25-2002 + boolean bbox; boolean rotated; UINT8 translucency; //alpha level 0-255 diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index a819171b4..3a4a0687f 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -70,6 +70,7 @@ static void HWR_ProjectSprite(mobj_t *thing); #ifdef HWPRECIP static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing); #endif +static void HWR_ProjectBoundingBox(mobj_t *thing); static void HWR_RollTransform(FTransform *tr, angle_t roll); void HWR_AddTransparentFloor(levelflat_t *levelflat, extrasubsector_t *xsub, boolean isceiling, fixed_t fixedheight, INT32 lightlevel, INT32 alpha, sector_t *FOFSector, FBITFIELD blend, boolean fogplane, extracolormap_t *planecolormap); @@ -4109,6 +4110,54 @@ static void HWR_SplitSprite(gl_vissprite_t *spr) HWR_LinkDrawHackAdd(wallVerts, spr); } +static void HWR_DrawBoundingBox(gl_vissprite_t *vis) +{ + FOutVector v[24]; + FSurfaceInfo Surf = {0}; + + // + // create a cube (side view) + // + // 5--4 3 + // | + // | + // 0--1 2 + // + // repeat this 4 times (overhead) + // + // + // 17 20 21 11 + // 16 15 14 10 + // 27 22 *--* 07 12 + // | | + // 26 23 *--* 06 13 + // 24 00 01 02 + // 25 05 04 03 + // + + v[000].x = v[005].x = v[015].x = v[016].x = v[017].x = v[020].x = + v[022].x = v[023].x = v[024].x = v[025].x = v[026].x = v[027].x = vis->x1; // west + + v[001].x = v[002].x = v[003].x = v[004].x = v[006].x = v[007].x = + v[010].x = v[011].x = v[012].x = v[013].x = v[014].x = v[021].x = vis->x2; // east + + v[000].z = v[001].z = v[002].z = v[003].z = v[004].z = v[005].z = + v[006].z = v[013].z = v[023].z = v[024].z = v[025].z = v[026].z = vis->z1; // south + + v[007].z = v[010].z = v[011].z = v[012].z = v[014].z = v[015].z = + v[016].z = v[017].z = v[020].z = v[021].z = v[022].z = v[027].z = vis->z2; // north + + v[000].y = v[001].y = v[002].y = v[006].y = v[007].y = v[010].y = + v[014].y = v[015].y = v[016].y = v[022].y = v[023].y = v[024].y = vis->gz; // bottom + + v[003].y = v[004].y = v[005].y = v[011].y = v[012].y = v[013].y = + v[017].y = v[020].y = v[021].y = v[025].y = v[026].y = v[027].y = vis->gzt; // top + + Surf.PolyColor = V_GetColor(R_GetBoundingBoxColor(vis->mobj)); + + HWR_ProcessPolygon(&Surf, v, 24, PF_Modulated|PF_NoTexture|PF_WireFrame, SHADER_NONE, false); +} + // -----------------+ // HWR_DrawSprite : Draw flat sprites // : (monsters, bonuses, weapons, lights, ...) @@ -4562,9 +4611,16 @@ static int CompareVisSprites(const void *p1, const void *p2) int frame1; int frame2; + int linkdraw1; + int linkdraw2; + + // bbox doesn't need to be sorted + if (spr1->bbox || spr2->bbox) + return 0; + // check for precip first, because then sprX->mobj is actually a precipmobj_t and does not have flags2 or tracer - int linkdraw1 = !spr1->precip && (spr1->mobj->flags2 & MF2_LINKDRAW) && spr1->mobj->tracer; - int linkdraw2 = !spr2->precip && (spr2->mobj->flags2 & MF2_LINKDRAW) && spr2->mobj->tracer; + linkdraw1 = !spr1->precip && (spr1->mobj->flags2 & MF2_LINKDRAW) && spr1->mobj->tracer; + linkdraw2 = !spr2->precip && (spr2->mobj->flags2 & MF2_LINKDRAW) && spr2->mobj->tracer; // ^ is the XOR operation // if comparing a linkdraw and non-linkdraw sprite or 2 linkdraw sprites with different tracers, then use @@ -4954,6 +5010,9 @@ static void HWR_DrawSprites(void) for (i = 0; i < gl_visspritecount; i++) { gl_vissprite_t *spr = gl_vsprorder[i]; + if (spr->bbox) + HWR_DrawBoundingBox(spr); + else #ifdef HWPRECIP if (spr->precip) HWR_DrawPrecipitationSprite(spr); @@ -5052,8 +5111,15 @@ static void HWR_AddSprites(sector_t *sec) limit_dist = (fixed_t)(cv_drawdist.value) * mapobjectscale; for (thing = sec->thinglist; thing; thing = thing->snext) { - if (R_ThingVisibleWithinDist(thing, limit_dist)) - HWR_ProjectSprite(thing); + if (R_ThingWithinDist(thing, limit_dist)) + { + if (R_ThingVisible(thing)) + { + HWR_ProjectSprite(thing); + } + + HWR_ProjectBoundingBox(thing); + } } #ifdef HWPRECIP @@ -5552,6 +5618,7 @@ static void HWR_ProjectSprite(mobj_t *thing) vis->vflip = vflip; vis->precip = false; + vis->bbox = false; } #ifdef HWPRECIP @@ -5675,6 +5742,7 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing) vis->gz = vis->gzt - (FIXED_TO_FLOAT(spritecachedinfo[lumpoff].height) * this_scale); vis->precip = true; + vis->bbox = false; // okay... this is a hack, but weather isn't networked, so it should be ok if (!(thing->precipflags & PCF_THUNK)) @@ -5685,6 +5753,60 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing) } #endif +static void HWR_ProjectBoundingBox(mobj_t *thing) +{ + gl_vissprite_t *vis; + float tr_x, tr_y; + float tz; + float rad; + + // uncapped/interpolation + interpmobjstate_t interp = {0}; + + if (!thing) + return; + + if (!R_ThingBoundingBoxVisible(thing)) + return; + + if (R_UsingFrameInterpolation() && !paused) + { + R_InterpolateMobjState(thing, rendertimefrac, &interp); + } + else + { + R_InterpolateMobjState(thing, FRACUNIT, &interp); + } + + // transform the origin point + tr_x = FIXED_TO_FLOAT(interp.x) - gl_viewx; + tr_y = FIXED_TO_FLOAT(interp.y) - gl_viewy; + + // rotation around vertical axis + tz = (tr_x * gl_viewcos) + (tr_y * gl_viewsin); + + // thing is behind view plane? + if (tz < ZCLIP_PLANE) + return; + + tr_x += gl_viewx; + tr_y += gl_viewy; + + rad = FIXED_TO_FLOAT(thing->radius); + + vis = HWR_NewVisSprite(); + vis->x1 = tr_x - rad; + vis->x2 = tr_x + rad; + vis->z1 = tr_y - rad; + vis->z2 = tr_y + rad; + vis->gz = FIXED_TO_FLOAT(interp.z); + vis->gzt = vis->gz + FIXED_TO_FLOAT(thing->height); + vis->mobj = thing; + + vis->precip = false; + vis->bbox = true; +} + // ========================================================================== // Sky dome rendering, ported from PrBoom+ // ========================================================================== diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c index 32b7ff7f4..746f094e2 100644 --- a/src/hardware/hw_md2.c +++ b/src/hardware/hw_md2.c @@ -713,11 +713,11 @@ static void HWR_CreateBlendedTexture(patch_t *gpatch, patch_t *blendgpatch, GLMi UINT16 w = gpatch->width, h = gpatch->height; UINT32 size = w*h; RGBA_t *image, *blendimage, *cur, blendcolor; - UINT8 translation[16]; // First the color index - UINT8 cutoff[16]; // Brightness cutoff before using the next color + UINT8 translation[17]; // First the color index + UINT8 cutoff[17]; // Brightness cutoff before using the next color UINT8 translen = 0; UINT8 i; - UINT8 colorbrightnesses[16]; + UINT8 colorbrightnesses[17]; UINT8 color_match_lookup[256]; // optimization attempt blendcolor = V_GetColor(0); // initialize @@ -788,6 +788,11 @@ static void HWR_CreateBlendedTexture(patch_t *gpatch, patch_t *blendgpatch, GLMi translen++; } + if (translen > 0) + translation[translen] = translation[translen-1]; // extended to accomodate secondi if firsti equal to translen-1 + if (translen > 1) + cutoff[translen] = cutoff[translen-1] = 0; // as above + if (skinnum == TC_RAINBOW && translen > 0) { UINT16 b; @@ -803,7 +808,7 @@ static void HWR_CreateBlendedTexture(patch_t *gpatch, patch_t *blendgpatch, GLMi { UINT16 brightdif = 256; - color_match_lookup[i] = 0; + color_match_lookup[b] = 0; for (i = 0; i < translen; i++) { if (b > colorbrightnesses[i]) // don't allow greater matches (because calculating a makeshift gradient for this is already a huge mess as is) @@ -820,6 +825,9 @@ static void HWR_CreateBlendedTexture(patch_t *gpatch, patch_t *blendgpatch, GLMi } } + if (translen > 0) + colorbrightnesses[translen] = colorbrightnesses[translen-1]; + while (size--) { if (skinnum == TC_HITLAG) diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c index 84ddc2492..00125221c 100644 --- a/src/hardware/r_opengl/r_opengl.c +++ b/src/hardware/r_opengl/r_opengl.c @@ -1061,6 +1061,12 @@ EXPORT void HWRAPI(LoadCustomShader) (int number, char *code, size_t size, boole EXPORT void HWRAPI(SetShader) (int type) { #ifdef GL_SHADERS + if (type == SHADER_NONE) + { + UnSetShader(); + return; + } + if (gl_allowshaders != HWD_SHADEROPTION_OFF) { gl_shader_t *shader = gl_shaderstate.current; @@ -2319,7 +2325,7 @@ EXPORT void HWRAPI(DrawPolygon) (FSurfaceInfo *pSurf, FOutVector *pOutVerts, FUI pglVertexPointer(3, GL_FLOAT, sizeof(FOutVector), &pOutVerts[0].x); pglTexCoordPointer(2, GL_FLOAT, sizeof(FOutVector), &pOutVerts[0].s); - pglDrawArrays(GL_TRIANGLE_FAN, 0, iNumPts); + pglDrawArrays(PolyFlags & PF_WireFrame ? GL_LINES : GL_TRIANGLE_FAN, 0, iNumPts); if (PolyFlags & PF_RemoveYWrap) pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); diff --git a/src/http-mserv.c b/src/http-mserv.c index b5e3ba6c7..d7d65d871 100644 --- a/src/http-mserv.c +++ b/src/http-mserv.c @@ -55,6 +55,8 @@ consvar_t cv_masterserver_token = CVAR_INIT NULL ); +#define HMS_QUERY_VERSION "?v=2.2" + #ifdef MASTERSERVER static int hms_started; @@ -174,7 +176,7 @@ HMS_connect (const char *format, ...) va_start (ap, format); url = malloc(seek + vsnprintf(0, 0, format, ap) + - sizeof "?v=2" - 1 + + sizeof HMS_QUERY_VERSION - 1 + token_length + 1); va_end (ap); @@ -188,8 +190,8 @@ HMS_connect (const char *format, ...) seek += vsprintf(&url[seek], format, ap); va_end (ap); - strcpy(&url[seek], "?v=2"); - seek += sizeof "?v=2" - 1; + strcpy(&url[seek], HMS_QUERY_VERSION); + seek += sizeof HMS_QUERY_VERSION - 1; if (quack_token) sprintf(&url[seek], "&token=%s", quack_token); diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 40987fc80..fd57f4a36 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -64,8 +64,11 @@ #define HU_INPUTX 0 #define HU_INPUTY 0 -#define HU_SERVER_SAY 1 // Server message (dedicated). -#define HU_CSAY 2 // Server CECHOes to everyone. +typedef enum +{ + HU_SHOUT = 1, // Shout message + HU_CSAY = 1<<1, // Middle-of-screen server message +} sayflags_t; //------------------------------------------- // heads up font @@ -75,6 +78,7 @@ // Note: I'd like to adress that at this point we might *REALLY* want to work towards a common drawString function that can take any font we want because this is really turning into a MESS. :V -Lat' patch_t *pinggfx[5]; // small ping graphic patch_t *mping[5]; // smaller ping graphic +patch_t *pingmeasure[2]; // ping measurement graphic patch_t *framecounter; patch_t *frameslash; // framerate stuff. Used in screen.c @@ -168,6 +172,7 @@ static void Command_Say_f(void); static void Command_Sayto_f(void); static void Command_Sayteam_f(void); static void Command_CSay_f(void); +static void Command_Shout(void); static void Got_Saycmd(UINT8 **p, INT32 playernum); #endif @@ -189,6 +194,9 @@ void HU_LoadGraphics(void) HU_UpdatePatch(&mping[i], "MPING%d", i+1); } + HU_UpdatePatch(&pingmeasure[0], "PINGD"); + HU_UpdatePatch(&pingmeasure[1], "PINGMS"); + // fps stuff HU_UpdatePatch(&framecounter, "FRAMER"); HU_UpdatePatch(&frameslash, "FRAMESL"); @@ -206,6 +214,7 @@ void HU_Init(void) COM_AddCommand("sayto", Command_Sayto_f); COM_AddCommand("sayteam", Command_Sayteam_f); COM_AddCommand("csay", Command_CSay_f); + COM_AddCommand("shout", Command_Shout); RegisterNetXCmd(XD_SAY, Got_Saycmd); #endif @@ -470,7 +479,7 @@ void HU_AddChatText(const char *text, boolean playsound) * to -32 to say to everyone on that player's team. Note: This means you * have to add 1 to the player number, since they are 0 to 31 internally. * - * The flag HU_SERVER_SAY will be set if it is the dedicated server speaking. + * The flag HU_SHOUT will be set if it is the dedicated server speaking. * * This function obtains the message using COM_Argc() and COM_Argv(). * @@ -497,14 +506,17 @@ static void DoSayCommand(SINT8 target, size_t usedargs, UINT8 flags) return; } - // Only servers/admins can CSAY. - if(!server && !(IsPlayerAdmin(consoleplayer))) - flags &= ~HU_CSAY; + // Only servers/admins can shout or CSAY. + if (!server && !IsPlayerAdmin(consoleplayer)) + { + flags &= ~(HU_SHOUT|HU_CSAY); + } - // We handle HU_SERVER_SAY, not the caller. - flags &= ~HU_SERVER_SAY; - if(dedicated && !(flags & HU_CSAY)) - flags |= HU_SERVER_SAY; + // Enforce shout for the dedicated server. + if (dedicated && !(flags & HU_CSAY)) + { + flags |= HU_SHOUT; + } buf[0] = target; buf[1] = flags; @@ -578,6 +590,8 @@ static void Command_Say_f(void) return; } + // Autoshout is handled by HU_queueChatChar. + // If you're using the say command, you can use the shout command, lol. DoSayCommand(0, 1, 0); } @@ -641,7 +655,7 @@ static void Command_CSay_f(void) return; } - if(!server && !IsPlayerAdmin(consoleplayer)) + if (!server && !IsPlayerAdmin(consoleplayer)) { CONS_Alert(CONS_NOTICE, M_GetText("Only servers and admins can use csay.\n")); return; @@ -649,6 +663,24 @@ static void Command_CSay_f(void) DoSayCommand(0, 1, HU_CSAY); } + +static void Command_Shout(void) +{ + if (COM_Argc() < 2) + { + CONS_Printf(M_GetText("shout : send a message with special alert sound, name, and color\n")); + return; + } + + if (!server && !IsPlayerAdmin(consoleplayer)) + { + CONS_Alert(CONS_NOTICE, M_GetText("Only servers and admins can use shout.\n")); + return; + } + + DoSayCommand(0, 1, HU_SHOUT); +} + static tic_t stop_spamming[MAXPLAYERS]; /** Receives a message, processing an ::XD_SAY command. @@ -672,7 +704,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) msg = (char *)*p; SKIPSTRING(*p); - if ((cv_mute.value || flags & (HU_CSAY|HU_SERVER_SAY)) && playernum != serverplayer && !(IsPlayerAdmin(playernum))) + if ((cv_mute.value || flags & (HU_CSAY|HU_SHOUT)) && playernum != serverplayer && !(IsPlayerAdmin(playernum))) { CONS_Alert(CONS_WARNING, cv_mute.value ? M_GetText("Illegal say command received from %s while muted\n") : M_GetText("Illegal csay command received from non-admin %s\n"), @@ -701,7 +733,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) // before we do anything, let's verify the guy isn't spamming, get this easier on us. //if (stop_spamming[playernum] != 0 && cv_chatspamprotection.value && !(flags & HU_CSAY)) - if (stop_spamming[playernum] != 0 && consoleplayer != playernum && cv_chatspamprotection.value && !(flags & HU_CSAY)) + if (stop_spamming[playernum] != 0 && consoleplayer != playernum && cv_chatspamprotection.value && !(flags & (HU_CSAY|HU_SHOUT))) { CONS_Debug(DBG_NETPLAY,"Received SAY cmd too quickly from Player %d (%s), assuming as spam and blocking message.\n", playernum+1, player_names[playernum]); stop_spamming[playernum] = 4; @@ -734,8 +766,8 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) action = true; } - if (flags & HU_SERVER_SAY) - dispname = "SERVER"; + if (flags & HU_SHOUT) + dispname = cv_shoutname.zstring; else dispname = player_names[playernum]; @@ -761,7 +793,30 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) char *tempchar = NULL; char color_prefix[2]; - if (players[playernum].spectator) + if (flags & HU_SHOUT) + { + if (cv_shoutcolor.value == -1) + { + UINT16 chatcolor = skincolors[players[playernum].skincolor].chatcolor; + + if (chatcolor > V_TANMAP) + { + sprintf(color_prefix, "%c", '\x80'); + } + else + { + sprintf(color_prefix, "%c", '\x80' + (chatcolor >> V_CHARCOLORSHIFT)); + } + } + else + { + sprintf(color_prefix, "%c", '\x80' + cv_shoutcolor.value); + } + + // Colorize full text + cstart = textcolor = color_prefix; + } + else if (players[playernum].spectator) { // grey text cstart = textcolor = "\x86"; @@ -848,7 +903,10 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) fmt2 = "%s<%s%s>\x80%s %s%s"; }*/ - HU_AddChatText(va(fmt2, prefix, cstart, dispname, cend, textcolor, msg), cv_chatnotifications.value); // add to chat + HU_AddChatText(va(fmt2, prefix, cstart, dispname, cend, textcolor, msg), (cv_chatnotifications.value) && !(flags & HU_SHOUT)); // add to chat + + if ((cv_chatnotifications.value) && (flags & HU_SHOUT)) + S_StartSound(NULL, sfx_sysmsg); if (tempchar) Z_Free(tempchar); @@ -1172,7 +1230,7 @@ static void HU_queueChatChar(INT32 c) else buf[0] = target; - buf[1] = 0; // flags + buf[1] = ((server || IsPlayerAdmin(consoleplayer)) && cv_autoshout.value) ? HU_SHOUT : 0; // flags SendNetXCmd(XD_SAY, buf, 2 + strlen(&buf[2]) + 1); } return; @@ -2274,15 +2332,15 @@ void HU_Erase(void) //====================================================================== static int -Ping_gfx_num (int ping) +Ping_gfx_num (int lag) { - if (ping < 76) + if (lag < 2) return 0; - else if (ping < 137) + else if (lag < 4) return 1; - else if (ping < 256) + else if (lag < 7) return 2; - else if (ping < 500) + else if (lag < 10) return 3; else return 4; @@ -2291,22 +2349,37 @@ Ping_gfx_num (int ping) // // HU_drawPing // -void HU_drawPing(INT32 x, INT32 y, UINT32 ping, INT32 flags) +void HU_drawPing(INT32 x, INT32 y, UINT32 lag, INT32 flags) { - INT32 gfxnum; // gfx to draw - UINT8 const *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_RASPBERRY, GTC_CACHE); + UINT8 *colormap = NULL; + INT32 measureid = cv_pingmeasurement.value ? 1 : 0; + INT32 gfxnum; // gfx to draw - gfxnum = Ping_gfx_num(ping); + gfxnum = Ping_gfx_num(lag); - V_DrawScaledPatch(x, y, flags, pinggfx[gfxnum]); - if (servermaxping && ping > servermaxping && hu_tick < 4) // flash ping red if too high - V_DrawPingNum(x, y+9, flags, ping, colormap); - else - V_DrawPingNum(x, y+9, flags, ping, NULL); + if (measureid == 1) + V_DrawScaledPatch(x+11 - pingmeasure[measureid]->width, y+9, flags, pingmeasure[measureid]); + V_DrawScaledPatch(x+2, y, flags, pinggfx[gfxnum]); + + if (servermaxping && lag > servermaxping && hu_tick < 4) + { + // flash ping red if too high + colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_RASPBERRY, GTC_CACHE); + } + + if (cv_pingmeasurement.value) + { + lag = (INT32)(lag * (1000.00f / TICRATE)); + } + + x = V_DrawPingNum(x + (measureid == 1 ? 11 - pingmeasure[measureid]->width : 10), y+9, flags, lag, colormap); + + if (measureid == 0) + V_DrawScaledPatch(x+1 - pingmeasure[measureid]->width, y+9, flags, pingmeasure[measureid]); } void -HU_drawMiniPing (INT32 x, INT32 y, UINT32 ping, INT32 flags) +HU_drawMiniPing (INT32 x, INT32 y, UINT32 lag, INT32 flags) { patch_t *patch; INT32 w = BASEVIDWIDTH; @@ -2316,7 +2389,7 @@ HU_drawMiniPing (INT32 x, INT32 y, UINT32 ping, INT32 flags) w /= 2; } - patch = mping[Ping_gfx_num(ping)]; + patch = mping[Ping_gfx_num(lag)]; if (( flags & V_SNAPTORIGHT )) x += ( w - SHORT (patch->width) ); diff --git a/src/i_net.h b/src/i_net.h index 8caa0edcc..8ad08e08c 100644 --- a/src/i_net.h +++ b/src/i_net.h @@ -31,6 +31,8 @@ /// For use on the internet #define INETPACKETLENGTH 1024 +#define NO_BAN_TIME (time_t)(-1) + extern INT16 hardware_MAXPACKETLENGTH; extern INT32 net_bandwidth; // in byte/s @@ -150,7 +152,7 @@ extern void (*I_NetCloseSocket)(void); /** \brief send a hole punching request */ -extern void (*I_NetRequestHolePunch)(void); +extern void (*I_NetRequestHolePunch)(INT32 node); /** \brief register this machine on the hole punching server */ @@ -162,8 +164,20 @@ extern void (*I_ClearBans)(void); extern const char *(*I_GetNodeAddress) (INT32 node); extern const char *(*I_GetBanAddress) (size_t ban); extern const char *(*I_GetBanMask) (size_t ban); +extern const char *(*I_GetBanUsername) (size_t ban); +extern const char *(*I_GetBanReason) (size_t ban); +extern time_t (*I_GetUnbanTime) (size_t ban); extern boolean (*I_SetBanAddress) (const char *address,const char *mask); -extern boolean *bannednode; +extern boolean (*I_SetBanUsername) (const char *username); +extern boolean (*I_SetBanReason) (const char *reason); +extern boolean (*I_SetUnbanTime) (time_t timestamp); + +typedef struct +{ + size_t banid; + time_t timeleft; +} bannednode_t; +extern bannednode_t *bannednode; /// \brief Called by D_SRB2Main to be defined by extern network driver boolean I_InitNetwork(void); diff --git a/src/i_tcp.c b/src/i_tcp.c index fef81d05c..603a467e0 100644 --- a/src/i_tcp.c +++ b/src/i_tcp.c @@ -138,8 +138,6 @@ #endif // !NONET -#define MAXBANS 100 - #include "i_system.h" #include "i_time.h" #include "i_net.h" @@ -148,6 +146,7 @@ #include "i_tcp.h" #include "m_argv.h" #include "stun.h" +#include "z_zone.h" #include "doomstat.h" @@ -189,6 +188,16 @@ #if (defined (WATTCP) && !defined (__libsocket_socklen_t)) || defined (USE_WINSOCK1) typedef int socklen_t; #endif + + typedef struct + { + mysockaddr_t address; + UINT8 mask; + char *username; + char *reason; + time_t timestamp; + } banned_t; + static SOCKET_TYPE mysockets[MAXNETNODES+1] = {ERRSOCKET}; static size_t mysocketses = 0; static int myfamily[MAXNETNODES+1] = {0}; @@ -197,13 +206,14 @@ static mysockaddr_t broadcastaddress[MAXNETNODES+1]; static size_t broadcastaddresses = 0; static boolean nodeconnected[MAXNETNODES+1]; - static mysockaddr_t banned[MAXBANS]; - static UINT8 bannedmask[MAXBANS]; + static banned_t *banned; static const INT32 hole_punch_magic = MSBF_LONG (0x52eb11); #endif static size_t numbans = 0; -static boolean SOCK_bannednode[MAXNETNODES+1]; /// \note do we really need the +1? +static size_t banned_size = 0; + +static bannednode_t SOCK_bannednode[MAXNETNODES+1]; /// \note do we really need the +1? static boolean init_tcp_driver = false; static const char *serverport_name = DEFAULTPORT; @@ -431,7 +441,7 @@ static const char *SOCK_GetBanAddress(size_t ban) #ifdef NONET return NULL; #else - return SOCK_AddrToStr(&banned[ban]); + return SOCK_AddrToStr(&banned[ban].address); #endif } @@ -443,12 +453,48 @@ static const char *SOCK_GetBanMask(size_t ban) static char s[16]; //255.255.255.255 netmask? no, just CDIR for only if (ban >= numbans) return NULL; - if (sprintf(s,"%d",bannedmask[ban]) > 0) + if (sprintf(s,"%d",banned[ban].mask) > 0) return s; #endif return NULL; } +static const char *SOCK_GetBanUsername(size_t ban) +{ +#ifdef NONET + (void)ban; + return NULL; +#else + if (ban >= numbans) + return NULL; + return banned[ban].username; +#endif +} + +static const char *SOCK_GetBanReason(size_t ban) +{ +#ifdef NONET + (void)ban; + return NULL; +#else + if (ban >= numbans) + return NULL; + return banned[ban].reason; +#endif +} + +static time_t SOCK_GetUnbanTime(size_t ban) +{ +#ifdef NONET + (void)ban; + return NO_BAN_TIME; +#else + if (ban >= numbans) + return NO_BAN_TIME; + return banned[ban].timestamp; +#endif +} + #ifndef NONET static boolean SOCK_cmpaddr(mysockaddr_t *a, mysockaddr_t *b, UINT8 mask) { @@ -622,6 +668,8 @@ static boolean SOCK_Get(void) j = getfreenode(); if (j > 0) { + const time_t curTime = time(NULL); + M_Memcpy(&clientaddress[j], &fromaddress, fromlen); nodesocket[j] = mysockets[n]; DEBFILE(va("New node detected: node:%d address:%s\n", j, @@ -632,15 +680,39 @@ static boolean SOCK_Get(void) // check if it's a banned dude so we can send a refusal later for (i = 0; i < numbans; i++) { - if (SOCK_cmpaddr(&fromaddress, &banned[i], bannedmask[i])) + if (SOCK_cmpaddr(&fromaddress, &banned[i].address, banned[i].mask)) { - SOCK_bannednode[j] = true; - DEBFILE("This dude has been banned\n"); - break; + if (banned[i].timestamp != NO_BAN_TIME) + { + if (curTime >= banned[i].timestamp) + { + SOCK_bannednode[j].timeleft = NO_BAN_TIME; + SOCK_bannednode[j].banid = SIZE_MAX; + DEBFILE("This dude was banned, but enough time has passed\n"); + break; + } + + SOCK_bannednode[j].timeleft = banned[i].timestamp - curTime; + SOCK_bannednode[j].banid = i; + DEBFILE("This dude has been temporarily banned\n"); + break; + } + else + { + SOCK_bannednode[j].timeleft = NO_BAN_TIME; + SOCK_bannednode[j].banid = i; + DEBFILE("This dude has been banned\n"); + break; + } } } + if (i == numbans) - SOCK_bannednode[j] = false; + { + SOCK_bannednode[j].timeleft = NO_BAN_TIME; + SOCK_bannednode[j].banid = SIZE_MAX; + } + return true; } else @@ -1382,9 +1454,9 @@ static void rendezvous(int size) free(addrs); } -static void SOCK_RequestHolePunch(void) +static void SOCK_RequestHolePunch(INT32 node) { - mysockaddr_t * addr = &clientaddress[doomcom->remotenode]; + mysockaddr_t * addr = &clientaddress[node]; holepunchpacket->addr = addr->ip4.sin_addr.s_addr; holepunchpacket->port = addr->ip4.sin_port; @@ -1435,30 +1507,116 @@ static boolean SOCK_OpenSocket(void) #endif } +static void AddBannedIndex(void) +{ + if (numbans >= banned_size) + { + if (banned_size == 0) + { + banned_size = 8; + } + else + { + banned_size *= 2; + } + + banned = Z_ReallocAlign( + (void*) banned, + sizeof(banned_t) * banned_size, + PU_STATIC, + NULL, + sizeof(banned_t) * 8 + ); + } + + numbans++; +} + static boolean SOCK_Ban(INT32 node) { + INT32 ban; + if (node > MAXNETNODES) return false; + #ifdef NONET + (void)ban; return false; #else - if (numbans == MAXBANS) - return false; - M_Memcpy(&banned[numbans], &clientaddress[node], sizeof (mysockaddr_t)); - if (banned[numbans].any.sa_family == AF_INET) + ban = numbans; + AddBannedIndex(); + + M_Memcpy(&banned[ban].address, &clientaddress[node], sizeof (mysockaddr_t)); + + if (banned[ban].address.any.sa_family == AF_INET) { - banned[numbans].ip4.sin_port = 0; - bannedmask[numbans] = 32; + banned[ban].address.ip4.sin_port = 0; + banned[ban].mask = 32; } #ifdef HAVE_IPV6 - else if (banned[numbans].any.sa_family == AF_INET6) + else if (banned[ban].address.any.sa_family == AF_INET6) { - banned[numbans].ip6.sin6_port = 0; - bannedmask[numbans] = 128; + banned[ban].address.ip6.sin6_port = 0; + banned[ban].mask = 128; } #endif - numbans++; + + return true; +#endif +} + +static boolean SOCK_SetBanUsername(const char *username) +{ +#ifdef NONET + (void)username; + return false; +#else + if (username == NULL || strlen(username) == 0) + { + username = "Direct IP ban"; + } + + if (banned[numbans - 1].username) + { + Z_Free(banned[numbans - 1].username); + banned[numbans - 1].username = NULL; + } + + banned[numbans - 1].username = Z_StrDup(username); + return true; +#endif +} + +static boolean SOCK_SetBanReason(const char *reason) +{ +#ifdef NONET + (void)reason; + return false; +#else + if (reason == NULL || strlen(reason) == 0) + { + reason = "No reason given"; + } + + if (banned[numbans - 1].reason) + { + Z_Free(banned[numbans - 1].reason); + banned[numbans - 1].reason = NULL; + } + + banned[numbans - 1].reason = Z_StrDup(reason); + return true; +#endif +} + +static boolean SOCK_SetUnbanTime(time_t timestamp) +{ +#ifdef NONET + (void)reason; + return false; +#else + banned[numbans - 1].timestamp = timestamp; return true; #endif } @@ -1473,7 +1631,7 @@ static boolean SOCK_SetBanAddress(const char *address, const char *mask) struct my_addrinfo *ai, *runp, hints; int gaie; - if (numbans == MAXBANS || !address) + if (!address) return false; memset(&hints, 0x00, sizeof(hints)); @@ -1488,26 +1646,42 @@ static boolean SOCK_SetBanAddress(const char *address, const char *mask) runp = ai; - while(runp != NULL && numbans != MAXBANS) + while (runp != NULL) { - memcpy(&banned[numbans], runp->ai_addr, runp->ai_addrlen); + INT32 ban; + UINT8 numericalmask; + + ban = numbans; + AddBannedIndex(); + + memcpy(&banned[ban].address, runp->ai_addr, runp->ai_addrlen); + +#ifdef HAVE_IPV6 + if (runp->ai_family == AF_INET6) + banned[ban].mask = 128; + else +#endif + banned[ban].mask = 32; if (mask) - bannedmask[numbans] = (UINT8)atoi(mask); -#ifdef HAVE_IPV6 - else if (runp->ai_family == AF_INET6) - bannedmask[numbans] = 128; -#endif + { + numericalmask = (UINT8)atoi(mask); + } else - bannedmask[numbans] = 32; + { + numericalmask = 0; + } + + if (numericalmask > 0 && numericalmask < banned[ban].mask) + { + banned[ban].mask = numericalmask; + } + + // Set defaults, in case anything funny happens. + SOCK_SetBanUsername(NULL); + SOCK_SetBanReason(NULL); + SOCK_SetUnbanTime(NO_BAN_TIME); - if (bannedmask[numbans] > 32 && runp->ai_family == AF_INET) - bannedmask[numbans] = 32; -#ifdef HAVE_IPV6 - else if (bannedmask[numbans] > 128 && runp->ai_family == AF_INET6) - bannedmask[numbans] = 128; -#endif - numbans++; runp = runp->ai_next; } @@ -1520,6 +1694,9 @@ static boolean SOCK_SetBanAddress(const char *address, const char *mask) static void SOCK_ClearBans(void) { numbans = 0; + banned_size = 0; + Z_Free(banned); + banned = NULL; } boolean I_InitTcpNetwork(void) @@ -1614,7 +1791,13 @@ boolean I_InitTcpNetwork(void) I_GetNodeAddress = SOCK_GetNodeAddress; I_GetBanAddress = SOCK_GetBanAddress; I_GetBanMask = SOCK_GetBanMask; + I_GetBanUsername = SOCK_GetBanUsername; + I_GetBanReason = SOCK_GetBanReason; + I_GetUnbanTime = SOCK_GetUnbanTime; I_SetBanAddress = SOCK_SetBanAddress; + I_SetBanUsername = SOCK_SetBanUsername; + I_SetBanReason = SOCK_SetBanReason; + I_SetUnbanTime = SOCK_SetUnbanTime; bannednode = SOCK_bannednode; return ret; diff --git a/src/k_kart.c b/src/k_kart.c index 61a32bd76..3b9de17da 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -6667,7 +6667,7 @@ static void K_MoveHeldObjects(player_t *player) targz += (player->mo->height/2 - 32*player->mo->scale)*6; } - if (cur->tracer) + if (cur->tracer && !P_MobjWasRemoved(cur->tracer)) { fixed_t diffx, diffy, diffz; diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index d5cc7e2ab..d699db2b5 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -57,16 +57,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/lua_hudlib_drawlist.c b/src/lua_hudlib_drawlist.c index 9b318ee91..ce5b9d49f 100644 --- a/src/lua_hudlib_drawlist.c +++ b/src/lua_hudlib_drawlist.c @@ -54,7 +54,7 @@ typedef struct drawitem_s { fixed_t sy; INT32 num; INT32 digits; - const char *str; + size_t stroffset; // offset into strbuf to get str UINT16 color; UINT8 strength; INT32 align; @@ -129,6 +129,10 @@ void LUA_HUD_DestroyDrawList(huddrawlist_h list) { Z_Free(list->items); } + if (list->strbuf) + { + Z_Free(list->strbuf); + } Z_Free(list); } @@ -156,7 +160,7 @@ static size_t AllocateDrawItem(huddrawlist_h list) // copy string to list's internal string buffer // lua can deallocate the string before we get to use it, so it's important to // keep our own copy -static const char *CopyString(huddrawlist_h list, const char* str) +static size_t CopyString(huddrawlist_h list, const char* str) { size_t lenstr; @@ -168,10 +172,13 @@ static const char *CopyString(huddrawlist_h list, const char* str) else list->strbuf_capacity *= 2; list->strbuf = (char*) Z_ReallocAlign(list->strbuf, sizeof(char) * list->strbuf_capacity, PU_STATIC, NULL, 8); } - const char *result = (const char *) &list->strbuf[list->strbuf_len]; - strncpy(&list->strbuf[list->strbuf_len], str, lenstr + 1); - list->strbuf_len += lenstr + 1; - return result; + + { + size_t old_len = list->strbuf_len; + strncpy(&list->strbuf[old_len], str, lenstr + 1); + list->strbuf_len += lenstr + 1; + return old_len; + } } void LUA_HUD_AddDraw( @@ -325,7 +332,7 @@ void LUA_HUD_AddDrawString( item->type = DI_DrawString; item->x = x; item->y = y; - item->str = CopyString(list, str); + item->stroffset = CopyString(list, str); item->flags = flags; item->align = align; } @@ -360,7 +367,7 @@ void LUA_HUD_AddDrawTitleCardString( item->x = x; item->y = y; item->flags = flags; - item->str = CopyString(list, str); + item->stroffset = CopyString(list, str); item->bossmode = bossmode; item->timer = timer; item->threshold = threshold; @@ -379,8 +386,8 @@ void LUA_HUD_AddDrawKartString( item->type = DI_DrawKartString; item->x = x; item->y = y; + item->stroffset = CopyString(list, str); item->flags = flags; - item->str = CopyString(list, str); } void LUA_HUD_DrawList(huddrawlist_h list) @@ -394,6 +401,7 @@ void LUA_HUD_DrawList(huddrawlist_h list) for (i = 0; i < list->items_len; i++) { drawitem_t *item = &list->items[i]; + const char *itemstr = &list->strbuf[item->stroffset]; switch (item->type) { @@ -423,33 +431,33 @@ void LUA_HUD_DrawList(huddrawlist_h list) { // hu_font case align_left: - V_DrawString(item->x, item->y, item->flags, item->str); + V_DrawString(item->x, item->y, item->flags, itemstr); break; case align_center: - V_DrawCenteredString(item->x, item->y, item->flags, item->str); + V_DrawCenteredString(item->x, item->y, item->flags, itemstr); break; case align_right: - V_DrawRightAlignedString(item->x, item->y, item->flags, item->str); + V_DrawRightAlignedString(item->x, item->y, item->flags, itemstr); break; // hu_font, 0.5x scale case align_small: - V_DrawSmallString(item->x, item->y, item->flags, item->str); + V_DrawSmallString(item->x, item->y, item->flags, itemstr); break; case align_smallcenter: - V_DrawCenteredSmallString(item->x, item->y, item->flags, item->str); + V_DrawCenteredSmallString(item->x, item->y, item->flags, itemstr); break; case align_smallright: - V_DrawRightAlignedSmallString(item->x, item->y, item->flags, item->str); + V_DrawRightAlignedSmallString(item->x, item->y, item->flags, itemstr); break; // tny_font case align_thin: - V_DrawThinString(item->x, item->y, item->flags, item->str); + V_DrawThinString(item->x, item->y, item->flags, itemstr); break; case align_thincenter: - V_DrawCenteredThinString(item->x, item->y, item->flags, item->str); + V_DrawCenteredThinString(item->x, item->y, item->flags, itemstr); break; case align_thinright: - V_DrawRightAlignedThinString(item->x, item->y, item->flags, item->str); + V_DrawRightAlignedThinString(item->x, item->y, item->flags, itemstr); break; } break; @@ -457,10 +465,10 @@ void LUA_HUD_DrawList(huddrawlist_h list) V_DrawFadeScreen(item->color, item->strength); break; case DI_DrawTitleCardString: - V_DrawTitleCardString(item->x, item->y, item->str, item->flags, item->bossmode, item->timer, item->threshold); + V_DrawTitleCardString(item->x, item->y, itemstr, item->flags, item->bossmode, item->timer, item->threshold); break; case DI_DrawKartString: - V_DrawKartString(item->x, item->y, item->flags, item->str); + V_DrawKartString(item->x, item->y, item->flags, itemstr); break; default: I_Error("can't draw draw list item: invalid draw list item type"); diff --git a/src/p_mobj.c b/src/p_mobj.c index 4e58e53d6..a86d1af4e 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -10350,7 +10350,7 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) mobj_t *side = P_SpawnMobj(mobj->x + FINECOSINE((ang>>ANGLETOFINESHIFT) & FINEMASK), mobj->y + FINESINE((ang>>ANGLETOFINESHIFT) & FINEMASK), mobj->z, MT_DAYTONAPINETREE_SIDE); P_InitAngle(side, ang); - side->target = mobj; + P_SetTarget(&side->target, mobj); side->threshold = i; } break; @@ -10767,6 +10767,7 @@ void P_RemoveSavegameMobj(mobj_t *mobj) // free block P_RemoveThinker((thinker_t *)mobj); + R_RemoveMobjInterpolator(mobj); } static CV_PossibleValue_t respawnitemtime_cons_t[] = {{1, "MIN"}, {300, "MAX"}, {0, NULL}}; diff --git a/src/p_saveg.c b/src/p_saveg.c index 72101fc9c..f35ddc305 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -4193,21 +4193,21 @@ static void P_RelinkPointers(void) { temp = (UINT32)(size_t)mobj->hnext; mobj->hnext = NULL; - if (!(mobj->hnext = P_FindNewPosition(temp))) + if (!P_SetTarget(&mobj->hnext, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "hnext not found on %d\n", mobj->type); } if (mobj->hprev) { temp = (UINT32)(size_t)mobj->hprev; mobj->hprev = NULL; - if (!(mobj->hprev = P_FindNewPosition(temp))) + if (!P_SetTarget(&mobj->hprev, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "hprev not found on %d\n", mobj->type); } if (mobj->itnext) { temp = (UINT32)(size_t)mobj->itnext; mobj->itnext = NULL; - if (!(mobj->itnext = P_FindNewPosition(temp))) + if (!P_SetTarget(&mobj->itnext, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "itnext not found on %d\n", mobj->type); } if (mobj->terrain) @@ -4423,7 +4423,7 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride) static void P_NetArchiveMisc(boolean resending) { - INT32 i; + size_t i; WRITEUINT32(save_p, ARCHIVEBLOCK_MISC); @@ -4552,11 +4552,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"); @@ -4700,6 +4712,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; } diff --git a/src/p_tick.c b/src/p_tick.c index 5e60ba711..58b11406f 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -721,7 +721,7 @@ void P_Ticker(boolean run) G_WriteAllGhostTics(); if (cv_recordmultiplayerdemos.value && (demo.savemode == DSM_NOTSAVING || demo.savemode == DSM_WILLAUTOSAVE)) - if (demo.savebutton && demo.savebutton + 3*TICRATE < leveltime && G_PlayerInputDown(0, gc_y, 0)) + if (demo.savebutton && demo.savebutton + 3*TICRATE < leveltime && !menuactive && (G_PlayerInputDown(0, gc_b, 0) || G_PlayerInputDown(0, gc_x, 0))) demo.savemode = DSM_TITLEENTRY; } else if (demo.playback) // Use Ghost data for consistency checks. diff --git a/src/r_bbox.c b/src/r_bbox.c index 969aeacff..7c8887398 100644 --- a/src/r_bbox.c +++ b/src/r_bbox.c @@ -169,12 +169,11 @@ draw_bbox_row } } -static UINT8 -get_bbox_color (vissprite_t *vis) +UINT8 R_GetBoundingBoxColor(mobj_t *thing) { - UINT32 flags = vis->mobjflags; + UINT32 flags = thing->flags; - if (vis->mobj->player) + if (thing->player) return 255; // 0FF if (flags & (MF_NOCLIPTHING)) @@ -205,7 +204,7 @@ void R_DrawThingBoundingBox(vissprite_t *vis) struct bbox_config bb = { .height = vis->thingheight, .tz = vis->texturemid, - .color = get_bbox_color(vis), + .color = R_GetBoundingBoxColor(vis->mobj), }; // 1--3 diff --git a/src/r_things.c b/src/r_things.c index d7d2a12b5..c0e727fd9 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -3425,16 +3425,6 @@ boolean R_ThingWithinDist (mobj_t *thing, fixed_t limit_dist) return true; } -// For OpenGL, TODO: REMOVE!! -boolean R_ThingVisibleWithinDist (mobj_t *thing, - fixed_t limit_dist) -{ - if (! R_ThingVisible(thing)) - return false; - - return R_ThingWithinDist(thing, limit_dist); -} - /* Check if precipitation may be drawn from our current view. */ boolean R_PrecipThingVisible (precipmobj_t *precipthing, fixed_t limit_dist) diff --git a/src/r_things.h b/src/r_things.h index 085d15512..b89645fff 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -66,6 +66,7 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel); void R_InitSprites(void); void R_ClearSprites(void); +UINT8 R_GetBoundingBoxColor(mobj_t *thing); boolean R_ThingBoundingBoxVisible(mobj_t *thing); boolean R_ThingVisible (mobj_t *thing); @@ -73,9 +74,6 @@ boolean R_ThingVisible (mobj_t *thing); boolean R_ThingWithinDist (mobj_t *thing, fixed_t draw_dist); -boolean R_ThingVisibleWithinDist (mobj_t *thing, - fixed_t draw_dist); - boolean R_PrecipThingVisible (precipmobj_t *precipthing, fixed_t precip_draw_dist); diff --git a/src/s_sound.c b/src/s_sound.c index 4b193fa8c..12e35e5c5 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -31,6 +31,7 @@ #include "m_cond.h" // for conditionsets #include "lua_hook.h" // MusicChange hook #include "k_boss.h" // bossinfo +#include "byteptr.h" #ifdef HW3SOUND // 3D Sound Interface @@ -43,6 +44,8 @@ CV_PossibleValue_t soundvolume_cons_t[] = {{0, "MIN"}, {MAX_VOLUME, "MAX"}, {0, static void SetChannelsNum(void); static void Command_Tunes_f(void); static void Command_RestartAudio_f(void); +static void Command_PlaySound(void); +static void Got_PlaySound(UINT8 **p, INT32 playernum); // Sound system toggles static void GameSounds_OnChange(void); @@ -268,6 +271,8 @@ void S_RegisterSoundStuff(void) COM_AddCommand("tunes", Command_Tunes_f); COM_AddCommand("restartaudio", Command_RestartAudio_f); + COM_AddCommand("playsound", Command_PlaySound); + RegisterNetXCmd(XD_PLAYSOUND, Got_PlaySound); } static void SetChannelsNum(void) @@ -2478,6 +2483,71 @@ static void Command_RestartAudio_f(void) S_ChangeMusicInternal("titles", looptitle); } +static void Command_PlaySound(void) +{ + const char *sound; + const size_t argc = COM_Argc(); + sfxenum_t sfx = NUMSFX; + UINT8 buf[4]; + UINT8 *buf_p = buf; + + if (argc < 2) + { + CONS_Printf("playsound : Plays a sound effect for the entire server.\n"); + return; + } + + if (client && !IsPlayerAdmin(consoleplayer)) + { + CONS_Printf("This can only be used by the server host.\n"); + return; + } + + sound = COM_Argv(1); + if (*sound >= '0' && *sound <= '9') + { + sfx = atoi(sound); + } + else + { + for (sfx = 0; sfx < sfxfree; sfx++) + { + if (S_sfx[sfx].name && fasticmp(sound, S_sfx[sfx].name)) + break; + } + } + + if (sfx < 0 || sfx >= NUMSFX) + { + CONS_Printf("Could not find sound effect named \"sfx_%s\".\n", sound); + return; + } + + WRITEINT32(buf_p, sfx); + SendNetXCmd(XD_PLAYSOUND, buf, buf_p - buf); +} + +static void Got_PlaySound(UINT8 **cp, INT32 playernum) +{ + INT32 sound_id = READINT32(*cp); + + if (playernum != serverplayer && !IsPlayerAdmin(playernum)) // hacked client, or disasterous bug + { + CONS_Alert(CONS_WARNING, M_GetText("Illegal playsound received from %s (serverplayer is %s)\n"), player_names[playernum], player_names[serverplayer]); + if (server) + SendKick(playernum, KICK_MSG_CON_FAIL); + return; + } + + if (sound_id < 0 || sound_id >= NUMSFX) + { + // bad sound effect, ignore + return; + } + + S_StartSound(NULL, sound_id); +} + void GameSounds_OnChange(void) { if (M_CheckParm("-nosound") || M_CheckParm("-noaudio")) diff --git a/src/sounds.c b/src/sounds.c index 9240577fa..351e1553f 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -1114,6 +1114,9 @@ sfxinfo_t S_sfx[NUMSFX] = {"ffbonc", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + // Shout message sound effect + {"sysmsg", false, 60, 0, -1, NULL, 0, -1, -1, LUMPERROR, "Server notification"}, + // SRB2Kart - Engine sounds // Engine class A {"krta00", false, 48, 65, -1, NULL, 0, -1, -1, LUMPERROR, ""}, diff --git a/src/sounds.h b/src/sounds.h index c5eb638e0..e7248a369 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -1179,6 +1179,9 @@ typedef enum // Fast fall bounce sfx_ffbonc, + // Shout message sound effect + sfx_sysmsg, + // Next up: UNIQUE ENGINE SOUNDS! Hoooooo boy... // Engine class A - Low Speed, Low Weight sfx_krta00, diff --git a/src/st_stuff.c b/src/st_stuff.c index 08e1305c1..af18b1441 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -1218,7 +1218,7 @@ void ST_Drawer(void) switch (demo.savemode) { case DSM_NOTSAVING: - V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|((gametyperules & GTR_BUMPERS) ? V_REDMAP : V_SKYMAP), "Look Backward: Save replay"); + V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|((gametyperules & GTR_BUMPERS) ? V_REDMAP : V_SKYMAP), "(B) or (X): Save replay"); break; case DSM_WILLAUTOSAVE: diff --git a/src/v_video.c b/src/v_video.c index 7c957e078..193339574 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -2560,6 +2560,30 @@ void V_DrawRightAlignedThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, con V_DrawThinStringAtFixed(x, y, option, string); } +// Draws a number using the PING font thingy. +// TODO: Merge number drawing functions into one with "font name" selection. + +INT32 V_DrawPingNum(INT32 x, INT32 y, INT32 flags, INT32 num, const UINT8 *colormap) +{ + INT32 w = SHORT(fontv[PINGNUM_FONT].font[0]->width); // this SHOULD always be 5 but I guess custom graphics exist. + + if (flags & V_NOSCALESTART) + w *= vid.dupx; + + if (num < 0) + num = -num; + + // draw the number + do + { + x -= (w-1); // Oni wanted their outline to intersect. + V_DrawFixedPatch(x<width); // this SHOULD always be 5 but I guess custom graphics exist. - - if (flags & V_NOSCALESTART) - w *= vid.dupx; - - if (num < 0) - num = -num; - - // draw the number - do - { - x -= (w-1); // Oni wanted their outline to intersect. - V_DrawFixedPatch(x<