diff --git a/src/Sourcefile b/src/Sourcefile index 986b75f74..ea5f2d0d5 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -65,6 +65,7 @@ r_skins.c r_sky.c r_splats.c r_things.c +r_bbox.c r_textures.c r_patch.c r_patchrotation.c 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 368b94684..b8e4d7276 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); @@ -890,6 +892,9 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) UINT8 *p; size_t mirror_length; const char *httpurl = cv_httpsource.string; + UINT8 prefgametype = (cv_kartgametypepreference.value == -1) + ? gametype + : cv_kartgametypepreference.value; netbuffer->packettype = PT_SERVERINFO; netbuffer->u.serverinfo._255 = 255; @@ -914,7 +919,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) else netbuffer->u.serverinfo.refusereason = 0; - strncpy(netbuffer->u.serverinfo.gametypename, Gametype_Names[gametype], + strncpy(netbuffer->u.serverinfo.gametypename, Gametype_Names[prefgametype], sizeof netbuffer->u.serverinfo.gametypename); netbuffer->u.serverinfo.modifiedgame = (UINT8)modifiedgame; netbuffer->u.serverinfo.cheatsenabled = CV_CheatsEnabled(); @@ -1348,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(); @@ -2028,7 +2033,10 @@ static void CL_ConnectToServer(void) wipegamestate = GS_WAITINGPLAYERS; ClearAdminPlayers(); + Schedule_Clear(); + Automate_Clear(); K_ClearClientPowerLevels(); + pnumnodes = 1; oldtic = 0; #ifndef NONET @@ -2092,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; } @@ -2154,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) @@ -2235,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) @@ -2397,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 } // @@ -2426,14 +2554,14 @@ void CL_RemovePlayer(INT32 playernum, kickreason_t reason) K_CalculateBattleWanted(); - LUAh_PlayerQuit(&players[playernum], reason); // Lua hook for player quitting + LUA_HookPlayerQuit(&players[playernum], reason); // Lua hook for player quitting // don't look through someone's view who isn't there if (playernum == displayplayers[0] && !demo.playback) { // Call ViewpointSwitch hooks here. // The viewpoint was forcibly changed. - LUAh_ViewpointSwitch(&players[consoleplayer], &players[consoleplayer], true); + LUA_HookViewpointSwitch(&players[consoleplayer], &players[consoleplayer], true); displayplayers[0] = consoleplayer; } @@ -2454,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(); @@ -2602,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; } @@ -2617,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 @@ -2771,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); @@ -2829,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) @@ -2855,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: @@ -2909,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; @@ -2937,23 +3102,25 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) if (playernode[pnum] == playernode[consoleplayer]) { - LUAh_GameQuit(false); #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); } @@ -3109,17 +3276,17 @@ consvar_t cv_discordinvites = CVAR_INIT ("discordinvites", "Everyone", CV_SAVE|C static CV_PossibleValue_t resynchattempts_cons_t[] = {{1, "MIN"}, {20, "MAX"}, {0, "No"}, {0, NULL}}; -consvar_t cv_resynchattempts = CVAR_INIT ("resynchattempts", "10", CV_SAVE|CV_NETVAR, resynchattempts_cons_t, NULL); +consvar_t cv_resynchattempts = CVAR_INIT ("resynchattempts", "2", CV_SAVE|CV_NETVAR, resynchattempts_cons_t, NULL); consvar_t cv_blamecfail = CVAR_INIT ("blamecfail", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL); // max file size to send to a player (in kilobytes) static CV_PossibleValue_t maxsend_cons_t[] = {{0, "MIN"}, {51200, "MAX"}, {0, NULL}}; -consvar_t cv_maxsend = CVAR_INIT ("maxsend", "4096", CV_SAVE|CV_NETVAR, maxsend_cons_t, NULL); +consvar_t cv_maxsend = CVAR_INIT ("maxsend", "51200", CV_SAVE|CV_NETVAR, maxsend_cons_t, NULL); consvar_t cv_noticedownload = CVAR_INIT ("noticedownload", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL); // Speed of file downloading (in packets per tic) -static CV_PossibleValue_t downloadspeed_cons_t[] = {{0, "MIN"}, {32, "MAX"}, {0, NULL}}; -consvar_t cv_downloadspeed = CVAR_INIT ("downloadspeed", "16", CV_SAVE|CV_NETVAR, downloadspeed_cons_t, NULL); +static CV_PossibleValue_t downloadspeed_cons_t[] = {{1, "MIN"}, {300, "MAX"}, {0, NULL}}; +consvar_t cv_downloadspeed = CVAR_INIT ("downloadspeed", "32", CV_SAVE|CV_NETVAR, downloadspeed_cons_t, NULL); static void Got_AddPlayer(UINT8 **p, INT32 playernum); static void Got_RemovePlayer(UINT8 **p, INT32 playernum); @@ -3181,7 +3348,7 @@ void D_ClientServerInit(void) #ifdef DUMPCONSISTENCY CV_RegisterVar(&cv_dumpconsistency); #endif - Ban_Load_File(false); + D_LoadBan(false); #endif gametic = 0; @@ -3211,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) @@ -3233,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); @@ -3325,6 +3499,8 @@ void D_QuitNetGame(void) D_CloseConnection(); ClearAdminPlayers(); + Schedule_Clear(); + Automate_Clear(); K_ClearClientPowerLevels(); DEBFILE("===========================================================================\n" @@ -3455,7 +3631,7 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) if (server && multiplayer && motd[0] != '\0') COM_BufAddText(va("sayto %d %s\n", newplayernum, motd)); - LUAh_PlayerJoin(newplayernum); + LUA_HookInt(newplayernum, HOOK(PlayerJoin)); #ifdef HAVE_DISCORDRPC DRPC_UpdatePresence(); @@ -3537,7 +3713,7 @@ static void Got_AddBot(UINT8 **p, INT32 playernum) HU_AddChatText(va("\x82*Bot %d has been added to the game", newplayernum+1), false); } - LUAh_PlayerJoin(newplayernum); + LUA_HookInt(newplayernum, HOOK(PlayerJoin)); } static boolean SV_AddWaitingPlayers(SINT8 node, const char *name, const char *name2, const char *name3, const char *name4) @@ -3788,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 @@ -3883,7 +4114,7 @@ static void HandleConnect(SINT8 node) static void HandleShutdown(SINT8 node) { (void)node; - LUAh_GameQuit(false); + LUA_HookBool(false, HOOK(GameQuit)); D_QuitNetGame(); CL_Reset(); D_StartTitle(); @@ -3898,7 +4129,7 @@ static void HandleShutdown(SINT8 node) static void HandleTimeout(SINT8 node) { (void)node; - LUAh_GameQuit(false); + LUA_HookBool(false, HOOK(GameQuit)); D_QuitNetGame(); CL_Reset(); D_StartTitle(); @@ -4087,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. @@ -4704,7 +4944,10 @@ static void GetPackets(void) if (netbuffer->packettype == PT_CLIENTJOIN && server) { - HandleConnect(node); + if (!levelloading) // Otherwise just ignore + { + HandleConnect(node); + } continue; } if (node == servernode && client && cl_mode != CL_SEARCHING) @@ -5291,7 +5534,18 @@ boolean TryRunTics(tic_t realtics) ps_tictime = I_GetPreciseTime(); G_Ticker((gametic % NEWTICRATERATIO) == 0); + if (Playing() && netgame && (gametic % TICRATE == 0)) + { + Schedule_Run(); + + if (cv_livestudioaudience.value) + { + LiveStudioAudience(); + } + } + ExtraDataTicker(); + gametic++; consistancy[gametic%BACKUPTICS] = Consistancy(); @@ -5421,7 +5675,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; @@ -5429,7 +5683,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]); } } } @@ -5447,7 +5701,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 ab56fc296..0644b680e 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 // ========================================================================= @@ -406,6 +420,8 @@ static CV_PossibleValue_t kartencore_cons_t[] = {{-1, "Auto"}, {0, "Off"}, {1, " consvar_t cv_kartencore = CVAR_INIT ("kartencore", "Auto", CV_NETVAR|CV_CALL|CV_NOINIT, kartencore_cons_t, KartEncore_OnChange); static CV_PossibleValue_t kartvoterulechanges_cons_t[] = {{0, "Never"}, {1, "Sometimes"}, {2, "Frequent"}, {3, "Always"}, {0, NULL}}; consvar_t cv_kartvoterulechanges = CVAR_INIT ("kartvoterulechanges", "Frequent", CV_NETVAR, kartvoterulechanges_cons_t, NULL); +static CV_PossibleValue_t kartgametypepreference_cons_t[] = {{-1, "None"}, {GT_RACE, "Race"}, {GT_BATTLE, "Battle"}, {0, NULL}}; +consvar_t cv_kartgametypepreference = CVAR_INIT ("kartgametypepreference", "None", CV_NETVAR, kartgametypepreference_cons_t, NULL); static CV_PossibleValue_t kartspeedometer_cons_t[] = {{0, "Off"}, {1, "Percentage"}, {2, "Kilometers"}, {3, "Miles"}, {4, "Fracunits"}, {0, NULL}}; consvar_t cv_kartspeedometer = CVAR_INIT ("kartdisplayspeed", "Percentage", CV_SAVE, kartspeedometer_cons_t, NULL); // use tics in display static CV_PossibleValue_t kartvoices_cons_t[] = {{0, "Never"}, {1, "Tasteful"}, {2, "Meme"}, {0, NULL}}; @@ -503,20 +519,23 @@ consvar_t cv_allowexitlevel = CVAR_INIT ("allowexitlevel", "No", CV_NETVAR, CV_Y consvar_t cv_netstat = CVAR_INIT ("netstat", "Off", 0, CV_OnOff, NULL); // show bandwidth statistics static CV_PossibleValue_t nettimeout_cons_t[] = {{TICRATE/7, "MIN"}, {60*TICRATE, "MAX"}, {0, NULL}}; -consvar_t cv_nettimeout = CVAR_INIT ("nettimeout", "105", CV_CALL|CV_SAVE, nettimeout_cons_t, NetTimeout_OnChange); +consvar_t cv_nettimeout = CVAR_INIT ("nettimeout", "210", 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_jointimeout = CVAR_INIT ("jointimeout", "210", CV_CALL|CV_SAVE, nettimeout_cons_t, JoinTimeout_OnChange); +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 @@ -545,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]; @@ -560,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 +}; + +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 }; // ========================================================================= @@ -646,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 @@ -663,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); @@ -701,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(); @@ -760,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 @@ -961,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); @@ -1008,6 +1081,7 @@ void D_RegisterClientCommands(void) // screen.c CV_RegisterVar(&cv_fullscreen); CV_RegisterVar(&cv_renderview); + CV_RegisterVar(&cv_renderhitbox); CV_RegisterVar(&cv_vhseffect); CV_RegisterVar(&cv_shittyscreen); CV_RegisterVar(&cv_renderer); @@ -1076,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; @@ -1097,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. @@ -1188,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) ; @@ -2475,27 +2557,28 @@ void D_SetupVote(void) UINT8 buf[5*2]; // four UINT16 maps (at twice the width of a UINT8), and two gametypes UINT8 *p = buf; INT32 i; - UINT8 secondgt = G_SometimesGetDifferentGametype(); + UINT8 gt = (cv_kartgametypepreference.value == -1) ? gametype : cv_kartgametypepreference.value; + UINT8 secondgt = G_SometimesGetDifferentGametype(gt); INT16 votebuffer[4] = {-1,-1,-1,0}; - if ((cv_kartencore.value == 1) && (gametyperules & GTR_CIRCUIT)) - WRITEUINT8(p, (gametype|0x80)); + if ((cv_kartencore.value == 1) && (gametypedefaultrules[gt] & GTR_CIRCUIT)) + WRITEUINT8(p, (gt|VOTEMODIFIER_ENCORE)); else - WRITEUINT8(p, gametype); + WRITEUINT8(p, gt); WRITEUINT8(p, secondgt); - secondgt &= ~0x80; + secondgt &= ~VOTEMODIFIER_ENCORE; for (i = 0; i < 4; i++) { UINT16 m; if (i == 2) // sometimes a different gametype m = G_RandMap(G_TOLFlag(secondgt), prevmap, ((secondgt != gametype) ? 2 : 0), 0, true, votebuffer); - else if (i >= 3) // unknown-random and force-unknown MAP HELL - m = G_RandMap(G_TOLFlag(gametype), prevmap, 0, (i-2), (i < 4), votebuffer); + else if (i >= 3) // unknown-random and formerly force-unknown MAP HELL + m = G_RandMap(G_TOLFlag(gt), prevmap, 0, (i-2), (i < 4), votebuffer); else - m = G_RandMap(G_TOLFlag(gametype), prevmap, 0, 0, true, votebuffer); + m = G_RandMap(G_TOLFlag(gt), prevmap, 0, 0, true, votebuffer); if (i < 3) - votebuffer[i] = m; // min() is a dumb workaround for gcc 4.4 array-bounds error + votebuffer[i] = m; WRITEUINT16(p, m); } @@ -2951,10 +3034,6 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) demo.savemode = (cv_recordmultiplayerdemos.value == 2) ? DSM_WILLAUTOSAVE : DSM_NOTSAVING; demo.savebutton = 0; - // Sal: Is this needed? - // From experimenting with Lua scripts in vanilla I found a lot of annoying & potentially desync-y things with MapChange. - LUAh_MapChange(mapnumber); - G_InitNew(pencoremode, mapnumber, resetplayer, skipprecutscene, FLS); if (demo.playback && !demo.timing) precache = true; @@ -2972,6 +3051,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]; @@ -3438,7 +3578,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum) } // Don't switch team, just go away, please, go awaayyyy, aaauuauugghhhghgh - if (!LUAh_TeamSwitch(&players[playernum], NetPacket.packet.newteam, players[playernum].spectator, NetPacket.packet.autobalance, NetPacket.packet.scrambled)) + if (!LUA_HookTeamSwitch(&players[playernum], NetPacket.packet.newteam, players[playernum].spectator, NetPacket.packet.autobalance, NetPacket.packet.scrambled)) return; //Make sure that the right team number is sent. Keep in mind that normal clients cannot change to certain teams in certain gametypes. @@ -3534,7 +3674,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum) { if (localplayertable[i] == playernum) { - LUAh_ViewpointSwitch(players+playernum, players+playernum, true); + LUA_HookViewpointSwitch(players+playernum, players+playernum, true); displayplayers[i] = playernum; break; } @@ -3688,9 +3828,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) @@ -3807,6 +3945,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; @@ -4454,7 +4804,7 @@ static void Command_Playintro_f(void) */ FUNCNORETURN static ATTRNORETURN void Command_Quit_f(void) { - LUAh_GameQuit(true); + LUA_HookBool(true, HOOK(GameQuit)); I_Quit(); } @@ -4918,6 +5268,13 @@ static void Got_SetupVotecmd(UINT8 **cp, INT32 playernum) gt = (UINT8)READUINT8(*cp); secondgt = (UINT8)READUINT8(*cp); + // Strip illegal Encore flag. + if ((gt & VOTEMODIFIER_ENCORE) + && !(gametypedefaultrules[(gt & ~VOTEMODIFIER_ENCORE)] & GTR_CIRCUIT)) + { + gt &= ~VOTEMODIFIER_ENCORE; + } + for (i = 0; i < 4; i++) { tempvotelevels[i][0] = (UINT16)READUINT16(*cp); @@ -4931,6 +5288,19 @@ static void Got_SetupVotecmd(UINT8 **cp, INT32 playernum) return; } + // If third entry has an illelegal Encore flag... (illelegal!?) + if ((secondgt & VOTEMODIFIER_ENCORE) + && !(gametypedefaultrules[(secondgt & ~VOTEMODIFIER_ENCORE)] & GTR_CIRCUIT)) + { + secondgt &= ~VOTEMODIFIER_ENCORE; + // Apply it to the second entry instead, gametype permitting! + if (gametypedefaultrules[gt] & GTR_CIRCUIT) + { + tempvotelevels[1][1] |= VOTEMODIFIER_ENCORE; + } + } + + // Finally, set third entry's gametype/Encore status. tempvotelevels[2][1] = secondgt; memcpy(votelevels, tempvotelevels, sizeof(votelevels)); @@ -4989,6 +5359,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. @@ -5027,7 +5492,7 @@ void Command_ExitGame_f(void) { INT32 i; - LUAh_GameQuit(false); + LUA_HookBool(false, HOOK(GameQuit)); D_QuitNetGame(); CL_Reset(); @@ -5253,6 +5718,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 @@ -5871,6 +6465,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 aa4f1a80e..3a67fc458 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -90,6 +90,7 @@ extern consvar_t cv_kartfrantic; extern consvar_t cv_kartcomeback; extern consvar_t cv_kartencore; extern consvar_t cv_kartvoterulechanges; +extern consvar_t cv_kartgametypepreference; extern consvar_t cv_kartspeedometer; extern consvar_t cv_kartvoices; extern consvar_t cv_kartbot; @@ -117,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; @@ -127,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]; @@ -170,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; @@ -238,6 +248,37 @@ 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); + +extern UINT32 livestudioaudience_timer; +void LiveStudioAudience(void); + // used for the player setup menu UINT8 CanChangeSkin(INT32 playernum); diff --git a/src/d_netfil.c b/src/d_netfil.c index ca2ee3886..dcce1767c 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -1007,7 +1007,6 @@ static void SV_EndFileSend(INT32 node) filestosend--; } -#define PACKETPERTIC net_bandwidth/(TICRATE*software_MAXPACKETLENGTH) #define FILEFRAGMENTSIZE (software_MAXPACKETLENGTH - (FILETXHEADER + BASEPACKETSIZE)) /** Handles file transmission @@ -1040,14 +1039,7 @@ void FileSendTicker(void) if (!filestosend) // No file to send return; - if (cv_downloadspeed.value) // New behavior - packetsent = cv_downloadspeed.value; - else // Old behavior - { - packetsent = PACKETPERTIC; - if (!packetsent) - packetsent = 1; - } + packetsent = cv_downloadspeed.value; netbuffer->packettype = PT_FILEFRAGMENT; diff --git a/src/d_player.h b/src/d_player.h index 91989279c..5675dfb5e 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -584,6 +584,8 @@ typedef struct player_s UINT8 stairjank; + UINT8 shrinkLaserDelay; + #ifdef HWRENDER fixed_t fovadd; // adjust FOV for hw rendering #endif diff --git a/src/deh_tables.c b/src/deh_tables.c index d58550367..5fe950e9e 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -3755,6 +3755,15 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi // Caked-Up Booty-Sheet Ghost "S_HYUDORO", + // Grow + "S_GROW_PARTICLE", + + // Shrink + "S_SHRINK_GUN", + "S_SHRINK_CHAIN", + "S_SHRINK_LASER", + "S_SHRINK_PARTICLE", + // The legend "S_SINK", "S_SINK_SHIELD", @@ -4506,6 +4515,7 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi // because sadly no one remembers this place while searching for full state names. const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity testing later. "MT_NULL", + "MT_RAY", "MT_UNKNOWN", "MT_THOK", // Thok! mobj @@ -5336,6 +5346,14 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_HYUDORO", "MT_HYUDORO_CENTER", + "MT_GROW_PARTICLE", + + "MT_SHRINK_POHBEE", + "MT_SHRINK_GUN", + "MT_SHRINK_CHAIN", + "MT_SHRINK_LASER", + "MT_SHRINK_PARTICLE", + "MT_SINK", // Kitchen Sink Stuff "MT_SINK_SHIELD", "MT_SINKTRAIL", diff --git a/src/doomstat.h b/src/doomstat.h index 9c48985e6..f99797abc 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -817,7 +817,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/doomtype.h b/src/doomtype.h index 7037c41cc..5d14fceae 100644 --- a/src/doomtype.h +++ b/src/doomtype.h @@ -368,6 +368,8 @@ typedef UINT32 tic_t; #define UINT2RGBA(a) (UINT32)((a&0xff)<<24)|((a&0xff00)<<8)|((a&0xff0000)>>8)|(((UINT32)a&0xff000000)>>24) #endif +#define TOSTR(x) #x + /* preprocessor dumb and needs second macro to expand input */ #define WSTRING2(s) L ## s #define WSTRING(s) WSTRING2 (s) diff --git a/src/f_finale.c b/src/f_finale.c index ec70678b0..8da3564d7 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -40,6 +40,7 @@ #include "fastcmp.h" #include "lua_hud.h" +#include "lua_hook.h" // SRB2Kart #include "k_menu.h" @@ -2103,7 +2104,7 @@ luahook: if (renderisnewtic) { LUA_HUD_ClearDrawList(luahuddrawlist_title); - LUAh_TitleHUD(luahuddrawlist_title); + LUA_HookHUD(luahuddrawlist_title, HUD_HOOK(title)); } LUA_HUD_DrawList(luahuddrawlist_title); } diff --git a/src/g_game.c b/src/g_game.c index 1cdcaaae4..7bff448a5 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -380,6 +380,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); @@ -1172,7 +1202,7 @@ aftercmdinput: */ if (addedtogame && gamestate == GS_LEVEL) { - LUAh_PlayerCmd(player, cmd); + LUA_HookTiccmd(player, cmd, HOOK(PlayerCmd)); // Send leveltime when this tic was generated to the server for control lag calculations. // Only do this when in a level. Also do this after the hook, so that it can't overwrite this. @@ -1202,7 +1232,7 @@ aftercmdinput: { // Call ViewpointSwitch hooks here. // The viewpoint was forcibly changed. - LUAh_ViewpointSwitch(player, &players[consoleplayer], true); + LUA_HookViewpointSwitch(player, &players[consoleplayer], true); displayplayers[0] = consoleplayer; } } @@ -1253,6 +1283,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! @@ -1286,6 +1317,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); @@ -1324,6 +1359,11 @@ void G_DoLoadLevel(boolean resetplayer) CON_ClearHUD(); server_lagless = cv_lagless.value; + + if (doAutomate == true) + { + Automate_Run(AEV_ROUNDSTART); + } } // @@ -2475,7 +2515,7 @@ void G_SpawnPlayer(INT32 playernum) P_SpawnPlayer(playernum); G_MovePlayerToSpawnOrStarpost(playernum); - LUAh_PlayerSpawn(&players[playernum]); // Lua hook for player spawning :) + LUA_HookPlayer(&players[playernum], HOOK(PlayerSpawn)); // Lua hook for player spawning :) } void G_MovePlayerToSpawnOrStarpost(INT32 playernum) @@ -3202,51 +3242,53 @@ boolean G_GametypeHasSpectators(void) // // Oh, yeah, and we sometimes flip encore mode on here too. // -INT16 G_SometimesGetDifferentGametype(void) +INT16 G_SometimesGetDifferentGametype(UINT8 prefgametype) { - boolean encorepossible = ((M_SecretUnlocked(SECRET_ENCORE) || encorescramble == 1) && (gametyperules & GTR_CIRCUIT)); + // Most of the gametype references in this condition are intentionally not prefgametype. + // This is so a server CAN continue playing a gametype if they like the taste of it. + // The encore check needs prefgametype so can't use G_RaceGametype... + boolean encorepossible = ((M_SecretUnlocked(SECRET_ENCORE) || encorescramble == 1) + && ((gametyperules|gametypedefaultrules[prefgametype]) & GTR_CIRCUIT)); + UINT8 encoremodifier = 0; - if (!cv_kartvoterulechanges.value // never - && encorescramble != 1) // destroying the code for this one instance - return gametype; - - if (randmapbuffer[NUMMAPS] > 0 && (encorepossible || cv_kartvoterulechanges.value != 3)) + if (encorepossible) { - randmapbuffer[NUMMAPS]--; - if (encorepossible) + if (encorescramble != -1) { - if (encorescramble != -1) - encorepossible = (boolean)encorescramble; // FORCE to what was scrambled on intermission - else - { - switch (cv_kartvoterulechanges.value) - { - case 3: // always - randmapbuffer[NUMMAPS] = 0; // gotta prep this in case it isn't already set - break; - case 2: // frequent - encorepossible = M_RandomChance(FRACUNIT>>1); - break; - case 1: // sometimes - default: - encorepossible = M_RandomChance(FRACUNIT>>2); - break; - } - } - if (encorepossible != (cv_kartencore.value == 1)) - return (gametype|0x80); + encorepossible = (boolean)encorescramble; // FORCE to what was scrambled on intermission } - return gametype; + else + { + switch (cv_kartvoterulechanges.value) + { + case 3: // always + encorepossible = true; + break; + case 2: // frequent + encorepossible = M_RandomChance(FRACUNIT>>1); + break; + case 1: // sometimes + encorepossible = M_RandomChance(FRACUNIT>>2); + break; + default: + break; + } + } + if (encorepossible != (cv_kartencore.value == 1)) + encoremodifier = VOTEMODIFIER_ENCORE; } - if (!cv_kartvoterulechanges.value) // never (again) - return gametype; + if (!cv_kartvoterulechanges.value) // never + return (gametype|encoremodifier); + + if (randmapbuffer[NUMMAPS] > 0 && (cv_kartvoterulechanges.value != 3)) + { + randmapbuffer[NUMMAPS]--; + return (gametype|encoremodifier); + } switch (cv_kartvoterulechanges.value) // okay, we're having a gametype change! when's the next one, luv? { - case 3: // always - randmapbuffer[NUMMAPS] = 1; // every other vote (or always if !encorepossible) - break; case 1: // sometimes randmapbuffer[NUMMAPS] = 5; // per "cup" break; @@ -3257,9 +3299,17 @@ INT16 G_SometimesGetDifferentGametype(void) break; } - if (gametype == GT_BATTLE) - return GT_RACE; - return GT_BATTLE; + // Only this response is prefgametype-based. + // todo custom gametypes + if (prefgametype == GT_BATTLE) + { + // Intentionally does not use encoremodifier! + if (cv_kartencore.value == 1) + return (GT_RACE|VOTEMODIFIER_ENCORE); + return (GT_RACE); + } + // This might appear wrong HERE, but the game will display the Encore possibility on the second voting choice instead. + return (GT_BATTLE|encoremodifier); } // @@ -4627,7 +4677,7 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr F_StartCustomCutscene(mapheaderinfo[gamemap-1]->precutscenenum-1, true, resetplayer); else { - LUAh_MapChange(gamemap); + LUA_HookInt(gamemap, HOOK(MapChange)); G_DoLoadLevel(resetplayer); } diff --git a/src/g_game.h b/src/g_game.h index 1a62ceef0..9da38f681 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; @@ -181,7 +182,8 @@ boolean G_IsSpecialStage(INT32 mapnum); boolean G_GametypeUsesLives(void); boolean G_GametypeHasTeams(void); boolean G_GametypeHasSpectators(void); -INT16 G_SometimesGetDifferentGametype(void); +#define VOTEMODIFIER_ENCORE 0x80 +INT16 G_SometimesGetDifferentGametype(UINT8 prefgametype); UINT8 G_GetGametypeColor(INT16 gt); void G_ExitLevel(void); void G_NextLevel(void); 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 e4f090ffb..b6f66c359 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 @@ -169,6 +173,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 @@ -192,6 +197,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"); @@ -209,6 +217,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 @@ -473,7 +482,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(). * @@ -500,14 +509,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; @@ -581,6 +593,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); } @@ -644,7 +658,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; @@ -652,6 +666,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. @@ -675,7 +707,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"), @@ -704,7 +736,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; @@ -715,7 +747,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) // run the lua hook even if we were supposed to eat the msg, netgame consistency goes first. - if (LUAh_PlayerMsg(playernum, target, flags, msg, spam_eatmsg)) + if (LUA_HookPlayerMsg(playernum, target, flags, msg, spam_eatmsg)) return; if (spam_eatmsg) @@ -737,8 +769,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]; @@ -764,7 +796,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"; @@ -851,7 +906,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); @@ -1175,7 +1233,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; @@ -2154,7 +2212,7 @@ void HU_Drawer(void) if (renderisnewtic) { LUA_HUD_ClearDrawList(luahuddrawlist_scores); - LUAh_ScoresHUD(luahuddrawlist_scores); + LUA_HookHUD(luahuddrawlist_scores, HUD_HOOK(scores)); } LUA_HUD_DrawList(luahuddrawlist_scores); } @@ -2277,15 +2335,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; @@ -2294,22 +2352,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; @@ -2319,7 +2392,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/info.c b/src/info.c index 72665927e..b0a3becef 100644 --- a/src/info.c +++ b/src/info.c @@ -573,6 +573,9 @@ char sprnames[NUMSPRITES + 1][5] = "FLML", // Flame Shield speed lines "FLMF", // Flame Shield flash "HYUU", // Hyudoro + "GRWP", // Grow + "POHB", // Shrink Poh-Bee + "SHRG", // Shrink gun / laser "SINK", // Kitchen Sink "SITR", // Kitchen Sink Trail "KBLN", // Battle Mode Bumper @@ -4314,6 +4317,13 @@ state_t states[NUMSTATES] = {SPR_HYUU, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_HYUDORO + {SPR_GRWP, FF_FULLBRIGHT|FF_ANIMATE, 13, {NULL}, 7, 1, S_NULL}, // S_GROW_PARTICLE + + {SPR_SHRG, 0, -1, {NULL}, 0, 0, S_NULL}, // S_SHRINK_GUN + {SPR_POHB, 0, -1, {NULL}, 0, 0, S_NULL}, // S_SHRINK_CHAIN + {SPR_SHRG, FF_FULLBRIGHT|1, -1, {NULL}, 0, 0, S_NULL}, // S_SHRINK_LASER + {SPR_SHRG, FF_FULLBRIGHT|2, -1, {NULL}, 0, 0, S_NULL}, // S_SHRINK_PARTICLE + {SPR_SINK, 0, 1, {A_SmokeTrailer}, MT_SINKTRAIL, 0, S_SINK}, // S_SINK {SPR_SINK, 0|FF_TRANS80|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_SINK_SHIELD}, // S_SINK_SHIELD {SPR_SITR, 0, 1, {NULL}, 0, 0, S_SINKTRAIL2}, // S_SINKTRAIL1 @@ -5120,6 +5130,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_RAY + -1, // doomednum + S_NULL, // spawnstate + 0, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 0, // radius + 0, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags + S_NULL // raisestate + }, + { // MT_UNKNOWN -1, // doomednum S_UNKNOWN, // spawnstate @@ -24009,6 +24046,168 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_GROW_PARTICLE + -1, // doomednum + S_GROW_PARTICLE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 4*FRACUNIT, // radius + 8*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SCENERY|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, + + { // MT_SHRINK_POHBEE + -1, // doomednum + S_HYUDORO, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 32*FRACUNIT, // radius + 24*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, + + { // MT_SHRINK_GUN + -1, // doomednum + S_SHRINK_GUN, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 52*FRACUNIT, // radius + 120*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, + + { // MT_SHRINK_CHAIN + -1, // doomednum + S_SHRINK_CHAIN, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 26*FRACUNIT, // radius + 26*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SCENERY|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, + + { // MT_SHRINK_LASER + -1, // doomednum + S_SHRINK_LASER, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 33*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SCENERY|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, + + { // MT_SHRINK_PARTICLE + -1, // doomednum + S_SHRINK_PARTICLE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 26*FRACUNIT, // radius + 26*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, + { // MT_SINK -1, // doomednum S_SINK, // spawnstate @@ -24017,7 +24216,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = sfx_tossed, // seesound 8, // reactiontime sfx_None, // attacksound - 256*FRACUNIT, // painstate + S_NULL, // painstate 100, // painchance sfx_None, // painsound S_NULL, // meleestate diff --git a/src/info.h b/src/info.h index ab1867973..8acf73ba8 100644 --- a/src/info.h +++ b/src/info.h @@ -1119,6 +1119,9 @@ typedef enum sprite SPR_FLML, // Flame Shield speed lines SPR_FLMF, // Flame Shield flash SPR_HYUU, // Hyudoro + SPR_GRWP, // Grow + SPR_POHB, // Shrink Poh-Bee + SPR_SHRG, // Shrink gun / laser SPR_SINK, // Kitchen Sink SPR_SITR, // Kitchen Sink Trail SPR_KBLN, // Battle Mode Bumper @@ -4745,6 +4748,15 @@ typedef enum state // Caked-Up Booty-Sheet Ghost S_HYUDORO, + // Grow + S_GROW_PARTICLE, + + // Shrink + S_SHRINK_GUN, + S_SHRINK_CHAIN, + S_SHRINK_LASER, + S_SHRINK_PARTICLE, + // The legend S_SINK, S_SINK_SHIELD, @@ -5532,6 +5544,7 @@ extern playersprite_t free_spr2; typedef enum mobj_type { MT_NULL, + MT_RAY, // General purpose mobj MT_UNKNOWN, MT_THOK, // Thok! mobj @@ -6362,6 +6375,14 @@ typedef enum mobj_type MT_HYUDORO, MT_HYUDORO_CENTER, + MT_GROW_PARTICLE, + + MT_SHRINK_POHBEE, + MT_SHRINK_GUN, + MT_SHRINK_CHAIN, + MT_SHRINK_LASER, + MT_SHRINK_PARTICLE, + MT_SINK, // Kitchen Sink Stuff MT_SINK_SHIELD, MT_SINKTRAIL, diff --git a/src/k_bot.c b/src/k_bot.c index 3fbb5278d..968ba8632 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -298,7 +298,7 @@ boolean K_BotCanTakeCut(player_t *player) /*-------------------------------------------------- static fixed_t K_BotSpeedScaled(player_t *player, fixed_t speed) - Gets the bot's speed value, adjusted for predictions. + What the bot "thinks" their speed is, for predictions. Mainly to make bots brake earlier when on friction sectors. Input Arguments:- @@ -312,6 +312,12 @@ static fixed_t K_BotSpeedScaled(player_t *player, fixed_t speed) { fixed_t result = speed; + if (P_IsObjectOnGround(player->mo) == false) + { + // You have no air control, so don't predict too far ahead. + return 0; + } + if (player->mo->movefactor != FRACUNIT) { fixed_t moveFactor = player->mo->movefactor; @@ -650,7 +656,8 @@ static botprediction_t *K_CreateBotPrediction(player_t *player) const fixed_t speed = K_BotSpeedScaled(player, P_AproxDistance(player->mo->momx, player->mo->momy)); const INT32 startDist = (DEFAULT_WAYPOINT_RADIUS * 2 * mapobjectscale) / FRACUNIT; - const INT32 distance = ((speed / FRACUNIT) * futuresight) + startDist; + const INT32 maxDist = startDist * 4; // This function gets very laggy when it goes far distances, and going too far isn't very helpful anyway. + const INT32 distance = min(((speed / FRACUNIT) * (INT32)futuresight) + startDist, maxDist); // Halves radius when encountering a wall on your way to your destination. fixed_t radreduce = FRACUNIT; @@ -660,7 +667,6 @@ static botprediction_t *K_CreateBotPrediction(player_t *player) angle_t angletonext = ANGLE_MAX; INT32 disttonext = INT32_MAX; - waypoint_t *finishLine = K_GetFinishLineWaypoint(); waypoint_t *wp = player->nextwaypoint; mobj_t *prevwpmobj = player->mo; @@ -676,8 +682,8 @@ static botprediction_t *K_CreateBotPrediction(player_t *player) angletonext = R_PointToAngle2(prevwpmobj->x, prevwpmobj->y, wp->mobj->x, wp->mobj->y); disttonext = P_AproxDistance(prevwpmobj->x - wp->mobj->x, prevwpmobj->y - wp->mobj->y) / FRACUNIT; - pathfindsuccess = K_PathfindToWaypoint( - player->nextwaypoint, finishLine, + pathfindsuccess = K_PathfindThruCircuit( + player->nextwaypoint, (unsigned)distanceleft, &pathtofinish, useshortcuts, huntbackwards ); @@ -720,35 +726,6 @@ static botprediction_t *K_CreateBotPrediction(player_t *player) // We're done!! break; } - - if (i == pathtofinish.numnodes-1 && disttonext > 0) - { - // We were pathfinding to the finish, but we want to go past it. - // Set up a new pathfind. - - waypoint_t *next = NULL; - - if (finishLine->numnextwaypoints == 0) - { - distanceleft = 0; - break; - } - - // default to first one - next = wp->nextwaypoints[0]; - - pathfindsuccess = K_PathfindToWaypoint( - next, finishLine, - &pathtofinish, - useshortcuts, huntbackwards - ); - - if (pathfindsuccess == false) - { - distanceleft = 0; - break; - } - } } Z_Free(pathtofinish.array); @@ -1247,7 +1224,7 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) } // Complete override of all ticcmd functionality - if (LUAh_BotTiccmd(player, cmd) == true) + if (LUA_HookTiccmd(player, cmd, HOOK(BotTiccmd)) == true) { return; } diff --git a/src/k_botsearch.c b/src/k_botsearch.c index af7464c30..74a458bce 100644 --- a/src/k_botsearch.c +++ b/src/k_botsearch.c @@ -425,6 +425,16 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) case MT_BUBBLESHIELDTRAP: K_AddDodgeObject(thing, side, 20); break; + case MT_SHRINK_GUN: + if (thing->target == globalsmuggle.botmo) + { + K_AddAttackObject(thing, side, 20); + } + else + { + K_AddDodgeObject(thing, side, 20); + } + break; case MT_RANDOMITEM: if (anglediff >= 45) { diff --git a/src/k_brightmap.c b/src/k_brightmap.c index c62d645e6..a70d7a955 100644 --- a/src/k_brightmap.c +++ b/src/k_brightmap.c @@ -75,7 +75,7 @@ static brightmapStorage_t *K_GetBrightmapStorageByTextureName(const char *checkN { brightmapStorage_t *bms = &brightmapStorage[i]; - if (checkHash == bms->textureHash) + if (checkHash == bms->textureHash && !strncmp(checkName, bms->textureName, 8)) { // Name matches. return bms; @@ -119,8 +119,8 @@ static boolean K_BRIGHTLumpParser(UINT8 *data, size_t size) if (bms == NULL) { bms = K_NewBrightmap(); - strncpy(bms->textureName, tkn, 9); - bms->textureHash = quickncasehash(bms->textureName, 8); + strncpy(bms->textureName, tkn, 8); + bms->textureHash = quickncasehash(tkn, 8); } Z_Free(tkn); @@ -129,8 +129,8 @@ static boolean K_BRIGHTLumpParser(UINT8 *data, size_t size) if (tkn && pos < size) { - strncpy(bms->brightmapName, tkn, 9); - bms->brightmapHash = quickncasehash(bms->brightmapName, 8); + strncpy(bms->brightmapName, tkn, 8); + bms->brightmapHash = quickncasehash(tkn, 8); } else { diff --git a/src/k_brightmap.h b/src/k_brightmap.h index 72bc7e9df..33a1faec2 100644 --- a/src/k_brightmap.h +++ b/src/k_brightmap.h @@ -23,11 +23,11 @@ typedef struct brightmapStorage_s // Stores data for brightmap definitions, // before putting them into texturebrightmaps. - char textureName[9]; // The texture's name. - UINT32 textureHash; // The texture name's hash. + char textureName[8]; // The texture's name. + UINT32 textureHash; // The texture name's hash. - char brightmapName[9]; // The brightmap's name. - UINT32 brightmapHash; // The brightmap name's hash. + char brightmapName[8]; // The brightmap's name. + UINT32 brightmapHash; // The brightmap name's hash. } brightmapStorage_t; /*-------------------------------------------------- diff --git a/src/k_kart.c b/src/k_kart.c index ab8f04da9..03859aee6 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -248,6 +248,7 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartcomeback); CV_RegisterVar(&cv_kartencore); CV_RegisterVar(&cv_kartvoterulechanges); + CV_RegisterVar(&cv_kartgametypepreference); CV_RegisterVar(&cv_kartspeedometer); CV_RegisterVar(&cv_kartvoices); CV_RegisterVar(&cv_kartbot); @@ -354,7 +355,7 @@ static INT32 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = //P-Odds 0 1 2 3 4 5 6 7 /*Sneaker*/ { 0, 0, 2, 4, 6, 0, 0, 0 }, // Sneaker /*Rocket Sneaker*/ { 0, 0, 0, 0, 0, 2, 4, 6 }, // Rocket Sneaker - /*Invincibility*/ { 0, 0, 0, 0, 3, 4, 6, 9 }, // Invincibility + /*Invincibility*/ { 0, 0, 0, 0, 3, 4, 5, 7 }, // Invincibility /*Banana*/ { 2, 3, 1, 0, 0, 0, 0, 0 }, // Banana /*Eggman Monitor*/ { 1, 2, 0, 0, 0, 0, 0, 0 }, // Eggman Monitor /*Orbinaut*/ { 5, 5, 2, 2, 0, 0, 0, 0 }, // Orbinaut @@ -364,7 +365,7 @@ static INT32 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = /*Ballhog*/ { 0, 0, 2, 2, 0, 0, 0, 0 }, // Ballhog /*Self-Propelled Bomb*/ { 0, 0, 0, 0, 0, 2, 4, 0 }, // Self-Propelled Bomb /*Grow*/ { 0, 0, 0, 1, 2, 3, 0, 0 }, // Grow - /*Shrink*/ { 0, 0, 0, 0, 0, 0, 2, 0 }, // Shrink + /*Shrink*/ { 0, 0, 0, 0, 0, 1, 3, 2 }, // Shrink /*Lightning Shield*/ { 1, 0, 0, 0, 0, 0, 0, 0 }, // Lightning Shield /*Bubble Shield*/ { 0, 1, 2, 1, 0, 0, 0, 0 }, // Bubble Shield /*Flame Shield*/ { 0, 0, 0, 0, 0, 1, 3, 5 }, // Flame Shield @@ -374,7 +375,7 @@ static INT32 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = /*Kitchen Sink*/ { 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink /*Drop Target*/ { 3, 0, 0, 0, 0, 0, 0, 0 }, // Drop Target /*Sneaker x2*/ { 0, 0, 2, 2, 2, 0, 0, 0 }, // Sneaker x2 - /*Sneaker x3*/ { 0, 0, 0, 1, 6,10, 5, 0 }, // Sneaker x3 + /*Sneaker x3*/ { 0, 0, 0, 1, 6, 9, 5, 0 }, // Sneaker x3 /*Banana x3*/ { 0, 1, 1, 0, 0, 0, 0, 0 }, // Banana x3 /*Banana x10*/ { 0, 0, 0, 1, 0, 0, 0, 0 }, // Banana x10 /*Orbinaut x3*/ { 0, 0, 1, 0, 0, 0, 0, 0 }, // Orbinaut x3 @@ -2283,6 +2284,77 @@ void K_SpawnInvincibilitySpeedLines(mobj_t *mo) fast->destscale = 6*((mo->player->invincibilitytimer/TICRATE)*FRACUNIT)/8; } +static void K_SpawnGrowShrinkParticles(mobj_t *mo, INT32 timer) +{ + const boolean shrink = (timer < 0); + const INT32 maxTime = (10*TICRATE); + const INT32 noTime = (2*TICRATE); + INT32 spawnFreq = 1; + + mobj_t *particle = NULL; + fixed_t particleScale = FRACUNIT; + fixed_t particleSpeed = 0; + + spawnFreq = abs(timer); + + if (spawnFreq < noTime) + { + return; + } + + spawnFreq -= noTime; + + if (spawnFreq > maxTime) + { + spawnFreq = maxTime; + } + + spawnFreq = (maxTime - spawnFreq) / TICRATE / 4; + if (spawnFreq == 0) + { + spawnFreq++; + } + + if (leveltime % spawnFreq != 0) + { + return; + } + + particle = P_SpawnMobjFromMobj( + mo, + P_RandomRange(-32, 32) * FRACUNIT, + P_RandomRange(-32, 32) * FRACUNIT, + (P_RandomRange(0, 24) + (shrink ? 48 : 0)) * FRACUNIT, + MT_GROW_PARTICLE + ); + + P_SetTarget(&particle->target, mo); + + particle->momx = mo->momx; + particle->momy = mo->momy; + particle->momz = P_GetMobjZMovement(mo); + + K_MatchGenericExtraFlags(particle, mo); + + particleScale = FixedMul((shrink ? SHRINK_PHYSICS_SCALE : GROW_PHYSICS_SCALE), mapobjectscale); + particleSpeed = mo->scale * 4 * P_MobjFlip(mo); // NOT particleScale + + particle->destscale = particleScale; + P_SetScale(particle, particle->destscale); + + if (shrink == true) + { + particle->color = SKINCOLOR_KETCHUP; + particle->momz -= particleSpeed; + particle->renderflags |= RF_VERTICALFLIP; + } + else + { + particle->color = SKINCOLOR_SAPPHIRE; + particle->momz += particleSpeed; + } +} + void K_SpawnBumpEffect(mobj_t *mo) { mobj_t *fx = P_SpawnMobj(mo->x, mo->y, mo->z, MT_BUMP); @@ -3642,7 +3714,7 @@ void K_SpinPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 typ P_SetPlayerMobjState(player->mo, S_KART_SPINOUT); } -static void K_RemoveGrowShrink(player_t *player) +void K_RemoveGrowShrink(player_t *player) { if (player->mo && !P_MobjWasRemoved(player->mo)) { @@ -5581,46 +5653,12 @@ void K_DoSneaker(player_t *player, INT32 type) static void K_DoShrink(player_t *user) { - INT32 i; mobj_t *mobj, *next; S_StartSound(user->mo, sfx_kc46); // Sound the BANG! user->pflags |= PF_ATTACKDOWN; - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator || !players[i].mo) - continue; - if (&players[i] == user) - continue; - if (players[i].position < user->position) - { - //P_FlashPal(&players[i], PAL_NUKE, 10); - - // Grow should get taken away. - if (players[i].growshrinktimer > 0) - K_RemoveGrowShrink(&players[i]); - else - { - // Start shrinking! - K_DropItems(&players[i]); - players[i].growshrinktimer = -(15*TICRATE); - - if (players[i].mo && !P_MobjWasRemoved(players[i].mo)) - { - players[i].mo->scalespeed = mapobjectscale/TICRATE; - players[i].mo->destscale = FixedMul(mapobjectscale, SHRINK_SCALE); - - if (K_PlayerShrinkCheat(&players[i]) == true) - { - players[i].mo->destscale = FixedMul(players[i].mo->destscale, SHRINK_SCALE); - } - - S_StartSound(players[i].mo, sfx_kc59); - } - } - } - } + Obj_CreateShrinkPohbees(user); // kill everything in the kitem list while we're at it: for (mobj = kitemcap; mobj; mobj = next) @@ -6700,7 +6738,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; @@ -7358,6 +7396,11 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) } } + if (player->growshrinktimer != 0) + { + K_SpawnGrowShrinkParticles(player->mo, player->growshrinktimer); + } + if (gametype == GT_RACE && player->rings <= 0) // spawn ring debt indicator { mobj_t *debtflag = P_SpawnMobj(player->mo->x + player->mo->momx, player->mo->y + player->mo->momy, @@ -7522,6 +7565,9 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) comebackshowninfo = true; // client has already seen the message } + if (player->shrinkLaserDelay) + player->shrinkLaserDelay--; + if (player->ringdelay) player->ringdelay--; @@ -9483,7 +9529,7 @@ void K_AdjustPlayerFriction(player_t *player) } // Wipeout slowdown - if (player->spinouttimer && player->wipeoutslow) + if (player->speed > 0 && player->spinouttimer && player->wipeoutslow) { if (player->offroad) player->mo->friction -= 4912; @@ -10018,7 +10064,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } // TODO: gametyperules - player->growshrinktimer = (gametype == GT_BATTLE ? 8 : 12) * TICRATE; + player->growshrinktimer = max(player->growshrinktimer, (gametype == GT_BATTLE ? 8 : 12) * TICRATE); if (player->invincibilitytimer > 0) { diff --git a/src/k_kart.h b/src/k_kart.h index bab501200..4529b2611 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -71,6 +71,7 @@ void K_AwardPlayerRings(player_t *player, INT32 rings, boolean overload); void K_DoInstashield(player_t *player); void K_DoPowerClash(player_t *t1, player_t *t2); void K_BattleAwardHit(player_t *player, player_t *victim, mobj_t *inflictor, UINT8 bumpersRemoved); +void K_RemoveGrowShrink(player_t *player); void K_SpinPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 type); void K_TumblePlayer(player_t *player, mobj_t *inflictor, mobj_t *source); void K_TumbleInterrupt(player_t *player); diff --git a/src/k_objects.h b/src/k_objects.h index 45f9df78c..cc80d2555 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -8,4 +8,11 @@ void Obj_HyudoroThink(mobj_t *actor); void Obj_HyudoroCenterThink(mobj_t *actor); void Obj_HyudoroCollide(mobj_t *special, mobj_t *toucher); +/* Shrink */ +void Obj_PohbeeThinker(mobj_t *pohbee); +void Obj_PohbeeRemoved(mobj_t *pohbee); +void Obj_ShrinkGunRemoved(mobj_t *gun); +boolean Obj_ShrinkLaserCollide(mobj_t *gun, mobj_t *victim); +void Obj_CreateShrinkPohbees(player_t *owner); + #endif/*k_objects_H*/ diff --git a/src/k_pathfind.c b/src/k_pathfind.c index 857f0343a..bd11ecdeb 100644 --- a/src/k_pathfind.c +++ b/src/k_pathfind.c @@ -190,6 +190,10 @@ static boolean K_PathfindSetupValid(const pathfindsetup_t *const pathfindsetup) { CONS_Debug(DBG_GAMELOGIC, "Pathfindsetup has NULL gettraversable function.\n"); } + else if (pathfindsetup->getfinished == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "Pathfindsetup has NULL getfinished function.\n"); + } else if (pathfindsetup->getconnectednodes(pathfindsetup->startnodedata, &sourcenodenumconnectednodes) == NULL) { CONS_Debug(DBG_GAMELOGIC, "K_PathfindSetupValid: Source node returned NULL connecting nodes.\n"); @@ -295,242 +299,244 @@ boolean K_PathfindAStar(path_t *const path, pathfindsetup_t *const pathfindsetup { CONS_Debug(DBG_GAMELOGIC, "K_PathfindAStar: Pathfinding setup is not valid.\n"); } - else if (pathfindsetup->startnodedata == pathfindsetup->endnodedata) + else { - // At the destination, return a simple 1 node path pathfindnode_t singlenode = {0}; singlenode.camefrom = NULL; - singlenode.nodedata = pathfindsetup->endnodedata; + singlenode.nodedata = pathfindsetup->startnodedata; singlenode.heapindex = SIZE_MAX; singlenode.hscore = 0U; singlenode.gscore = 0U; - K_ReconstructPath(path, &singlenode); - - pathfindsuccess = true; - } - else - { - bheap_t openset = {0}; - bheapitem_t poppedbheapitem = {0}; - pathfindnode_t *nodesarray = NULL; - pathfindnode_t **closedset = NULL; - pathfindnode_t *newnode = NULL; - pathfindnode_t *currentnode = NULL; - pathfindnode_t *connectingnode = NULL; - void **connectingnodesdata = NULL; - void *checknodedata = NULL; - UINT32 *connectingnodecosts = NULL; - size_t numconnectingnodes = 0U; - size_t connectingnodeheapindex = 0U; - size_t nodesarraycount = 0U; - size_t closedsetcount = 0U; - size_t i = 0U; - UINT32 tentativegscore = 0U; - - // Set the dynamic structure capacites to defaults if they are 0 - if (pathfindsetup->nodesarraycapacity == 0U) + if (pathfindsetup->getfinished(&singlenode, pathfindsetup) == true) { - pathfindsetup->nodesarraycapacity = DEFAULT_NODEARRAY_CAPACITY; + // At the destination, return a simple 1 node path + K_ReconstructPath(path, &singlenode); + pathfindsuccess = true; } - if (pathfindsetup->opensetcapacity == 0U) + else { - pathfindsetup->opensetcapacity = DEFAULT_OPENSET_CAPACITY; - } - if (pathfindsetup->closedsetcapacity == 0U) - { - pathfindsetup->closedsetcapacity = DEFAULT_CLOSEDSET_CAPACITY; - } + bheap_t openset = {0}; + bheapitem_t poppedbheapitem = {0}; + pathfindnode_t *nodesarray = NULL; + pathfindnode_t **closedset = NULL; + pathfindnode_t *newnode = NULL; + pathfindnode_t *currentnode = NULL; + pathfindnode_t *connectingnode = NULL; + void **connectingnodesdata = NULL; + void *checknodedata = NULL; + UINT32 *connectingnodecosts = NULL; + size_t numconnectingnodes = 0U; + size_t connectingnodeheapindex = 0U; + size_t nodesarraycount = 0U; + size_t closedsetcount = 0U; + size_t i = 0U; + UINT32 tentativegscore = 0U; - // Allocate the necessary memory - nodesarray = Z_Calloc(pathfindsetup->nodesarraycapacity * sizeof(pathfindnode_t), PU_STATIC, NULL); - if (nodesarray == NULL) - { - I_Error("K_PathfindAStar: Out of memory allocating nodes array."); - } - closedset = Z_Calloc(pathfindsetup->closedsetcapacity * sizeof(pathfindnode_t*), PU_STATIC, NULL); - if (closedset == NULL) - { - I_Error("K_PathfindAStar: Out of memory allocating closed set."); - } - K_BHeapInit(&openset, pathfindsetup->opensetcapacity); - - // Create the first node and add it to the open set - newnode = &nodesarray[nodesarraycount]; - newnode->heapindex = SIZE_MAX; - newnode->nodedata = pathfindsetup->startnodedata; - newnode->camefrom = NULL; - newnode->gscore = 0U; - newnode->hscore = pathfindsetup->getheuristic(newnode->nodedata, pathfindsetup->endnodedata); - nodesarraycount++; - K_BHeapPush(&openset, newnode, K_NodeGetFScore(newnode), K_NodeUpdateHeapIndex); - - // update openset capacity if it changed - if (openset.capacity != pathfindsetup->opensetcapacity) - { - pathfindsetup->opensetcapacity = openset.capacity; - } - - // Go through each node in the openset, adding new ones from each node to it - // this continues until a path is found or there are no more nodes to check - while (openset.count > 0U) - { - // pop the best node off of the openset - K_BHeapPop(&openset, &poppedbheapitem); - currentnode = (pathfindnode_t*)poppedbheapitem.data; - - if (currentnode->nodedata == pathfindsetup->endnodedata) + // Set the dynamic structure capacites to defaults if they are 0 + if (pathfindsetup->nodesarraycapacity == 0U) { - pathfindsuccess = K_ReconstructPath(path, currentnode); - break; + pathfindsetup->nodesarraycapacity = DEFAULT_NODEARRAY_CAPACITY; + } + if (pathfindsetup->opensetcapacity == 0U) + { + pathfindsetup->opensetcapacity = DEFAULT_OPENSET_CAPACITY; + } + if (pathfindsetup->closedsetcapacity == 0U) + { + pathfindsetup->closedsetcapacity = DEFAULT_CLOSEDSET_CAPACITY; } - // Place the node we just popped into the closed set, as we are now evaluating it - if (closedsetcount >= pathfindsetup->closedsetcapacity) + // Allocate the necessary memory + nodesarray = Z_Calloc(pathfindsetup->nodesarraycapacity * sizeof(pathfindnode_t), PU_STATIC, NULL); + if (nodesarray == NULL) { - // Need to reallocate closedset to fit another node - pathfindsetup->closedsetcapacity = pathfindsetup->closedsetcapacity * 2; - closedset = - Z_Realloc(closedset, pathfindsetup->closedsetcapacity * sizeof(pathfindnode_t*), PU_STATIC, NULL); - if (closedset == NULL) + I_Error("K_PathfindAStar: Out of memory allocating nodes array."); + } + closedset = Z_Calloc(pathfindsetup->closedsetcapacity * sizeof(pathfindnode_t*), PU_STATIC, NULL); + if (closedset == NULL) + { + I_Error("K_PathfindAStar: Out of memory allocating closed set."); + } + K_BHeapInit(&openset, pathfindsetup->opensetcapacity); + + // Create the first node and add it to the open set + newnode = &nodesarray[nodesarraycount]; + newnode->heapindex = SIZE_MAX; + newnode->nodedata = pathfindsetup->startnodedata; + newnode->camefrom = NULL; + newnode->gscore = 0U; + newnode->hscore = pathfindsetup->getheuristic(newnode->nodedata, pathfindsetup->endnodedata); + nodesarraycount++; + K_BHeapPush(&openset, newnode, K_NodeGetFScore(newnode), K_NodeUpdateHeapIndex); + + // update openset capacity if it changed + if (openset.capacity != pathfindsetup->opensetcapacity) + { + pathfindsetup->opensetcapacity = openset.capacity; + } + + // Go through each node in the openset, adding new ones from each node to it + // this continues until a path is found or there are no more nodes to check + while (openset.count > 0U) + { + // pop the best node off of the openset + K_BHeapPop(&openset, &poppedbheapitem); + currentnode = (pathfindnode_t*)poppedbheapitem.data; + + if (pathfindsetup->getfinished(currentnode, pathfindsetup) == true) { - I_Error("K_PathfindAStar: Out of memory reallocating closed set."); + pathfindsuccess = K_ReconstructPath(path, currentnode); + break; } - } - closedset[closedsetcount] = currentnode; - closedsetcount++; - // Get the needed data for the next nodes from the current node - connectingnodesdata = pathfindsetup->getconnectednodes(currentnode->nodedata, &numconnectingnodes); - connectingnodecosts = pathfindsetup->getconnectioncosts(currentnode->nodedata); - - if (connectingnodesdata == NULL) - { - CONS_Debug(DBG_GAMELOGIC, "K_PathfindAStar: A Node returned NULL connecting node data.\n"); - } - else if (connectingnodecosts == NULL) - { - CONS_Debug(DBG_GAMELOGIC, "K_PathfindAStar: A Node returned NULL connecting node costs.\n"); - } - else - { - // For each connecting node add it to the openset if it's unevaluated and not there, - // skip it if it's in the closedset or not traversable - for (i = 0; i < numconnectingnodes; i++) + // Place the node we just popped into the closed set, as we are now evaluating it + if (closedsetcount >= pathfindsetup->closedsetcapacity) { - checknodedata = connectingnodesdata[i]; - - if (checknodedata == NULL) + // Need to reallocate closedset to fit another node + pathfindsetup->closedsetcapacity = pathfindsetup->closedsetcapacity * 2; + closedset = + Z_Realloc(closedset, pathfindsetup->closedsetcapacity * sizeof(pathfindnode_t*), PU_STATIC, NULL); + if (closedset == NULL) { - CONS_Debug(DBG_GAMELOGIC, "K_PathfindAStar: A Node has a NULL connecting node.\n"); + I_Error("K_PathfindAStar: Out of memory reallocating closed set."); } - else + } + closedset[closedsetcount] = currentnode; + closedsetcount++; + + // Get the needed data for the next nodes from the current node + connectingnodesdata = pathfindsetup->getconnectednodes(currentnode->nodedata, &numconnectingnodes); + connectingnodecosts = pathfindsetup->getconnectioncosts(currentnode->nodedata); + + if (connectingnodesdata == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_PathfindAStar: A Node returned NULL connecting node data.\n"); + } + else if (connectingnodecosts == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_PathfindAStar: A Node returned NULL connecting node costs.\n"); + } + else + { + // For each connecting node add it to the openset if it's unevaluated and not there, + // skip it if it's in the closedset or not traversable + for (i = 0; i < numconnectingnodes; i++) { - // skip this node if it isn't traversable - if (pathfindsetup->gettraversable(checknodedata, currentnode->nodedata) == false) + checknodedata = connectingnodesdata[i]; + + if (checknodedata == NULL) { - continue; - } - - // Figure out what the gscore of this route for the connecting node is - tentativegscore = currentnode->gscore + connectingnodecosts[i]; - - // find this data in the nodes array if it's been generated before - connectingnode = K_NodesArrayContainsNodeData(nodesarray, checknodedata, nodesarraycount); - - if (connectingnode != NULL) - { - // The connecting node has been seen before, so it must be in either the closedset (skip it) - // or the openset (re-evaluate it's gscore) - if (K_ClosedsetContainsNode(closedset, connectingnode, closedsetcount) == true) - { - continue; - } - else if (tentativegscore < connectingnode->gscore) - { - // The node is not in the closedset, update it's gscore if this path to it is faster - connectingnode->gscore = tentativegscore; - connectingnode->camefrom = currentnode; - - connectingnodeheapindex = - K_BHeapContains(&openset, connectingnode, connectingnode->heapindex); - if (connectingnodeheapindex != SIZE_MAX) - { - K_UpdateBHeapItemValue( - &openset.array[connectingnodeheapindex], K_NodeGetFScore(connectingnode)); - } - else - { - // SOMEHOW the node is not in either the closed set OR the open set - CONS_Debug(DBG_GAMELOGIC, "K_PathfindAStar: A Node is not in either set.\n"); - } - } + CONS_Debug(DBG_GAMELOGIC, "K_PathfindAStar: A Node has a NULL connecting node.\n"); } else { - // Node is not created yet, so it hasn't been seen so far - // Reallocate nodesarray if it's full - if (nodesarraycount >= pathfindsetup->nodesarraycapacity) + // skip this node if it isn't traversable + if (pathfindsetup->gettraversable(checknodedata, currentnode->nodedata) == false) { - pathfindnode_t *nodesarrayrealloc = NULL; - pathfindsetup->nodesarraycapacity = pathfindsetup->nodesarraycapacity * 2; - nodesarrayrealloc = Z_Realloc(nodesarray, pathfindsetup->nodesarraycapacity * sizeof(pathfindnode_t), PU_STATIC, NULL); - - if (nodesarrayrealloc == NULL) - { - I_Error("K_PathfindAStar: Out of memory reallocating nodes array."); - } - - // Need to update pointers in closedset, openset, and node "camefrom" if nodesarray moved. - if (nodesarray != nodesarrayrealloc) - { - size_t j = 0U; - size_t arrayindex = 0U; - for (j = 0U; j < closedsetcount; j++) - { - arrayindex = closedset[j] - nodesarray; - closedset[j] = &nodesarrayrealloc[arrayindex]; - } - for (j = 0U; j < openset.count; j++) - { - arrayindex = ((pathfindnode_t *)(openset.array[j].data)) - nodesarray; - openset.array[j].data = &nodesarrayrealloc[arrayindex]; - } - for (j = 0U; j < nodesarraycount; j++) - { - if (nodesarrayrealloc[j].camefrom != NULL) - { - arrayindex = nodesarrayrealloc[j].camefrom - nodesarray; - nodesarrayrealloc[j].camefrom = &nodesarrayrealloc[arrayindex]; - } - } - - arrayindex = currentnode - nodesarray; - currentnode = &nodesarrayrealloc[arrayindex]; - } - - nodesarray = nodesarrayrealloc; + continue; } - // Create the new node and add it to the nodes array and open set - newnode = &nodesarray[nodesarraycount]; - newnode->heapindex = SIZE_MAX; - newnode->nodedata = checknodedata; - newnode->camefrom = currentnode; - newnode->gscore = tentativegscore; - newnode->hscore = pathfindsetup->getheuristic(newnode->nodedata, pathfindsetup->endnodedata); - nodesarraycount++; - K_BHeapPush(&openset, newnode, K_NodeGetFScore(newnode), K_NodeUpdateHeapIndex); + // Figure out what the gscore of this route for the connecting node is + tentativegscore = currentnode->gscore + connectingnodecosts[i]; + + // find this data in the nodes array if it's been generated before + connectingnode = K_NodesArrayContainsNodeData(nodesarray, checknodedata, nodesarraycount); + + if (connectingnode != NULL) + { + // The connecting node has been seen before, so it must be in either the closedset (skip it) + // or the openset (re-evaluate it's gscore) + if (K_ClosedsetContainsNode(closedset, connectingnode, closedsetcount) == true) + { + continue; + } + else if (tentativegscore < connectingnode->gscore) + { + // The node is not in the closedset, update it's gscore if this path to it is faster + connectingnode->gscore = tentativegscore; + connectingnode->camefrom = currentnode; + + connectingnodeheapindex = + K_BHeapContains(&openset, connectingnode, connectingnode->heapindex); + if (connectingnodeheapindex != SIZE_MAX) + { + K_UpdateBHeapItemValue( + &openset.array[connectingnodeheapindex], K_NodeGetFScore(connectingnode)); + } + else + { + // SOMEHOW the node is not in either the closed set OR the open set + CONS_Debug(DBG_GAMELOGIC, "K_PathfindAStar: A Node is not in either set.\n"); + } + } + } + else + { + // Node is not created yet, so it hasn't been seen so far + // Reallocate nodesarray if it's full + if (nodesarraycount >= pathfindsetup->nodesarraycapacity) + { + pathfindnode_t *nodesarrayrealloc = NULL; + pathfindsetup->nodesarraycapacity = pathfindsetup->nodesarraycapacity * 2; + nodesarrayrealloc = Z_Realloc(nodesarray, pathfindsetup->nodesarraycapacity * sizeof(pathfindnode_t), PU_STATIC, NULL); + + if (nodesarrayrealloc == NULL) + { + I_Error("K_PathfindAStar: Out of memory reallocating nodes array."); + } + + // Need to update pointers in closedset, openset, and node "camefrom" if nodesarray moved. + if (nodesarray != nodesarrayrealloc) + { + size_t j = 0U; + size_t arrayindex = 0U; + for (j = 0U; j < closedsetcount; j++) + { + arrayindex = closedset[j] - nodesarray; + closedset[j] = &nodesarrayrealloc[arrayindex]; + } + for (j = 0U; j < openset.count; j++) + { + arrayindex = ((pathfindnode_t *)(openset.array[j].data)) - nodesarray; + openset.array[j].data = &nodesarrayrealloc[arrayindex]; + } + for (j = 0U; j < nodesarraycount; j++) + { + if (nodesarrayrealloc[j].camefrom != NULL) + { + arrayindex = nodesarrayrealloc[j].camefrom - nodesarray; + nodesarrayrealloc[j].camefrom = &nodesarrayrealloc[arrayindex]; + } + } + + arrayindex = currentnode - nodesarray; + currentnode = &nodesarrayrealloc[arrayindex]; + } + + nodesarray = nodesarrayrealloc; + } + + // Create the new node and add it to the nodes array and open set + newnode = &nodesarray[nodesarraycount]; + newnode->heapindex = SIZE_MAX; + newnode->nodedata = checknodedata; + newnode->camefrom = currentnode; + newnode->gscore = tentativegscore; + newnode->hscore = pathfindsetup->getheuristic(newnode->nodedata, pathfindsetup->endnodedata); + nodesarraycount++; + K_BHeapPush(&openset, newnode, K_NodeGetFScore(newnode), K_NodeUpdateHeapIndex); + } } } } } - } - // Clean up the memory - K_BHeapFree(&openset); - Z_Free(closedset); - Z_Free(nodesarray); + // Clean up the memory + K_BHeapFree(&openset); + Z_Free(closedset); + Z_Free(nodesarray); + } } return pathfindsuccess; diff --git a/src/k_pathfind.h b/src/k_pathfind.h index 7b7f1475f..3d08d87e7 100644 --- a/src/k_pathfind.h +++ b/src/k_pathfind.h @@ -29,6 +29,9 @@ typedef UINT32(*getnodeheuristicfunc)(void*, void*); // function pointer for getting if a node is traversable from its base data typedef boolean(*getnodetraversablefunc)(void*, void*); +// function pointer for getting if a node is our pathfinding end point +typedef boolean(*getpathfindfinishedfunc)(void*, void*); + // A pathfindnode contains information about a node from the pathfinding // heapindex is only used within the pathfinding algorithm itself, and is always 0 after it is completed @@ -58,10 +61,12 @@ typedef struct pathfindsetup_s { size_t nodesarraycapacity; void *startnodedata; void *endnodedata; + UINT32 endgscore; getconnectednodesfunc getconnectednodes; getnodeconnectioncostsfunc getconnectioncosts; getnodeheuristicfunc getheuristic; getnodetraversablefunc gettraversable; + getpathfindfinishedfunc getfinished; } pathfindsetup_t; 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/k_terrain.c b/src/k_terrain.c index 44b8b3163..5b12295df 100644 --- a/src/k_terrain.c +++ b/src/k_terrain.c @@ -91,6 +91,7 @@ t_splash_t *K_GetSplashByIndex(size_t checkIndex) --------------------------------------------------*/ t_splash_t *K_GetSplashByName(const char *checkName) { + UINT32 checkHash = quickncasehash(checkName, TERRAIN_NAME_LEN); size_t i; if (numSplashDefs == 0) @@ -102,7 +103,7 @@ t_splash_t *K_GetSplashByName(const char *checkName) { t_splash_t *s = &splashDefs[i]; - if (stricmp(checkName, s->name) == 0) + if (checkHash == s->hash && !strncmp(checkName, s->name, TERRAIN_NAME_LEN)) { // Name matches. return s; @@ -159,6 +160,7 @@ t_footstep_t *K_GetFootstepByIndex(size_t checkIndex) --------------------------------------------------*/ t_footstep_t *K_GetFootstepByName(const char *checkName) { + UINT32 checkHash = quickncasehash(checkName, TERRAIN_NAME_LEN); size_t i; if (numFootstepDefs == 0) @@ -170,7 +172,7 @@ t_footstep_t *K_GetFootstepByName(const char *checkName) { t_footstep_t *fs = &footstepDefs[i]; - if (stricmp(checkName, fs->name) == 0) + if (checkHash == fs->hash && !strncmp(checkName, fs->name, TERRAIN_NAME_LEN)) { // Name matches. return fs; @@ -227,21 +229,20 @@ terrain_t *K_GetTerrainByIndex(size_t checkIndex) --------------------------------------------------*/ terrain_t *K_GetTerrainByName(const char *checkName) { + UINT32 checkHash = quickncasehash(checkName, TERRAIN_NAME_LEN); size_t i; - if (numTerrainDefs == 0) + if (numTerrainDefs > 0) { - return NULL; - } - - for (i = 0; i < numTerrainDefs; i++) - { - terrain_t *t = &terrainDefs[i]; - - if (stricmp(checkName, t->name) == 0) + for (i = 0; i < numTerrainDefs; i++) { - // Name matches. - return t; + terrain_t *t = &terrainDefs[i]; + + if (checkHash == t->hash && !strncmp(checkName, t->name, TERRAIN_NAME_LEN)) + { + // Name matches. + return t; + } } } @@ -265,20 +266,19 @@ terrain_t *K_GetDefaultTerrain(void) --------------------------------------------------*/ terrain_t *K_GetTerrainForTextureName(const char *checkName) { + UINT32 checkHash = quickncasehash(checkName, 8); size_t i; - if (numTerrainFloorDefs == 0) + if (numTerrainFloorDefs > 0) { - return NULL; - } - - for (i = 0; i < numTerrainFloorDefs; i++) - { - t_floor_t *f = &terrainFloorDefs[i]; - - if (strncasecmp(checkName, f->textureName, 8) == 0) + for (i = 0; i < numTerrainFloorDefs; i++) { - return K_GetTerrainByIndex(f->terrainID); + t_floor_t *f = &terrainFloorDefs[i]; + + if (checkHash == f->textureHash && !strncmp(checkName, f->textureName, 8)) + { + return K_GetTerrainByIndex(f->terrainID); + } } } @@ -294,15 +294,15 @@ terrain_t *K_GetTerrainForTextureName(const char *checkName) --------------------------------------------------*/ terrain_t *K_GetTerrainForTextureNum(INT32 textureNum) { - texture_t *tex = NULL; - - if (textureNum < 0 || textureNum >= numtextures) + if (textureNum >= 0 && textureNum < numtextures) { - return NULL; + texture_t *tex = textures[textureNum]; + return K_GetTerrainForTextureName(tex->name); } - tex = textures[textureNum]; - return K_GetTerrainForTextureName(tex->name); + // This texture doesn't have a terrain directly applied to it, + // so we fallback to the default terrain. + return K_GetDefaultTerrain(); } /*-------------------------------------------------- @@ -1187,6 +1187,7 @@ static boolean K_DoTERRAINLumpParse(size_t num, void (*parser)(size_t, char *, c static boolean K_TERRAINLumpParser(UINT8 *data, size_t size) { char *tkn = M_GetToken((char *)data); + UINT32 tknHash = 0; size_t pos = 0; size_t i; @@ -1211,11 +1212,13 @@ static boolean K_TERRAINLumpParser(UINT8 *data, size_t size) { t_splash_t *s = NULL; + tknHash = quickncasehash(tkn, TERRAIN_NAME_LEN); + for (i = 0; i < numSplashDefs; i++) { s = &splashDefs[i]; - if (stricmp(tkn, s->name) == 0) + if (tknHash == s->hash && !strncmp(tkn, s->name, TERRAIN_NAME_LEN)) { break; } @@ -1227,6 +1230,8 @@ static boolean K_TERRAINLumpParser(UINT8 *data, size_t size) s = &splashDefs[i]; strncpy(s->name, tkn, TERRAIN_NAME_LEN); + s->hash = tknHash; + CONS_Printf("Created new Splash type '%s'\n", s->name); } @@ -1248,11 +1253,13 @@ static boolean K_TERRAINLumpParser(UINT8 *data, size_t size) { t_footstep_t *fs = NULL; + tknHash = quickncasehash(tkn, TERRAIN_NAME_LEN); + for (i = 0; i < numFootstepDefs; i++) { fs = &footstepDefs[i]; - if (stricmp(tkn, fs->name) == 0) + if (tknHash == fs->hash && !strncmp(tkn, fs->name, TERRAIN_NAME_LEN)) { break; } @@ -1264,6 +1271,8 @@ static boolean K_TERRAINLumpParser(UINT8 *data, size_t size) fs = &footstepDefs[i]; strncpy(fs->name, tkn, TERRAIN_NAME_LEN); + fs->hash = tknHash; + CONS_Printf("Created new Footstep type '%s'\n", fs->name); } @@ -1285,11 +1294,13 @@ static boolean K_TERRAINLumpParser(UINT8 *data, size_t size) { terrain_t *t = NULL; + tknHash = quickncasehash(tkn, TERRAIN_NAME_LEN); + for (i = 0; i < numTerrainDefs; i++) { t = &terrainDefs[i]; - if (stricmp(tkn, t->name) == 0) + if (tknHash == t->hash && !strncmp(tkn, t->name, TERRAIN_NAME_LEN)) { break; } @@ -1301,6 +1312,8 @@ static boolean K_TERRAINLumpParser(UINT8 *data, size_t size) t = &terrainDefs[i]; strncpy(t->name, tkn, TERRAIN_NAME_LEN); + t->hash = tknHash; + CONS_Printf("Created new Terrain type '%s'\n", t->name); } @@ -1333,11 +1346,13 @@ static boolean K_TERRAINLumpParser(UINT8 *data, size_t size) { t_floor_t *f = NULL; + tknHash = quickncasehash(tkn, 8); + for (i = 0; i < numTerrainFloorDefs; i++) { f = &terrainFloorDefs[i]; - if (stricmp(tkn, f->textureName) == 0) + if (f->textureHash == tknHash && !strncmp(tkn, f->textureName, 8)) { break; } @@ -1348,7 +1363,8 @@ static boolean K_TERRAINLumpParser(UINT8 *data, size_t size) K_NewTerrainFloorDefs(); f = &terrainFloorDefs[i]; - strncpy(f->textureName, tkn, 9); + strncpy(f->textureName, tkn, 8); + f->textureHash = tknHash; } Z_Free(tkn); @@ -1398,11 +1414,13 @@ static boolean K_TERRAINLumpParser(UINT8 *data, size_t size) { terrain_t *t = NULL; + tknHash = quickncasehash(tkn, TERRAIN_NAME_LEN); + for (i = 0; i < numTerrainDefs; i++) { t = &terrainDefs[i]; - if (stricmp(tkn, t->name) == 0) + if (tknHash == t->hash && !strncmp(tkn, t->name, TERRAIN_NAME_LEN)) { break; } @@ -1435,11 +1453,13 @@ static boolean K_TERRAINLumpParser(UINT8 *data, size_t size) { t_footstep_t *fs = NULL; + tknHash = quickncasehash(tkn, TERRAIN_NAME_LEN); + for (i = 0; i < numFootstepDefs; i++) { fs = &footstepDefs[i]; - if (stricmp(tkn, fs->name) == 0) + if (tknHash == fs->hash && !strncmp(tkn, fs->name, TERRAIN_NAME_LEN)) { break; } diff --git a/src/k_terrain.h b/src/k_terrain.h index 924f7a1b1..c83f199d2 100644 --- a/src/k_terrain.h +++ b/src/k_terrain.h @@ -28,6 +28,7 @@ typedef struct t_splash_s // These are particles spawned when hitting the floor. char name[TERRAIN_NAME_LEN]; // Lookup name. + UINT32 hash; // Lookup name's hash. UINT16 mobjType; // Thing type. MT_NULL to not spawn anything. UINT16 sfx; // Sound to play. @@ -48,6 +49,7 @@ typedef struct t_footstep_s // These are particles spawned when moving fast enough on a floor. char name[TERRAIN_NAME_LEN]; // Lookup name. + UINT32 hash; // Lookup name's hash. UINT16 mobjType; // Thing type. MT_NULL to not spawn anything. UINT16 sfx; // Sound to play. @@ -79,6 +81,7 @@ typedef struct terrain_s // These are all of the properties that the floor gets. char name[TERRAIN_NAME_LEN]; // Lookup name. + UINT32 hash; // Lookup name's hash. size_t splashID; // Splash defintion ID. size_t footstepID; // Footstep defintion ID. @@ -93,16 +96,14 @@ typedef struct terrain_s typedef struct t_floor_s { // Terrain floor definition. - // Ties texture names to a . + // Ties a texture name to a terrain definition. - // (Could be optimized by using texture IDs instead of names, - // but was concerned because I recall sooomething about those not being netsafe? - // Someone confirm if I just hallucinated that. :V) - - char textureName[9]; // Floor texture name. + char textureName[8]; // Floor texture name. + UINT32 textureHash; // Floor texture hash. size_t terrainID; // Terrain definition ID. } t_floor_t; + /*-------------------------------------------------- size_t K_GetSplashHeapIndex(t_splash_t *splash); @@ -285,6 +286,7 @@ terrain_t *K_GetTerrainByIndex(size_t checkIndex); terrain_t *K_GetTerrainByName(const char *checkName); + /*-------------------------------------------------- terrain_t *K_GetDefaultTerrain(void); diff --git a/src/k_waypoint.c b/src/k_waypoint.c index 357731bd9..2c9ebec9a 100644 --- a/src/k_waypoint.c +++ b/src/k_waypoint.c @@ -1039,6 +1039,102 @@ static boolean K_WaypointPathfindTraversableNoShortcuts(void *data, void *prevda return traversable; } +/*-------------------------------------------------- + static boolean K_WaypointPathfindReachedEnd(void *data, void *setupData) + + Returns if the current waypoint data is our end point of our pathfinding. + + Input Arguments:- + data - Should point to a pathfindnode_t to compare + setupData - Should point to the pathfindsetup_t to compare + + Return:- + True if the waypoint is the pathfind end point, false otherwise. +--------------------------------------------------*/ +static boolean K_WaypointPathfindReachedEnd(void *data, void *setupData) +{ + boolean isEnd = false; + + if (data == NULL || setupData == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindReachedEnd received NULL data.\n"); + } + else + { + pathfindnode_t *node = (pathfindnode_t *)data; + pathfindsetup_t *setup = (pathfindsetup_t *)setupData; + + isEnd = (node->nodedata == setup->endnodedata); + } + + return isEnd; +} + +/*-------------------------------------------------- + static boolean K_WaypointPathfindReachedGScore(void *data, void *setupData) + + Returns if the current waypoint data reaches our end G score. + + Input Arguments:- + data - Should point to a pathfindnode_t to compare + setupData - Should point to the pathfindsetup_t to compare + + Return:- + True if the waypoint reached the G score, false otherwise. +--------------------------------------------------*/ +static boolean K_WaypointPathfindReachedGScore(void *data, void *setupData) +{ + boolean scoreReached = false; + + if (data == NULL || setupData == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindReachedGScore received NULL data.\n"); + } + else + { + pathfindnode_t *node = (pathfindnode_t *)data; + pathfindsetup_t *setup = (pathfindsetup_t *)setupData; + + scoreReached = (node->gscore >= setup->endgscore); + } + + return scoreReached; +} + +/*-------------------------------------------------- + static boolean K_WaypointPathfindReachedGScoreSpawnable(void *data, void *setupData) + + Returns if the current waypoint data reaches our end G score. + + Input Arguments:- + data - Should point to a pathfindnode_t to compare + setupData - Should point to the pathfindsetup_t to compare + + Return:- + True if the waypoint reached the G score, false otherwise. +--------------------------------------------------*/ +static boolean K_WaypointPathfindReachedGScoreSpawnable(void *data, void *setupData) +{ + boolean scoreReached = false; + boolean spawnable = false; + + if (data == NULL || setupData == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindReachedGScoreSpawnable received NULL data.\n"); + } + else + { + pathfindnode_t *node = (pathfindnode_t *)data; + pathfindsetup_t *setup = (pathfindsetup_t *)setupData; + waypoint_t *wp = (waypoint_t *)node->nodedata; + + scoreReached = (node->gscore >= setup->endgscore); + spawnable = K_GetWaypointIsSpawnpoint(wp); + } + + return (scoreReached && spawnable); +} + /*-------------------------------------------------- boolean K_PathfindToWaypoint( waypoint_t *const sourcewaypoint, @@ -1087,18 +1183,19 @@ boolean K_PathfindToWaypoint( getnodeconnectioncostsfunc nodecostsfunc = K_WaypointPathfindGetNextCosts; getnodeheuristicfunc heuristicfunc = K_WaypointPathfindGetHeuristic; getnodetraversablefunc traversablefunc = K_WaypointPathfindTraversableNoShortcuts; + getpathfindfinishedfunc finishedfunc = K_WaypointPathfindReachedEnd; if (huntbackwards) { nextnodesfunc = K_WaypointPathfindGetPrev; nodecostsfunc = K_WaypointPathfindGetPrevCosts; } + if (useshortcuts) { traversablefunc = K_WaypointPathfindTraversableAllEnabled; } - pathfindsetup.opensetcapacity = K_GetOpensetBaseSize(); pathfindsetup.closedsetcapacity = K_GetClosedsetBaseSize(); pathfindsetup.nodesarraycapacity = K_GetNodesArrayBaseSize(); @@ -1108,6 +1205,173 @@ boolean K_PathfindToWaypoint( pathfindsetup.getconnectioncosts = nodecostsfunc; pathfindsetup.getheuristic = heuristicfunc; pathfindsetup.gettraversable = traversablefunc; + pathfindsetup.getfinished = finishedfunc; + + pathfound = K_PathfindAStar(returnpath, &pathfindsetup); + + K_UpdateOpensetBaseSize(pathfindsetup.opensetcapacity); + K_UpdateClosedsetBaseSize(pathfindsetup.closedsetcapacity); + K_UpdateNodesArrayBaseSize(pathfindsetup.nodesarraycapacity); + } + + return pathfound; +} + +/*-------------------------------------------------- + boolean K_PathfindThruCircuit( + waypoint_t *const sourcewaypoint, + const UINT32 traveldistance, + path_t *const returnpath, + const boolean useshortcuts, + const boolean huntbackwards) + + See header file for description. +--------------------------------------------------*/ +boolean K_PathfindThruCircuit( + waypoint_t *const sourcewaypoint, + const UINT32 traveldistance, + path_t *const returnpath, + const boolean useshortcuts, + const boolean huntbackwards) +{ + boolean pathfound = false; + + if (sourcewaypoint == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL sourcewaypoint in K_PathfindThruCircuit.\n"); + } + else if (finishline == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL finishline in K_PathfindThruCircuit.\n"); + } + else if (((huntbackwards == false) && (sourcewaypoint->numnextwaypoints == 0)) + || ((huntbackwards == true) && (sourcewaypoint->numprevwaypoints == 0))) + { + CONS_Debug(DBG_GAMELOGIC, + "K_PathfindThruCircuit: sourcewaypoint with ID %d has no next waypoint\n", + K_GetWaypointID(sourcewaypoint)); + } + else if (((huntbackwards == false) && (finishline->numprevwaypoints == 0)) + || ((huntbackwards == true) && (finishline->numnextwaypoints == 0))) + { + CONS_Debug(DBG_GAMELOGIC, + "K_PathfindThruCircuit: finishline with ID %d has no previous waypoint\n", + K_GetWaypointID(finishline)); + } + else + { + pathfindsetup_t pathfindsetup = {0}; + getconnectednodesfunc nextnodesfunc = K_WaypointPathfindGetNext; + getnodeconnectioncostsfunc nodecostsfunc = K_WaypointPathfindGetNextCosts; + getnodeheuristicfunc heuristicfunc = K_WaypointPathfindGetHeuristic; + getnodetraversablefunc traversablefunc = K_WaypointPathfindTraversableNoShortcuts; + getpathfindfinishedfunc finishedfunc = K_WaypointPathfindReachedGScore; + + if (huntbackwards) + { + nextnodesfunc = K_WaypointPathfindGetPrev; + nodecostsfunc = K_WaypointPathfindGetPrevCosts; + } + + if (useshortcuts) + { + traversablefunc = K_WaypointPathfindTraversableAllEnabled; + } + + pathfindsetup.opensetcapacity = K_GetOpensetBaseSize(); + pathfindsetup.closedsetcapacity = K_GetClosedsetBaseSize(); + pathfindsetup.nodesarraycapacity = K_GetNodesArrayBaseSize(); + pathfindsetup.startnodedata = sourcewaypoint; + pathfindsetup.endnodedata = finishline; + pathfindsetup.endgscore = traveldistance; + pathfindsetup.getconnectednodes = nextnodesfunc; + pathfindsetup.getconnectioncosts = nodecostsfunc; + pathfindsetup.getheuristic = heuristicfunc; + pathfindsetup.gettraversable = traversablefunc; + pathfindsetup.getfinished = finishedfunc; + + pathfound = K_PathfindAStar(returnpath, &pathfindsetup); + + K_UpdateOpensetBaseSize(pathfindsetup.opensetcapacity); + K_UpdateClosedsetBaseSize(pathfindsetup.closedsetcapacity); + K_UpdateNodesArrayBaseSize(pathfindsetup.nodesarraycapacity); + } + + return pathfound; +} + +/*-------------------------------------------------- + boolean K_PathfindThruCircuitSpawnable( + waypoint_t *const sourcewaypoint, + const UINT32 traveldistance, + path_t *const returnpath, + const boolean useshortcuts, + const boolean huntbackwards) + + See header file for description. +--------------------------------------------------*/ +boolean K_PathfindThruCircuitSpawnable( + waypoint_t *const sourcewaypoint, + const UINT32 traveldistance, + path_t *const returnpath, + const boolean useshortcuts, + const boolean huntbackwards) +{ + boolean pathfound = false; + + if (sourcewaypoint == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL sourcewaypoint in K_PathfindThruCircuitSpawnable.\n"); + } + else if (finishline == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL finishline in K_PathfindThruCircuitSpawnable.\n"); + } + else if (((huntbackwards == false) && (sourcewaypoint->numnextwaypoints == 0)) + || ((huntbackwards == true) && (sourcewaypoint->numprevwaypoints == 0))) + { + CONS_Debug(DBG_GAMELOGIC, + "K_PathfindThruCircuitSpawnable: sourcewaypoint with ID %d has no next waypoint\n", + K_GetWaypointID(sourcewaypoint)); + } + else if (((huntbackwards == false) && (finishline->numprevwaypoints == 0)) + || ((huntbackwards == true) && (finishline->numnextwaypoints == 0))) + { + CONS_Debug(DBG_GAMELOGIC, + "K_PathfindThruCircuitSpawnable: finishline with ID %d has no previous waypoint\n", + K_GetWaypointID(finishline)); + } + else + { + pathfindsetup_t pathfindsetup = {0}; + getconnectednodesfunc nextnodesfunc = K_WaypointPathfindGetNext; + getnodeconnectioncostsfunc nodecostsfunc = K_WaypointPathfindGetNextCosts; + getnodeheuristicfunc heuristicfunc = K_WaypointPathfindGetHeuristic; + getnodetraversablefunc traversablefunc = K_WaypointPathfindTraversableNoShortcuts; + getpathfindfinishedfunc finishedfunc = K_WaypointPathfindReachedGScoreSpawnable; + + if (huntbackwards) + { + nextnodesfunc = K_WaypointPathfindGetPrev; + nodecostsfunc = K_WaypointPathfindGetPrevCosts; + } + + if (useshortcuts) + { + traversablefunc = K_WaypointPathfindTraversableAllEnabled; + } + + pathfindsetup.opensetcapacity = K_GetOpensetBaseSize(); + pathfindsetup.closedsetcapacity = K_GetClosedsetBaseSize(); + pathfindsetup.nodesarraycapacity = K_GetNodesArrayBaseSize(); + pathfindsetup.startnodedata = sourcewaypoint; + pathfindsetup.endnodedata = finishline; + pathfindsetup.endgscore = traveldistance; + pathfindsetup.getconnectednodes = nextnodesfunc; + pathfindsetup.getconnectioncosts = nodecostsfunc; + pathfindsetup.getheuristic = heuristicfunc; + pathfindsetup.gettraversable = traversablefunc; + pathfindsetup.getfinished = finishedfunc; pathfound = K_PathfindAStar(returnpath, &pathfindsetup); @@ -1183,18 +1447,19 @@ waypoint_t *K_GetNextWaypointToDestination( getnodeconnectioncostsfunc nodecostsfunc = K_WaypointPathfindGetNextCosts; getnodeheuristicfunc heuristicfunc = K_WaypointPathfindGetHeuristic; getnodetraversablefunc traversablefunc = K_WaypointPathfindTraversableNoShortcuts; + getpathfindfinishedfunc finishedfunc = K_WaypointPathfindReachedEnd; if (huntbackwards) { nextnodesfunc = K_WaypointPathfindGetPrev; nodecostsfunc = K_WaypointPathfindGetPrevCosts; } + if (useshortcuts) { traversablefunc = K_WaypointPathfindTraversableAllEnabled; } - pathfindsetup.opensetcapacity = K_GetOpensetBaseSize(); pathfindsetup.closedsetcapacity = K_GetClosedsetBaseSize(); pathfindsetup.nodesarraycapacity = K_GetNodesArrayBaseSize(); @@ -1204,6 +1469,7 @@ waypoint_t *K_GetNextWaypointToDestination( pathfindsetup.getconnectioncosts = nodecostsfunc; pathfindsetup.getheuristic = heuristicfunc; pathfindsetup.gettraversable = traversablefunc; + pathfindsetup.getfinished = finishedfunc; pathfindsuccess = K_PathfindAStar(&pathtowaypoint, &pathfindsetup); @@ -1895,6 +2161,7 @@ boolean K_SetupWaypointList(void) // Loop through the waypointcap here so that all waypoints are added to the heap, and allow easier debugging for (waypointmobj = waypointcap; waypointmobj; waypointmobj = waypointmobj->tracer) { + waypointmobj->cusval = (INT32)numwaypoints; K_SetupWaypoint(waypointmobj); } diff --git a/src/k_waypoint.h b/src/k_waypoint.h index f49e162e8..1cb659dbe 100644 --- a/src/k_waypoint.h +++ b/src/k_waypoint.h @@ -214,6 +214,67 @@ boolean K_PathfindToWaypoint( const boolean huntbackwards); +/*-------------------------------------------------- + boolean K_PathfindThruCircuit( + waypoint_t *const sourcewaypoint, + const UINT32 traveldistance, + path_t *const returnpath, + const boolean useshortcuts, + const boolean huntbackwards) + + Tries a pathfind to the finish line waypoint, similar to K_PathfindToWaypoint, but it will continue + until it reaches the specified distance. The final path returned will only have the waypoints up to the + specified distance. + + Input Arguments:- + sourcewaypoint - The waypoint to start searching from + traveldistance - How far along the circuit it will try to pathfind. + returnpath - The path_t that will contain the final found path + useshortcuts - Whether to use waypoints that are marked as being shortcuts in the search + huntbackwards - Goes through the waypoints backwards if true + + Return:- + True if a circuit path could be constructed, false if it couldn't. +--------------------------------------------------*/ + +boolean K_PathfindThruCircuit( + waypoint_t *const sourcewaypoint, + const UINT32 traveldistance, + path_t *const returnpath, + const boolean useshortcuts, + const boolean huntbackwards); + + +/*-------------------------------------------------- + boolean K_PathfindThruCircuitSpawnable( + waypoint_t *const sourcewaypoint, + const UINT32 traveldistance, + path_t *const returnpath, + const boolean useshortcuts, + const boolean huntbackwards) + + The same as K_PathfindThruCircuit, but continues until hitting a waypoint that + can be respawned at. + + Input Arguments:- + sourcewaypoint - The waypoint to start searching from + traveldistance - How far along the circuit it will try to pathfind. + returnpath - The path_t that will contain the final found path + useshortcuts - Whether to use waypoints that are marked as being shortcuts in the search + huntbackwards - Goes through the waypoints backwards if true + + Return:- + True if a circuit path could be constructed, false if it couldn't. +--------------------------------------------------*/ + +boolean K_PathfindThruCircuitSpawnable( + waypoint_t *const sourcewaypoint, + const UINT32 traveldistance, + path_t *const returnpath, + const boolean useshortcuts, + const boolean huntbackwards); + + /*-------------------------------------------------- waypoint_t *K_GetNextWaypointToDestination( waypoint_t *const sourcewaypoint, diff --git a/src/lua_hook.h b/src/lua_hook.h index 7760c188b..ce98a5ec7 100644 --- a/src/lua_hook.h +++ b/src/lua_hook.h @@ -1,7 +1,7 @@ -// SONIC ROBO BLAST 2 + //----------------------------------------------------------------------------- // Copyright (C) 2012-2016 by John "JTE" Muniz. -// Copyright (C) 2012-2020 by Sonic Team Junior. +// Copyright (C) 2012-2022 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -12,115 +12,134 @@ #include "r_defs.h" #include "d_player.h" +#include "s_sound.h" +#include "d_event.h" + #include "lua_hudlib_drawlist.h" -enum hook { - hook_NetVars=0, - hook_MapChange, - hook_MapLoad, - hook_PlayerJoin, - hook_PreThinkFrame, - hook_ThinkFrame, - hook_PostThinkFrame, - hook_MobjSpawn, - hook_MobjCollide, - hook_MobjLineCollide, - hook_MobjMoveCollide, - hook_TouchSpecial, - hook_MobjFuse, - hook_MobjThinker, - hook_BossThinker, - hook_ShouldDamage, - hook_MobjDamage, - hook_MobjDeath, - hook_BossDeath, - hook_MobjRemoved, - hook_JumpSpecial, - hook_AbilitySpecial, - hook_SpinSpecial, - hook_JumpSpinSpecial, - hook_BotTiccmd, - hook_BotAI, - hook_BotRespawn, - hook_LinedefExecute, - hook_PlayerMsg, - hook_HurtMsg, - hook_PlayerSpawn, - hook_ShieldSpawn, - hook_ShieldSpecial, - hook_MobjMoveBlocked, - hook_MapThingSpawn, - hook_FollowMobj, - hook_PlayerCanDamage, - hook_PlayerQuit, - hook_MusicChange, - hook_TeamSwitch, - hook_ViewpointSwitch, - hook_PlayerThink, - hook_ShouldJingleContinue, - hook_GameQuit, - hook_PlayerCmd, +/* +Do you know what an 'X Macro' is? Such a macro is called over each element of +a list and expands the input. I use it for the hook lists because both an enum +and array of hook names need to be kept in order. The X Macro handles this +automatically. +*/ - // SRB2Kart - hook_IntermissionThinker, - hook_VoteThinker, +#define MOBJ_HOOK_LIST(X) \ + X (MobjSpawn),/* P_SpawnMobj */\ + X (MobjCollide),/* PIT_CheckThing */\ + X (MobjLineCollide),/* ditto */\ + X (MobjMoveCollide),/* tritto */\ + X (TouchSpecial),/* P_TouchSpecialThing */\ + X (MobjFuse),/* when mobj->fuse runs out */\ + X (MobjThinker),/* P_MobjThinker, P_SceneryThinker */\ + X (BossThinker),/* P_GenericBossThinker */\ + X (ShouldDamage),/* P_DamageMobj (Should mobj take damage?) */\ + X (MobjDamage),/* P_DamageMobj (Mobj actually takes damage!) */\ + X (MobjDeath),/* P_KillMobj */\ + X (BossDeath),/* A_BossDeath */\ + X (MobjRemoved),/* P_RemoveMobj */\ + X (BotRespawn),/* B_CheckRespawn */\ + X (MobjMoveBlocked),/* P_XYMovement (when movement is blocked) */\ + X (MapThingSpawn),/* P_SpawnMapThing */\ + X (FollowMobj),/* P_PlayerAfterThink Smiles mobj-following */\ - hook_MAX // last hook -}; -extern const char *const hookNames[]; +#define HOOK_LIST(X) \ + X (NetVars),/* add to archive table (netsave) */\ + X (MapChange),/* (before map load) */\ + X (MapLoad),\ + X (PlayerJoin),/* Got_AddPlayer */\ + X (PreThinkFrame)/* frame (before mobj and player thinkers) */,\ + X (ThinkFrame),/* frame (after mobj and player thinkers) */\ + X (PostThinkFrame),/* frame (at end of tick, ie after overlays, precipitation, specials) */\ + X (JumpSpecial),/* P_DoJumpStuff (Any-jumping) */\ + X (AbilitySpecial),/* P_DoJumpStuff (Double-jumping) */\ + X (SpinSpecial),/* P_DoSpinAbility (Spin button effect) */\ + X (JumpSpinSpecial),/* P_DoJumpStuff (Spin button effect (mid-air)) */\ + X (BotTiccmd),/* B_BuildTiccmd */\ + X (PlayerMsg),/* chat messages */\ + X (HurtMsg),/* imhurttin */\ + X (PlayerSpawn),/* G_SpawnPlayer */\ + X (ShieldSpawn),/* P_SpawnShieldOrb */\ + X (ShieldSpecial),/* shield abilities */\ + X (PlayerCanDamage),/* P_PlayerCanDamage */\ + X (PlayerQuit),\ + X (IntermissionThinker),/* Y_Ticker */\ + X (TeamSwitch),/* team switching in... uh... *what* speak, spit it the fuck out */\ + X (ViewpointSwitch),/* spy mode (no trickstabs) */\ + X (SeenPlayer),/* MT_NAMECHECK */\ + X (PlayerThink),/* P_PlayerThink */\ + X (GameQuit),\ + X (PlayerCmd),/* building the player's ticcmd struct */\ + X (MusicChange),\ + X (VoteThinker),/* Y_VoteTicker */\ + +#define STRING_HOOK_LIST(X) \ + X (LinedefExecute),\ + X (ShouldJingleContinue),/* should jingle of the given music continue playing */\ + +#define HUD_HOOK_LIST(X) \ + X (game),\ + X (scores),/* emblems/multiplayer list */\ + X (title),/* titlescreen */\ + X (titlecard),\ + X (intermission),\ + +/* +I chose to access the hook enums through a macro as well. This could provide +a hint to lookup the macro's definition instead of the enum's definition. +(Since each enumeration is not defined in the source code, but by the list +macros above, it is not greppable.) The name passed to the macro can also be +grepped and found in the lists above. +*/ + +#define MOBJ_HOOK(name) mobjhook_ ## name +#define HOOK(name) hook_ ## name +#define HUD_HOOK(name) hudhook_ ## name +#define STRING_HOOK(name) stringhook_ ## name + +#define ENUM(X) enum { X ## _LIST (X) X(MAX) } + +ENUM (MOBJ_HOOK); +ENUM (HOOK); +ENUM (HUD_HOOK); +ENUM (STRING_HOOK); + +#undef ENUM + +/* dead simple, LUA_HOOK(GameQuit) */ +#define LUA_HOOK(type) LUA_HookVoid(HOOK(type)) +//#define LUA_HUDHOOK(type) LUA_HookHUD(HUD_HOOK(type)) extern boolean hook_cmd_running; extern int hook_defrosting; -void LUAh_MapChange(INT16 mapnumber); // Hook for map change (before load) -void LUAh_MapLoad(void); // Hook for map load -void LUAh_PlayerJoin(int playernum); // Hook for Got_AddPlayer -void LUAh_PreThinkFrame(void); // Hook for frame (before mobj and player thinkers) -void LUAh_ThinkFrame(void); // Hook for frame (after mobj and player thinkers) -void LUAh_PostThinkFrame(void); // Hook for frame (at end of tick, ie after overlays, precipitation, specials) -boolean LUAh_MobjHook(mobj_t *mo, enum hook which); -boolean LUAh_PlayerHook(player_t *plr, enum hook which); -#define LUAh_MobjSpawn(mo) LUAh_MobjHook(mo, hook_MobjSpawn) // Hook for P_SpawnMobj by mobj type -UINT8 LUAh_MobjCollideHook(mobj_t *thing1, mobj_t *thing2, enum hook which); -UINT8 LUAh_MobjLineCollideHook(mobj_t *thing, line_t *line, enum hook which); -#define LUAh_MobjCollide(thing1, thing2) LUAh_MobjCollideHook(thing1, thing2, hook_MobjCollide) // Hook for PIT_CheckThing by (thing) mobj type -#define LUAh_MobjLineCollide(thing, line) LUAh_MobjLineCollideHook(thing, line, hook_MobjLineCollide) // Hook for PIT_CheckThing by (thing) mobj type -#define LUAh_MobjMoveCollide(thing1, thing2) LUAh_MobjCollideHook(thing1, thing2, hook_MobjMoveCollide) // Hook for PIT_CheckThing by (tmthing) mobj type -boolean LUAh_TouchSpecial(mobj_t *special, mobj_t *toucher); // Hook for P_TouchSpecialThing by mobj type -#define LUAh_MobjFuse(mo) LUAh_MobjHook(mo, hook_MobjFuse) // Hook for mobj->fuse == 0 by mobj type -boolean LUAh_MobjThinker(mobj_t *mo); // Hook for P_MobjThinker or P_SceneryThinker by mobj type -#define LUAh_BossThinker(mo) LUAh_MobjHook(mo, hook_BossThinker) // Hook for P_GenericBossThinker by mobj type -UINT8 LUAh_ShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype); // Hook for P_DamageMobj by mobj type (Should mobj take damage?) -boolean LUAh_MobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype); // Hook for P_DamageMobj by mobj type (Mobj actually takes damage!) -boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damagetype); // Hook for P_KillMobj by mobj type -#define LUAh_BossDeath(mo) LUAh_MobjHook(mo, hook_BossDeath) // Hook for A_BossDeath by mobj type -#define LUAh_MobjRemoved(mo) LUAh_MobjHook(mo, hook_MobjRemoved) // Hook for P_RemoveMobj by mobj type -#define LUAh_JumpSpecial(player) LUAh_PlayerHook(player, hook_JumpSpecial) // Hook for P_DoJumpStuff (Any-jumping) -#define LUAh_AbilitySpecial(player) LUAh_PlayerHook(player, hook_AbilitySpecial) // Hook for P_DoJumpStuff (Double-jumping) -#define LUAh_SpinSpecial(player) LUAh_PlayerHook(player, hook_SpinSpecial) // Hook for P_DoSpinAbility (Spin button effect) -#define LUAh_JumpSpinSpecial(player) LUAh_PlayerHook(player, hook_JumpSpinSpecial) // Hook for P_DoJumpStuff (Spin button effect (mid-air)) -boolean LUAh_BotTiccmd(player_t *bot, ticcmd_t *cmd); // Hook for B_BuildTiccmd -boolean LUAh_BotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd); // Hook for B_BuildTailsTiccmd by skin name -boolean LUAh_BotRespawn(mobj_t *sonic, mobj_t *tails); // Hook for B_CheckRespawn -boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo, sector_t *sector); // Hook for linedef executors -boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg, int mute); // Hook for chat messages -boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8 damagetype); // Hook for hurt messages -#define LUAh_PlayerSpawn(player) LUAh_PlayerHook(player, hook_PlayerSpawn) // Hook for G_SpawnPlayer -void LUAh_PlayerQuit(player_t *plr, kickreason_t reason); // Hook for player quitting -boolean LUAh_MusicChange(const char *oldname, char *newname, UINT16 *mflags, boolean *looping, - UINT32 *position, UINT32 *prefadems, UINT32 *fadeinms); // Hook for music changes +void LUA_HookVoid(int hook); +void LUA_HookHUD(huddrawlist_h, int hook); -boolean LUAh_PlayerCmd(player_t *player, ticcmd_t *cmd); // Allows to write to player cmd before the game does anything with them. +int LUA_HookMobj(mobj_t *, int hook); +int LUA_Hook2Mobj(mobj_t *, mobj_t *, int hook); +void LUA_HookInt(INT32 integer, int hook); +void LUA_HookBool(boolean value, int hook); +int LUA_HookPlayer(player_t *, int hook); +int LUA_HookTiccmd(player_t *, ticcmd_t *, int hook); +int LUA_HookKey(event_t *event, int hook); // Hooks for key events -void LUAh_IntermissionThinker(void); // Hook for Y_Ticker -void LUAh_VoteThinker(void); // Hook for Y_VoteTicker - -#define LUAh_MobjMoveBlocked(mo) LUAh_MobjHook(mo, hook_MobjMoveBlocked) // Hook for P_XYMovement (when movement is blocked) -boolean LUAh_MapThingSpawn(mobj_t *mo, mapthing_t *mthing); // Hook for P_SpawnMapThing by mobj type -boolean LUAh_FollowMobj(player_t *player, mobj_t *mobj); // Hook for P_PlayerAfterThink Smiles mobj-following -UINT8 LUAh_PlayerCanDamage(player_t *player, mobj_t *mobj); // Hook for P_PlayerCanDamage -boolean LUAh_TeamSwitch(player_t *player, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble); // Hook for team switching in... uh.... -UINT8 LUAh_ViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced); // Hook for spy mode -#define LUAh_PlayerThink(player) LUAh_PlayerHook(player, hook_PlayerThink) // Hook for P_PlayerThink -boolean LUAh_ShouldJingleContinue(player_t *player, const char *musname); // Hook for whether a jingle of the given music should continue playing -void LUAh_GameQuit(boolean quitting); // Hook for game quitting +void LUA_HookThinkFrame(void); +int LUA_HookMobjLineCollide(mobj_t *, line_t *); +int LUA_HookTouchSpecial(mobj_t *special, mobj_t *toucher); +int LUA_HookShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype); +int LUA_HookMobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype); +int LUA_HookMobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damagetype); +int LUA_HookMobjMoveBlocked(mobj_t *, mobj_t *, line_t *); +void LUA_HookLinedefExecute(line_t *, mobj_t *, sector_t *); +int LUA_HookPlayerMsg(int source, int target, int flags, char *msg, int mute); +int LUA_HookHurtMsg(player_t *, mobj_t *inflictor, mobj_t *source, UINT8 damagetype); +int LUA_HookMapThingSpawn(mobj_t *, mapthing_t *); +int LUA_HookFollowMobj(player_t *, mobj_t *); +int LUA_HookPlayerCanDamage(player_t *, mobj_t *); +void LUA_HookPlayerQuit(player_t *, kickreason_t); +int LUA_HookTeamSwitch(player_t *, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble); +int LUA_HookViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced); +int LUA_HookSeenPlayer(player_t *player, player_t *seenfriend); +int LUA_HookShouldJingleContinue(player_t *, const char *musname); +int LUA_HookMusicChange(const char *oldname, struct MusicChange *); diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c index 771182a59..8d47b7389 100644 --- a/src/lua_hooklib.c +++ b/src/lua_hooklib.c @@ -1,7 +1,7 @@ // SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 2012-2016 by John "JTE" Muniz. -// Copyright (C) 2012-2020 by Sonic Team Junior. +// Copyright (C) 2012-2022 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -27,1939 +27,1029 @@ #include "d_netcmd.h" // for cv_perfstats #include "i_system.h" // I_GetPreciseTime -static UINT8 hooksAvailable[(hook_MAX/8)+1]; +/* ========================================================================= + ABSTRACTION + ========================================================================= */ -const char *const hookNames[hook_MAX+1] = { - "NetVars", - "MapChange", - "MapLoad", - "PlayerJoin", - "PreThinkFrame", - "ThinkFrame", - "PostThinkFrame", - "MobjSpawn", - "MobjCollide", - "MobjLineCollide", - "MobjMoveCollide", - "TouchSpecial", - "MobjFuse", - "MobjThinker", - "BossThinker", - "ShouldDamage", - "MobjDamage", - "MobjDeath", - "BossDeath", - "MobjRemoved", - "JumpSpecial", - "AbilitySpecial", - "SpinSpecial", - "JumpSpinSpecial", - "BotTiccmd", - "BotAI", - "BotRespawn", - "LinedefExecute", - "PlayerMsg", - "HurtMsg", - "PlayerSpawn", - "ShieldSpawn", - "ShieldSpecial", - "MobjMoveBlocked", - "MapThingSpawn", - "FollowMobj", - "PlayerCanDamage", - "PlayerQuit", - "MusicChange", - "TeamSwitch", - "ViewpointSwitch", - "PlayerThink", - "ShouldJingleContinue", - "GameQuit", - "PlayerCmd", +#define LIST(id, M) \ + static const char * const id [] = { M (TOSTR) NULL } - // SRB2Kart - "IntermissionThinker", - "VoteThinker", +LIST (mobjHookNames, MOBJ_HOOK_LIST); +LIST (hookNames, HOOK_LIST); +LIST (hudHookNames, HUD_HOOK_LIST); +LIST (stringHookNames, STRING_HOOK_LIST); - NULL -}; +#undef LIST -// Hook metadata -struct hook_s +typedef struct { + int numHooks; + int *ids; +} hook_t; + +typedef struct { + int numGeneric; + int ref; +} stringhook_t; + +static hook_t hookIds[HOOK(MAX)]; +static hook_t hudHookIds[HUD_HOOK(MAX)]; +static hook_t mobjHookIds[NUMMOBJTYPES][MOBJ_HOOK(MAX)]; + +// Lua tables are used to lookup string hook ids. +static stringhook_t stringHooks[STRING_HOOK(MAX)]; + +// This will be indexed by hook id, the value of which fetches the registry. +static int * hookRefs; +static int nextid; + +// After a hook errors once, don't print the error again. +static UINT8 * hooksErrored; + +static int errorRef; + +static boolean mobj_hook_available(int hook_type, mobjtype_t mobj_type) { - struct hook_s *next; - enum hook type; - UINT16 id; - union { - mobjtype_t mt; - char *str; - } s; - boolean error; -}; -typedef struct hook_s* hook_p; + return + ( + mobjHookIds [MT_NULL] [hook_type].numHooks > 0 || + mobjHookIds[mobj_type][hook_type].numHooks > 0 + ); +} -#define FMT_HOOKID "hook_%d" +static int hook_in_list +( + const char * const name, + const char * const * const list +){ + int type; -// For each mobj type, a linked list to its thinker and collision hooks. -// That way, we don't have to iterate through all the hooks. -// We could do that with all other mobj hooks, but it would probably just be -// a waste of memory since they are only called occasionally. Probably... -static hook_p mobjthinkerhooks[NUMMOBJTYPES]; -static hook_p mobjcollidehooks[NUMMOBJTYPES]; + for (type = 0; list[type] != NULL; ++type) + { + if (strcmp(name, list[type]) == 0) + break; + } -// For each mobj type, a linked list for other mobj hooks -static hook_p mobjhooks[NUMMOBJTYPES]; + return type; +} -// A linked list for player hooks -static hook_p playerhooks; - -// A linked list for linedef executor hooks -static hook_p linedefexecutorhooks; - -// For other hooks, a unique linked list -hook_p roothook; - -static void PushHook(lua_State *L, hook_p hookp) +static void get_table(lua_State *L) { - lua_pushfstring(L, FMT_HOOKID, hookp->id); - lua_gettable(L, LUA_REGISTRYINDEX); + lua_pushvalue(L, -1); + lua_rawget(L, -3); + + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + lua_createtable(L, 1, 0); + lua_pushvalue(L, -2); + lua_pushvalue(L, -2); + lua_rawset(L, -5); + } + + lua_remove(L, -2); +} + +static void add_hook_to_table(lua_State *L, int n) +{ + lua_pushnumber(L, nextid); + lua_rawseti(L, -2, n); +} + +static void add_string_hook(lua_State *L, int type) +{ + stringhook_t * hook = &stringHooks[type]; + + char * string = NULL; + + switch (type) + { + case STRING_HOOK(ShouldJingleContinue): + if (lua_isstring(L, 3)) + { // lowercase copy + string = Z_StrDup(lua_tostring(L, 3)); + strlwr(string); + } + break; + + case STRING_HOOK(LinedefExecute): + string = Z_StrDup(luaL_checkstring(L, 3)); + strupr(string); + break; + } + + if (hook->ref > 0) + lua_getref(L, hook->ref); + else + { + lua_newtable(L); + lua_pushvalue(L, -1); + hook->ref = luaL_ref(L, LUA_REGISTRYINDEX); + } + + if (string) + { + lua_pushstring(L, string); + get_table(L); + add_hook_to_table(L, 1 + lua_objlen(L, -1)); + } + else + add_hook_to_table(L, ++hook->numGeneric); +} + +static void add_hook(hook_t *map) +{ + Z_Realloc(map->ids, (map->numHooks + 1) * sizeof *map->ids, + PU_STATIC, &map->ids); + map->ids[map->numHooks++] = nextid; +} + +static void add_mobj_hook(lua_State *L, int hook_type) +{ + mobjtype_t mobj_type = luaL_optnumber(L, 3, MT_NULL); + + luaL_argcheck(L, mobj_type < NUMMOBJTYPES, 3, "invalid mobjtype_t"); + + add_hook(&mobjHookIds[mobj_type][hook_type]); +} + +static void add_hud_hook(lua_State *L, int idx) +{ + add_hook(&hudHookIds[luaL_checkoption(L, + idx, "game", hudHookNames)]); +} + +static void add_hook_ref(lua_State *L, int idx) +{ + if (!(nextid & 7)) + { + Z_Realloc(hooksErrored, + BIT_ARRAY_SIZE (nextid + 1) * sizeof *hooksErrored, + PU_STATIC, &hooksErrored); + hooksErrored[nextid >> 3] = 0; + } + + Z_Realloc(hookRefs, (nextid + 1) * sizeof *hookRefs, PU_STATIC, &hookRefs); + + // set the hook function in the registry. + lua_pushvalue(L, idx); + hookRefs[nextid++] = luaL_ref(L, LUA_REGISTRYINDEX); } // Takes hook, function, and additional arguments (mobj type to act on, etc.) static int lib_addHook(lua_State *L) { - static struct hook_s hook = {NULL, 0, 0, {0}, false}; - static UINT32 nextid; - hook_p hookp, *lastp; - - hook.type = luaL_checkoption(L, 1, NULL, hookNames); - lua_remove(L, 1); - - luaL_checktype(L, 1, LUA_TFUNCTION); + const char * name; + int type; if (!lua_lumploading) return luaL_error(L, "This function cannot be called from within a hook or coroutine!"); - switch(hook.type) + name = luaL_checkstring(L, 1); + luaL_checktype(L, 2, LUA_TFUNCTION); + + /* this is a very special case */ + if (( type = hook_in_list(name, stringHookNames) ) < STRING_HOOK(MAX)) { - // Take a mobjtype enum which this hook is specifically for. - case hook_MobjSpawn: - case hook_MobjCollide: - case hook_MobjLineCollide: - case hook_MobjMoveCollide: - case hook_TouchSpecial: - case hook_MobjFuse: - case hook_MobjThinker: - case hook_BossThinker: - case hook_ShouldDamage: - case hook_MobjDamage: - case hook_MobjDeath: - case hook_BossDeath: - case hook_MobjRemoved: - case hook_HurtMsg: - case hook_MobjMoveBlocked: - case hook_MapThingSpawn: - case hook_FollowMobj: - hook.s.mt = MT_NULL; - if (lua_isnumber(L, 2)) - hook.s.mt = lua_tonumber(L, 2); - luaL_argcheck(L, hook.s.mt < NUMMOBJTYPES, 2, "invalid mobjtype_t"); - break; - case hook_BotAI: - case hook_ShouldJingleContinue: - hook.s.str = NULL; - if (lua_isstring(L, 2)) - { // lowercase copy - hook.s.str = Z_StrDup(lua_tostring(L, 2)); - strlwr(hook.s.str); - } - break; - case hook_LinedefExecute: // Linedef executor functions - hook.s.str = Z_StrDup(luaL_checkstring(L, 2)); - strupr(hook.s.str); - break; - default: - break; + add_string_hook(L, type); } - lua_settop(L, 1); // lua stack contains only the function now. - - hooksAvailable[hook.type/8] |= 1<<(hook.type%8); - - // set hook.id to the highest id + 1 - hook.id = nextid++; - - // Special cases for some hook types (see the comments above mobjthinkerhooks declaration) - switch(hook.type) + else if (( type = hook_in_list(name, mobjHookNames) ) < MOBJ_HOOK(MAX)) { - case hook_MobjThinker: - lastp = &mobjthinkerhooks[hook.s.mt]; - break; - case hook_MobjCollide: - case hook_MobjLineCollide: - case hook_MobjMoveCollide: - lastp = &mobjcollidehooks[hook.s.mt]; - break; - case hook_MobjSpawn: - case hook_TouchSpecial: - case hook_MobjFuse: - case hook_BossThinker: - case hook_ShouldDamage: - case hook_MobjDamage: - case hook_MobjDeath: - case hook_BossDeath: - case hook_MobjRemoved: - case hook_MobjMoveBlocked: - case hook_MapThingSpawn: - case hook_FollowMobj: - lastp = &mobjhooks[hook.s.mt]; - break; - case hook_JumpSpecial: - case hook_AbilitySpecial: - case hook_SpinSpecial: - case hook_JumpSpinSpecial: - case hook_PlayerSpawn: - case hook_PlayerCanDamage: - case hook_TeamSwitch: - case hook_ViewpointSwitch: - case hook_ShieldSpawn: - case hook_ShieldSpecial: - case hook_PlayerThink: - lastp = &playerhooks; - break; - case hook_LinedefExecute: - lastp = &linedefexecutorhooks; - break; - default: - lastp = &roothook; - break; + add_mobj_hook(L, type); + } + else if (( type = hook_in_list(name, hookNames) ) < HOOK(MAX)) + { + add_hook(&hookIds[type]); + } + else if (strcmp(name, "HUD") == 0) + { + add_hud_hook(L, 3); + } + else + { + return luaL_argerror(L, 1, lua_pushfstring(L, "invalid hook " LUA_QS, name)); } - // iterate the hook metadata structs - // set lastp to the last hook struct's "next" pointer. - for (hookp = *lastp; hookp; hookp = hookp->next) - lastp = &hookp->next; - // allocate a permanent memory struct to stuff hook. - hookp = ZZ_Alloc(sizeof(struct hook_s)); - memcpy(hookp, &hook, sizeof(struct hook_s)); - // tack it onto the end of the linked list. - *lastp = hookp; + add_hook_ref(L, 2);/* the function */ - // set the hook function in the registry. - lua_pushfstring(L, FMT_HOOKID, hook.id); - lua_pushvalue(L, 1); - lua_settable(L, LUA_REGISTRYINDEX); return 0; } int LUA_HookLib(lua_State *L) { - memset(hooksAvailable,0,sizeof(UINT8[(hook_MAX/8)+1])); - roothook = NULL; + lua_pushcfunction(L, LUA_GetErrorMessage); + errorRef = luaL_ref(L, LUA_REGISTRYINDEX); + lua_register(L, "addHook", lib_addHook); + return 0; } -boolean LUAh_MobjHook(mobj_t *mo, enum hook which) +/* TODO: remove in next backwards incompatible release */ +int lib_hudadd(lua_State *L);/* yeah compiler */ +int lib_hudadd(lua_State *L) { - hook_p hookp; - boolean hooked = false; - if (!gL || !(hooksAvailable[which/8] & (1<<(which%8)))) - return false; + if (!lua_lumploading) + return luaL_error(L, "This function cannot be called from within a hook or coroutine!"); - I_Assert(mo->type < NUMMOBJTYPES); + luaL_checktype(L, 1, LUA_TFUNCTION); - if (!(mobjhooks[MT_NULL] || mobjhooks[mo->type])) - return false; + add_hud_hook(L, 2); + add_hook_ref(L, 1); + return 0; +} + +typedef struct Hook_State Hook_State; +typedef void (*Hook_Callback)(Hook_State *); + +struct Hook_State { + INT32 status;/* return status to calling function */ + void * userdata; + int hook_type; + mobjtype_t mobj_type;/* >0 if mobj hook */ + const char * string;/* used to fetch table, ran first if set */ + int top;/* index of last argument passed to hook */ + int id;/* id to fetch ref */ + int values;/* num arguments passed to hook */ + int results;/* num values returned by hook */ + Hook_Callback results_handler;/* callback when hook successfully returns */ +}; + +enum { + EINDEX = 1,/* error handler */ + SINDEX = 2,/* string itself is pushed in case of string hook */ +}; + +static void push_error_handler(void) +{ + lua_getref(gL, errorRef); +} + +/* repush hook string */ +static void push_string(void) +{ + lua_pushvalue(gL, SINDEX); +} + +static boolean begin_hook_values(Hook_State *hook) +{ + hook->top = lua_gettop(gL); + return true; +} + +static void start_hook_stack(void) +{ lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); + push_error_handler(); +} - // Look for all generic mobj hooks - for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next) +static boolean init_hook_type +( + Hook_State * hook, + int status, + int hook_type, + mobjtype_t mobj_type, + const char * string, + int nonzero +){ + hook->status = status; + + if (nonzero) { - if (hookp->type != which) - continue; + start_hook_stack(); + hook->hook_type = hook_type; + hook->mobj_type = mobj_type; + hook->string = string; + return begin_hook_values(hook); + } + else + return false; +} - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - LUA_PushUserdata(gL, mo, META_MOBJ); - PushHook(gL, hookp); - lua_pushvalue(gL, -2); - if (lua_pcall(gL, 1, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; +static boolean prepare_hook +( + Hook_State * hook, + int default_status, + int hook_type +){ + return init_hook_type(hook, default_status, + hook_type, 0, NULL, + hookIds[hook_type].numHooks); +} + +static boolean prepare_mobj_hook +( + Hook_State * hook, + int default_status, + int hook_type, + mobjtype_t mobj_type +){ +#ifdef PARANOIA + if (mobj_type == MT_NULL) + I_Error("MT_NULL has been passed to a mobj hook\n"); +#endif + return init_hook_type(hook, default_status, + hook_type, mobj_type, NULL, + mobj_hook_available(hook_type, mobj_type)); +} + +static boolean prepare_string_hook +( + Hook_State * hook, + int default_status, + int hook_type, + const char * string +){ + if (init_hook_type(hook, default_status, + hook_type, 0, string, + stringHooks[hook_type].ref)) + { + lua_pushstring(gL, string); + return begin_hook_values(hook); + } + else + return false; +} + +static void init_hook_call +( + Hook_State * hook, + int results, + Hook_Callback results_handler +){ + const int top = lua_gettop(gL); + hook->values = (top - hook->top); + hook->top = top; + hook->results = results; + hook->results_handler = results_handler; +} + +static void get_hook(Hook_State *hook, const int *ids, int n) +{ + hook->id = ids[n]; + lua_getref(gL, hookRefs[hook->id]); +} + +static void get_hook_from_table(Hook_State *hook, int n) +{ + lua_rawgeti(gL, -1, n); + hook->id = lua_tonumber(gL, -1); + lua_pop(gL, 1); + lua_getref(gL, hookRefs[hook->id]); +} + +static int call_single_hook_no_copy(Hook_State *hook) +{ + if (lua_pcall(gL, hook->values, hook->results, EINDEX) == 0) + { + if (hook->results > 0) + { + (*hook->results_handler)(hook); + lua_pop(gL, hook->results); + } + } + else + { + /* print the error message once */ + if (cv_debug & DBG_LUA || !in_bit_array(hooksErrored, hook->id)) + { + CONS_Alert(CONS_WARNING, "%s\n", lua_tostring(gL, -1)); + set_bit_array(hooksErrored, hook->id); } - if (lua_toboolean(gL, -1)) - hooked = true; lua_pop(gL, 1); } - for (hookp = mobjhooks[mo->type]; hookp; hookp = hookp->next) - { - if (hookp->type != which) - continue; + return 1; +} - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - LUA_PushUserdata(gL, mo, META_MOBJ); - PushHook(gL, hookp); - lua_pushvalue(gL, -2); - if (lua_pcall(gL, 1, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } +static int call_single_hook(Hook_State *hook) +{ + int i; + + for (i = -(hook->values) + 1; i <= 0; ++i) + lua_pushvalue(gL, hook->top + i); + + return call_single_hook_no_copy(hook); +} + +static int call_hook_table_for(Hook_State *hook, int n) +{ + int k; + + for (k = 1; k <= n; ++k) + { + get_hook_from_table(hook, k); + call_single_hook(hook); + } + + return n; +} + +static int call_hook_table(Hook_State *hook) +{ + return call_hook_table_for(hook, lua_objlen(gL, -1)); +} + +static int call_mapped(Hook_State *hook, const hook_t *map) +{ + int k; + + for (k = 0; k < map->numHooks; ++k) + { + get_hook(hook, map->ids, k); + call_single_hook(hook); + } + + return map->numHooks; +} + +static int call_string_hooks(Hook_State *hook) +{ + const stringhook_t *map = &stringHooks[hook->hook_type]; + + int calls = 0; + + lua_getref(gL, map->ref); + + /* call generic string hooks first */ + calls += call_hook_table_for(hook, map->numGeneric); + + push_string(); + lua_rawget(gL, -2); + calls += call_hook_table(hook); + + return calls; +} + +static int call_mobj_type_hooks(Hook_State *hook, mobjtype_t mobj_type) +{ + return call_mapped(hook, &mobjHookIds[mobj_type][hook->hook_type]); +} + +static int call_hooks +( + Hook_State * hook, + int results, + Hook_Callback results_handler +){ + int calls = 0; + + init_hook_call(hook, results, results_handler); + + if (hook->string) + { + calls += call_string_hooks(hook); + } + else if (hook->mobj_type > 0) + { + /* call generic mobj hooks first */ + calls += call_mobj_type_hooks(hook, MT_NULL); + calls += call_mobj_type_hooks(hook, hook->mobj_type); + + ps_lua_mobjhooks += calls; + } + else + calls += call_mapped(hook, &hookIds[hook->hook_type]); + + lua_settop(gL, 0); + + return calls; +} + +/* ========================================================================= + COMMON RESULT HANDLERS + ========================================================================= */ + +#define res_none NULL + +static void res_true(Hook_State *hook) +{ + if (lua_toboolean(gL, -1)) + hook->status = true; +} + +static void res_false(Hook_State *hook) +{ + if (!lua_isnil(gL, -1) && !lua_toboolean(gL, -1)) + hook->status = false; +} + +static void res_force(Hook_State *hook) +{ + if (!lua_isnil(gL, -1)) + { if (lua_toboolean(gL, -1)) - hooked = true; - lua_pop(gL, 1); + hook->status = 1; // Force yes + else + hook->status = 2; // Force no } - - lua_settop(gL, 0); - return hooked; } -boolean LUAh_PlayerHook(player_t *plr, enum hook which) +/* ========================================================================= + GENERALISED HOOKS + ========================================================================= */ + +int LUA_HookMobj(mobj_t *mobj, int hook_type) { - hook_p hookp; - boolean hooked = false; - if (!gL || !(hooksAvailable[which/8] & (1<<(which%8)))) - return false; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - for (hookp = playerhooks; hookp; hookp = hookp->next) + Hook_State hook; + if (prepare_mobj_hook(&hook, false, hook_type, mobj->type)) { - if (hookp->type != which) - continue; - - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - LUA_PushUserdata(gL, plr, META_PLAYER); - PushHook(gL, hookp); - lua_pushvalue(gL, -2); - if (lua_pcall(gL, 1, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (lua_toboolean(gL, -1)) - hooked = true; - lua_pop(gL, 1); + LUA_PushUserdata(gL, mobj, META_MOBJ); + call_hooks(&hook, 1, res_true); } - - lua_settop(gL, 0); - return hooked; + return hook.status; } -// Hook for map change (before load) -void LUAh_MapChange(INT16 mapnumber) +int LUA_Hook2Mobj(mobj_t *t1, mobj_t *t2, int hook_type) { - hook_p hookp; - if (!gL || !(hooksAvailable[hook_MapChange/8] & (1<<(hook_MapChange%8)))) - return; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - lua_pushinteger(gL, mapnumber); - - for (hookp = roothook; hookp; hookp = hookp->next) + Hook_State hook; + if (prepare_mobj_hook(&hook, 0, hook_type, t1->type)) { - if (hookp->type != hook_MapChange) - continue; - - PushHook(gL, hookp); - lua_pushvalue(gL, -2); - if (lua_pcall(gL, 1, 0, 1)) { - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - } + LUA_PushUserdata(gL, t1, META_MOBJ); + LUA_PushUserdata(gL, t2, META_MOBJ); + call_hooks(&hook, 1, res_force); } - - lua_settop(gL, 0); + return hook.status; } -// Hook for map load -void LUAh_MapLoad(void) +void LUA_HookVoid(int type) { - hook_p hookp; - if (!gL || !(hooksAvailable[hook_MapLoad/8] & (1<<(hook_MapLoad%8)))) - return; + Hook_State hook; + if (prepare_hook(&hook, 0, type)) + call_hooks(&hook, 0, res_none); +} - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - lua_pushinteger(gL, gamemap); - - for (hookp = roothook; hookp; hookp = hookp->next) +void LUA_HookInt(INT32 number, int hook_type) +{ + Hook_State hook; + if (prepare_hook(&hook, 0, hook_type)) { - if (hookp->type != hook_MapLoad) - continue; - - PushHook(gL, hookp); - lua_pushvalue(gL, -2); - if (lua_pcall(gL, 1, 0, 1)) { - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - } + lua_pushinteger(gL, number); + call_hooks(&hook, 0, res_none); } - - lua_settop(gL, 0); } -// Hook for Got_AddPlayer -void LUAh_PlayerJoin(int playernum) +void LUA_HookBool(boolean value, int hook_type) { - hook_p hookp; - if (!gL || !(hooksAvailable[hook_PlayerJoin/8] & (1<<(hook_PlayerJoin%8)))) - return; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - lua_pushinteger(gL, playernum); - - for (hookp = roothook; hookp; hookp = hookp->next) + Hook_State hook; + if (prepare_hook(&hook, 0, hook_type)) { - if (hookp->type != hook_PlayerJoin) - continue; - - PushHook(gL, hookp); - lua_pushvalue(gL, -2); - if (lua_pcall(gL, 1, 0, 1)) { - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - } + lua_pushboolean(gL, value); + call_hooks(&hook, 0, res_none); } - - lua_settop(gL, 0); } -// Hook for frame (before mobj and player thinkers) -void LUAh_PreThinkFrame(void) +int LUA_HookPlayer(player_t *player, int hook_type) { - hook_p hookp; - if (!gL || !(hooksAvailable[hook_PreThinkFrame/8] & (1<<(hook_PreThinkFrame%8)))) - return; - - lua_pushcfunction(gL, LUA_GetErrorMessage); - - for (hookp = roothook; hookp; hookp = hookp->next) + Hook_State hook; + if (prepare_hook(&hook, false, hook_type)) { - if (hookp->type != hook_PreThinkFrame) - continue; - - PushHook(gL, hookp); - if (lua_pcall(gL, 0, 0, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - } + LUA_PushUserdata(gL, player, META_PLAYER); + call_hooks(&hook, 1, res_true); } - - lua_pop(gL, 1); // Pop error handler + return hook.status; } -// Hook for frame (after mobj and player thinkers) -void LUAh_ThinkFrame(void) +int LUA_HookTiccmd(player_t *player, ticcmd_t *cmd, int hook_type) { - hook_p hookp; + Hook_State hook; + if (prepare_hook(&hook, false, hook_type)) + { + LUA_PushUserdata(gL, player, META_PLAYER); + LUA_PushUserdata(gL, cmd, META_TICCMD); + + if (hook_type == HOOK(PlayerCmd)) + hook_cmd_running = true; + + call_hooks(&hook, 1, res_true); + + if (hook_type == HOOK(PlayerCmd)) + hook_cmd_running = false; + } + return hook.status; +} + +void LUA_HookHUD(huddrawlist_h list, int hook_type) +{ + const hook_t * map = &hudHookIds[hook_type]; + Hook_State hook; + if (map->numHooks > 0) + { + start_hook_stack(); + begin_hook_values(&hook); + + LUA_SetHudHook(hook_type, list); + + hud_running = true; // local hook + init_hook_call(&hook, 0, res_none); + call_mapped(&hook, map); + hud_running = false; + } +} + +/* ========================================================================= + SPECIALIZED HOOKS + ========================================================================= */ + +void LUA_HookThinkFrame(void) +{ + const int type = HOOK(ThinkFrame); + // variables used by perf stats int hook_index = 0; precise_t time_taken = 0; - if (!gL || !(hooksAvailable[hook_ThinkFrame/8] & (1<<(hook_ThinkFrame%8)))) - return; - lua_pushcfunction(gL, LUA_GetErrorMessage); + Hook_State hook; - for (hookp = roothook; hookp; hookp = hookp->next) + const hook_t * map = &hookIds[type]; + int k; + + if (prepare_hook(&hook, 0, type)) { - if (hookp->type != hook_ThinkFrame) - continue; + init_hook_call(&hook, 0, res_none); - if (cv_perfstats.value == PS_THINKFRAME) - time_taken = I_GetPreciseTime(); - PushHook(gL, hookp); - if (lua_pcall(gL, 0, 0, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - } - if (cv_perfstats.value == PS_THINKFRAME) + for (k = 0; k < map->numHooks; ++k) { - lua_Debug ar; - time_taken = I_GetPreciseTime() - time_taken; - // we need the function, let's just retrieve it again - PushHook(gL, hookp); - lua_getinfo(gL, ">S", &ar); - PS_SetThinkFrameHookInfo(hook_index, time_taken, ar.short_src); - hook_index++; - } - } + get_hook(&hook, map->ids, k); - lua_pop(gL, 1); // Pop error handler -} - -// Hook for frame (at end of tick, ie after overlays, precipitation, specials) -void LUAh_PostThinkFrame(void) -{ - hook_p hookp; - if (!gL || !(hooksAvailable[hook_PostThinkFrame/8] & (1<<(hook_PostThinkFrame%8)))) - return; - - lua_pushcfunction(gL, LUA_GetErrorMessage); - - for (hookp = roothook; hookp; hookp = hookp->next) - { - if (hookp->type != hook_PostThinkFrame) - continue; - - PushHook(gL, hookp); - if (lua_pcall(gL, 0, 0, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - } - } - - lua_pop(gL, 1); // Pop error handler -} - -// Hook for mobj collisions -UINT8 LUAh_MobjCollideHook(mobj_t *thing1, mobj_t *thing2, enum hook which) -{ - hook_p hookp; - UINT8 shouldCollide = 0; // 0 = default, 1 = force yes, 2 = force no. - if (!gL || !(hooksAvailable[which/8] & (1<<(which%8)))) - return 0; - - I_Assert(thing1->type < NUMMOBJTYPES); - - if (!(mobjcollidehooks[MT_NULL] || mobjcollidehooks[thing1->type])) - return 0; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - // Look for all generic mobj collision hooks - for (hookp = mobjcollidehooks[MT_NULL]; hookp; hookp = hookp->next) - { - if (hookp->type != which) - continue; - - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, thing1, META_MOBJ); - LUA_PushUserdata(gL, thing2, META_MOBJ); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -3); - lua_pushvalue(gL, -3); - if (lua_pcall(gL, 2, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (!lua_isnil(gL, -1)) - { // if nil, leave shouldCollide = 0. - if (lua_toboolean(gL, -1)) - shouldCollide = 1; // Force yes - else - shouldCollide = 2; // Force no - } - lua_pop(gL, 1); - } - - for (hookp = mobjcollidehooks[thing1->type]; hookp; hookp = hookp->next) - { - if (hookp->type != which) - continue; - - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, thing1, META_MOBJ); - LUA_PushUserdata(gL, thing2, META_MOBJ); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -3); - lua_pushvalue(gL, -3); - if (lua_pcall(gL, 2, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (!lua_isnil(gL, -1)) - { // if nil, leave shouldCollide = 0. - if (lua_toboolean(gL, -1)) - shouldCollide = 1; // Force yes - else - shouldCollide = 2; // Force no - } - lua_pop(gL, 1); - } - - lua_settop(gL, 0); - return shouldCollide; -} - -UINT8 LUAh_MobjLineCollideHook(mobj_t *thing, line_t *line, enum hook which) -{ - hook_p hookp; - UINT8 shouldCollide = 0; // 0 = default, 1 = force yes, 2 = force no. - if (!gL || !(hooksAvailable[which/8] & (1<<(which%8)))) - return 0; - - I_Assert(thing->type < NUMMOBJTYPES); - - if (!(mobjcollidehooks[MT_NULL] || mobjcollidehooks[thing->type])) - return 0; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - // Look for all generic mobj collision hooks - for (hookp = mobjcollidehooks[MT_NULL]; hookp; hookp = hookp->next) - { - if (hookp->type != which) - continue; - - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, thing, META_MOBJ); - LUA_PushUserdata(gL, line, META_LINE); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -3); - lua_pushvalue(gL, -3); - if (lua_pcall(gL, 2, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (!lua_isnil(gL, -1)) - { // if nil, leave shouldCollide = 0. - if (lua_toboolean(gL, -1)) - shouldCollide = 1; // Force yes - else - shouldCollide = 2; // Force no - } - lua_pop(gL, 1); - } - - for (hookp = mobjcollidehooks[thing->type]; hookp; hookp = hookp->next) - { - if (hookp->type != which) - continue; - - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, thing, META_MOBJ); - LUA_PushUserdata(gL, line, META_LINE); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -3); - lua_pushvalue(gL, -3); - if (lua_pcall(gL, 2, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (!lua_isnil(gL, -1)) - { // if nil, leave shouldCollide = 0. - if (lua_toboolean(gL, -1)) - shouldCollide = 1; // Force yes - else - shouldCollide = 2; // Force no - } - lua_pop(gL, 1); - } - - lua_settop(gL, 0); - return shouldCollide; -} - -// Hook for mobj thinkers -boolean LUAh_MobjThinker(mobj_t *mo) -{ - hook_p hookp; - boolean hooked = false; - if (!gL || !(hooksAvailable[hook_MobjThinker/8] & (1<<(hook_MobjThinker%8)))) - return false; - - I_Assert(mo->type < NUMMOBJTYPES); - - if (!(mobjthinkerhooks[MT_NULL] || mobjthinkerhooks[mo->type])) - return false; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - // Look for all generic mobj thinker hooks - for (hookp = mobjthinkerhooks[MT_NULL]; hookp; hookp = hookp->next) - { - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - LUA_PushUserdata(gL, mo, META_MOBJ); - PushHook(gL, hookp); - lua_pushvalue(gL, -2); - if (lua_pcall(gL, 1, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (lua_toboolean(gL, -1)) - hooked = true; - lua_pop(gL, 1); - } - - for (hookp = mobjthinkerhooks[mo->type]; hookp; hookp = hookp->next) - { - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - LUA_PushUserdata(gL, mo, META_MOBJ); - PushHook(gL, hookp); - lua_pushvalue(gL, -2); - if (lua_pcall(gL, 1, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (lua_toboolean(gL, -1)) - hooked = true; - lua_pop(gL, 1); - } - - lua_settop(gL, 0); - return hooked; -} - -// Hook for P_TouchSpecialThing by mobj type -boolean LUAh_TouchSpecial(mobj_t *special, mobj_t *toucher) -{ - hook_p hookp; - boolean hooked = false; - if (!gL || !(hooksAvailable[hook_TouchSpecial/8] & (1<<(hook_TouchSpecial%8)))) - return false; - - I_Assert(special->type < NUMMOBJTYPES); - - if (!(mobjhooks[MT_NULL] || mobjhooks[special->type])) - return false; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - // Look for all generic touch special hooks - for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next) - { - if (hookp->type != hook_TouchSpecial) - continue; - - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, special, META_MOBJ); - LUA_PushUserdata(gL, toucher, META_MOBJ); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -3); - lua_pushvalue(gL, -3); - if (lua_pcall(gL, 2, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (lua_toboolean(gL, -1)) - hooked = true; - lua_pop(gL, 1); - } - - for (hookp = mobjhooks[special->type]; hookp; hookp = hookp->next) - { - if (hookp->type != hook_TouchSpecial) - continue; - - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, special, META_MOBJ); - LUA_PushUserdata(gL, toucher, META_MOBJ); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -3); - lua_pushvalue(gL, -3); - if (lua_pcall(gL, 2, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (lua_toboolean(gL, -1)) - hooked = true; - lua_pop(gL, 1); - } - - lua_settop(gL, 0); - return hooked; -} - -// Hook for P_DamageMobj by mobj type (Should mobj take damage?) -UINT8 LUAh_ShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype) -{ - hook_p hookp; - UINT8 shouldDamage = 0; // 0 = default, 1 = force yes, 2 = force no. - if (!gL || !(hooksAvailable[hook_ShouldDamage/8] & (1<<(hook_ShouldDamage%8)))) - return 0; - - I_Assert(target->type < NUMMOBJTYPES); - - if (!(mobjhooks[MT_NULL] || mobjhooks[target->type])) - return 0; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - // Look for all generic should damage hooks - for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next) - { - if (hookp->type != hook_ShouldDamage) - continue; - - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, target, META_MOBJ); - LUA_PushUserdata(gL, inflictor, META_MOBJ); - LUA_PushUserdata(gL, source, META_MOBJ); - lua_pushinteger(gL, damage); - lua_pushinteger(gL, damagetype); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - if (lua_pcall(gL, 5, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (!lua_isnil(gL, -1)) - { - if (lua_toboolean(gL, -1)) - shouldDamage = 1; // Force yes - else - shouldDamage = 2; // Force no - } - lua_pop(gL, 1); - } - - for (hookp = mobjhooks[target->type]; hookp; hookp = hookp->next) - { - if (hookp->type != hook_ShouldDamage) - continue; - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, target, META_MOBJ); - LUA_PushUserdata(gL, inflictor, META_MOBJ); - LUA_PushUserdata(gL, source, META_MOBJ); - lua_pushinteger(gL, damage); - lua_pushinteger(gL, damagetype); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - if (lua_pcall(gL, 5, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (!lua_isnil(gL, -1)) - { - if (lua_toboolean(gL, -1)) - shouldDamage = 1; // Force yes - else - shouldDamage = 2; // Force no - } - lua_pop(gL, 1); - } - - lua_settop(gL, 0); - return shouldDamage; -} - -// Hook for P_DamageMobj by mobj type (Mobj actually takes damage!) -boolean LUAh_MobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype) -{ - hook_p hookp; - boolean hooked = false; - if (!gL || !(hooksAvailable[hook_MobjDamage/8] & (1<<(hook_MobjDamage%8)))) - return false; - - I_Assert(target->type < NUMMOBJTYPES); - - if (!(mobjhooks[MT_NULL] || mobjhooks[target->type])) - return false; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - // Look for all generic mobj damage hooks - for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next) - { - if (hookp->type != hook_MobjDamage) - continue; - - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, target, META_MOBJ); - LUA_PushUserdata(gL, inflictor, META_MOBJ); - LUA_PushUserdata(gL, source, META_MOBJ); - lua_pushinteger(gL, damage); - lua_pushinteger(gL, damagetype); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - if (lua_pcall(gL, 5, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (lua_toboolean(gL, -1)) - hooked = true; - lua_pop(gL, 1); - } - - for (hookp = mobjhooks[target->type]; hookp; hookp = hookp->next) - { - if (hookp->type != hook_MobjDamage) - continue; - - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, target, META_MOBJ); - LUA_PushUserdata(gL, inflictor, META_MOBJ); - LUA_PushUserdata(gL, source, META_MOBJ); - lua_pushinteger(gL, damage); - lua_pushinteger(gL, damagetype); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - if (lua_pcall(gL, 5, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (lua_toboolean(gL, -1)) - hooked = true; - lua_pop(gL, 1); - } - - lua_settop(gL, 0); - return hooked; -} - -// Hook for P_KillMobj by mobj type -boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damagetype) -{ - hook_p hookp; - boolean hooked = false; - if (!gL || !(hooksAvailable[hook_MobjDeath/8] & (1<<(hook_MobjDeath%8)))) - return false; - - I_Assert(target->type < NUMMOBJTYPES); - - if (!(mobjhooks[MT_NULL] || mobjhooks[target->type])) - return false; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - // Look for all generic mobj death hooks - for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next) - { - if (hookp->type != hook_MobjDeath) - continue; - - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, target, META_MOBJ); - LUA_PushUserdata(gL, inflictor, META_MOBJ); - LUA_PushUserdata(gL, source, META_MOBJ); - lua_pushinteger(gL, damagetype); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -5); - lua_pushvalue(gL, -5); - lua_pushvalue(gL, -5); - lua_pushvalue(gL, -5); - if (lua_pcall(gL, 4, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (lua_toboolean(gL, -1)) - hooked = true; - lua_pop(gL, 1); - } - - for (hookp = mobjhooks[target->type]; hookp; hookp = hookp->next) - { - if (hookp->type != hook_MobjDeath) - continue; - - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, target, META_MOBJ); - LUA_PushUserdata(gL, inflictor, META_MOBJ); - LUA_PushUserdata(gL, source, META_MOBJ); - lua_pushinteger(gL, damagetype); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -5); - lua_pushvalue(gL, -5); - lua_pushvalue(gL, -5); - lua_pushvalue(gL, -5); - if (lua_pcall(gL, 4, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (lua_toboolean(gL, -1)) - hooked = true; - lua_pop(gL, 1); - } - - lua_settop(gL, 0); - return hooked; -} - -// Hook for B_BuildTiccmd -boolean LUAh_BotTiccmd(player_t *bot, ticcmd_t *cmd) -{ - hook_p hookp; - boolean hooked = false; - if (!gL || !(hooksAvailable[hook_BotTiccmd/8] & (1<<(hook_BotTiccmd%8)))) - return false; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - for (hookp = roothook; hookp; hookp = hookp->next) - { - if (hookp->type != hook_BotTiccmd) - continue; - - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, bot, META_PLAYER); - LUA_PushUserdata(gL, cmd, META_TICCMD); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -3); - lua_pushvalue(gL, -3); - if (lua_pcall(gL, 2, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (lua_toboolean(gL, -1)) - hooked = true; - lua_pop(gL, 1); - } - - lua_settop(gL, 0); - return hooked; -} - -// Hook for B_BuildTailsTiccmd by skin name -boolean LUAh_BotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd) -{ -#if 1 - (void)sonic; - (void)tails; - (void)cmd; - return false; -#else - hook_p hookp; - boolean hooked = false; - if (!gL || !(hooksAvailable[hook_BotAI/8] & (1<<(hook_BotAI%8)))) - return false; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - for (hookp = roothook; hookp; hookp = hookp->next) - { - if (hookp->type != hook_BotAI - || (hookp->s.str && strcmp(hookp->s.str, ((skin_t*)tails->skin)->name))) - continue; - - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, sonic, META_MOBJ); - LUA_PushUserdata(gL, tails, META_MOBJ); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -3); - lua_pushvalue(gL, -3); - if (lua_pcall(gL, 2, 8, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - - // This turns forward, backward, left, right, jump, and spin into a proper ticcmd for tails. - if (lua_istable(gL, 2+1)) { - boolean forward=false, backward=false, left=false, right=false, strafeleft=false, straferight=false, jump=false, spin=false; -#define CHECKFIELD(field) \ - lua_getfield(gL, 2+1, #field);\ - if (lua_toboolean(gL, -1))\ - field = true;\ - lua_pop(gL, 1); - - CHECKFIELD(forward) - CHECKFIELD(backward) - CHECKFIELD(left) - CHECKFIELD(right) - CHECKFIELD(strafeleft) - CHECKFIELD(straferight) - CHECKFIELD(jump) - CHECKFIELD(spin) -#undef CHECKFIELD - B_KeysToTiccmd(tails, cmd, forward, backward, left, right, strafeleft, straferight, jump, spin); - } else - B_KeysToTiccmd(tails, cmd, lua_toboolean(gL, 2+1), lua_toboolean(gL, 2+2), lua_toboolean(gL, 2+3), lua_toboolean(gL, 2+4), lua_toboolean(gL, 2+5), lua_toboolean(gL, 2+6), lua_toboolean(gL, 2+7), lua_toboolean(gL, 2+8)); - - lua_pop(gL, 8); - hooked = true; - } - - lua_settop(gL, 0); - return hooked; -#endif -} - -// Hook for B_CheckRespawn -boolean LUAh_BotRespawn(mobj_t *sonic, mobj_t *tails) -{ - hook_p hookp; - UINT8 shouldRespawn = 0; // 0 = default, 1 = force yes, 2 = force no. - if (!gL || !(hooksAvailable[hook_BotRespawn/8] & (1<<(hook_BotRespawn%8)))) - return false; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - for (hookp = roothook; hookp; hookp = hookp->next) - { - if (hookp->type != hook_BotRespawn) - continue; - - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, sonic, META_MOBJ); - LUA_PushUserdata(gL, tails, META_MOBJ); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -3); - lua_pushvalue(gL, -3); - if (lua_pcall(gL, 2, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (!lua_isnil(gL, -1)) - { - if (lua_toboolean(gL, -1)) - shouldRespawn = 1; // Force yes - else - shouldRespawn = 2; // Force no - } - lua_pop(gL, 1); - } - - lua_settop(gL, 0); - return shouldRespawn; -} - -// Hook for linedef executors -boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo, sector_t *sector) -{ - hook_p hookp; - boolean hooked = false; - if (!gL || !(hooksAvailable[hook_LinedefExecute/8] & (1<<(hook_LinedefExecute%8)))) - return 0; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - for (hookp = linedefexecutorhooks; hookp; hookp = hookp->next) - { - if (strcmp(hookp->s.str, line->stringargs[0])) - continue; - - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, line, META_LINE); - LUA_PushUserdata(gL, mo, META_MOBJ); - LUA_PushUserdata(gL, sector, META_SECTOR); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -4); - lua_pushvalue(gL, -4); - lua_pushvalue(gL, -4); - if (lua_pcall(gL, 3, 0, 1)) { - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - } - hooked = true; - } - - lua_settop(gL, 0); - return hooked; -} - -// Hook for player chat -// Added the "mute" field. It's set to true if the message was supposed to be eaten by spam protection. -// But for netgame consistency purposes, this hook is ran first reguardless, so this boolean allows for modders to adapt if they so desire. -boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg, int mute) -{ - hook_p hookp; - boolean hooked = false; - if (!gL || !(hooksAvailable[hook_PlayerMsg/8] & (1<<(hook_PlayerMsg%8)))) - return false; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - for (hookp = roothook; hookp; hookp = hookp->next) - { - if (hookp->type != hook_PlayerMsg) - continue; - - if (lua_gettop(gL) == 1) - { - if (lua_gettop(gL) == 0) + if (cv_perfstats.value == 3) { - LUA_PushUserdata(gL, &players[source], META_PLAYER); // Source player - if (flags & 2 /*HU_CSAY*/) { // csay TODO: make HU_CSAY accessible outside hu_stuff.c - lua_pushinteger(gL, 3); // type - lua_pushnil(gL); // target - } else if (target == -1) { // sayteam - lua_pushinteger(gL, 1); // type - lua_pushnil(gL); // target - } else if (target == 0) { // say - lua_pushinteger(gL, 0); // type - lua_pushnil(gL); // target - } else { // sayto - lua_pushinteger(gL, 2); // type - LUA_PushUserdata(gL, &players[target-1], META_PLAYER); // target - } - lua_pushstring(gL, msg); // msg - if (mute) - lua_pushboolean(gL, true); // the message was supposed to be eaten by spamprotecc. - else - lua_pushboolean(gL, false); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - if (lua_pcall(gL, 4, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (lua_toboolean(gL, -1)) - hooked = true; - lua_pop(gL, 1); - } - } - - lua_settop(gL, 0); - return hooked; -} - -// Hook for hurt messages -boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8 damagetype) -{ - hook_p hookp; - boolean hooked = false; - if (!gL || !(hooksAvailable[hook_HurtMsg/8] & (1<<(hook_HurtMsg%8)))) - return false; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - for (hookp = roothook; hookp; hookp = hookp->next) - { - if (hookp->type != hook_HurtMsg - || (hookp->s.mt && !(inflictor && hookp->s.mt == inflictor->type))) - continue; - - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, player, META_PLAYER); - LUA_PushUserdata(gL, inflictor, META_MOBJ); - LUA_PushUserdata(gL, source, META_MOBJ); - lua_pushinteger(gL, damagetype); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -5); - lua_pushvalue(gL, -5); - lua_pushvalue(gL, -5); - lua_pushvalue(gL, -5); - if (lua_pcall(gL, 4, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (lua_toboolean(gL, -1)) - hooked = true; - lua_pop(gL, 1); - } - - lua_settop(gL, 0); - return hooked; -} - -void LUAh_NetArchiveHook(lua_CFunction archFunc) -{ - hook_p hookp; - int errorhandlerindex; - if (!gL || !(hooksAvailable[hook_NetVars/8] & (1<<(hook_NetVars%8)))) - return; - - // stack: tables - I_Assert(lua_gettop(gL) > 0); - I_Assert(lua_istable(gL, -1)); - - lua_pushcfunction(gL, LUA_GetErrorMessage); - errorhandlerindex = lua_gettop(gL); - - // tables becomes an upvalue of archFunc - lua_pushvalue(gL, -2); - lua_pushcclosure(gL, archFunc, 1); - // stack: tables, archFunc - - for (hookp = roothook; hookp; hookp = hookp->next) - { - if (hookp->type != hook_NetVars) - continue; - - PushHook(gL, hookp); - lua_pushvalue(gL, -2); // archFunc - if (lua_pcall(gL, 1, 0, errorhandlerindex)) { - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - } - } - - lua_pop(gL, 2); // Pop archFunc and error handler - // stack: tables -} - -boolean LUAh_MapThingSpawn(mobj_t *mo, mapthing_t *mthing) -{ - hook_p hookp; - boolean hooked = false; - if (!gL || !(hooksAvailable[hook_MapThingSpawn/8] & (1<<(hook_MapThingSpawn%8)))) - return false; - - if (!(mobjhooks[MT_NULL] || mobjhooks[mo->type])) - return false; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - // Look for all generic mobj map thing spawn hooks - for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next) - { - if (hookp->type != hook_MapThingSpawn) - continue; - - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, mo, META_MOBJ); - LUA_PushUserdata(gL, mthing, META_MAPTHING); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -3); - lua_pushvalue(gL, -3); - if (lua_pcall(gL, 2, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (lua_toboolean(gL, -1)) - hooked = true; - lua_pop(gL, 1); - } - - for (hookp = mobjhooks[mo->type]; hookp; hookp = hookp->next) - { - if (hookp->type != hook_MapThingSpawn) - continue; - - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, mo, META_MOBJ); - LUA_PushUserdata(gL, mthing, META_MAPTHING); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -3); - lua_pushvalue(gL, -3); - if (lua_pcall(gL, 2, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (lua_toboolean(gL, -1)) - hooked = true; - lua_pop(gL, 1); - } - - lua_settop(gL, 0); - return hooked; -} - -// Hook for P_PlayerAfterThink Smiles mobj-following -boolean LUAh_FollowMobj(player_t *player, mobj_t *mobj) -{ - hook_p hookp; - boolean hooked = false; - if (!gL || !(hooksAvailable[hook_FollowMobj/8] & (1<<(hook_FollowMobj%8)))) - return 0; - - if (!(mobjhooks[MT_NULL] || mobjhooks[mobj->type])) - return 0; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - // Look for all generic mobj follow item hooks - for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next) - { - if (hookp->type != hook_FollowMobj) - continue; - - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, player, META_PLAYER); - LUA_PushUserdata(gL, mobj, META_MOBJ); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -3); - lua_pushvalue(gL, -3); - if (lua_pcall(gL, 2, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (lua_toboolean(gL, -1)) - hooked = true; - lua_pop(gL, 1); - } - - for (hookp = mobjhooks[mobj->type]; hookp; hookp = hookp->next) - { - if (hookp->type != hook_FollowMobj) - continue; - - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, player, META_PLAYER); - LUA_PushUserdata(gL, mobj, META_MOBJ); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -3); - lua_pushvalue(gL, -3); - if (lua_pcall(gL, 2, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (lua_toboolean(gL, -1)) - hooked = true; - lua_pop(gL, 1); - } - - lua_settop(gL, 0); - return hooked; -} - -// Hook for P_PlayerCanDamage -UINT8 LUAh_PlayerCanDamage(player_t *player, mobj_t *mobj) -{ - hook_p hookp; - UINT8 shouldCollide = 0; // 0 = default, 1 = force yes, 2 = force no. - if (!gL || !(hooksAvailable[hook_PlayerCanDamage/8] & (1<<(hook_PlayerCanDamage%8)))) - return 0; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - for (hookp = playerhooks; hookp; hookp = hookp->next) - { - if (hookp->type != hook_PlayerCanDamage) - continue; - - ps_lua_mobjhooks++; - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, player, META_PLAYER); - LUA_PushUserdata(gL, mobj, META_MOBJ); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -3); - lua_pushvalue(gL, -3); - if (lua_pcall(gL, 2, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (!lua_isnil(gL, -1)) - { // if nil, leave shouldCollide = 0. - if (lua_toboolean(gL, -1)) - shouldCollide = 1; // Force yes - else - shouldCollide = 2; // Force no - } - lua_pop(gL, 1); - } - - lua_settop(gL, 0); - return shouldCollide; -} - -void LUAh_PlayerQuit(player_t *plr, kickreason_t reason) -{ - hook_p hookp; - if (!gL || !(hooksAvailable[hook_PlayerQuit/8] & (1<<(hook_PlayerQuit%8)))) - return; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - for (hookp = roothook; hookp; hookp = hookp->next) - { - if (hookp->type != hook_PlayerQuit) - continue; - - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, plr, META_PLAYER); // Player that quit - lua_pushinteger(gL, reason); // Reason for quitting - } - PushHook(gL, hookp); - lua_pushvalue(gL, -3); - lua_pushvalue(gL, -3); - if (lua_pcall(gL, 2, 0, 1)) { - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - } - } - - lua_settop(gL, 0); -} - -// Hook for Y_Ticker -void LUAh_IntermissionThinker(void) -{ - hook_p hookp; - if (!gL || !(hooksAvailable[hook_IntermissionThinker/8] & (1<<(hook_IntermissionThinker%8)))) - return; - - lua_pushcfunction(gL, LUA_GetErrorMessage); - - for (hookp = roothook; hookp; hookp = hookp->next) - { - if (hookp->type != hook_IntermissionThinker) - continue; - - PushHook(gL, hookp); - if (lua_pcall(gL, 0, 0, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - } - } - - lua_pop(gL, 1); // Pop error handler -} - -// Hook for team switching -// It's just an edit of LUAh_ViewpointSwitch. -boolean LUAh_TeamSwitch(player_t *player, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble) -{ - hook_p hookp; - boolean canSwitchTeam = true; - if (!gL || !(hooksAvailable[hook_TeamSwitch/8] & (1<<(hook_TeamSwitch%8)))) - return true; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - for (hookp = playerhooks; hookp; hookp = hookp->next) - { - if (hookp->type != hook_TeamSwitch) - continue; - - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, player, META_PLAYER); - lua_pushinteger(gL, newteam); - lua_pushboolean(gL, fromspectators); - lua_pushboolean(gL, tryingautobalance); - lua_pushboolean(gL, tryingscramble); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - lua_pushvalue(gL, -6); - if (lua_pcall(gL, 5, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (!lua_isnil(gL, -1) && !lua_toboolean(gL, -1)) - canSwitchTeam = false; // Can't switch team - lua_pop(gL, 1); - } - - lua_settop(gL, 0); - return canSwitchTeam; -} - -// Hook for spy mode -UINT8 LUAh_ViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced) -{ - hook_p hookp; - UINT8 canSwitchView = 0; // 0 = default, 1 = force yes, 2 = force no. - if (!gL || !(hooksAvailable[hook_ViewpointSwitch/8] & (1<<(hook_ViewpointSwitch%8)))) - return 0; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - hud_running = true; // local hook - - for (hookp = playerhooks; hookp; hookp = hookp->next) - { - if (hookp->type != hook_ViewpointSwitch) - continue; - - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, player, META_PLAYER); - LUA_PushUserdata(gL, newdisplayplayer, META_PLAYER); - lua_pushboolean(gL, forced); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -4); - lua_pushvalue(gL, -4); - lua_pushvalue(gL, -4); - if (lua_pcall(gL, 3, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (!lua_isnil(gL, -1)) - { // if nil, leave canSwitchView = 0. - if (lua_toboolean(gL, -1)) - canSwitchView = 1; // Force viewpoint switch - else - canSwitchView = 2; // Skip viewpoint switch - } - lua_pop(gL, 1); - } - - lua_settop(gL, 0); - - hud_running = false; - - return canSwitchView; -} - -boolean LUAh_ShouldJingleContinue(player_t *player, const char *musname) -{ - hook_p hookp; - boolean keepplaying = false; - if (!gL || !(hooksAvailable[hook_ShouldJingleContinue/8] & (1<<(hook_ShouldJingleContinue%8)))) - return true; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - hud_running = true; // local hook - - for (hookp = roothook; hookp; hookp = hookp->next) - { - if (hookp->type != hook_ShouldJingleContinue - || (hookp->s.str && strcmp(hookp->s.str, musname))) - continue; - - if (lua_gettop(gL) == 1) - { - LUA_PushUserdata(gL, player, META_PLAYER); - lua_pushstring(gL, musname); - } - PushHook(gL, hookp); - lua_pushvalue(gL, -3); - lua_pushvalue(gL, -3); - if (lua_pcall(gL, 2, 1, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (!lua_isnil(gL, -1) && lua_toboolean(gL, -1)) - keepplaying = true; // Keep playing this boolean - lua_pop(gL, 1); - } - - lua_settop(gL, 0); - - hud_running = false; - - return keepplaying; -} - -// Hook for music changes -boolean LUAh_MusicChange(const char *oldname, char *newname, UINT16 *mflags, boolean *looping, - UINT32 *position, UINT32 *prefadems, UINT32 *fadeinms) -{ - hook_p hookp; - boolean hooked = false; - - if (!gL || !(hooksAvailable[hook_MusicChange/8] & (1<<(hook_MusicChange%8)))) - return false; - - lua_settop(gL, 0); - lua_pushcfunction(gL, LUA_GetErrorMessage); - - for (hookp = roothook; hookp; hookp = hookp->next) - if (hookp->type == hook_MusicChange) - { - PushHook(gL, hookp); - lua_pushstring(gL, oldname); - lua_pushstring(gL, newname); - lua_pushinteger(gL, *mflags); - lua_pushboolean(gL, *looping); - lua_pushinteger(gL, *position); - lua_pushinteger(gL, *prefadems); - lua_pushinteger(gL, *fadeinms); - if (lua_pcall(gL, 7, 6, 1)) { - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1)); - lua_pop(gL, 1); - continue; + lua_pushvalue(gL, -1);/* need the function again */ + time_taken = I_GetPreciseTime(); } - // output 1: true, false, or string musicname override - if (lua_isboolean(gL, -6) && lua_toboolean(gL, -6)) - hooked = true; - else if (lua_isstring(gL, -6)) - strncpy(newname, lua_tostring(gL, -6), 7); - // output 2: mflags override - if (lua_isnumber(gL, -5)) - *mflags = lua_tonumber(gL, -5); - // output 3: looping override - if (lua_isboolean(gL, -4)) - *looping = lua_toboolean(gL, -4); - // output 4: position override - if (lua_isnumber(gL, -3)) - *position = lua_tonumber(gL, -3); - // output 5: prefadems override - if (lua_isnumber(gL, -2)) - *prefadems = lua_tonumber(gL, -2); - // output 6: fadeinms override - if (lua_isnumber(gL, -1)) - *fadeinms = lua_tonumber(gL, -1); + call_single_hook(&hook); - lua_pop(gL, 7); // Pop returned values and error handler - } - - lua_settop(gL, 0); - newname[6] = 0; - return hooked; -} - -// Hook for Y_VoteTicker -void LUAh_VoteThinker(void) -{ - hook_p hookp; - if (!gL || !(hooksAvailable[hook_VoteThinker/8] & (1<<(hook_VoteThinker%8)))) - return; - - for (hookp = roothook; hookp; hookp = hookp->next) - if (hookp->type == hook_VoteThinker) - { - lua_pushfstring(gL, FMT_HOOKID, hookp->id); - lua_gettable(gL, LUA_REGISTRYINDEX); - if (lua_pcall(gL, 0, 0, 0)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; + if (cv_perfstats.value == 3) + { + lua_Debug ar; + time_taken = I_GetPreciseTime() - time_taken; + lua_getinfo(gL, ">S", &ar); + PS_SetThinkFrameHookInfo(hook_index, time_taken, ar.short_src); + hook_index++; } } -} -// Hook for game quitting -void LUAh_GameQuit(boolean quitting) -{ - hook_p hookp; - if (!gL || !(hooksAvailable[hook_GameQuit/8] & (1<<(hook_GameQuit%8)))) - return; - - lua_pushcfunction(gL, LUA_GetErrorMessage); - - for (hookp = roothook; hookp; hookp = hookp->next) - { - if (hookp->type != hook_GameQuit) - continue; - - PushHook(gL, hookp); - lua_pushboolean(gL, quitting); - if (lua_pcall(gL, 1, 0, 1)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - } + lua_settop(gL, 0); } - - lua_pop(gL, 1); // Pop error handler } -// Hook for G_BuildTicCmd +int LUA_HookMobjLineCollide(mobj_t *mobj, line_t *line) +{ + Hook_State hook; + if (prepare_mobj_hook(&hook, 0, MOBJ_HOOK(MobjLineCollide), mobj->type)) + { + LUA_PushUserdata(gL, mobj, META_MOBJ); + LUA_PushUserdata(gL, line, META_LINE); + call_hooks(&hook, 1, res_force); + } + return hook.status; +} + +int LUA_HookTouchSpecial(mobj_t *special, mobj_t *toucher) +{ + Hook_State hook; + if (prepare_mobj_hook(&hook, false, MOBJ_HOOK(TouchSpecial), special->type)) + { + LUA_PushUserdata(gL, special, META_MOBJ); + LUA_PushUserdata(gL, toucher, META_MOBJ); + call_hooks(&hook, 1, res_true); + } + return hook.status; +} + +static int damage_hook +( + mobj_t *target, + mobj_t *inflictor, + mobj_t *source, + INT32 damage, + UINT8 damagetype, + int hook_type, + Hook_Callback results_handler +){ + Hook_State hook; + if (prepare_mobj_hook(&hook, 0, hook_type, target->type)) + { + LUA_PushUserdata(gL, target, META_MOBJ); + LUA_PushUserdata(gL, inflictor, META_MOBJ); + LUA_PushUserdata(gL, source, META_MOBJ); + if (hook_type != MOBJ_HOOK(MobjDeath)) + lua_pushinteger(gL, damage); + lua_pushinteger(gL, damagetype); + call_hooks(&hook, 1, results_handler); + } + return hook.status; +} + +int LUA_HookShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype) +{ + return damage_hook(target, inflictor, source, damage, damagetype, + MOBJ_HOOK(ShouldDamage), res_force); +} + +int LUA_HookMobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype) +{ + return damage_hook(target, inflictor, source, damage, damagetype, + MOBJ_HOOK(MobjDamage), res_true); +} + +int LUA_HookMobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damagetype) +{ + return damage_hook(target, inflictor, source, 0, damagetype, + MOBJ_HOOK(MobjDeath), res_true); +} + +int LUA_HookMobjMoveBlocked(mobj_t *t1, mobj_t *t2, line_t *line) +{ + Hook_State hook; + if (prepare_mobj_hook(&hook, 0, MOBJ_HOOK(MobjMoveBlocked), t1->type)) + { + LUA_PushUserdata(gL, t1, META_MOBJ); + LUA_PushUserdata(gL, t2, META_MOBJ); + LUA_PushUserdata(gL, line, META_LINE); + call_hooks(&hook, 1, res_true); + } + return hook.status; +} + +void LUA_HookLinedefExecute(line_t *line, mobj_t *mo, sector_t *sector) +{ + Hook_State hook; + if (prepare_string_hook + (&hook, 0, STRING_HOOK(LinedefExecute), line->stringargs[0])) + { + LUA_PushUserdata(gL, line, META_LINE); + LUA_PushUserdata(gL, mo, META_MOBJ); + LUA_PushUserdata(gL, sector, META_SECTOR); + ps_lua_mobjhooks += call_hooks(&hook, 0, res_none); + } +} + +int LUA_HookPlayerMsg(int source, int target, int flags, char *msg, int mute) +{ + Hook_State hook; + if (prepare_hook(&hook, false, HOOK(PlayerMsg))) + { + LUA_PushUserdata(gL, &players[source], META_PLAYER); // Source player + if (flags & 2 /*HU_CSAY*/) { // csay TODO: make HU_CSAY accessible outside hu_stuff.c + lua_pushinteger(gL, 3); // type + lua_pushnil(gL); // target + } else if (target == -1) { // sayteam + lua_pushinteger(gL, 1); // type + lua_pushnil(gL); // target + } else if (target == 0) { // say + lua_pushinteger(gL, 0); // type + lua_pushnil(gL); // target + } else { // sayto + lua_pushinteger(gL, 2); // type + LUA_PushUserdata(gL, &players[target-1], META_PLAYER); // target + } + lua_pushstring(gL, msg); // msg + lua_pushboolean(gL, mute); // the message was supposed to be eaten by spamprotecc. + + call_hooks(&hook, 1, res_true); + } + return hook.status; +} + +int LUA_HookHurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8 damagetype) +{ + Hook_State hook; + if (prepare_hook(&hook, false, HOOK(HurtMsg))) + { + LUA_PushUserdata(gL, player, META_PLAYER); + LUA_PushUserdata(gL, inflictor, META_MOBJ); + LUA_PushUserdata(gL, source, META_MOBJ); + lua_pushinteger(gL, damagetype); + call_hooks(&hook, 1, res_true); + } + return hook.status; +} + +void LUA_HookNetArchive(lua_CFunction archFunc) +{ + const hook_t * map = &hookIds[HOOK(NetVars)]; + Hook_State hook; + /* this is a remarkable case where the stack isn't reset */ + if (map->numHooks > 0) + { + // stack: tables + I_Assert(lua_gettop(gL) > 0); + I_Assert(lua_istable(gL, -1)); + + push_error_handler(); + lua_insert(gL, EINDEX); + + begin_hook_values(&hook); + + // tables becomes an upvalue of archFunc + lua_pushvalue(gL, -1); + lua_pushcclosure(gL, archFunc, 1); + // stack: tables, archFunc + + init_hook_call(&hook, 0, res_none); + call_mapped(&hook, map); + + lua_pop(gL, 1); // pop archFunc + lua_remove(gL, EINDEX); // pop error handler + // stack: tables + } +} + +int LUA_HookMapThingSpawn(mobj_t *mobj, mapthing_t *mthing) +{ + Hook_State hook; + if (prepare_mobj_hook(&hook, false, MOBJ_HOOK(MapThingSpawn), mobj->type)) + { + LUA_PushUserdata(gL, mobj, META_MOBJ); + LUA_PushUserdata(gL, mthing, META_MAPTHING); + call_hooks(&hook, 1, res_true); + } + return hook.status; +} + +int LUA_HookFollowMobj(player_t *player, mobj_t *mobj) +{ + Hook_State hook; + if (prepare_mobj_hook(&hook, false, MOBJ_HOOK(FollowMobj), mobj->type)) + { + LUA_PushUserdata(gL, player, META_PLAYER); + LUA_PushUserdata(gL, mobj, META_MOBJ); + call_hooks(&hook, 1, res_true); + } + return hook.status; +} + +int LUA_HookPlayerCanDamage(player_t *player, mobj_t *mobj) +{ + Hook_State hook; + if (prepare_hook(&hook, 0, HOOK(PlayerCanDamage))) + { + LUA_PushUserdata(gL, player, META_PLAYER); + LUA_PushUserdata(gL, mobj, META_MOBJ); + call_hooks(&hook, 1, res_force); + } + return hook.status; +} + +void LUA_HookPlayerQuit(player_t *plr, kickreason_t reason) +{ + Hook_State hook; + if (prepare_hook(&hook, 0, HOOK(PlayerQuit))) + { + LUA_PushUserdata(gL, plr, META_PLAYER); // Player that quit + lua_pushinteger(gL, reason); // Reason for quitting + call_hooks(&hook, 0, res_none); + } +} + +int LUA_HookTeamSwitch(player_t *player, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble) +{ + Hook_State hook; + if (prepare_hook(&hook, true, HOOK(TeamSwitch))) + { + LUA_PushUserdata(gL, player, META_PLAYER); + lua_pushinteger(gL, newteam); + lua_pushboolean(gL, fromspectators); + lua_pushboolean(gL, tryingautobalance); + lua_pushboolean(gL, tryingscramble); + call_hooks(&hook, 1, res_false); + } + return hook.status; +} + +int LUA_HookViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced) +{ + Hook_State hook; + if (prepare_hook(&hook, 0, HOOK(ViewpointSwitch))) + { + LUA_PushUserdata(gL, player, META_PLAYER); + LUA_PushUserdata(gL, newdisplayplayer, META_PLAYER); + lua_pushboolean(gL, forced); + + hud_running = true; // local hook + call_hooks(&hook, 1, res_force); + hud_running = false; + } + return hook.status; +} + +int LUA_HookSeenPlayer(player_t *player, player_t *seenfriend) +{ + Hook_State hook; + if (prepare_hook(&hook, true, HOOK(SeenPlayer))) + { + LUA_PushUserdata(gL, player, META_PLAYER); + LUA_PushUserdata(gL, seenfriend, META_PLAYER); + + hud_running = true; // local hook + call_hooks(&hook, 1, res_false); + hud_running = false; + } + return hook.status; +} + +int LUA_HookShouldJingleContinue(player_t *player, const char *musname) +{ + Hook_State hook; + if (prepare_string_hook + (&hook, false, STRING_HOOK(ShouldJingleContinue), musname)) + { + LUA_PushUserdata(gL, player, META_PLAYER); + push_string(); + + hud_running = true; // local hook + call_hooks(&hook, 1, res_true); + hud_running = false; + } + return hook.status; +} + boolean hook_cmd_running = false; -boolean LUAh_PlayerCmd(player_t *player, ticcmd_t *cmd) + +static void update_music_name(struct MusicChange *musicchange) { - hook_p hookp; - boolean hooked = false; - if (!gL || !(hooksAvailable[hook_PlayerCmd/8] & (1<<(hook_PlayerCmd%8)))) - return false; + size_t length; + const char * new = lua_tolstring(gL, -6, &length); - lua_settop(gL, 0); + if (length < 7) + { + strcpy(musicchange->newname, new); + lua_pushvalue(gL, -6);/* may as well keep it for next call */ + } + else + { + memcpy(musicchange->newname, new, 6); + musicchange->newname[6] = '\0'; + lua_pushlstring(gL, new, 6); + } - hook_cmd_running = true; - for (hookp = roothook; hookp; hookp = hookp->next) - if (hookp->type == hook_PlayerCmd) + lua_replace(gL, -7); +} + +static void res_musicchange(Hook_State *hook) +{ + struct MusicChange *musicchange = hook->userdata; + + // output 1: true, false, or string musicname override + if (lua_isstring(gL, -6)) + update_music_name(musicchange); + else if (lua_isboolean(gL, -6) && lua_toboolean(gL, -6)) + hook->status = true; + + // output 2: mflags override + if (lua_isnumber(gL, -5)) + *musicchange->mflags = lua_tonumber(gL, -5); + // output 3: looping override + if (lua_isboolean(gL, -4)) + *musicchange->looping = lua_toboolean(gL, -4); + // output 4: position override + if (lua_isnumber(gL, -3)) + *musicchange->position = lua_tonumber(gL, -3); + // output 5: prefadems override + if (lua_isnumber(gL, -2)) + *musicchange->prefadems = lua_tonumber(gL, -2); + // output 6: fadeinms override + if (lua_isnumber(gL, -1)) + *musicchange->fadeinms = lua_tonumber(gL, -1); +} + +int LUA_HookMusicChange(const char *oldname, struct MusicChange *param) +{ + const int type = HOOK(MusicChange); + const hook_t * map = &hookIds[type]; + + Hook_State hook; + + int k; + + if (prepare_hook(&hook, false, type)) + { + init_hook_call(&hook, 6, res_musicchange); + hook.values = 7;/* values pushed later */ + hook.userdata = param; + + lua_pushstring(gL, oldname);/* the only constant value */ + lua_pushstring(gL, param->newname);/* semi constant */ + + for (k = 0; k < map->numHooks; ++k) { - if (lua_gettop(gL) == 0) - { - LUA_PushUserdata(gL, player, META_PLAYER); - LUA_PushUserdata(gL, cmd, META_TICCMD); - } - lua_pushfstring(gL, FMT_HOOKID, hookp->id); - lua_gettable(gL, LUA_REGISTRYINDEX); + get_hook(&hook, map->ids, k); + lua_pushvalue(gL, -3); lua_pushvalue(gL, -3); - if (lua_pcall(gL, 2, 1, 0)) { - if (!hookp->error || cv_debug & DBG_LUA) - CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); - lua_pop(gL, 1); - hookp->error = true; - continue; - } - if (lua_toboolean(gL, -1)) - hooked = true; - lua_pop(gL, 1); + lua_pushinteger(gL, *param->mflags); + lua_pushboolean(gL, *param->looping); + lua_pushinteger(gL, *param->position); + lua_pushinteger(gL, *param->prefadems); + lua_pushinteger(gL, *param->fadeinms); + + call_single_hook_no_copy(&hook); } - hook_cmd_running = false; - lua_settop(gL, 0); - return hooked; + lua_settop(gL, 0); + } + + return hook.status; } diff --git a/src/lua_hud.h b/src/lua_hud.h index 35db4ef87..fc91811f0 100644 --- a/src/lua_hud.h +++ b/src/lua_hud.h @@ -47,10 +47,6 @@ extern boolean hud_running; boolean LUA_HudEnabled(enum hud option); -void LUAh_GameHUD(player_t *stplyr, huddrawlist_h list); -void LUAh_ScoresHUD(huddrawlist_h list); -void LUAh_TitleHUD(huddrawlist_h list); -void LUAh_TitleCardHUD(player_t *stplayr, huddrawlist_h list); -void LUAh_IntermissionHUD(huddrawlist_h list); +void LUA_SetHudHook(int hook, huddrawlist_h list); #endif // __LUA_HUD_H__ diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c index 6ce41dea5..ffacf34c5 100644 --- a/src/lua_hudlib.c +++ b/src/lua_hudlib.c @@ -29,14 +29,13 @@ #include "lua_script.h" #include "lua_libs.h" #include "lua_hud.h" +#include "lua_hook.h" #define HUDONLY if (!hud_running) return luaL_error(L, "HUD rendering code should not be called outside of rendering hooks!"); boolean hud_running = false; static UINT8 hud_enabled[(hud_MAX/8)+1]; -static UINT8 hudAvailable; // hud hooks field - static UINT8 camnum = 1; // must match enum hud in lua_hud.h @@ -79,21 +78,6 @@ static const char *const patch_opt[] = { "topoffset", NULL}; -enum hudhook { - hudhook_game = 0, - hudhook_scores, - hudhook_intermission, - hudhook_title, - hudhook_titlecard -}; -static const char *const hudhook_opt[] = { - "game", - "scores", - "intermission", - "title", - "titlecard", - NULL}; - // alignment types for v.drawString enum align { align_left = 0, @@ -1249,6 +1233,8 @@ static luaL_Reg lib_draw[] = { {NULL, NULL} }; +static int lib_draw_ref; + // // lib_hud // @@ -1282,28 +1268,7 @@ static int lib_hudenabled(lua_State *L) } // add a HUD element for rendering -static int lib_hudadd(lua_State *L) -{ - enum hudhook field; - - luaL_checktype(L, 1, LUA_TFUNCTION); - field = luaL_checkoption(L, 2, "game", hudhook_opt); - - if (!lua_lumploading) - return luaL_error(L, "This function cannot be called from within a hook or coroutine!"); - - lua_getfield(L, LUA_REGISTRYINDEX, "HUD"); - I_Assert(lua_istable(L, -1)); - lua_rawgeti(L, -1, field+2); // HUD[2+] - I_Assert(lua_istable(L, -1)); - lua_remove(L, -2); - - lua_pushvalue(L, 1); - lua_rawseti(L, -2, (int)(lua_objlen(L, -2) + 1)); - - hudAvailable |= 1<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/lua_script.c b/src/lua_script.c index 627a11a30..14c066bc0 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -1687,7 +1687,7 @@ void LUA_Archive(UINT8 **p) WRITEUINT32(*p, UINT32_MAX); // end of mobjs marker, replaces mobjnum. - LUAh_NetArchiveHook(NetArchive); // call the NetArchive hook in archive mode + LUA_HookNetArchive(NetArchive); // call the NetArchive hook in archive mode } ArchiveTables(p); @@ -1726,7 +1726,7 @@ void LUA_UnArchive(UINT8 **p) } } while(mobjnum != UINT32_MAX); // repeat until end of mobjs marker. - LUAh_NetArchiveHook(NetUnArchive); // call the NetArchive hook in unarchive mode + LUA_HookNetArchive(NetUnArchive); // call the NetArchive hook in unarchive mode } UnArchiveTables(p); diff --git a/src/lua_script.h b/src/lua_script.h index 1c6d4587d..6268407f3 100644 --- a/src/lua_script.h +++ b/src/lua_script.h @@ -63,7 +63,7 @@ void Got_Luacmd(UINT8 **cp, INT32 playernum); // lua_consolelib.c void LUA_CVarChanged(void *cvar); // lua_consolelib.c int Lua_optoption(lua_State *L, int narg, const char *def, const char *const lst[]); -void LUAh_NetArchiveHook(lua_CFunction archFunc); +void LUA_HookNetArchive(lua_CFunction archFunc); void LUA_PushTaggableObjectArray ( lua_State *L, diff --git a/src/m_fixed.h b/src/m_fixed.h index 9f3bb2910..021f84d89 100644 --- a/src/m_fixed.h +++ b/src/m_fixed.h @@ -204,7 +204,7 @@ FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FixedInt(fixed_t a) */ FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FixedDiv(fixed_t a, fixed_t b) { - if ((abs(a) >> (FRACBITS-2)) >= abs(b)) + if ((abs(a / (FRACUNIT/4))) >= abs(b)) return (a^b) < 0 ? INT32_MIN : INT32_MAX; return FixedDiv2(a, b); diff --git a/src/objects/Sourcefile b/src/objects/Sourcefile index f7e4f2491..94f7dd25b 100644 --- a/src/objects/Sourcefile +++ b/src/objects/Sourcefile @@ -1 +1,2 @@ hyudoro.c +shrink.c diff --git a/src/objects/shrink.c b/src/objects/shrink.c new file mode 100644 index 000000000..017c19829 --- /dev/null +++ b/src/objects/shrink.c @@ -0,0 +1,782 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2022 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2022 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file shrink.c +/// \brief Shrink laser item code. + +#include "../doomdef.h" +#include "../doomstat.h" +#include "../info.h" +#include "../k_kart.h" +#include "../k_objects.h" +#include "../m_random.h" +#include "../p_local.h" +#include "../r_main.h" +#include "../s_sound.h" +#include "../g_game.h" +#include "../z_zone.h" +#include "../k_waypoint.h" + +// +// ███████╗██╗██╗░░██╗███╗░░░███╗███████╗ +// ██╔════╝██║╚██╗██╔╝████╗░████║██╔════╝ +// █████╗░░██║░╚███╔╝░██╔████╔██║█████╗░░ +// ██╔══╝░░██║░██╔██╗░██║╚██╔╝██║██╔══╝░░ +// ██║░░░░░██║██╔╝╚██╗██║░╚═╝░██║███████╗ +// ╚═╝░░░░░╚═╝╚═╝░░╚═╝╚═╝░░░░░╚═╝╚══════╝ +// +// vertical flip +// + +#define POHBEE_HOVER (256 << FRACBITS) +#define POHBEE_SPEED (128 << FRACBITS) +#define POHBEE_TIME (30 * TICRATE) +#define POHBEE_DIST (4096 << FRACBITS) + +#define GUN_SWING (ANGLE_90 - ANG10) +#define GUN_SWINGTIME (4 * TICRATE) + +#define CHAIN_SIZE (52) + +#define EXTRA_FOR_FIRST (7) + +enum +{ + POHBEE_MODE_SPAWN, + POHBEE_MODE_ACT, + POHBEE_MODE_DESPAWN, +}; + +#define pohbee_mode(o) ((o)->cusval) +#define pohbee_timer(o) ((o)->reactiontime) +#define pohbee_waypoint_cur(o) ((o)->extravalue1) +#define pohbee_waypoint_dest(o) ((o)->extravalue2) +#define pohbee_height(o) ((o)->movefactor) + +#define pohbee_owner(o) ((o)->target) +#define pohbee_guns(o) ((o)->hnext) + +#define gun_offset(o) ((o)->movecount) +#define gun_numsegs(o) ((o)->extravalue1) + +#define gun_pohbee(o) ((o)->target) +#define gun_laser(o) ((o)->tracer) +#define gun_chains(o) ((o)->hprev) + +#define chain_index(o) ((o)->extravalue1) + +enum +{ + LASER_SHRINK, + LASER_GROW, +}; + +static skincolornum_t ShrinkLaserColor(mobj_t *pohbee) +{ + UINT8 laserState = LASER_SHRINK; + player_t *owner = NULL; + + if (pohbee_owner(pohbee) != NULL && P_MobjWasRemoved(pohbee_owner(pohbee)) == false) + { + owner = pohbee_owner(pohbee)->player; + } + + if (owner != NULL && P_IsDisplayPlayer(owner) == true) + { + laserState = LASER_GROW; + + if (r_splitscreen > 0 && (leveltime & 1)) + { + // TODO: make this properly screen dependent, + // instead of flashing. + laserState = LASER_SHRINK; + } + } + + switch (laserState) + { + default: + case LASER_SHRINK: + return SKINCOLOR_KETCHUP; + + case LASER_GROW: + return SKINCOLOR_SAPPHIRE; + } +} + +static boolean ShrinkLaserActive(mobj_t *pohbee) +{ + return (pohbee_mode(pohbee) == POHBEE_MODE_ACT); +} + +static void PohbeeMoveTo(mobj_t *pohbee, fixed_t destx, fixed_t desty, fixed_t destz) +{ + pohbee->momx = destx - pohbee->x; + pohbee->momy = desty - pohbee->y; + pohbee->momz = destz - pohbee->z; +} + +static fixed_t GenericDistance( + fixed_t curx, fixed_t cury, fixed_t curz, + fixed_t destx, fixed_t desty, fixed_t destz) +{ + return P_AproxDistance(P_AproxDistance(destx - curx, desty - cury), destz - curz); +} + +static fixed_t PohbeeWaypointZ(mobj_t *pohbee, mobj_t *dest) +{ + return dest->z + (pohbee_height(pohbee) + FixedMul(POHBEE_HOVER, mapobjectscale) * P_MobjFlip(dest)); +} + +static void PohbeeSpawn(mobj_t *pohbee) +{ + waypoint_t *curWaypoint = NULL; + waypoint_t *destWaypoint = NULL; + + fixed_t distLeft = INT32_MAX; + fixed_t newX = pohbee->x; + fixed_t newY = pohbee->y; + fixed_t newZ = pohbee->z; + + boolean finalize = false; + + const boolean useshortcuts = false; + const boolean huntbackwards = false; + boolean pathfindsuccess = false; + path_t pathtofinish = {0}; + size_t pathIndex = 0; + + curWaypoint = K_GetWaypointFromIndex((size_t)pohbee_waypoint_cur(pohbee)); + destWaypoint = K_GetWaypointFromIndex((size_t)pohbee_waypoint_dest(pohbee)); + + if (curWaypoint == NULL || destWaypoint == NULL) + { + // Waypoints aren't valid. + // Just transition into the next state. + pohbee_mode(pohbee) = POHBEE_MODE_ACT; + return; + } + + distLeft = FixedMul(POHBEE_SPEED, mapobjectscale); + + while (distLeft > 0) + { + fixed_t wpX = curWaypoint->mobj->x; + fixed_t wpY = curWaypoint->mobj->y; + fixed_t wpZ = PohbeeWaypointZ(pohbee, curWaypoint->mobj); + + fixed_t distToNext = GenericDistance( + newX, newY, newZ, + wpX, wpY, wpZ + ); + + if (distToNext > distLeft) + { + // Only made it partially there. + newX += FixedMul(FixedDiv(wpX - newX, distToNext), distLeft); + newY += FixedMul(FixedDiv(wpY - newY, distToNext), distLeft); + newZ += FixedMul(FixedDiv(wpZ - newZ, distToNext), distLeft); + + distLeft = 0; + } + else + { + // Close enough to the next waypoint, + // move there and remove the distance. + newX = wpX; + newY = wpY; + newZ = wpZ; + + distLeft -= distToNext; + + if (curWaypoint == destWaypoint) + { + // Reached the end. + finalize = true; + break; + } + + // Create waypoint path to our destination. + // Crazy over-engineered, just to catch when + // waypoints are insanely close to each other :P + if (pathfindsuccess == false) + { + pathfindsuccess = K_PathfindToWaypoint( + curWaypoint, destWaypoint, + &pathtofinish, + useshortcuts, huntbackwards + ); + + if (pathfindsuccess == false) + { + // Path isn't valid. + // Just transition into the next state. + finalize = true; + break; + } + } + + pathIndex++; + + if (pathIndex >= pathtofinish.numnodes) + { + // Successfully reached the end of the path. + finalize = true; + break; + } + + // Now moving to the next waypoint. + curWaypoint = (waypoint_t *)pathtofinish.array[pathIndex].nodedata; + pohbee_waypoint_cur(pohbee) = (INT32)K_GetWaypointHeapIndex(curWaypoint); + } + } + + PohbeeMoveTo(pohbee, newX, newY, newZ); + pohbee->angle = K_MomentumAngle(pohbee); + + if (finalize == true) + { + // Move to next state + pohbee_mode(pohbee) = POHBEE_MODE_ACT; + } + + if (pathfindsuccess == true) + { + Z_Free(pathtofinish.array); + } +} + +static void PohbeeAct(mobj_t *pohbee) +{ + pohbee_timer(pohbee)--; + + if (pohbee_timer(pohbee) <= 0) + { + // Move to next state + pohbee_mode(pohbee) = POHBEE_MODE_DESPAWN; + pohbee->fuse = 5*TICRATE; + } +} + +static void PohbeeDespawn(mobj_t *pohbee) +{ + pohbee->momz = 16 * pohbee->scale * P_MobjFlip(pohbee); +} + +static void DoGunSwing(mobj_t *gun, mobj_t *pohbee) +{ + const angle_t angle = gun->angle + ANGLE_90; + const tic_t swingTimer = leveltime + gun_offset(gun); + + const angle_t swingAmt = swingTimer * (ANGLE_MAX / GUN_SWINGTIME); + const fixed_t swingCos = FINECOSINE(swingAmt >> ANGLETOFINESHIFT); + + const angle_t pitch = -ANGLE_90 + FixedMul(swingCos, GUN_SWING); + const fixed_t dist = gun_numsegs(gun) * CHAIN_SIZE * gun->scale; + + fixed_t offsetX = FixedMul( + dist, FixedMul( + FINECOSINE(angle >> ANGLETOFINESHIFT), + FINECOSINE(pitch >> ANGLETOFINESHIFT) + ) + ); + + fixed_t offsetY = FixedMul( + dist, FixedMul( + FINESINE(angle >> ANGLETOFINESHIFT), + FINECOSINE(pitch >> ANGLETOFINESHIFT) + ) + ); + + fixed_t offsetZ = FixedMul( + dist, FINESINE(pitch >> ANGLETOFINESHIFT) + ); + + PohbeeMoveTo(gun, pohbee->x + offsetX, pohbee->y + offsetY, pohbee->z + offsetZ); +} + +static void ShrinkLaserThinker(mobj_t *pohbee, mobj_t *gun, mobj_t *laser) +{ + const fixed_t gunX = gun->x + gun->momx; + const fixed_t gunY = gun->y + gun->momy; + const fixed_t gunZ = P_GetMobjFeet(gun) + gun->momz; + + PohbeeMoveTo(laser, gunX, gunY, gun->floorz); + + if (ShrinkLaserActive(pohbee) == true) + { + mobj_t *particle = NULL; + + laser->renderflags &= ~RF_DONTDRAW; + laser->color = gun->color; + + if (leveltime & 1) + { + laser->spritexscale = 5*FRACUNIT/2; + } + else + { + laser->spritexscale = FRACUNIT; + } + + laser->spriteyscale = FixedDiv(FixedDiv(gunZ - gun->floorz, mapobjectscale), laser->info->height); + + particle = P_SpawnMobjFromMobj( + laser, + P_RandomRange(-16, 16) * FRACUNIT, + P_RandomRange(-16, 16) * FRACUNIT, + 0, + MT_SHRINK_PARTICLE + ); + + P_SetTarget(&gun_pohbee(particle), pohbee); + + particle->color = laser->color; + + P_SetScale(particle, particle->scale * 2); + particle->destscale = 0; + + //particle->momz = 2 * particle->scale * P_MobjFlip(particle); + } + else + { + laser->renderflags |= RF_DONTDRAW; + } +} + +static void DoGunChains(mobj_t *gun, mobj_t *pohbee) +{ + const fixed_t gunX = gun->x + gun->momx; + const fixed_t gunY = gun->y + gun->momy; + const fixed_t gunZ = P_GetMobjHead(gun) + gun->momz; + + const fixed_t beeX = pohbee->x + pohbee->momx; + const fixed_t beeY = pohbee->y + pohbee->momy; + const fixed_t beeZ = P_GetMobjFeet(pohbee) + pohbee->momz; + + const fixed_t offsetX = (beeX - gunX) / gun_numsegs(gun); + const fixed_t offsetY = (beeY - gunY) / gun_numsegs(gun); + const fixed_t offsetZ = (beeZ - gunZ) / gun_numsegs(gun); + + mobj_t *chain = NULL; + + fixed_t curX = gunX + (offsetX / 2); + fixed_t curY = gunY + (offsetY / 2); + fixed_t curZ = gunZ + (offsetZ / 2); + + chain = gun_chains(gun); + while (chain != NULL && P_MobjWasRemoved(chain) == false) + { + PohbeeMoveTo(chain, curX, curY, curZ); + + curX += offsetX; + curY += offsetY; + curZ += offsetZ; + + chain = gun_chains(chain); + } +} + +static void ShrinkGunThinker(mobj_t *gun) +{ + mobj_t *pohbee = gun_pohbee(gun); + + if (pohbee == NULL || P_MobjWasRemoved(pohbee) == true) + { + P_RemoveMobj(gun); + return; + } + + gun->angle = pohbee->angle; + gun->color = ShrinkLaserColor(pohbee); + + DoGunSwing(gun, pohbee); + + if (gun_laser(gun) != NULL && P_MobjWasRemoved(gun_laser(gun)) == false) + { + ShrinkLaserThinker(pohbee, gun, gun_laser(gun)); + } + + DoGunChains(gun, pohbee); +} + +void Obj_PohbeeThinker(mobj_t *pohbee) +{ + mobj_t *gun = NULL; + + pohbee->momx = pohbee->momy = pohbee->momz = 0; + + switch (pohbee_mode(pohbee)) + { + case POHBEE_MODE_SPAWN: + PohbeeSpawn(pohbee); + break; + + case POHBEE_MODE_ACT: + PohbeeAct(pohbee); + break; + + case POHBEE_MODE_DESPAWN: + PohbeeDespawn(pohbee); + break; + + default: + // failsafe + pohbee_mode(pohbee) = POHBEE_MODE_SPAWN; + break; + } + + gun = pohbee_guns(pohbee); + while (gun != NULL && P_MobjWasRemoved(gun) == false) + { + ShrinkGunThinker(gun); + gun = pohbee_guns(gun); + } +} + +void Obj_PohbeeRemoved(mobj_t *pohbee) +{ + mobj_t *gun = pohbee_guns(pohbee); + + while (gun != NULL && P_MobjWasRemoved(gun) == false) + { + mobj_t *nextGun = pohbee_guns(gun); + P_RemoveMobj(gun); + gun = nextGun; + } +} + +void Obj_ShrinkGunRemoved(mobj_t *gun) +{ + mobj_t *chain = NULL; + + if (gun_laser(gun) != NULL && P_MobjWasRemoved(gun_laser(gun)) == false) + { + P_RemoveMobj(gun_laser(gun)); + } + + chain = gun_chains(gun); + while (chain != NULL && P_MobjWasRemoved(chain) == false) + { + mobj_t *nextChain = gun_chains(chain); + P_RemoveMobj(chain); + chain = nextChain; + } +} + +boolean Obj_ShrinkLaserCollide(mobj_t *gun, mobj_t *victim) +{ + mobj_t *pohbee = gun_pohbee(gun); + mobj_t *owner = NULL; + INT32 prevTimer = 0; + + if (pohbee == NULL || P_MobjWasRemoved(pohbee) == true) + { + return true; + } + + if (ShrinkLaserActive(pohbee) == false) + { + return true; + } + + if (victim->player->shrinkLaserDelay > 0) + { + victim->player->shrinkLaserDelay = TICRATE; + return true; + } + + victim->player->shrinkLaserDelay = TICRATE; + + owner = pohbee_owner(pohbee); + prevTimer = victim->player->growshrinktimer; + + if (owner != NULL && victim == owner) + { + // Belongs to us. Give us Grow! + if (prevTimer < 0) + { + // Take away Shrink. + K_RemoveGrowShrink(victim->player); + } + else + { + victim->player->growshrinktimer += 3*TICRATE; + S_StartSound(victim, sfx_kc5a); + + if (prevTimer <= 0) + { + victim->scalespeed = mapobjectscale/TICRATE; + victim->destscale = FixedMul(mapobjectscale, GROW_SCALE); + + if (K_PlayerShrinkCheat(victim->player) == true) + { + victim->destscale = FixedMul(victim->destscale, SHRINK_SCALE); + } + + if (victim->player->invincibilitytimer > 0) + { + ; // invincibility has priority in P_RestoreMusic, no point in starting here + } + else if (P_IsLocalPlayer(victim->player) == true) + { + S_ChangeMusicSpecial("kgrow"); + } + else //used to be "if (P_IsDisplayPlayer(victim->player) == false)" + { + S_StartSound(victim, (cv_kartinvinsfx.value ? sfx_alarmg : sfx_kgrow)); + } + + P_RestoreMusic(victim->player); + } + } + } + else + { + if (prevTimer > 0) + { + // Take away Grow. + K_RemoveGrowShrink(victim->player); + } + else + { + // Start shrinking! + victim->player->growshrinktimer -= 5*TICRATE; + S_StartSound(victim, sfx_kc59); + + if (prevTimer >= 0) + { + //K_DropItems(victim->player); + + victim->scalespeed = mapobjectscale/TICRATE; + victim->destscale = FixedMul(mapobjectscale, SHRINK_SCALE); + + if (K_PlayerShrinkCheat(victim->player) == true) + { + victim->destscale = FixedMul(victim->destscale, SHRINK_SCALE); + } + } + } + } + + return true; +} + +static waypoint_t *GetPohbeeWaypoint(waypoint_t *anchor, const UINT32 traveldist, const boolean huntbackwards) +{ + const boolean useshortcuts = false; + boolean pathfindsuccess = false; + path_t pathtofinish = {0}; + waypoint_t *ret = NULL; + + pathfindsuccess = K_PathfindThruCircuitSpawnable( + anchor, traveldist, + &pathtofinish, + useshortcuts, huntbackwards + ); + + if (pathfindsuccess == true) + { + ret = (waypoint_t *)pathtofinish.array[ pathtofinish.numnodes - 1 ].nodedata; + Z_Free(pathtofinish.array); + } + else + { + ret = anchor; + } + + return ret; + +} + +static waypoint_t *GetPohbeeStart(waypoint_t *anchor) +{ + const UINT32 traveldist = FixedMul(POHBEE_DIST >> 1, mapobjectscale) / FRACUNIT; + const boolean huntbackwards = true; + + return GetPohbeeWaypoint(anchor, traveldist, huntbackwards); +} + +static waypoint_t *GetPohbeeEnd(waypoint_t *anchor) +{ + const UINT32 traveldist = FixedMul(POHBEE_DIST, mapobjectscale) / FRACUNIT; + const boolean huntbackwards = false; + + return GetPohbeeWaypoint(anchor, traveldist, huntbackwards); +} + +static void CreatePohbee(player_t *owner, waypoint_t *start, waypoint_t *end, UINT8 numLasers) +{ + mobj_t *pohbee = NULL; + + fixed_t size = 0; + INT32 baseSegs = INT32_MAX; + INT32 segVal = INT32_MAX; + mobj_t *prevGun = NULL; + + size_t i, j; + + if (owner == NULL || owner->mo == NULL || P_MobjWasRemoved(owner->mo) == true + || start == NULL || end == NULL + || numLasers == 0) + { + // Invalid inputs + return; + } + + // Calculate number of chain segments added per laser. + size = FixedMul(end->mobj->radius, 3*FRACUNIT/2); + segVal = max(1, 1 + ((size / start->mobj->scale) / CHAIN_SIZE) / numLasers); + baseSegs = segVal * numLasers; + + // Valid spawning conditions, + // we can start creating each individual part. + pohbee = P_SpawnMobjFromMobj(start->mobj, 0, 0, (baseSegs * CHAIN_SIZE * FRACUNIT) + POHBEE_HOVER * 3, MT_SHRINK_POHBEE); + P_SetTarget(&pohbee_owner(pohbee), owner->mo); + + pohbee_mode(pohbee) = POHBEE_MODE_SPAWN; + pohbee_timer(pohbee) = POHBEE_TIME; + pohbee_height(pohbee) = size; + + pohbee_waypoint_cur(pohbee) = (INT32)K_GetWaypointHeapIndex(start); + pohbee_waypoint_dest(pohbee) = (INT32)K_GetWaypointHeapIndex(end); + + prevGun = pohbee; + + for (i = 0; i < numLasers; i++) + { + const UINT8 numSegs = segVal * (i + 1); + + mobj_t *gun = P_SpawnMobjFromMobj(pohbee, 0, 0, 0, MT_SHRINK_GUN); + mobj_t *laser = NULL; + mobj_t *prevChain = NULL; + + P_SetTarget(&gun_pohbee(gun), pohbee); + P_SetTarget(&pohbee_guns(prevGun), gun); + + gun_numsegs(gun) = numSegs; + gun_offset(gun) = P_RandomKey(GUN_SWINGTIME); + + laser = P_SpawnMobjFromMobj(gun, 0, 0, 0, MT_SHRINK_LASER); + P_SetTarget(&gun_laser(gun), laser); + + prevChain = gun; + for (j = 0; j < numSegs; j++) + { + mobj_t *chain = P_SpawnMobjFromMobj(gun, 0, 0, 0, MT_SHRINK_CHAIN); + + P_SetTarget(&gun_chains(prevChain), chain); + chain_index(chain) = j; + + prevChain = chain; + } + + prevGun = gun; + } +} + +void Obj_CreateShrinkPohbees(player_t *owner) +{ + UINT8 ownerPos = 1; + + struct { + waypoint_t *start; + waypoint_t *end; + UINT8 lasers; + boolean first; + } pohbees[MAXPLAYERS]; + size_t numPohbees = 0; + + size_t i, j; + + if (owner == NULL || owner->mo == NULL || P_MobjWasRemoved(owner->mo) == true) + { + return; + } + + ownerPos = owner->position; + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *player = NULL; + waypoint_t *endWaypoint = NULL; + + if (playeringame[i] == false) + { + // Not valid. + continue; + } + + player = &players[i]; + + if (player->spectator == true) + { + // Not playing. + continue; + } + + if (player->position > ownerPos) + { + // Too far behind. + continue; + } + + if (player->nextwaypoint == NULL) + { + // No waypoint? + continue; + } + + endWaypoint = GetPohbeeEnd(player->nextwaypoint); + + for (j = 0; j < numPohbees; j++) + { + if (pohbees[j].end == endWaypoint) + { + // Increment laser count for the already existing poh-bee, + // if another one would occupy the same space. + pohbees[j].lasers++; + break; + } + } + + if (j == numPohbees) + { + // Push a new poh-bee + pohbees[j].start = GetPohbeeStart(player->nextwaypoint); + pohbees[j].end = endWaypoint; + pohbees[j].lasers = 1; + + if (player->position == 1) + { + pohbees[j].first = true; + } + + numPohbees++; + } + } + + for (i = 0; i < numPohbees; i++) + { + CreatePohbee(owner, pohbees[i].start, pohbees[i].end, pohbees[i].lasers); + + if (pohbees[i].first == true) + { + // Add a chain of extra ones for 1st place. + waypoint_t *prev = pohbees[i].end; + + for (j = 0; j < EXTRA_FOR_FIRST; j++) + { + waypoint_t *new = GetPohbeeEnd(prev); + CreatePohbee(owner, prev, new, 1); + prev = new; + } + } + } +} diff --git a/src/p_enemy.c b/src/p_enemy.c index 2c69e7bfe..d5f999624 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -2474,7 +2474,7 @@ void A_LobShot(mobj_t *actor) { INT32 locvar1 = var1; INT32 locvar2 = var2 >> 16; - mobj_t *shot, *hitspot; + mobj_t *shot; angle_t an; fixed_t z; fixed_t dist; @@ -2502,11 +2502,6 @@ void A_LobShot(mobj_t *actor) shot->destscale = actor->scale; P_SetScale(shot, actor->scale); - // Keep track of where it's going to land - hitspot = P_SpawnMobj(actor->target->x&(64*FRACUNIT-1), actor->target->y&(64*FRACUNIT-1), actor->target->subsector->sector->floorheight, MT_NULL); - hitspot->tics = airtime; - P_SetTarget(&shot->tracer, hitspot); - P_SetTarget(&shot->target, actor); // where it came from P_InitAngle(shot, actor->angle); @@ -3142,20 +3137,18 @@ void A_SkullAttack(mobj_t *actor) actor->angle += (P_RandomChance(FRACUNIT/2)) ? ANGLE_90 : -ANGLE_90; else if (locvar1 == 3) { - statenum_t oldspawnstate = mobjinfo[MT_NULL].spawnstate; - UINT32 oldflags = mobjinfo[MT_NULL].flags; - fixed_t oldradius = mobjinfo[MT_NULL].radius; - fixed_t oldheight = mobjinfo[MT_NULL].height; - mobj_t *check; + statenum_t oldspawnstate = mobjinfo[MT_RAY].spawnstate; + UINT32 oldflags = mobjinfo[MT_RAY].flags; + fixed_t oldradius = mobjinfo[MT_RAY].radius; + fixed_t oldheight = mobjinfo[MT_RAY].height; INT32 i, j; static INT32 k;/* static for (at least) GCC 9.1 weirdness */ - boolean allow; angle_t testang = 0; - mobjinfo[MT_NULL].spawnstate = S_INVISIBLE; - mobjinfo[MT_NULL].flags = MF_NOGRAVITY|MF_NOTHINK|MF_NOCLIPTHING|MF_NOBLOCKMAP; - mobjinfo[MT_NULL].radius = mobjinfo[actor->type].radius; - mobjinfo[MT_NULL].height = mobjinfo[actor->type].height; + mobjinfo[MT_RAY].spawnstate = S_INVISIBLE; + mobjinfo[MT_RAY].flags = MF_NOGRAVITY|MF_NOTHINK|MF_NOCLIPTHING|MF_NOBLOCKMAP; + mobjinfo[MT_RAY].radius = mobjinfo[actor->type].radius; + mobjinfo[MT_RAY].height = mobjinfo[actor->type].height; if (P_RandomChance(FRACUNIT/2)) // port priority 1? { @@ -3168,15 +3161,12 @@ void A_SkullAttack(mobj_t *actor) j = 9; } -#define dostuff(q) check = P_SpawnMobjFromMobj(actor, 0, 0, 0, MT_NULL);\ +#define dostuff(q) \ testang = actor->angle + ((i+(q))*ANG10);\ - allow = (P_TryMove(check,\ - P_ReturnThrustX(check, testang, dist + 2*actor->radius),\ - P_ReturnThrustY(check, testang, dist + 2*actor->radius),\ - true));\ - P_RemoveMobj(check);\ - if (allow)\ - break; + if (P_CheckMove(actor,\ + P_ReturnThrustX(actor, testang, dist + 2*actor->radius),\ + P_ReturnThrustY(actor, testang, dist + 2*actor->radius),\ + true)) break; if (P_RandomChance(FRACUNIT/2)) // port priority 2? { @@ -3202,10 +3192,10 @@ void A_SkullAttack(mobj_t *actor) #undef dostuff - mobjinfo[MT_NULL].spawnstate = oldspawnstate; - mobjinfo[MT_NULL].flags = oldflags; - mobjinfo[MT_NULL].radius = oldradius; - mobjinfo[MT_NULL].height = oldheight; + mobjinfo[MT_RAY].spawnstate = oldspawnstate; + mobjinfo[MT_RAY].flags = oldflags; + mobjinfo[MT_RAY].radius = oldradius; + mobjinfo[MT_RAY].height = oldheight; } an = actor->angle >> ANGLETOFINESHIFT; @@ -3495,7 +3485,7 @@ void A_BossDeath(mobj_t *mo) } bossjustdie: - if (LUAh_BossDeath(mo)) + if (LUA_HookMobj(mo, MOBJ_HOOK(BossDeath))) return; else if (P_MobjWasRemoved(mo)) return; diff --git a/src/p_inter.c b/src/p_inter.c index 4f614b5ec..438909980 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -219,7 +219,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (special->flags & (MF_ENEMY|MF_BOSS) && special->flags2 & MF2_FRET) return; - if (LUAh_TouchSpecial(special, toucher) || P_MobjWasRemoved(special)) + if (LUA_HookTouchSpecial(special, toucher) || P_MobjWasRemoved(special)) return; if ((special->flags & (MF_ENEMY|MF_BOSS)) && !(special->flags & MF_MISSILE)) @@ -973,7 +973,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget target->shadowscale = 0; } - if (LUAh_MobjDeath(target, inflictor, source, damagetype) || P_MobjWasRemoved(target)) + if (LUA_HookMobjDeath(target, inflictor, source, damagetype) || P_MobjWasRemoved(target)) return; //K_SetHitLagForObjects(target, inflictor, MAXHITLAGTICS, true); @@ -1861,7 +1861,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da // Everything above here can't be forced. if (!metalrecording) { - UINT8 shouldForce = LUAh_ShouldDamage(target, inflictor, source, damage, damagetype); + UINT8 shouldForce = LUA_HookShouldDamage(target, inflictor, source, damage, damagetype); if (P_MobjWasRemoved(target)) return (shouldForce == 1); // mobj was removed if (shouldForce == 1) @@ -1887,7 +1887,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da if (!force && target->flags2 & MF2_FRET) // Currently flashing from being hit return false; - if (LUAh_MobjDamage(target, inflictor, source, damage, damagetype) || P_MobjWasRemoved(target)) + if (LUA_HookMobjDamage(target, inflictor, source, damage, damagetype) || P_MobjWasRemoved(target)) return true; if (target->health > 1) @@ -1917,7 +1917,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da if (!P_KillPlayer(player, inflictor, source, damagetype)) return false; } - else if (LUAh_MobjDamage(target, inflictor, source, damage, damagetype)) + else if (LUA_HookMobjDamage(target, inflictor, source, damage, damagetype)) { return true; } diff --git a/src/p_local.h b/src/p_local.h index 0e03b8482..6c885191c 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -410,6 +410,7 @@ boolean P_IsLineBlocking(const line_t *ld, const mobj_t *thing); boolean P_IsLineTripWire(const line_t *ld); boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y); boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam); +boolean P_CheckMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff); fixed_t P_BaseStepUp(void); fixed_t P_GetThingStepUp(mobj_t *thing); boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff); diff --git a/src/p_map.c b/src/p_map.c index 2c21a7718..e00cbea1c 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -32,6 +32,7 @@ #include "hu_stuff.h" // SRB2kart #include "i_system.h" // SRB2kart #include "k_terrain.h" +#include "k_objects.h" #include "r_splats.h" @@ -656,7 +657,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) } { - UINT8 shouldCollide = LUAh_MobjCollide(thing, tmthing); // checks hook for thing's type + UINT8 shouldCollide = LUA_Hook2Mobj(thing, tmthing, MOBJ_HOOK(MobjCollide)); // checks hook for thing's type if (P_MobjWasRemoved(tmthing) || P_MobjWasRemoved(thing)) return BMIT_CONTINUE; // one of them was removed??? if (shouldCollide == 1) @@ -664,7 +665,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) else if (shouldCollide == 2) return BMIT_CONTINUE; // force no collide - shouldCollide = LUAh_MobjMoveCollide(tmthing, thing); // checks hook for tmthing's type + shouldCollide = LUA_Hook2Mobj(tmthing, thing, MOBJ_HOOK(MobjMoveCollide)); // checks hook for tmthing's type if (P_MobjWasRemoved(tmthing) || P_MobjWasRemoved(thing)) return BMIT_CONTINUE; // one of them was removed??? if (shouldCollide == 1) @@ -739,6 +740,81 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) // SRB2kart 011617 - Colission[sic] code for kart items //{ + if (thing->type == MT_SHRINK_GUN || thing->type == MT_SHRINK_PARTICLE) + { + if (tmthing->type != MT_PLAYER) + { + return BMIT_CONTINUE; + } + + if (thing->type == MT_SHRINK_GUN) + { + // Use special collision for the laser gun. + // The laser sprite itself is just a visual, + // the gun itself does the colliding for us. + if (tmthing->z > thing->z) + { + return BMIT_CONTINUE; // overhead + } + + if (tmthing->z + tmthing->height < thing->floorz) + { + return BMIT_CONTINUE; // underneath + } + } + else + { + if (tmthing->z > thing->z + thing->height) + { + return BMIT_CONTINUE; // overhead + } + + if (tmthing->z + tmthing->height < thing->z) + { + return BMIT_CONTINUE; // underneath + } + } + + return Obj_ShrinkLaserCollide(thing, tmthing) ? BMIT_CONTINUE : BMIT_ABORT; + } + else if (tmthing->type == MT_SHRINK_GUN || tmthing->type == MT_SHRINK_PARTICLE) + { + if (thing->type != MT_PLAYER) + { + return BMIT_CONTINUE; + } + + if (tmthing->type == MT_SHRINK_GUN) + { + // Use special collision for the laser gun. + // The laser sprite itself is just a visual, + // the gun itself does the colliding for us. + if (thing->z > tmthing->z) + { + return BMIT_CONTINUE; // overhead + } + + if (thing->z + thing->height < tmthing->floorz) + { + return BMIT_CONTINUE; // underneath + } + } + else + { + if (tmthing->z > thing->z + thing->height) + { + return BMIT_CONTINUE; // overhead + } + + if (tmthing->z + tmthing->height < thing->z) + { + return BMIT_CONTINUE; // underneath + } + } + + return Obj_ShrinkLaserCollide(tmthing, thing) ? BMIT_CONTINUE : BMIT_ABORT; + } + if (tmthing->type == MT_SMK_ICEBLOCK) { // see if it went over / under @@ -1640,7 +1716,7 @@ static BlockItReturn_t PIT_CheckLine(line_t *ld) blockingline = ld; { - UINT8 shouldCollide = LUAh_MobjLineCollide(tmthing, blockingline); // checks hook for thing's type + UINT8 shouldCollide = LUA_HookMobjLineCollide(tmthing, blockingline); // checks hook for thing's type if (P_MobjWasRemoved(tmthing)) return BMIT_CONTINUE; // one of them was removed??? if (shouldCollide == 1) @@ -2483,21 +2559,19 @@ fixed_t P_GetThingStepUp(mobj_t *thing) return maxstep; } -// -// P_TryMove -// Attempt to move to a new position. -// -boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff) +static boolean +increment_move +( mobj_t * thing, + fixed_t x, + fixed_t y, + boolean allowdropoff, + fixed_t * return_stairjank) { fixed_t tryx = thing->x; fixed_t tryy = thing->y; - fixed_t oldx = tryx; - fixed_t oldy = tryy; fixed_t radius = thing->radius; fixed_t thingtop; - fixed_t startingonground = P_IsObjectOnGround(thing); fixed_t stairjank = 0; - pslope_t *oldslope = thing->standingslope; floatok = false; // reset this to 0 at the start of each trymove call as it's only used here @@ -2643,7 +2717,45 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff) } } while (tryx != x || tryy != y); + if (return_stairjank) + *return_stairjank = stairjank; + + return true; +} + +// +// P_CheckMove +// Check if a P_TryMove would be successful. +// +boolean P_CheckMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff) +{ + boolean moveok; + mobj_t *hack = P_SpawnMobjFromMobj(thing, 0, 0, 0, MT_RAY); + + hack->radius = thing->radius; + hack->height = thing->height; + + moveok = increment_move(hack, x, y, allowdropoff, NULL); + P_RemoveMobj(hack); + + return moveok; +} + +// +// P_TryMove +// Attempt to move to a new position. +// +boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff) +{ + fixed_t oldx = thing->x; + fixed_t oldy = thing->y; + fixed_t startingonground = P_IsObjectOnGround(thing); + fixed_t stairjank = 0; + pslope_t *oldslope = thing->standingslope; + // The move is ok! + if (!increment_move(thing, x, y, allowdropoff, &stairjank)) + return false; // If it's a pushable object, check if anything is // standing on top and move it, too. diff --git a/src/p_mobj.c b/src/p_mobj.c index b12430889..0505d6bda 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1574,7 +1574,7 @@ void P_XYMovement(mobj_t *mo) // blocked move moved = false; - if (LUAh_MobjMoveBlocked(mo)) + if (LUA_HookMobjMoveBlocked(mo, tmhitthing, blockingline)) { if (P_MobjWasRemoved(mo)) return; @@ -3976,7 +3976,7 @@ static void P_RingThinker(mobj_t *mobj) if (!mobj->fuse) { - if (!LUAh_MobjFuse(mobj)) + if (!LUA_HookMobj(mobj, MOBJ_HOOK(MobjFuse))) { mobj->renderflags &= ~RF_DONTDRAW; spark = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_SIGNSPARKLE); // Spawn a fancy sparkle @@ -5356,7 +5356,7 @@ static boolean P_ParticleGenSceneryThink(mobj_t *mobj) static void P_MobjSceneryThink(mobj_t *mobj) { - if (LUAh_MobjThinker(mobj)) + if (LUA_HookMobj(mobj, MOBJ_HOOK(MobjThinker))) return; if (P_MobjWasRemoved(mobj)) return; @@ -6218,7 +6218,7 @@ static void P_MobjSceneryThink(mobj_t *mobj) mobj->fuse--; if (!mobj->fuse) { - if (!LUAh_MobjFuse(mobj)) + if (!LUA_HookMobj(mobj, MOBJ_HOOK(MobjFuse))) P_RemoveMobj(mobj); return; } @@ -6238,7 +6238,7 @@ static boolean P_MobjPushableThink(mobj_t *mobj) static boolean P_MobjBossThink(mobj_t *mobj) { - if (LUAh_BossThinker(mobj)) + if (LUA_HookMobj(mobj, MOBJ_HOOK(BossThinker))) { if (P_MobjWasRemoved(mobj)) return false; @@ -7840,6 +7840,11 @@ static boolean P_MobjRegularThink(mobj_t *mobj) Obj_HyudoroCenterThink(mobj); break; } + case MT_SHRINK_POHBEE: + { + Obj_PohbeeThinker(mobj); + break; + } case MT_ROCKETSNEAKER: if (!mobj->target || !mobj->target->health) { @@ -9159,7 +9164,7 @@ static boolean P_FuseThink(mobj_t *mobj) if (mobj->fuse) return true; - if (LUAh_MobjFuse(mobj) || P_MobjWasRemoved(mobj)) + if (LUA_HookMobj(mobj, MOBJ_HOOK(MobjFuse)) || P_MobjWasRemoved(mobj)) ; else if (mobj->info->flags & MF_MONITOR) { @@ -9391,13 +9396,13 @@ void P_MobjThinker(mobj_t *mobj) // Check for a Lua thinker first if (!mobj->player) { - if (LUAh_MobjThinker(mobj) || P_MobjWasRemoved(mobj)) + if (LUA_HookMobj(mobj, MOBJ_HOOK(MobjThinker)) || P_MobjWasRemoved(mobj)) return; } else if (!mobj->player->spectator) { // You cannot short-circuit the player thinker like you can other thinkers. - LUAh_MobjThinker(mobj); + LUA_HookMobj(mobj, MOBJ_HOOK(MobjThinker)); if (P_MobjWasRemoved(mobj)) return; } @@ -9888,7 +9893,24 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) const mobjinfo_t *info = &mobjinfo[type]; SINT8 sc = -1; state_t *st; - mobj_t *mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL); + mobj_t *mobj; + + if (type == MT_NULL) + { +#if 0 +#ifdef PARANOIA + I_Error("Tried to spawn MT_NULL\n"); +#endif + return NULL; +#endif + // Hack: Some code assumes that P_SpawnMobj can never return NULL + // So replace MT_NULL with MT_RAY in the meantime + // Remove when dealt properly + CONS_Debug(DBG_GAMELOGIC, "Tried to spawn MT_NULL, using MT_RAY\n"); + type = MT_RAY; + } + + mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL); // this is officially a mobj, declared as soon as possible. mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker; @@ -9989,7 +10011,7 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) // DANGER! This can cause P_SpawnMobj to return NULL! // Avoid using P_RemoveMobj on the newly created mobj in "MobjSpawn" Lua hooks! - if (LUAh_MobjSpawn(mobj)) + if (LUA_HookMobj(mobj, MOBJ_HOOK(MobjSpawn))) { if (P_MobjWasRemoved(mobj)) return NULL; @@ -10328,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; @@ -10589,7 +10611,7 @@ void P_RemoveMobj(mobj_t *mobj) return; // something already removing this mobj. mobj->thinker.function.acp1 = (actionf_p1)P_RemoveThinkerDelayed; // shh. no recursing. - LUAh_MobjRemoved(mobj); + LUA_HookMobj(mobj, MOBJ_HOOK(MobjRemoved)); mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker; // needed for P_UnsetThingPosition, etc. to work. // Rings only, please! @@ -10634,6 +10656,16 @@ void P_RemoveMobj(mobj_t *mobj) P_SetTarget(&mobj->player->followmobj, NULL); } + if (mobj->type == MT_SHRINK_POHBEE) + { + Obj_PohbeeRemoved(mobj); + } + + if (mobj->type == MT_SHRINK_GUN) + { + Obj_ShrinkGunRemoved(mobj); + } + mobj->health = 0; // Just because // unlink from sector and block lists @@ -10745,6 +10777,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}}; @@ -12186,7 +12219,7 @@ static void P_SnapToFinishLine(mobj_t *mobj) static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean *doangle) { - boolean override = LUAh_MapThingSpawn(mobj, mthing); + boolean override = LUA_HookMapThingSpawn(mobj, mthing); if (P_MobjWasRemoved(mobj)) return false; diff --git a/src/p_mobj.h b/src/p_mobj.h index 3b947bec6..d6b176828 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -122,7 +122,7 @@ typedef enum MF_AMBIENT = 1<<10, // Slide this object when it hits a wall. MF_SLIDEME = 1<<11, - // Player cheat. + // Don't collide with walls or solid objects. Two MF_NOCLIP objects can't touch each other at all! MF_NOCLIP = 1<<12, // Allow moves to any height, no gravity. For active floaters. MF_FLOAT = 1<<13, diff --git a/src/p_saveg.c b/src/p_saveg.c index eaab7e899..6a1dd51b2 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -367,6 +367,8 @@ static void P_NetArchivePlayers(void) WRITEUINT8(save_p, players[i].stairjank); + WRITEUINT8(save_p, players[i].shrinkLaserDelay); + // respawnvars_t WRITEUINT8(save_p, players[i].respawn.state); WRITEUINT32(save_p, K_GetWaypointHeapIndex(players[i].respawn.wp)); @@ -654,6 +656,8 @@ static void P_NetUnArchivePlayers(void) players[i].stairjank = READUINT8(save_p); + players[i].shrinkLaserDelay = READUINT8(save_p); + // respawnvars_t players[i].respawn.state = READUINT8(save_p); players[i].respawn.wp = (waypoint_t *)(size_t)READUINT32(save_p); @@ -4193,21 +4197,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 +4427,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 +4556,25 @@ static void P_NetArchiveMisc(boolean resending) WRITEUINT8(save_p, 0x2f); else WRITEUINT8(save_p, 0x2e); + + WRITEUINT32(save_p, livestudioaudience_timer); + + // 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 +4718,26 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading) if (READUINT8(save_p) == 0x2f) paused = true; + livestudioaudience_timer = READUINT32(save_p); + + // 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_setup.c b/src/p_setup.c index a0c97d9e8..30f4d61cc 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -66,7 +66,7 @@ #include "md5.h" // map MD5 -// for LUAh_MapLoad +// for MapLoad hook #include "lua_script.h" #include "lua_hook.h" @@ -4455,7 +4455,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) } P_PreTicker(2); P_MapStart(); // just in case MapLoad modifies tmthing - LUAh_MapLoad(); + LUA_HookInt(gamemap, HOOK(MapLoad)); P_MapEnd(); // just in case MapLoad modifies tmthing } diff --git a/src/p_spec.c b/src/p_spec.c index 76ddc27d2..e0ea77c63 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -36,7 +36,7 @@ #include "v_video.h" // V_ALLOWLOWERCASE #include "m_misc.h" #include "m_cond.h" //unlock triggers -#include "lua_hook.h" // LUAh_LinedefExecute +#include "lua_hook.h" // LUA_HookLinedefExecute #include "f_finale.h" // control text prompt #include "r_skins.h" // skins @@ -3032,7 +3032,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 443: // Calls a named Lua function if (line->stringargs[0]) - LUAh_LinedefExecute(line, mo, callsec); + LUA_HookLinedefExecute(line, mo, callsec); else CONS_Alert(CONS_WARNING, "Linedef %s is missing the hook name of the Lua function to call! (This should be given in arg0str)\n", sizeu1(line-lines)); break; diff --git a/src/p_tick.c b/src/p_tick.c index ce45f48f3..58b11406f 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -581,7 +581,7 @@ void P_Ticker(boolean run) ps_lua_mobjhooks = 0; ps_checkposition_calls = 0; - LUAh_PreThinkFrame(); + LUA_HOOK(PreThinkFrame); ps_playerthink_time = I_GetPreciseTime(); @@ -654,7 +654,7 @@ void P_Ticker(boolean run) } ps_lua_thinkframe_time = I_GetPreciseTime(); - LUAh_ThinkFrame(); + LUA_HOOK(ThinkFrame); ps_lua_thinkframe_time = I_GetPreciseTime() - ps_lua_thinkframe_time; } @@ -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. @@ -748,7 +748,7 @@ void P_Ticker(boolean run) // Always move the camera. P_RunChaseCameras(); - LUAh_PostThinkFrame(); + LUA_HOOK(PostThinkFrame); if (run) { @@ -802,7 +802,7 @@ void P_PreTicker(INT32 frames) K_KartUpdatePosition(&players[i]); // OK! Now that we got all of that sorted, players can think! - LUAh_PreThinkFrame(); + LUA_HOOK(PreThinkFrame); for (i = 0; i < MAXPLAYERS; i++) if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) @@ -825,7 +825,7 @@ void P_PreTicker(INT32 frames) if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) P_PlayerAfterThink(&players[i]); - LUAh_ThinkFrame(); + LUA_HOOK(ThinkFrame); // Run shield positioning P_RunOverlays(); @@ -833,7 +833,7 @@ void P_PreTicker(INT32 frames) P_UpdateSpecials(); P_RespawnSpecials(); - LUAh_PostThinkFrame(); + LUA_HOOK(PostThinkFrame); R_UpdateLevelInterpolators(); R_UpdateViewInterpolation(); diff --git a/src/p_user.c b/src/p_user.c index d634f7ad1..1e1cfcbcb 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -665,7 +665,7 @@ boolean P_EvaluateMusicStatus(UINT16 status, const char *musname) break; case JT_OTHER: // Other state - result = LUAh_ShouldJingleContinue(&players[i], musname); + result = LUA_HookShouldJingleContinue(&players[i], musname); break; case JT_NONE: // Null state @@ -1197,7 +1197,7 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj) ghost->sprite2 = mobj->sprite2; ghost->frame = mobj->frame; ghost->tics = -1; - ghost->renderflags |= tr_trans50 << RF_TRANSSHIFT; + ghost->renderflags = (mobj->renderflags & ~RF_TRANSMASK)|RF_TRANS50; ghost->fuse = ghost->info->damage; ghost->skin = mobj->skin; ghost->standingslope = mobj->standingslope; @@ -1207,6 +1207,11 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj) ghost->sprzoff = mobj->sprzoff; ghost->rollangle = mobj->rollangle; + ghost->spritexscale = mobj->spritexscale; + ghost->spriteyscale = mobj->spriteyscale; + ghost->spritexoffset = mobj->spritexoffset; + ghost->spriteyoffset = mobj->spriteyoffset; + if (mobj->flags2 & MF2_OBJECTFLIP) ghost->flags |= MF2_OBJECTFLIP; @@ -3044,6 +3049,10 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall subsector_t *newsubsec; #endif + fixed_t playerScale = FixedDiv(player->mo->scale, mapobjectscale); + fixed_t scaleDiff = playerScale - FRACUNIT; + fixed_t cameraScale = mapobjectscale; + thiscam->old_x = thiscam->x; thiscam->old_y = thiscam->y; thiscam->old_z = thiscam->z; @@ -3132,8 +3141,11 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall return true; } - thiscam->radius = 20*mapobjectscale; - thiscam->height = 16*mapobjectscale; + // Adjust camera to match Grow/Shrink + cameraScale = FixedMul(cameraScale, FRACUNIT + (scaleDiff / 3)); + + thiscam->radius = 20*cameraScale; + thiscam->height = 16*cameraScale; // Don't run while respawning from a starpost // Inu 4/8/13 Why not?! @@ -3159,8 +3171,8 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall camspeed = cv_cam_speed[num].value; camstill = cv_cam_still[num].value; camrotate = cv_cam_rotate[num].value; - camdist = FixedMul(cv_cam_dist[num].value, mapobjectscale); - camheight = FixedMul(cv_cam_height[num].value, mapobjectscale); + camdist = FixedMul(cv_cam_dist[num].value, cameraScale); + camheight = FixedMul(cv_cam_height[num].value, cameraScale); if (timeover) { @@ -3171,8 +3183,8 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall { const INT32 introcam = (introtime - leveltime); camrotate += introcam*5; - camdist += (introcam * mapobjectscale)*3; - camheight += (introcam * mapobjectscale)*2; + camdist += (introcam * cameraScale)*3; + camheight += (introcam * cameraScale)*2; } else if (player->exiting) // SRB2Kart: Leave the camera behind while exiting, for dramatic effect! camstill = true; @@ -3236,7 +3248,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall // sets ideal cam pos { - const fixed_t speedthreshold = 48*mapobjectscale; + const fixed_t speedthreshold = 48*cameraScale; const fixed_t olddist = P_AproxDistance(mo->x - thiscam->x, mo->y - thiscam->y); fixed_t lag, distoffset; @@ -3541,7 +3553,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall // point viewed by the camera // this point is just 64 unit forward the player - dist = 64*mapobjectscale; + dist = 64*cameraScale; viewpointx = mo->x + FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist) + xpan; viewpointy = mo->y + FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist) + ypan; @@ -3684,7 +3696,7 @@ boolean P_SpectatorJoinGame(player_t *player) else changeto = (P_RandomFixed() & 1) + 1; - if (!LUAh_TeamSwitch(player, changeto, true, false, false)) + if (!LUA_HookTeamSwitch(player, changeto, true, false, false)) return false; } @@ -3710,7 +3722,7 @@ boolean P_SpectatorJoinGame(player_t *player) { if (localplayertable[i] == (player-players)) { - LUAh_ViewpointSwitch(player, player, true); + LUA_HookViewpointSwitch(player, player, true); displayplayers[i] = (player-players); break; } @@ -4220,7 +4232,7 @@ void P_PlayerThink(player_t *player) if (player->playerstate == PST_DEAD) { - LUAh_PlayerThink(player); + LUA_HookPlayer(player, HOOK(PlayerThink)); return; } } @@ -4269,7 +4281,7 @@ void P_PlayerThink(player_t *player) else player->mo->renderflags &= ~RF_GHOSTLYMASK; P_DeathThink(player); - LUAh_PlayerThink(player); + LUA_HookPlayer(player, HOOK(PlayerThink)); return; } @@ -4474,7 +4486,7 @@ void P_PlayerThink(player_t *player) if (player->carry == CR_SLIDING) player->carry = CR_NONE; - LUAh_PlayerThink(player); + LUA_HookPlayer(player, HOOK(PlayerThink)); } // @@ -4581,7 +4593,7 @@ void P_PlayerAfterThink(player_t *player) if (player->followmobj) { - if (LUAh_FollowMobj(player, player->followmobj) || P_MobjWasRemoved(player->followmobj)) + if (LUA_HookFollowMobj(player, player->followmobj) || P_MobjWasRemoved(player->followmobj)) {;} else { diff --git a/src/r_bbox.c b/src/r_bbox.c new file mode 100644 index 000000000..7c8887398 --- /dev/null +++ b/src/r_bbox.c @@ -0,0 +1,297 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1993-1996 by id Software, Inc. +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 1999-2021 by Sonic Team Junior. +// Copyright (C) 2022 by Kart Krew. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file r_bbox.c +/// \brief Boundary box (cube) renderer + +#include "doomdef.h" +#include "command.h" +#include "r_local.h" +#include "screen.h" // cv_renderhitbox +#include "v_video.h" // V_DrawFill + +enum { + RENDERHITBOX_OFF, + RENDERHITBOX_TANGIBLE, + RENDERHITBOX_ALL, + RENDERHITBOX_INTANGIBLE, + RENDERHITBOX_RINGS, +}; + +static CV_PossibleValue_t renderhitbox_cons_t[] = { + {RENDERHITBOX_OFF, "Off"}, + {RENDERHITBOX_TANGIBLE, "Tangible"}, + {RENDERHITBOX_ALL, "All"}, + {RENDERHITBOX_INTANGIBLE, "Intangible"}, + {RENDERHITBOX_RINGS, "Rings"}, + {0}}; + +consvar_t cv_renderhitbox = CVAR_INIT ("renderhitbox", "Off", 0, renderhitbox_cons_t, NULL); + +struct bbox_col { + INT32 x; + INT32 y; + INT32 h; +}; + +struct bbox_config { + fixed_t height; + fixed_t tz; + struct bbox_col col[4]; + UINT8 color; +}; + +static inline void +raster_bbox_seg +( INT32 x, + fixed_t y, + fixed_t h, + UINT8 pixel) +{ + y /= FRACUNIT; + + if (y < 0) + y = 0; + + h = y + (FixedCeil(abs(h)) / FRACUNIT); + + if (h >= viewheight) + h = viewheight; + + while (y < h) + { + topleft[x + y * vid.width] = pixel; + y++; + } +} + +static void +draw_bbox_col +( struct bbox_config * bb, + int p, + fixed_t tx, + fixed_t ty) +{ + struct bbox_col *col = &bb->col[p]; + + fixed_t xscale, yscale; + + if (ty < FRACUNIT) // projection breaks down here + ty = FRACUNIT; + + xscale = FixedDiv(projection[viewssnum], ty); + yscale = FixedDiv(projectiony[viewssnum], ty); + + col->x = (centerxfrac + FixedMul(tx, xscale)) / FRACUNIT; + col->y = (centeryfrac - FixedMul(bb->tz, yscale)); + col->h = FixedMul(bb->height, yscale); + + // Using this function is TOO EASY! + V_DrawFill( + viewwindowx + col->x, + viewwindowy + col->y / FRACUNIT, 1, + col->h / FRACUNIT, V_NOSCALESTART | bb->color); +} + +static void +draw_bbox_row +( struct bbox_config * bb, + int p1, + int p2) +{ + struct bbox_col + *a = &bb->col[p1], + *b = &bb->col[p2]; + + INT32 x1, x2; // left, right + INT32 dx; // width + + fixed_t y1, y2; // top, bottom + fixed_t s1, s2; // top and bottom increment + + if (a->x > b->x) + { + struct bbox_col *c = a; + a = b; + b = c; + } + + x1 = a->x; + x2 = b->x; + + if (x2 >= viewwidth) + x2 = viewwidth - 1; + + if (x1 == x2 || x1 >= viewwidth || x2 < 0) + return; + + dx = x2 - x1; + + y1 = a->y; + y2 = b->y; + s1 = (y2 - y1) / dx; + + y2 = y1 + a->h; + s2 = ((b->y + b->h) - y2) / dx; + + // FixedCeil needs a minimum!!! :D :D + + if (s1 == 0) + s1 = 1; + + if (s2 == 0) + s2 = 1; + + if (x1 < 0) + { + y1 -= x1 * s1; + y2 -= x1 * s2; + x1 = 0; + } + + while (x1 < x2) + { + raster_bbox_seg(x1, y1, s1, bb->color); + raster_bbox_seg(x1, y2, s2, bb->color); + + y1 += s1; + y2 += s2; + + x1++; + } +} + +UINT8 R_GetBoundingBoxColor(mobj_t *thing) +{ + UINT32 flags = thing->flags; + + if (thing->player) + return 255; // 0FF + + if (flags & (MF_NOCLIPTHING)) + return 7; // BFBFBF + + if (flags & (MF_SPECIAL)) + return 73; // FF0 + + if (flags & (MF_BOSS|MF_MISSILE|MF_ENEMY|MF_PAIN)) + return 35; // F00 + + if (flags & (MF_NOCLIP)) + return 152; // 00F + + return 0; // FFF +} + +void R_DrawThingBoundingBox(vissprite_t *vis) +{ + // radius offsets + fixed_t rs = vis->scale; + fixed_t rc = vis->xscale; + + // translated coordinates + fixed_t tx = vis->gx; + fixed_t ty = vis->gy; + + struct bbox_config bb = { + .height = vis->thingheight, + .tz = vis->texturemid, + .color = R_GetBoundingBoxColor(vis->mobj), + }; + + // 1--3 + // | | + // 0--2 + + // left + + draw_bbox_col(&bb, 0, tx, ty); // bottom + draw_bbox_col(&bb, 1, tx - rc, ty + rs); // top + + // right + + tx += rs; + ty += rc; + + draw_bbox_col(&bb, 2, tx, ty); // bottom + draw_bbox_col(&bb, 3, tx - rc, ty + rs); // top + + // connect all four columns + + draw_bbox_row(&bb, 0, 1); + draw_bbox_row(&bb, 1, 3); + draw_bbox_row(&bb, 3, 2); + draw_bbox_row(&bb, 2, 0); +} + +static boolean is_tangible (mobj_t *thing) +{ + // These objects can never touch another + if (thing->flags & (MF_NOCLIPTHING)) + { + return false; + } + + // These objects probably do nothing! :D + if ((thing->flags & (MF_SPECIAL|MF_SOLID|MF_SHOOTABLE + |MF_PUSHABLE|MF_BOSS|MF_MISSILE|MF_SPRING + |MF_MONITOR|MF_ENEMY|MF_PAIN|MF_STICKY + |MF_PICKUPFROMBELOW)) == 0U) + { + return false; + } + + return true; +} + +boolean R_ThingBoundingBoxVisible(mobj_t *thing) +{ + INT32 cvmode = cv_renderhitbox.value; + + if (thing->type == MT_WAYPOINT) + { + // Waypoints debugger serves this purpose + return false; + } + + if (thing == r_viewmobj) + { + // Rendering bbox right on top causes anomalies + return false; + } + + switch (cvmode) + { + case RENDERHITBOX_OFF: + return false; + + case RENDERHITBOX_ALL: + return true; + + case RENDERHITBOX_INTANGIBLE: + return !is_tangible(thing); + + case RENDERHITBOX_TANGIBLE: + // Exclude rings from here, lots of them! + if (thing->type == MT_RING) + { + return false; + } + + return is_tangible(thing); + + case RENDERHITBOX_RINGS: + return (thing->type == MT_RING); + + default: + return false; + } +} diff --git a/src/r_things.c b/src/r_things.c index d0fd57600..c76f77129 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -1449,6 +1449,104 @@ static void R_ProjectDropShadow( objectsdrawn++; } +static void R_ProjectBoundingBox(mobj_t *thing, vissprite_t *vis) +{ + fixed_t gx, gy; + fixed_t tx, tz; + + vissprite_t *box; + + // uncapped/interpolation + interpmobjstate_t interp = {0}; + + if (!R_ThingBoundingBoxVisible(thing)) + { + return; + } + + // do interpolation + if (R_UsingFrameInterpolation() && !paused) + { + R_InterpolateMobjState(thing, rendertimefrac, &interp); + } + else + { + R_InterpolateMobjState(thing, FRACUNIT, &interp); + } + + // 1--3 + // | | + // 0--2 + + // start in the (0) corner + gx = interp.x - thing->radius - viewx; + gy = interp.y - thing->radius - viewy; + + tz = FixedMul(gx, viewcos) + FixedMul(gy, viewsin); + + // thing is behind view plane? + // if parent vis is visible, ignore this + if (!vis && (tz < FixedMul(MINZ, interp.scale))) + { + return; + } + + tx = FixedMul(gx, viewsin) - FixedMul(gy, viewcos); + + // too far off the side? + if (!vis && abs(tx) > FixedMul(tz, fovtan[viewssnum])<<2) + { + return; + } + + box = R_NewVisSprite(); + box->mobj = thing; + box->mobjflags = thing->flags; + box->thingheight = thing->height; + box->cut = SC_BBOX; + + box->gx = tx; + box->gy = tz; + + box->scale = 2 * FixedMul(thing->radius, viewsin); + box->xscale = 2 * FixedMul(thing->radius, viewcos); + + box->pz = interp.z; + box->pzt = box->pz + box->thingheight; + + box->gzt = box->pzt; + box->gz = box->pz; + box->texturemid = box->gzt - viewz; + + if (vis) + { + box->x1 = vis->x1; + box->x2 = vis->x2; + box->szt = vis->szt; + box->sz = vis->sz; + + box->sortscale = vis->sortscale; // link sorting to sprite + box->dispoffset = vis->dispoffset + 5; + + box->cut |= SC_LINKDRAW; + } + else + { + fixed_t xscale = FixedDiv(projection[viewssnum], tz); + fixed_t yscale = FixedDiv(projectiony[viewssnum], tz); + fixed_t top = (centeryfrac - FixedMul(box->texturemid, yscale)); + + box->x1 = (centerxfrac + FixedMul(box->gx, xscale)) / FRACUNIT; + box->x2 = box->x1; + + box->szt = top / FRACUNIT; + box->sz = (top + FixedMul(box->thingheight, yscale)) / FRACUNIT; + + box->sortscale = yscale; + box->dispoffset = 0; + } +} + // // R_ProjectSprite // Generates a vissprite for a thing @@ -1690,8 +1788,14 @@ static void R_ProjectSprite(mobj_t *thing) if (spriterotangle && !(splat && !(thing->renderflags & RF_NOSPLATROLLANGLE))) { - rollangle = R_GetRollAngle(vflip - ? InvAngle(spriterotangle) : spriterotangle); + if ((papersprite && ang >= ANGLE_180) != vflip) + { + rollangle = R_GetRollAngle(InvAngle(spriterotangle)); + } + else + { + rollangle = R_GetRollAngle(spriterotangle); + } rotsprite = Patch_GetRotatedSprite(sprframe, (thing->frame & FF_FRAMEMASK), rot, flip, false, sprinfo, rollangle); if (rotsprite != NULL) @@ -2195,6 +2299,8 @@ static void R_ProjectSprite(mobj_t *thing) R_ProjectDropShadow(oldthing, vis, oldthing->shadowscale, basetx, basetz); } + R_ProjectBoundingBox(oldthing, vis); + // Debug ++objectsdrawn; } @@ -2429,8 +2535,26 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel) limit_dist = (fixed_t)(cv_drawdist.value) * mapobjectscale; for (thing = sec->thinglist; thing; thing = thing->snext) { - if (R_ThingVisibleWithinDist(thing, limit_dist)) - R_ProjectSprite(thing); + if (R_ThingWithinDist(thing, limit_dist)) + { + const INT32 oldobjectsdrawn = objectsdrawn; + + if (R_ThingVisible(thing)) + { + R_ProjectSprite(thing); + } + + // I'm so smart :^) + if (objectsdrawn == oldobjectsdrawn) + { + /* + Object is invisible OR is off screen but + render its bbox even if the latter because + radius could be bigger than sprite. + */ + R_ProjectBoundingBox(thing, NULL); + } + } } // no, no infinite draw distance for precipitation. this option at zero is supposed to turn it off @@ -2503,6 +2627,10 @@ static void R_SortVisSprites(vissprite_t* vsprsortedhead, UINT32 start, UINT32 e if (dsfirst->cut & SC_SHADOW) continue; + // don't connect to your bounding box! + if (dsfirst->cut & SC_BBOX) + continue; + // don't connect if it's not the tracer if (dsfirst->mobj != ds->mobj) continue; @@ -2946,7 +3074,9 @@ static void R_DrawSprite(vissprite_t *spr) mfloorclip = spr->clipbot; mceilingclip = spr->cliptop; - if (spr->cut & SC_SPLAT) + if (spr->cut & SC_BBOX) + R_DrawThingBoundingBox(spr); + else if (spr->cut & SC_SPLAT) R_DrawFloorSplat(spr); else R_DrawVisSprite(spr); @@ -3245,6 +3375,12 @@ void R_ClipSprites(drawseg_t* dsstart, portal_t* portal) INT32 x1 = (spr->cut & SC_SPLAT) ? 0 : spr->x1; INT32 x2 = (spr->cut & SC_SPLAT) ? viewwidth : spr->x2; + if (spr->cut & SC_BBOX) + { + // Do not clip bounding boxes + continue; + } + if (x2 < cx) { drawsegs_xrange = drawsegs_xranges[1].items; @@ -3283,18 +3419,14 @@ boolean R_ThingVisible (mobj_t *thing) return true; } -boolean R_ThingVisibleWithinDist (mobj_t *thing, - fixed_t limit_dist) +boolean R_ThingWithinDist (mobj_t *thing, fixed_t limit_dist) { - fixed_t approx_dist; + const fixed_t dist = R_PointToDist(thing->x, thing->y); - if (! R_ThingVisible(thing)) - return false; - - approx_dist = P_AproxDistance(viewx-thing->x, viewy-thing->y); - - if (limit_dist && approx_dist > limit_dist) + if (limit_dist && dist > limit_dist) + { return false; + } return true; } diff --git a/src/r_things.h b/src/r_things.h index bd7449d3e..b89645fff 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -66,9 +66,12 @@ 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); -boolean R_ThingVisibleWithinDist (mobj_t *thing, +boolean R_ThingWithinDist (mobj_t *thing, fixed_t draw_dist); boolean R_PrecipThingVisible (precipmobj_t *precipthing, @@ -134,6 +137,7 @@ typedef enum SC_SPLAT = 1<<11, // srb2kart SC_SEMIBRIGHT = 1<<12, + SC_BBOX = 1<<13, // masks SC_CUTMASK = SC_TOP|SC_BOTTOM, SC_FLAGMASK = ~SC_CUTMASK @@ -220,6 +224,8 @@ extern UINT32 visspritecount; void R_ClipSprites(drawseg_t* dsstart, portal_t* portal); void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, portal_t* portal); +void R_DrawThingBoundingBox(vissprite_t *spr); + UINT8 *R_GetSpriteTranslation(vissprite_t *vis); // ---------- diff --git a/src/s_sound.c b/src/s_sound.c index 1b1d6c0b0..07d0d40fc 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) @@ -2121,6 +2126,15 @@ void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32 { char newmusic[7]; + struct MusicChange hook_param = { + newmusic, + &mflags, + &looping, + &position, + &prefadems, + &fadeinms + }; + if (S_MusicDisabled() || demo.rewinding // Don't mess with music while rewinding! || demo.title) // SRB2Kart: Demos don't interrupt title screen music @@ -2128,7 +2142,7 @@ void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32 strncpy(newmusic, mmusic, 7); - if (LUAh_MusicChange(music_name, newmusic, &mflags, &looping, &position, &prefadems, &fadeinms)) + if (LUA_HookMusicChange(music_name, &hook_param)) return; newmusic[6] = 0; @@ -2470,6 +2484,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/s_sound.h b/src/s_sound.h index 515fcd4f4..461f376af 100644 --- a/src/s_sound.h +++ b/src/s_sound.h @@ -243,6 +243,16 @@ boolean S_RecallMusic(UINT16 status, boolean fromfirst); // Music Playback // +/* this is for the sake of the hook */ +struct MusicChange { + char * newname; + UINT16 * mflags; + boolean * looping; + UINT32 * position; + UINT32 * prefadems; + UINT32 * fadeinms; +}; + enum { MUS_SPECIAL = 1,/* powerups--invincibility, grow */ diff --git a/src/screen.h b/src/screen.h index 268b11e95..8abf77224 100644 --- a/src/screen.h +++ b/src/screen.h @@ -201,7 +201,7 @@ extern CV_PossibleValue_t cv_renderer_t[]; extern INT32 scr_bpp; extern UINT8 *scr_borderpatch; // patch used to fill the view borders -extern consvar_t cv_scr_width, cv_scr_height, cv_scr_depth, cv_renderview, cv_renderer, cv_fullscreen; +extern consvar_t cv_scr_width, cv_scr_height, cv_scr_depth, cv_renderview, cv_renderer, cv_renderhitbox, cv_fullscreen; extern consvar_t cv_vhseffect, cv_shittyscreen; // wait for page flipping to end or not diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c index 7b1c09b71..2361a5349 100644 --- a/src/sdl/i_video.c +++ b/src/sdl/i_video.c @@ -106,8 +106,10 @@ rendermode_t chosenrendermode = render_none; // set by command line arguments boolean highcolor = false; +static void Impl_SetVsync(void); + // synchronize page flipping with screen refresh -consvar_t cv_vidwait = CVAR_INIT ("vid_wait", "On", CV_SAVE, CV_OnOff, NULL); +consvar_t cv_vidwait = CVAR_INIT ("vid_wait", "On", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, Impl_SetVsync); static consvar_t cv_stretch = CVAR_INIT ("stretch", "Off", CV_SAVE|CV_NOSHOWHELP, CV_OnOff, NULL); static consvar_t cv_alwaysgrabmouse = CVAR_INIT ("alwaysgrabmouse", "Off", CV_SAVE, CV_OnOff, NULL); @@ -1041,7 +1043,7 @@ void I_GetEvent(void) #endif break; case SDL_QUIT: - LUAh_GameQuit(true); + LUA_HookBool(true, HOOK(GameQuit)); I_Quit(); break; } @@ -2002,3 +2004,11 @@ UINT32 I_GetRefreshRate(void) // trouble querying mode over and over again. return refresh_rate; } + +static void Impl_SetVsync(void) +{ +#if SDL_VERSION_ATLEAST(2,0,18) + if (renderer) + SDL_RenderSetVSync(renderer, cv_vidwait.value); +#endif +} 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 14bdaa15d..af18b1441 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -47,6 +47,7 @@ #include "lua_hudlib_drawlist.h" #include "lua_hud.h" +#include "lua_hook.h" // SRB2Kart #include "k_hud.h" // SRB2kart @@ -965,7 +966,7 @@ luahook: if (renderisnewtic) { LUA_HUD_ClearDrawList(luahuddrawlist_titlecard); - LUAh_TitleCardHUD(stplyr, luahuddrawlist_titlecard); + LUA_HookHUD(luahuddrawlist_titlecard, HUD_HOOK(titlecard)); } LUA_HUD_DrawList(luahuddrawlist_titlecard); } @@ -1012,7 +1013,7 @@ static void ST_overlayDrawer(void) if (renderisnewtic) { LUA_HUD_ClearDrawList(luahuddrawlist_game); - LUAh_GameHUD(stplyr, luahuddrawlist_game); + LUA_HookHUD(luahuddrawlist_game, HUD_HOOK(game)); } LUA_HUD_DrawList(luahuddrawlist_game); } @@ -1217,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<levelflags & LF_NOZONE || !mapheaderinfo[votelevels[i][0]]->zonttl[0]) @@ -1557,6 +1559,7 @@ void Y_StartVote(void) } voteclient.loaded = true; + Automate_Run(AEV_VOTESTART); } //