diff --git a/src/command.c b/src/command.c index a66522186..eb1204108 100644 --- a/src/command.c +++ b/src/command.c @@ -1080,9 +1080,10 @@ static void COM_Toggle_f(void) for (i = 2; i < COM_Argc() - 1; ++i) { const char *str = COM_Argv(i); - INT32 val; + INT32 val = 0; - if (CV_CompleteValue(cvar, &str, &val)) + if (!cvar->PossibleValue || + CV_CompleteValue(cvar, &str, &val)) { if (str ? !stricmp(cvar->string, str) : cvar->value == val) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 03045933d..b71ab15a5 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -531,8 +531,10 @@ typedef enum CL_SEARCHING, CL_CHECKFILES, CL_DOWNLOADFILES, + CL_DOWNLOADFAILED, CL_ASKJOIN, CL_LOADFILES, + CL_SETUPFILES, CL_WAITJOINRESPONSE, CL_DOWNLOADSAVEGAME, CL_CONNECTED, @@ -615,8 +617,12 @@ static inline void CL_DrawConnectionStatus(void) break; case CL_ASKFULLFILELIST: case CL_CONFIRMCONNECT: + case CL_DOWNLOADFAILED: cltext = ""; break; + case CL_SETUPFILES: + cltext = M_GetText("Configuring addons..."); + break; case CL_ASKJOIN: case CL_WAITJOINRESPONSE: if (serverisfull) @@ -655,8 +661,8 @@ static inline void CL_DrawConnectionStatus(void) V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-32, V_YELLOWMAP, "Checking server addons..."); totalfileslength = (INT32)((checkednum/(double)(fileneedednum)) * 256); M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-24-8, 32, 1); - V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, 256, 8, 175); - V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, totalfileslength, 8, 160); + V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, 256, 8, 111); + V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, totalfileslength, 8, 96); V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE, va(" %2u/%2u Files",checkednum,fileneedednum)); } @@ -677,8 +683,8 @@ static inline void CL_DrawConnectionStatus(void) V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-32, V_YELLOWMAP, "Loading server addons..."); totalfileslength = (INT32)((loadcompletednum/(double)(fileneedednum)) * 256); M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-24-8, 32, 1); - V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, 256, 8, 175); - V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, totalfileslength, 8, 160); + V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, 256, 8, 111); + V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, totalfileslength, 8, 96); V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE, va(" %2u/%2u Files",loadcompletednum,fileneedednum)); } @@ -719,8 +725,10 @@ static inline void CL_DrawConnectionStatus(void) strncpy(tempname, filename, sizeof(tempname)-1); } + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-58-30, 0, + va(M_GetText("%s downloading"), ((cl_mode == CL_DOWNLOADHTTPFILES) ? "\x82""HTTP" : "\x85""Direct"))); V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-58-22, V_YELLOWMAP, - va(M_GetText("Downloading \"%s\""), tempname)); + va(M_GetText("\"%s\""), tempname)); V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-58, V_20TRANS|V_MONOSPACE, va(" %4uK/%4uK",fileneeded[lastfilenum].currentsize>>10,file->totalsize>>10)); V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-58, V_20TRANS|V_MONOSPACE, @@ -736,8 +744,8 @@ static inline void CL_DrawConnectionStatus(void) V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-14, V_YELLOWMAP, "Overall Download Progress"); totalfileslength = (INT32)((totaldldsize/(double)totalfilesrequestedsize) * 256); M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-24-8, 32, 1); - V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, 256, 8, 175); - V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, totalfileslength, 8, 160); + V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, 256, 8, 111); + V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, totalfileslength, 8, 96); if (totalfilesrequestedsize>>20 >= 10) //display in MB if over 10MB V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE, @@ -1497,6 +1505,10 @@ static void M_ConfirmConnect(void) { cl_mode = CL_DOWNLOADFILES; } + else + { + cl_mode = CL_DOWNLOADFAILED; + } } #ifdef HAVE_CURL else @@ -1645,6 +1657,10 @@ static boolean CL_FinishedFileList(void) { cl_mode = CL_DOWNLOADFILES; } + else + { + cl_mode = CL_DOWNLOADFAILED; + } } #endif } @@ -1850,8 +1866,28 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic cl_mode = CL_LOADFILES; break; + case CL_DOWNLOADFAILED: + { + CONS_Printf(M_GetText("Legacy downloader request packet failed.\n")); + CONS_Printf(M_GetText("Network game synchronization aborted.\n")); + D_QuitNetGame(); + CL_Reset(); + D_StartTitle(); + M_StartMessage(M_GetText( + "The direct download encountered an error.\n" + "See the logfile for more info.\n" + "\n" + "Press (B)\n" + ), NULL, MM_NOTHING); + return false; + } case CL_LOADFILES: - if (CL_LoadServerFiles()) + if (CL_LoadServerFiles()) + cl_mode = CL_SETUPFILES; + + break; + case CL_SETUPFILES: + if (P_PartialAddGetStage() < 0 || P_MultiSetupWadFiles(false)) { *asksent = 0; //This ensure the first join ask is right away firstconnectattempttime = I_GetTime(); @@ -2079,7 +2115,12 @@ static void CL_ConnectToServer(void) { // If the connection was aborted for some reason, leave if (!CL_ServerConnectionTicker(tmpsave, &oldtic, &asksent)) + { + if (P_PartialAddGetStage() >= 0) + P_MultiSetupWadFiles(true); // in case any partial adds were done + return; + } if (server) { @@ -2487,6 +2528,11 @@ void CL_ClearPlayer(INT32 playernum) { int i; + if (players[playernum].follower) + { + K_RemoveFollower(&players[playernum]); + } + if (players[playernum].mo) { P_RemoveMobj(players[playernum].mo); @@ -3927,6 +3973,11 @@ static void HandleConnect(SINT8 node) // If a server filled out, then it'd overwrite the host and turn everyone into weird husks..... // 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); + UINT8 connectedplayers = 0; + + for (i = dedicated ? 1 : 0; i < MAXPLAYERS; i++) + if (playernode[i] != UINT8_MAX) // We use this to count players because it is affected by SV_AddWaitingPlayers when more than one client joins on the same tic, unlike playeringame and D_NumPlayers. UINT8_MAX denotes no node for that player + connectedplayers++; if (bannednode && bannednode[node].banid != SIZE_MAX) { @@ -3984,7 +4035,7 @@ static void HandleConnect(SINT8 node) { SV_SendRefuse(node, M_GetText("The server is not accepting\njoins for the moment.")); } - else if (D_NumPlayers() >= maxplayers) + else if (connectedplayers >= maxplayers) { SV_SendRefuse(node, va(M_GetText("Maximum players reached: %d"), maxplayers)); } @@ -3992,7 +4043,7 @@ static void HandleConnect(SINT8 node) { SV_SendRefuse(node, M_GetText("Too many players from\nthis node.")); } - else if (netgame && D_NumPlayers() + netbuffer->u.clientcfg.localplayers > maxplayers) + else if (netgame && connectedplayers + netbuffer->u.clientcfg.localplayers > maxplayers) { SV_SendRefuse(node, va(M_GetText("Number of local players\nwould exceed maximum: %d"), maxplayers)); } @@ -5475,14 +5526,6 @@ boolean TryRunTics(tic_t realtics) if (ticking) { - if (advancedemo) - { - if (timedemo_quit) - COM_ImmedExecute("quit"); - else - D_StartTitle(); - } - else { // run the count * tics while (neededtic > gametic) diff --git a/src/d_main.c b/src/d_main.c index bcfc5d1b8..78741c437 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -131,7 +131,6 @@ INT32 postimgparam[MAXSPLITSCREENPLAYERS]; boolean sound_disabled = false; boolean digital_disabled = false; -boolean advancedemo; #ifdef DEBUGFILE INT32 debugload = 0; #endif @@ -913,15 +912,6 @@ void D_SRB2Loop(void) } } -// -// D_AdvanceDemo -// Called after each demo or intro demosequence finishes -// -void D_AdvanceDemo(void) -{ - advancedemo = true; -} - // ========================================================================= // D_SRB2Main // ========================================================================= @@ -997,13 +987,13 @@ void D_StartTitle(void) //demosequence = -1; G_SetGametype(GT_RACE); // SRB2kart paused = false; - advancedemo = false; // clear cmd building stuff memset(gamekeydown, 0, sizeof (gamekeydown)); memset(deviceResponding, false, sizeof (deviceResponding)); F_StartTitleScreen(); + M_ClearMenus(false); // Reset the palette if (rendermode != render_none) @@ -1214,8 +1204,6 @@ void D_SRB2Main(void) { INT32 i, p; - INT32 numbasemapheaders; - INT32 pstartmap = 1; boolean autostart = false; @@ -1472,9 +1460,7 @@ void D_SRB2Main(void) // // search for mainwad maps // - P_InitMapData(0); - - numbasemapheaders = nummapheaders; + P_InitMapData(false); CON_SetLoadingProgress(LOADED_IWAD); @@ -1485,7 +1471,7 @@ void D_SRB2Main(void) // // search for pwad maps // - P_InitMapData(numbasemapheaders); + P_InitMapData(true); CON_SetLoadingProgress(LOADED_PWAD); diff --git a/src/d_main.h b/src/d_main.h index f01f9227f..c8e803521 100644 --- a/src/d_main.h +++ b/src/d_main.h @@ -18,8 +18,6 @@ #include "d_event.h" #include "w_wad.h" // for MAX_WADFILES -extern boolean advancedemo; - // make sure not to write back the config until it's been correctly loaded extern tic_t rendergametic; @@ -52,7 +50,6 @@ const char *D_Home(void); // // BASE LEVEL // -void D_AdvanceDemo(void); void D_StartTitle(void); #endif //__D_MAIN__ diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 51d205931..44480910a 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -295,10 +295,10 @@ consvar_t cv_follower[MAXSPLITSCREENPLAYERS] = { // player's follower colors... Also saved... consvar_t cv_followercolor[MAXSPLITSCREENPLAYERS] = { - CVAR_INIT ("followercolor", "1", CV_SAVE|CV_CALL|CV_NOINIT, Followercolor_cons_t, Followercolor_OnChange), - CVAR_INIT ("followercolor2", "1", CV_SAVE|CV_CALL|CV_NOINIT, Followercolor_cons_t, Followercolor2_OnChange), - CVAR_INIT ("followercolor3", "1", CV_SAVE|CV_CALL|CV_NOINIT, Followercolor_cons_t, Followercolor3_OnChange), - CVAR_INIT ("followercolor4", "1", CV_SAVE|CV_CALL|CV_NOINIT, Followercolor_cons_t, Followercolor4_OnChange) + CVAR_INIT ("followercolor", "Match", CV_SAVE|CV_CALL|CV_NOINIT, Followercolor_cons_t, Followercolor_OnChange), + CVAR_INIT ("followercolor2", "Match", CV_SAVE|CV_CALL|CV_NOINIT, Followercolor_cons_t, Followercolor2_OnChange), + CVAR_INIT ("followercolor3", "Match", CV_SAVE|CV_CALL|CV_NOINIT, Followercolor_cons_t, Followercolor3_OnChange), + CVAR_INIT ("followercolor4", "Match", CV_SAVE|CV_CALL|CV_NOINIT, Followercolor_cons_t, Followercolor4_OnChange) }; // last selected profile, unaccessible cvar only set internally but is saved. @@ -386,7 +386,6 @@ consvar_t cv_gardentop = CVAR_INIT ("gardentop", "On", CV_NETVAR, CV_OnOff, consvar_t cv_dualsneaker = CVAR_INIT ("dualsneaker", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_triplesneaker = CVAR_INIT ("triplesneaker", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_triplebanana = CVAR_INIT ("triplebanana", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_decabanana = CVAR_INIT ("decabanana", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_tripleorbinaut = CVAR_INIT ("tripleorbinaut", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_quadorbinaut = CVAR_INIT ("quadorbinaut", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_dualjawz = CVAR_INIT ("dualjawz", "On", CV_NETVAR, CV_OnOff, NULL); @@ -831,27 +830,6 @@ void D_RegisterClientCommands(void) { INT32 i; - for (i = 0; i < MAXSKINCOLORS; i++) - { - Color_cons_t[i].value = i; - Color_cons_t[i].strvalue = skincolors[i].name; - } - - for (i = 2; i < MAXSKINCOLORS; i++) - { - Followercolor_cons_t[i].value = i-2; - Followercolor_cons_t[i].strvalue = skincolors[i-2].name; - } - - Followercolor_cons_t[1].value = FOLLOWERCOLOR_MATCH; - Followercolor_cons_t[1].strvalue = "Match"; // Add "Match" option, which will make the follower color match the player's - - Followercolor_cons_t[0].value = FOLLOWERCOLOR_OPPOSITE; - Followercolor_cons_t[0].strvalue = "Opposite"; // Add "Opposite" option, ...which is like "Match", but for coloropposite. - - Color_cons_t[MAXSKINCOLORS].value = Followercolor_cons_t[MAXSKINCOLORS+2].value = 0; - Color_cons_t[MAXSKINCOLORS].strvalue = Followercolor_cons_t[MAXSKINCOLORS+2].strvalue = NULL; - // Set default player names // Monster Iestyn (12/08/19): not sure where else I could have actually put this, but oh well for (i = 0; i < MAXPLAYERS; i++) @@ -986,6 +964,9 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_bsaturation); CV_RegisterVar(&cv_msaturation); + CV_RegisterVar(&cv_palette); + CV_RegisterVar(&cv_palettenum); + // k_menu.c //CV_RegisterVar(&cv_compactscoreboard); CV_RegisterVar(&cv_chatheight); @@ -1089,6 +1070,7 @@ void D_RegisterClientCommands(void) COM_AddCommand("rteleport", Command_RTeleport_f); COM_AddCommand("skynum", Command_Skynum_f); COM_AddCommand("weather", Command_Weather_f); + COM_AddCommand("grayscale", Command_Grayscale_f); #ifdef _DEBUG COM_AddCommand("causecfail", Command_CauseCfail_f); #endif @@ -1489,7 +1471,7 @@ static void SendNameAndColor(UINT8 n) const INT32 playernum = g_localplayers[n]; player_t *player = &players[playernum]; - char buf[MAXPLAYERNAME+9]; + char buf[MAXPLAYERNAME+12]; char *p; if (splitscreen < n) @@ -1520,14 +1502,11 @@ static void SendNameAndColor(UINT8 n) if (!cv_followercolor[n].value) CV_StealthSet(&cv_followercolor[n], "Match"); // set it to "Match". I don't care about your stupidity! - // so like, this is sent before we even use anything like cvars or w/e so it's possible that follower is set to a pretty yikes value, so let's fix that before we send garbage that could crash the game: - if (cv_follower[n].value >= numfollowers || cv_follower[n].value < -1) - CV_StealthSet(&cv_follower[n], "-1"); - if (!strcmp(cv_playername[n].string, player_names[playernum]) && cv_playercolor[n].value == player->skincolor - && !strcmp(cv_skin[n].string, skins[player->skin].name) - && cv_follower[n].value == player->followerskin + && !stricmp(cv_skin[n].string, skins[player->skin].name) + && !stricmp(cv_follower[n].string, + (player->followerskin < 0 ? "None" : followers[player->followerskin].name)) && cv_followercolor[n].value == player->followercolor) return; @@ -1549,31 +1528,28 @@ static void SendNameAndColor(UINT8 n) K_KartResetPlayerColor(player); - // Update follower for local games: - if (cv_follower[n].value >= -1 && cv_follower[n].value != player->followerskin) - K_SetFollowerByNum(playernum, cv_follower[n].value); - - player->followercolor = cv_followercolor[n].value; - - if (metalrecording && n == 0) - { // Starring Metal Sonic as themselves, obviously. - SetPlayerSkinByNum(playernum, 5); - CV_StealthSet(&cv_skin[n], skins[5].name); - } - else if ((foundskin = R_SkinAvailable(cv_skin[n].string)) != -1 && R_SkinUsable(playernum, foundskin)) + if ((foundskin = R_SkinAvailable(cv_skin[n].string)) != -1 && R_SkinUsable(playernum, foundskin)) { - cv_skin[n].value = foundskin; SetPlayerSkin(playernum, cv_skin[n].string); - CV_StealthSet(&cv_skin[n], skins[cv_skin[n].value].name); + CV_StealthSet(&cv_skin[n], skins[foundskin].name); + cv_skin[n].value = foundskin; } else { - cv_skin[n].value = players[playernum].skin; CV_StealthSet(&cv_skin[n], skins[player->skin].name); + cv_skin[n].value = player->skin; // will always be same as current SetPlayerSkin(playernum, cv_skin[n].string); } + player->followercolor = cv_followercolor[n].value; + + // Update follower for local games: + foundskin = K_FollowerAvailable(cv_follower[n].string); + CV_StealthSet(&cv_follower[n], (foundskin == -1) ? "None" : followers[foundskin].name); + cv_follower[n].value = foundskin; + K_SetFollowerByNum(playernum, foundskin); + return; } @@ -1603,12 +1579,20 @@ static void SendNameAndColor(UINT8 n) cv_skin[n].value = 0; } + cv_follower[n].value = K_FollowerAvailable(cv_follower[n].string); + if (cv_follower[n].value < 0) + { + CV_StealthSet(&cv_follower[n], "None"); + cv_follower[n].value = -1; + } + // Finally write out the complete packet and send it off. WRITESTRINGN(p, cv_playername[n].zstring, MAXPLAYERNAME); WRITEUINT32(p, (UINT32)player->availabilities); WRITEUINT16(p, (UINT16)cv_playercolor[n].value); WRITEUINT8(p, (UINT8)cv_skin[n].value); - WRITESINT8(p, (SINT8)cv_follower[n].value); + WRITEINT16(p, (INT16)cv_follower[n].value); + //CONS_Printf("Sending follower id %d\n", (INT16)cv_follower[n].value); WRITEUINT16(p, (UINT16)cv_followercolor[n].value); SendNetXCmdForPlayer(n, XD_NAMEANDCOLOR, buf, p - buf); @@ -1620,7 +1604,7 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) char name[MAXPLAYERNAME+1]; UINT16 color, followercolor; UINT8 skin; - SINT8 follower; + INT16 follower; SINT8 localplayer = -1; UINT8 i; @@ -1649,7 +1633,8 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) p->availabilities = READUINT32(*cp); color = READUINT16(*cp); skin = READUINT8(*cp); - follower = READSINT8(*cp); + follower = READINT16(*cp); + //CONS_Printf("Recieved follower id %d\n", follower); followercolor = READUINT16(*cp); // set name @@ -6075,6 +6060,7 @@ static void Name_OnChange(void) { CONS_Alert(CONS_NOTICE, M_GetText("You may not change your name when chat is muted.\n")); CV_StealthSet(&cv_playername[0], player_names[consoleplayer]); + return; } else SendNameAndColor(0); @@ -6115,207 +6101,56 @@ static void Name4_OnChange(void) } // sends the follower change for players -static void Follower_OnChange(void) +static void FollowerAny_OnChange(UINT8 pnum) { - char str[SKINNAMESIZE+1], cpy[SKINNAMESIZE+1]; - INT32 num; - - // there is a slight chance that we will actually use a string instead so... - // let's investigate the string... - strcpy(str, cv_follower[0].string); - strcpy(cpy, cv_follower[0].string); - strlwr(str); - if (stricmp(cpy,"0") !=0 && !atoi(cpy)) // yep, that's a string alright... - { - if (stricmp(cpy, "None") == 0) - { - CV_StealthSet(&cv_follower[0], "-1"); - - if (!Playing()) - return; // don't send anything there. - - SendNameAndColor(0); - return; - } - - num = K_FollowerAvailable(str); - - if (num == -1) // that's an error. - CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str); - - CV_StealthSet(&cv_follower[0], str); - cv_follower[0].value = num; - } - if (!Playing()) return; // don't send anything there. - SendNameAndColor(0); + SendNameAndColor(pnum); +} + +// sends the follower change for players +static void Follower_OnChange(void) +{ + FollowerAny_OnChange(0); } // About the same as Color_OnChange but for followers. static void Followercolor_OnChange(void) { - if (!Playing()) - return; // do whatever you want if you aren't in the game or don't have a follower. - - if (!P_PlayerMoving(consoleplayer)) - { - // Color change menu scrolling fix is no longer necessary - SendNameAndColor(0); - } + FollowerAny_OnChange(0); } // repeat for the 3 other players static void Follower2_OnChange(void) { - char str[SKINNAMESIZE+1], cpy[SKINNAMESIZE+1]; - INT32 num; - - // there is a slight chance that we will actually use a string instead so... - // let's investigate the string... - strcpy(str, cv_follower[1].string); - strcpy(cpy, cv_follower[1].string); - strlwr(str); - if (stricmp(cpy,"0") !=0 && !atoi(cpy)) // yep, that's a string alright... - { - if (stricmp(cpy, "None") == 0) - { - CV_StealthSet(&cv_follower[1], "-1"); - - if (!Playing()) - return; // don't send anything there. - - SendNameAndColor(1); - return; - } - - num = K_FollowerAvailable(str); - - if (num == -1) // that's an error. - CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str); - - CV_StealthSet(&cv_follower[1], str); - cv_follower[1].value = num; - } - - if (!Playing()) - return; // don't send anything there. - - SendNameAndColor(1); + FollowerAny_OnChange(1); } static void Followercolor2_OnChange(void) { - if (!Playing()) - return; // do whatever you want if you aren't in the game or don't have a follower. - - if (!P_PlayerMoving(g_localplayers[1])) - { - // Color change menu scrolling fix is no longer necessary - SendNameAndColor(1); - } + FollowerAny_OnChange(1); } static void Follower3_OnChange(void) { - char str[SKINNAMESIZE+1], cpy[SKINNAMESIZE+1]; - INT32 num; - - // there is a slight chance that we will actually use a string instead so... - // let's investigate the string... - strcpy(str, cv_follower[2].string); - strcpy(cpy, cv_follower[2].string); - strlwr(str); - if (stricmp(cpy,"0") !=0 && !atoi(cpy)) // yep, that's a string alright... - { - if (stricmp(cpy, "None") == 0) - { - CV_StealthSet(&cv_follower[2], "-1"); - - if (!Playing()) - return; // don't send anything there. - - SendNameAndColor(2); - return; - } - - num = K_FollowerAvailable(str); - - if (num == -1) // that's an error. - CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str); - - CV_StealthSet(&cv_follower[2], str); - cv_follower[2].value = num; - } - - if (!Playing()) - return; // don't send anything there. - - SendNameAndColor(2); + FollowerAny_OnChange(2); } static void Followercolor3_OnChange(void) { - if (!Playing()) - return; // do whatever you want if you aren't in the game or don't have a follower. - - if (!P_PlayerMoving(g_localplayers[2])) - { - // Color change menu scrolling fix is no longer necessary - SendNameAndColor(2); - } + FollowerAny_OnChange(2); } static void Follower4_OnChange(void) { - char str[SKINNAMESIZE+1], cpy[SKINNAMESIZE+1]; - INT32 num; - - // there is a slight chance that we will actually use a string instead so... - // let's investigate the string... - strcpy(str, cv_follower[3].string); - strcpy(cpy, cv_follower[3].string); - strlwr(str); - if (stricmp(cpy,"0") !=0 && !atoi(cpy)) // yep, that's a string alright... - { - if (stricmp(cpy, "None") == 0) - { - CV_StealthSet(&cv_follower[3], "-1"); - - if (!Playing()) - return; // don't send anything there. - - SendNameAndColor(3); - return; - } - - num = K_FollowerAvailable(str); - - if (num == -1) // that's an error. - CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str); - - CV_StealthSet(&cv_follower[3], str); - cv_follower[3].value = num; - } - - if (!Playing()) - return; // don't send anything there. - - SendNameAndColor(3); + FollowerAny_OnChange(3); } static void Followercolor4_OnChange(void) { - if (!Playing()) - return; // do whatever you want if you aren't in the game or don't have a follower. - - if (!P_PlayerMoving(g_localplayers[3])) - { - // Color change menu scrolling fix is no longer necessary - SendNameAndColor(3); - } + FollowerAny_OnChange(3); } /** Sends a skin change for the console player, unless that player is moving. Also forces them to spectate if the change is done during gameplay @@ -6335,7 +6170,9 @@ static void Skin_OnChange(void) } if (CanChangeSkinWhilePlaying(consoleplayer)) + { SendNameAndColor(0); + } else { CONS_Alert(CONS_NOTICE, M_GetText("You can't change your skin at the moment.\n")); diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 266d73395..f3ace5cd9 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -100,7 +100,6 @@ extern consvar_t cv_dualsneaker, cv_triplesneaker, cv_triplebanana, - cv_decabanana, cv_tripleorbinaut, cv_quadorbinaut, cv_dualjawz; diff --git a/src/d_netfil.c b/src/d_netfil.c index 3de3a0030..246567c72 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -354,6 +354,9 @@ void CL_AbortDownloadResume(void) pauseddownload = NULL; } +// The following was written and, against all odds, works. +#define MORELEGACYDOWNLOADER + /** Sends requests for files in the ::fileneeded table with a status of * ::FS_NOTFOUND. * @@ -366,42 +369,135 @@ boolean CL_SendFileRequest(void) char *p; INT32 i; INT64 totalfreespaceneeded = 0, availablefreespace; + INT32 skippedafile = -1; +#ifdef MORELEGACYDOWNLOADER + boolean firstloop = true; +#endif #ifdef PARANOIA if (M_CheckParm("-nodownload")) - I_Error("Attempted to download files in -nodownload mode"); + { + CONS_Printf("Direct download - Attempted to download files in -nodownload mode"); + return false; + } +#endif for (i = 0; i < fileneedednum; i++) + { +#ifdef PARANOIA if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN && (fileneeded[i].willsend == 0 || fileneeded[i].willsend == 2)) { - I_Error("Attempted to download files that were not sendable"); + CONS_Printf("Direct download - attempted to download files that were not sendable\n"); + return false; } #endif + if ((fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD || fileneeded[i].status == FS_FALLBACK)) + { + // Error check for the first time around. + totalfreespaceneeded += fileneeded[i].totalsize; + } + } + + I_GetDiskFreeSpace(&availablefreespace); + if (totalfreespaceneeded > availablefreespace) + { + CONS_Printf("Direct download -\n" + " To play on this server you must download %s KB,\n" + " but you have only %s KB free space on this drive\n", + sizeu1((size_t)(totalfreespaceneeded>>10)), sizeu2((size_t)(availablefreespace>>10))); + return false; + } + +#ifdef MORELEGACYDOWNLOADER +tryagain: + skippedafile = -1; +#endif + +#ifdef VERBOSEREQUESTFILE + CONS_Printf("Preparing packet\n"); +#endif netbuffer->packettype = PT_REQUESTFILE; p = (char *)netbuffer->u.textcmd; + for (i = 0; i < fileneedednum; i++) + { if ((fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD || fileneeded[i].status == FS_FALLBACK)) { - totalfreespaceneeded += fileneeded[i].totalsize; + // Pre-prepare. + size_t checklen; nameonly(fileneeded[i].filename); + + // Figure out if we'd overrun our buffer. + checklen = strlen(fileneeded[i].filename)+2; // plus the fileid (and terminator, in case this is last) + if ((UINT8 *)(p + checklen) >= netbuffer->u.textcmd + MAXTEXTCMD) + { + skippedafile = i; + // we might have a shorter file that can fit in the remaining space, and file ID permits out-of-order data + continue; + } + + // Now write. WRITEUINT8(p, i); // fileid WRITESTRINGN(p, fileneeded[i].filename, MAX_WADPATH); + +#ifdef VERBOSEREQUESTFILE + CONS_Printf(" file \"%s\" (id %d)\n", i, fileneeded[i].filename); +#endif + // put it in download dir strcatbf(fileneeded[i].filename, downloaddir, "/"); fileneeded[i].status = FS_REQUESTED; } - WRITEUINT8(p, 0xFF); - I_GetDiskFreeSpace(&availablefreespace); - if (totalfreespaceneeded > availablefreespace) - I_Error("To play on this server you must download %s KB,\n" - "but you have only %s KB free space on this drive\n", - sizeu1((size_t)(totalfreespaceneeded>>10)), sizeu2((size_t)(availablefreespace>>10))); + } - // prepare to download - I_mkdir(downloaddir, 0755); - return HSendPacket(servernode, true, 0, p - (char *)netbuffer->u.textcmd); +#ifdef MORELEGACYDOWNLOADER + if (firstloop) +#else + // If we're not trying extralong legacy download requests, gotta bail. + if (skippedafile != -1) + { + CONS_Printf("Direct download - missing files are as follows:\n"); + for (i = 0; i < fileneedednum; i++) + { + if ((fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD || fileneeded[i].status == FS_FALLBACK || fileneeded[i].status == FS_REQUESTED)) // FS_REQUESTED added + CONS_Printf(" %s\n", fileneeded[i].filename); + } + return false; + } +#endif + I_mkdir(downloaddir, 0755); + +#ifdef PARANOIA + // Couldn't fit a single one in? + if (p == (char *)netbuffer->u.textcmd) + { + CONS_Printf("Direct download - fileneeded name for %s (fileneeded[%d]) too long??\n", (skippedafile != -1 ? fileneeded[skippedafile].filename : NULL), skippedafile); + return false; + } +#endif + + WRITEUINT8(p, 0xFF); // terminator + if (!HSendPacket(servernode, true, 0, p - (char *)netbuffer->u.textcmd)) + { + CONS_Printf("Direct download - unable to send packet.\n"); + return false; + } + +#ifdef MORELEGACYDOWNLOADER + if (skippedafile != -1) + { + firstloop = false; + goto tryagain; + } +#endif + +#ifdef VERBOSEREQUESTFILE + CONS_Printf("Returning true\n"); +#endif + + return true; } // get request filepak and put it on the send queue @@ -411,16 +507,18 @@ boolean PT_RequestFile(INT32 node) char wad[MAX_WADPATH+1]; UINT8 *p = netbuffer->u.textcmd; UINT8 id; - while (p < netbuffer->u.textcmd + MAXTEXTCMD-1) // Don't allow hacked client to overflow + while (p < netbuffer->u.textcmd + MAXTEXTCMD) // Don't allow hacked client to overflow { id = READUINT8(p); if (id == 0xFF) break; READSTRINGN(p, wad, MAX_WADPATH); - if (!AddFileToSendQueue(node, wad, id)) + if (p >= netbuffer->u.textcmd + MAXTEXTCMD || !AddFileToSendQueue(node, wad, id)) { + if (cv_noticedownload.value) + CONS_Printf("Bad PT_REQUESTFILE from node %d!\n", node); SV_AbortSendFiles(node); - return false; // don't read the rest of the files + return false; // don't read any more } } return true; // no problems with any files @@ -552,7 +650,7 @@ boolean CL_LoadServerFiles(void) continue; // Already loaded else if (fileneeded[i].status == FS_FOUND) { - P_AddWadFile(fileneeded[i].filename); + P_PartialAddWadFile(fileneeded[i].filename); G_SetGameModified(true, false); fileneeded[i].status = FS_OPEN; return false; @@ -798,7 +896,7 @@ static boolean AddFileToSendQueue(INT32 node, const char *filename, UINT8 fileid char wadfilename[MAX_WADPATH]; if (cv_noticedownload.value) - CONS_Printf("Sending file \"%s\" to node %d (%s)\n", filename, node, I_GetNodeAddress(node)); + CONS_Printf("Sending file \"%s\" (id %d) to node %d (%s)\n", filename, fileid, node, I_GetNodeAddress(node)); // Find the last file in the list and set a pointer to its "next" field q = &transfer[node].txlist; @@ -972,7 +1070,7 @@ static void SV_EndFileSend(INT32 node) { case SF_FILE: // It's a file, close it and free its filename if (cv_noticedownload.value) - CONS_Printf("Ending file transfer for node %d\n", node); + CONS_Printf("Ending file transfer (id %d) for node %d\n", p->fileid, node); if (transfer[node].currentfile) fclose(transfer[node].currentfile); free(p->id.filename); @@ -1750,6 +1848,7 @@ void CURLGetFile(void) int msgs_left; /* how many messages are left */ const char *easy_handle_error; long response_code = 0; + static char *filename; if (curl_runninghandles) { @@ -1774,6 +1873,8 @@ void CURLGetFile(void) { e = m->easy_handle; easyres = m->data.result; + filename = Z_StrDup(curl_realname); + nameonly(filename); if (easyres != CURLE_OK) { if (easyres == CURLE_HTTP_RETURNED_ERROR) @@ -1786,21 +1887,30 @@ void CURLGetFile(void) curl_failedwebdownload = true; fclose(curl_curfile->file); remove(curl_curfile->filename); - curl_curfile->file = NULL; - //nameonly(curl_curfile->filename); - nameonly(curl_realname); - CONS_Printf(M_GetText("Failed to download %s (%s)\n"), curl_realname, easy_handle_error); + CONS_Printf(M_GetText("Failed to download %s (%s)\n"), filename, easy_handle_error); } else { - nameonly(curl_realname); - CONS_Printf(M_GetText("Finished downloading %s\n"), curl_realname); - downloadcompletednum++; - downloadcompletedsize += curl_curfile->totalsize; - curl_curfile->status = FS_FOUND; fclose(curl_curfile->file); + + if (checkfilemd5(curl_curfile->filename, curl_curfile->md5sum) == FS_MD5SUMBAD) + { + CONS_Alert(CONS_ERROR, M_GetText("HTTP Download of %s finished but is corrupt or has been modified\n"), filename); + curl_curfile->status = FS_FALLBACK; + curl_failedwebdownload = true; + } + else + { + CONS_Printf(M_GetText("Finished HTTP download of %s\n"), filename); + downloadcompletednum++; + downloadcompletedsize += curl_curfile->totalsize; + curl_curfile->status = FS_FOUND; + } } + + Z_Free(filename); + curl_curfile->file = NULL; curl_running = false; curl_transfers--; curl_multi_remove_handle(multi_handle, e); diff --git a/src/d_player.h b/src/d_player.h index c5dcb495a..23834f7e9 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -168,7 +168,6 @@ typedef enum KRITEM_DUALSNEAKER = NUMKARTITEMS, KRITEM_TRIPLESNEAKER, KRITEM_TRIPLEBANANA, - KRITEM_TENFOLDBANANA, KRITEM_TRIPLEORBINAUT, KRITEM_QUADORBINAUT, KRITEM_DUALJAWZ, @@ -415,7 +414,7 @@ typedef struct player_s // Basic gameplay things UINT8 position; // Used for Kart positions, mostly for deterministic stuff UINT8 oldposition; // Used for taunting when you pass someone - UINT8 positiondelay; // Used for position number, so it can grow when passing/being passed + UINT8 positiondelay; // Used for position number, so it can grow when passing UINT32 distancetofinish; waypoint_t *nextwaypoint; respawnvars_t respawn; // Respawn info diff --git a/src/deh_soc.c b/src/deh_soc.c index 70f1305c6..1455f0641 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -224,7 +224,10 @@ void readfreeslots(MYFILE *f) // TODO: Out-of-slots warnings/errors. // TODO: Name too long (truncated) warnings. if (fastcmp(type, "SFX")) + { + CONS_Printf("Sound sfx_%s allocated.\n",word); S_AddSoundFx(word, false, 0, false); + } else if (fastcmp(type, "SPR")) { for (i = SPR_FIRSTFREESLOT; i <= SPR_LASTFREESLOT; i++) @@ -238,39 +241,54 @@ void readfreeslots(MYFILE *f) // Found a free slot! strncpy(sprnames[i],word,4); //sprnames[i][4] = 0; + CONS_Printf("Sprite SPR_%s allocated.\n",word); used_spr[(i-SPR_FIRSTFREESLOT)/8] |= 1<<(i%8); // Okay, this sprite slot has been named now. break; } + if (i > SPR_LASTFREESLOT) + I_Error("Out of Sprite Freeslots while allocating \"%s\"\nLoad less addons to fix this.", word); + } else if (fastcmp(type, "S")) { for (i = 0; i < NUMSTATEFREESLOTS; i++) if (!FREE_STATES[i]) { + CONS_Printf("State S_%s allocated.\n",word); FREE_STATES[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL); strcpy(FREE_STATES[i],word); freeslotusage[0][0]++; break; } + if (i == NUMSTATEFREESLOTS) + I_Error("Out of State Freeslots while allocating \"%s\"\nLoad less addons to fix this.", word); + } else if (fastcmp(type, "MT")) { for (i = 0; i < NUMMOBJFREESLOTS; i++) if (!FREE_MOBJS[i]) { + CONS_Printf("MobjType MT_%s allocated.\n",word); FREE_MOBJS[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL); strcpy(FREE_MOBJS[i],word); freeslotusage[1][0]++; break; } + if (i == NUMMOBJFREESLOTS) + I_Error("Out of Mobj Freeslots while allocating \"%s\"\nLoad less addons to fix this.", word); + } else if (fastcmp(type, "SKINCOLOR")) { for (i = 0; i < NUMCOLORFREESLOTS; i++) if (!FREE_SKINCOLORS[i]) { + CONS_Printf("Skincolor SKINCOLOR_%s allocated.\n",word); FREE_SKINCOLORS[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL); strcpy(FREE_SKINCOLORS[i],word); M_AddMenuColor(numskincolors++); break; } + if (i == NUMCOLORFREESLOTS) + I_Error("Out of Skincolor Freeslots while allocating \"%s\"\nLoad less addons to fix this.", word); } else if (fastcmp(type, "SPR2")) { @@ -287,7 +305,7 @@ void readfreeslots(MYFILE *f) spr2defaults[free_spr2] = 0; spr2names[free_spr2++][4] = 0; } else - deh_warning("Ran out of free SPR2 slots!\n"); + I_Error("Out of SPR2 Freeslots while allocating \"%s\"\nLoad less addons to fix this.", word); } else if (fastcmp(type, "TOL")) { @@ -302,7 +320,7 @@ void readfreeslots(MYFILE *f) // We don't, so freeslot it. if (lastcustomtol == (UINT32)MAXTOL) // Unless you have way too many, since they're flags. - deh_warning("Ran out of free typeoflevel slots!\n"); + I_Error("Out of Typeoflevel Freeslots while allocating \"%s\"\nLoad less addons to fix this.", word); else { G_AddTOL(lastcustomtol, word); @@ -326,7 +344,7 @@ void readfreeslots(MYFILE *f) precipprops[i].name = Z_StrDup(word); precip_freeslot++; } else - deh_warning("Ran out of free PRECIP slots!\n"); + I_Error("Out of Precipitation Freeslots while allocating \"%s\"\nLoad less addons to fix this.", word); } else deh_warning("Freeslots: unknown enum class '%s' for '%s_%s'", type, type, word); @@ -3112,7 +3130,7 @@ void readcupheader(MYFILE *f, cupheader_t *cup) void readfollower(MYFILE *f) { char *s; - char *word, *word2, dname[SKINNAMESIZE+1]; + char *word, *word2; char *tmp; char testname[SKINNAMESIZE+1]; @@ -3121,10 +3139,9 @@ void readfollower(MYFILE *f) INT32 res; INT32 i; - if (numfollowers > MAXSKINS) + if (numfollowers >= MAXSKINS) { - deh_warning("Error: Too many followers, cannot add anymore.\n"); - return; + I_Error("Out of Followers\nLoad less addons to fix this."); } s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); @@ -3143,8 +3160,9 @@ void readfollower(MYFILE *f) followers[numfollowers].bobspeed = TICRATE*2; followers[numfollowers].bobamp = 4*FRACUNIT; followers[numfollowers].hitconfirmtime = TICRATE; - followers[numfollowers].defaultcolor = SKINCOLOR_GREEN; - strcpy(followers[numfollowers].icon, "M_NORANK"); + followers[numfollowers].defaultcolor = FOLLOWERCOLOR_MATCH; + followers[numfollowers].category = UINT8_MAX; + strcpy(followers[numfollowers].icon, "MISSING"); do { @@ -3183,6 +3201,23 @@ void readfollower(MYFILE *f) strcpy(followers[numfollowers].icon, word2); nameset = true; } + else if (fastcmp(word, "CATEGORY")) + { + INT32 j; + for (j = 0; j < numfollowercategories; j++) + { + if (!stricmp(followercategories[j].name, word2)) + { + followers[numfollowers].category = j; + break; + } + } + + if (j == numfollowercategories) + { + deh_warning("Follower %d: unknown follower category '%s'", numfollowers, word2); + } + } else if (fastcmp(word, "MODE")) { if (word2) @@ -3197,7 +3232,20 @@ void readfollower(MYFILE *f) } else if (fastcmp(word, "DEFAULTCOLOR")) { - followers[numfollowers].defaultcolor = get_number(word2); + INT32 j; + for (j = 0; j < numskincolors +2; j++) // +2 because of Match and Opposite + { + if (!stricmp(Followercolor_cons_t[j].strvalue, word2)) + { + followers[numfollowers].defaultcolor = Followercolor_cons_t[j].value; + break; + } + } + + if (j == numskincolors+2) + { + deh_warning("Follower %d: unknown follower color '%s'", numfollowers, word2); + } } else if (fastcmp(word, "SCALE")) { @@ -3300,10 +3348,6 @@ void readfollower(MYFILE *f) // set skin name (this is just the follower's name in lowercases): // but before we do, let's... actually check if another follower isn't doing the same shit... - strcpy(testname, followers[numfollowers].name); - - // lower testname for skin checks... - strlwr(testname); res = K_FollowerAvailable(testname); if (res > -1) // yikes, someone else has stolen our name already { @@ -3315,8 +3359,7 @@ void readfollower(MYFILE *f) // in that case, we'll be very lazy and copy numfollowers to the end of our skin name. } - strcpy(followers[numfollowers].skinname, testname); - strcpy(dname, followers[numfollowers].skinname); // display name, just used for printing succesful stuff or errors later down the line. + strcpy(testname, followers[numfollowers].name); // now that the skin name is ready, post process the actual name to turn the underscores into spaces! for (i = 0; followers[numfollowers].name[i]; i++) @@ -3331,14 +3374,14 @@ void readfollower(MYFILE *f) if (followers[numfollowers].mode < FOLLOWERMODE_FLOAT || followers[numfollowers].mode >= FOLLOWERMODE__MAX) { followers[numfollowers].mode = FOLLOWERMODE_FLOAT; - deh_warning("Follower '%s': Value for 'mode' should be between %d and %d.", dname, FOLLOWERMODE_FLOAT, FOLLOWERMODE__MAX-1); + deh_warning("Follower '%s': Value for 'mode' should be between %d and %d.", testname, FOLLOWERMODE_FLOAT, FOLLOWERMODE__MAX-1); } #define FALLBACK(field, field2, threshold, set) \ if ((signed)followers[numfollowers].field < threshold) \ { \ followers[numfollowers].field = set; \ - deh_warning("Follower '%s': Value for '%s' is too low! Minimum should be %d. Value was overwritten to %d.", dname, field2, threshold, set); \ + deh_warning("Follower '%s': Value for '%s' is too low! Minimum should be %d. Value was overwritten to %d.", testname, field2, threshold, set); \ } \ FALLBACK(dist, "DIST", 0, 0); @@ -3355,13 +3398,6 @@ if ((signed)followers[numfollowers].field < threshold) \ #undef FALLBACK - // Special case for color I suppose - if (followers[numfollowers].defaultcolor > (unsigned)(numskincolors-1)) - { - followers[numfollowers].defaultcolor = SKINCOLOR_GREEN; - deh_warning("Follower \'%s\': Value for 'color' should be between 1 and %d.\n", dname, numskincolors-1); - } - // also check if we forgot states. If we did, we will set any missing state to the follower's idlestate. // Print a warning in case we don't have a fallback and set the state to S_INVISIBLE (rather than S_NULL) if unavailable. @@ -3370,7 +3406,7 @@ if (!followers[numfollowers].field) \ { \ followers[numfollowers].field = fallbackstate ? fallbackstate : S_INVISIBLE; \ if (!fallbackstate) \ - deh_warning("Follower '%s' is missing state definition for '%s', no idlestate fallback was found", dname, field2); \ + deh_warning("Follower '%s' is missing state definition for '%s', no idlestate fallback was found", testname, field2); \ } \ NOSTATE(idlestate, "IDLESTATE"); @@ -3381,11 +3417,83 @@ if (!followers[numfollowers].field) \ NOSTATE(hitconfirmstate, "HITCONFIRMSTATE"); #undef NOSTATE - CONS_Printf("Added follower '%s'\n", dname); + CONS_Printf("Added follower '%s'\n", testname); + if (followers[numfollowers].category < numfollowercategories) + followercategories[followers[numfollowers].category].numincategory++; numfollowers++; // add 1 follower Z_Free(s); } +void readfollowercategory(MYFILE *f) +{ + char *s; + char *word, *word2; + char *tmp; + + boolean nameset; + + if (numfollowercategories == MAXFOLLOWERCATEGORIES) + { + I_Error("Out of Follower categories\nLoad less addons to fix this."); + } + + s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); + + // Ready the default variables for followers. We will overwrite them as we go! We won't set the name or states RIGHT HERE as this is handled down instead. + strcpy(followercategories[numfollowercategories].icon, "MISSING"); + followercategories[numfollowercategories].numincategory = 0; + + do + { + if (myfgets(s, MAXLINELEN, f)) + { + if (s[0] == '\n') + break; + + tmp = strchr(s, '#'); + if (tmp) + *tmp = '\0'; + if (s == tmp) + continue; // Skip comment lines, but don't break. + + word = strtok(s, " "); + if (word) + strupr(word); + else + break; + + word2 = strtok(NULL, " = "); + + if (!word2) + break; + + if (word2[strlen(word2)-1] == '\n') + word2[strlen(word2)-1] = '\0'; + + if (fastcmp(word, "NAME")) + { + strcpy(followercategories[numfollowercategories].name, word2); + nameset = true; + } + else if (fastcmp(word, "ICON")) + { + strcpy(followercategories[numfollowercategories].icon, word2); + nameset = true; + } + } + } while (!myfeof(f)); // finish when the line is empty + + if (!nameset) + { + // well this is problematic. + strcpy(followercategories[numfollowercategories].name, va("Followercategory%d", numfollowercategories)); // this is lazy, so what + } + + CONS_Printf("Added follower category '%s'\n", followercategories[numfollowercategories].name); + numfollowercategories++; // add 1 follower + Z_Free(s); +} + void readweather(MYFILE *f, INT32 num) { char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); diff --git a/src/deh_soc.h b/src/deh_soc.h index 161422783..0d9beccab 100644 --- a/src/deh_soc.h +++ b/src/deh_soc.h @@ -82,6 +82,7 @@ void clear_conditionsets(void); void readcupheader(MYFILE *f, cupheader_t *cup); void readfollower(MYFILE *f); +void readfollowercategory(MYFILE *f); preciptype_t get_precip(const char *word); void readweather(MYFILE *f, INT32 num); diff --git a/src/deh_tables.c b/src/deh_tables.c index 4f0829415..a36b7af9d 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -3271,6 +3271,7 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_ITEM_DEBRIS_CLOUD_SPAWNER2", "S_ITEMICON", + "S_ITEMBACKDROP", // Item capsules "S_ITEMCAPSULE", @@ -3491,6 +3492,11 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_BANANA", "S_BANANA_DEAD", + "S_BANANA_SPARK", + "S_BANANA_SPARK2", + "S_BANANA_SPARK3", + "S_BANANA_SPARK4", + //{ Orbinaut "S_ORBINAUT1", "S_ORBINAUT2", @@ -5315,6 +5321,7 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_BANANA", // Banana Stuff "MT_BANANA_SHIELD", + "MT_BANANA_SPARK", "MT_ORBINAUT", // Orbinaut stuff "MT_ORBINAUT_SHIELD", @@ -5632,7 +5639,7 @@ const char *const MOBJFLAG_LIST[] = { "SLIDEME", "NOCLIP", "FLOAT", - "BOXICON", + "SLOPE", "MISSILE", "SPRING", "MONITOR", @@ -6761,14 +6768,13 @@ struct int_const_s const INT_CONST[] = { // SRB2Kart // kartitems_t -#define FOREACH( name, n ) { #name, KITEM_ ## name } +#define FOREACH( name, n ) { TOSTR (KITEM_ ## name), KITEM_ ## name } KART_ITEM_ITERATOR, // Actual items (can be set for k_itemtype) #undef FOREACH {"NUMKARTITEMS",NUMKARTITEMS}, {"KRITEM_DUALSNEAKER",KRITEM_DUALSNEAKER}, // Additional roulette IDs (not usable for much in Lua besides K_GetItemPatch) {"KRITEM_TRIPLESNEAKER",KRITEM_TRIPLESNEAKER}, {"KRITEM_TRIPLEBANANA",KRITEM_TRIPLEBANANA}, - {"KRITEM_TENFOLDBANANA",KRITEM_TENFOLDBANANA}, {"KRITEM_TRIPLEORBINAUT",KRITEM_TRIPLEORBINAUT}, {"KRITEM_QUADORBINAUT",KRITEM_QUADORBINAUT}, {"KRITEM_DUALJAWZ",KRITEM_DUALJAWZ}, diff --git a/src/dehacked.c b/src/dehacked.c index 690a49dbc..99ee7b0e8 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -235,6 +235,12 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) readfollower(f); continue; } + else if (fastcmp(word, "FOLLOWERCATEGORY")) + { + // This is not a major mod. + readfollowercategory(f); + continue; + } word2 = strtok(NULL, " "); if (word2) { diff --git a/src/doomdef.h b/src/doomdef.h index 0c462c8ee..8271a4ae2 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -398,6 +398,19 @@ typedef enum SKINCOLOR_CHAOSEMERALD7, SKINCOLOR_INVINCFLASH, + SKINCOLOR_POSNUM, + SKINCOLOR_POSNUM_WIN1, + SKINCOLOR_POSNUM_WIN2, + SKINCOLOR_POSNUM_WIN3, + SKINCOLOR_POSNUM_LOSE1, + SKINCOLOR_POSNUM_LOSE2, + SKINCOLOR_POSNUM_LOSE3, + SKINCOLOR_POSNUM_BEST1, + SKINCOLOR_POSNUM_BEST2, + SKINCOLOR_POSNUM_BEST3, + SKINCOLOR_POSNUM_BEST4, + SKINCOLOR_POSNUM_BEST5, + SKINCOLOR_POSNUM_BEST6, SKINCOLOR_FIRSTFREESLOT, SKINCOLOR_LASTFREESLOT = SKINCOLOR_FIRSTFREESLOT + NUMCOLORFREESLOTS - 1, diff --git a/src/g_demo.c b/src/g_demo.c index a4400d4ff..482c05aaa 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -316,11 +316,11 @@ void G_ReadDemoExtraData(void) demo_p += 16; for (i = 0; i < numskincolors +2; i++) // +2 because of Match and Opposite { - if (!stricmp(Followercolor_cons_t[i].strvalue, name)) - { - players[p].followercolor = Followercolor_cons_t[i].value; - break; - } + if (!stricmp(Followercolor_cons_t[i].strvalue, name)) + { + players[p].followercolor = Followercolor_cons_t[i].value; + break; + } } } if (extradata & DXD_PLAYSTATE) @@ -410,7 +410,7 @@ void G_ReadDemoExtraData(void) void G_WriteDemoExtraData(void) { - INT32 i; + INT32 i, j; char name[16]; for (i = 0; i < MAXPLAYERS; i++) @@ -456,13 +456,18 @@ void G_WriteDemoExtraData(void) if (players[i].followerskin == -1) strncpy(name, "None", 16); else - strncpy(name, followers[players[i].followerskin].skinname, 16); + strncpy(name, followers[players[i].followerskin].name, 16); M_Memcpy(demo_p, name, 16); demo_p += 16; // write follower color memset(name, 0, 16); - strncpy(name, Followercolor_cons_t[(UINT16)(players[i].followercolor+2)].strvalue, 16); // Not KartColor_Names because followercolor has extra values such as "Match" + for (j = (numskincolors+2)-1; j > 0; j--) + { + if (Followercolor_cons_t[j].value == players[i].followercolor) + break; + } + strncpy(name, Followercolor_cons_t[j].strvalue, 16); // Not KartColor_Names because followercolor has extra values such as "Match" M_Memcpy(demo_p,name,16); demo_p += 16; @@ -1954,7 +1959,7 @@ void G_RecordMetal(void) void G_BeginRecording(void) { - UINT8 i, p; + UINT8 i, j, p; char name[MAXCOLORNAME+1]; player_t *player = &players[consoleplayer]; @@ -2009,7 +2014,7 @@ void G_BeginRecording(void) if (wadfiles[i]->important) { nameonly(( filename = va("%s", wadfiles[i]->filename) )); - WRITESTRINGN(demo_p, filename, 64); + WRITESTRINGL(demo_p, filename, MAX_WADPATH); WRITEMEM(demo_p, wadfiles[i]->md5sum, 16); totalfiles++; @@ -2091,7 +2096,7 @@ void G_BeginRecording(void) memset(name, 0, 16); if (player->follower) - strncpy(name, followers[player->followerskin].skinname, 16); + strncpy(name, followers[player->followerskin].name, 16); else strncpy(name, "None", 16); // Say we don't have one, then. @@ -2100,7 +2105,12 @@ void G_BeginRecording(void) // Save follower's colour memset(name, 0, 16); - strncpy(name, Followercolor_cons_t[(UINT16)(player->followercolor+2)].strvalue, 16); // Not KartColor_Names because followercolor has extra values such as "Match" + for (j = (numskincolors+2)-1; j > 0; j--) + { + if (Followercolor_cons_t[j].value == players[i].followercolor) + break; + } + strncpy(name, Followercolor_cons_t[j].strvalue, 16); // Not KartColor_Names because followercolor has extra values such as "Match" M_Memcpy(demo_p, name, 16); demo_p += 16; @@ -2299,10 +2309,13 @@ static void G_LoadDemoExtraFiles(UINT8 **pp) } else { - P_AddWadFile(filename); + P_PartialAddWadFile(filename); } } } + + if (P_PartialAddGetStage() >= 0) + P_MultiSetupWadFiles(true); // in case any partial adds were done } static void G_SkipDemoExtraFiles(UINT8 **pp) @@ -3076,11 +3089,11 @@ void G_DoPlayDemo(char *defdemoname) demo_p += 16; for (i = 0; i < numskincolors +2; i++) // +2 because of Match and Opposite { - if (!stricmp(Followercolor_cons_t[i].strvalue, color)) - { - players[p].followercolor = i; - break; - } + if (!stricmp(Followercolor_cons_t[i].strvalue, color)) + { + players[p].followercolor = Followercolor_cons_t[i].value; + break; + } } // Score, since Kart uses this to determine where you start on the map @@ -3739,7 +3752,11 @@ static void G_StopTimingDemo(void) if (restorecv_vidwait != cv_vidwait.value) CV_SetValue(&cv_vidwait, restorecv_vidwait); - D_AdvanceDemo(); + + if (timedemo_quit) + COM_ImmedExecute("quit"); + else + D_StartTitle(); } // reset engine variable set for the demos @@ -3798,10 +3815,12 @@ boolean G_CheckDemoStatus(void) { G_StopDemo(); - if (modeattacking) + if (timedemo_quit) + COM_ImmedExecute("quit"); + else if (modeattacking) M_EndModeAttackRun(); else - D_AdvanceDemo(); + D_StartTitle(); } return true; diff --git a/src/g_game.c b/src/g_game.c index 8b27e6d46..5e722fe4f 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4280,8 +4280,26 @@ void G_EndGame(void) // Sets a tad of default info we need. void G_LoadGameSettings(void) { + INT32 i; + // initialize free sfx slots for skin sounds S_InitRuntimeSounds(); + + // Prepare skincolor material. + for (i = 0; i < MAXSKINCOLORS; i++) + { + Color_cons_t[i].value = Followercolor_cons_t[i+2].value = i; + Color_cons_t[i].strvalue = Followercolor_cons_t[i+2].strvalue = skincolors[i].name; + } + + Followercolor_cons_t[1].value = FOLLOWERCOLOR_MATCH; + Followercolor_cons_t[1].strvalue = "Match"; // Add "Match" option, which will make the follower color match the player's + + Followercolor_cons_t[0].value = FOLLOWERCOLOR_OPPOSITE; + Followercolor_cons_t[0].strvalue = "Opposite"; // Add "Opposite" option, ...which is like "Match", but for coloropposite. + + Color_cons_t[MAXSKINCOLORS].value = Followercolor_cons_t[MAXSKINCOLORS+2].value = 0; + Color_cons_t[MAXSKINCOLORS].strvalue = Followercolor_cons_t[MAXSKINCOLORS+2].strvalue = NULL; } #define GD_VERSIONCHECK 0xBA5ED444 // Change every major version, as usual diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 63d093ae1..3fbcddd7f 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -326,22 +326,28 @@ patch_t *HU_UpdateOrBlankPatch(patch_t **user, boolean required, const char *for va_list ap; char buffer[9]; - lumpnum_t lump; + lumpnum_t lump = INT16_MAX; patch_t *patch; va_start (ap, format); vsnprintf(buffer, sizeof buffer, format, ap); va_end (ap); - if (user && p_adding_file != INT16_MAX) + if (user && partadd_earliestfile != UINT16_MAX) { - lump = W_CheckNumForNamePwad(buffer, p_adding_file, 0); + UINT16 fileref = numwadfiles; + lump = INT16_MAX; + + while ((lump == INT16_MAX) && ((--fileref) >= partadd_earliestfile)) + { + lump = W_CheckNumForNamePwad(buffer, fileref, 0); + } /* no update in this wad */ - if (lump == INT16_MAX) + if (fileref < partadd_earliestfile) return *user; - lump |= (p_adding_file << 16); + lump |= (fileref << 16); } else { @@ -1251,7 +1257,7 @@ boolean HU_Responder(event_t *ev) if (chatlen+pastelen > HU_MAXMSGLEN) return true; // we can't paste this!! - memmove(&w_chat[c_input + pastelen], &w_chat[c_input], pastelen); + memmove(&w_chat[c_input + pastelen], &w_chat[c_input], (chatlen - c_input) + 1); // +1 for '\0' memcpy(&w_chat[c_input], paste, pastelen); // copy all of that. c_input += pastelen; return true; diff --git a/src/info.c b/src/info.c index d04bd078d..3824731d1 100644 --- a/src/info.c +++ b/src/info.c @@ -531,6 +531,7 @@ char sprnames[NUMSPRITES + 1][5] = "SBOX", // Sphere Box (for Battle) "RPOP", // Random Item Box Pop "ITRI", // Item Box Debris + "ITPA", // Paper item backdrop "SGNS", // Signpost sparkle "FAST", // Speed boost trail "DSHR", // Speed boost dust release @@ -559,6 +560,7 @@ char sprnames[NUMSPRITES + 1][5] = "RSHE", // Rocket sneaker "FITM", // Eggman Monitor "BANA", // Banana Peel + "BAND", // Banana Peel death particles "ORBN", // Orbinaut "JAWZ", // Jawz "SSMN", // SS Mine @@ -3882,6 +3884,7 @@ state_t states[NUMSTATES] = {SPR_NULL, 0, 7, {A_SpawnItemDebrisCloud}, 20, 0, S_ITEM_DEBRIS_CLOUD_SPAWNER1}, // S_ITEM_DEBRIS_CLOUD_SPAWNER2 {SPR_NULL, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_ITEMICON + {SPR_ITPA, FF_FULLBRIGHT, -1, {NULL}, 1, 0, S_NULL}, // S_ITEMBACKDROP {SPR_ICAP, FF_ADD|0, -1, {NULL}, 0, 0, S_NULL}, // S_ITEMCAPSULE {SPR_ICAP, FF_PAPERSPRITE|1, -1, {NULL}, 0, 0, S_NULL}, // S_ITEMCAPSULE_TOP_SIDE @@ -4082,7 +4085,12 @@ state_t states[NUMSTATES] = {SPR_FITM, 24|FF_FULLBRIGHT, 175, {NULL}, 0, 0, S_NULL}, // S_EGGMANITEM_DEAD {SPR_BANA, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BANANA - {SPR_BANA, 0, 175, {NULL}, 0, 0, S_NULL}, // S_BANANA_DEAD + {SPR_BANA, 1, 175, {NULL}, 0, 0, S_NULL}, // S_BANANA_DEAD + + {SPR_BAND, 0, -1, {NULL}, 0, 0, S_BANANA_SPARK2}, // S_BANANA_SPARK + {SPR_BAND, 1|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_BANANA_SPARK3}, // S_BANANA_SPARK2 + {SPR_BAND, 2|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_BANANA_SPARK4}, // S_BANANA_SPARK3 + {SPR_BAND, 3|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_BANANA_SPARK4 {SPR_ORBN, 0, 1, {NULL}, 0, 0, S_ORBINAUT2}, // S_ORBINAUT1 {SPR_ORBN, 1, 1, {NULL}, 0, 0, S_ORBINAUT3}, // S_ORBINAUT2 @@ -5294,7 +5302,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 1000, // mass MT_THOK, // damage sfx_None, // activesound - MF_SOLID|MF_SHOOTABLE|MF_DONTENCOREMAP|MF_APPLYTERRAIN, // flags + MF_SOLID|MF_SHOOTABLE|MF_DONTENCOREMAP|MF_APPLYTERRAIN|MF_SLOPE, // flags (statenum_t)MT_THOK // raisestate }, @@ -5321,7 +5329,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 1000, // mass 0, // damage sfx_None, // activesound - MF_SOLID|MF_DONTENCOREMAP|MF_APPLYTERRAIN, // flags + MF_SOLID|MF_DONTENCOREMAP|MF_APPLYTERRAIN|MF_SLOPE, // flags S_NULL // raisestate }, @@ -9991,7 +9999,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 62*FRACUNIT, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_BOXICON, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags S_NULL // raisestate }, @@ -10018,7 +10026,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 62*FRACUNIT, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_BOXICON, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags S_NULL // raisestate }, @@ -10045,7 +10053,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 62*FRACUNIT, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_BOXICON, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags S_NULL // raisestate }, @@ -10072,7 +10080,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 62*FRACUNIT, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_BOXICON, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags S_NULL // raisestate }, @@ -10099,7 +10107,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 62*FRACUNIT, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_BOXICON, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags S_NULL // raisestate }, @@ -10126,7 +10134,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 62*FRACUNIT, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_BOXICON, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags S_NULL // raisestate }, @@ -10153,7 +10161,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 62*FRACUNIT, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_BOXICON, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags S_NULL // raisestate }, @@ -10180,7 +10188,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 62*FRACUNIT, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_BOXICON, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags S_NULL // raisestate }, @@ -10207,7 +10215,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 62*FRACUNIT, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_BOXICON, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags S_NULL // raisestate }, @@ -10234,7 +10242,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 62*FRACUNIT, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_BOXICON, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags S_NULL // raisestate }, @@ -10261,7 +10269,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 62*FRACUNIT, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_BOXICON, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags S_NULL // raisestate }, @@ -10288,7 +10296,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 62*FRACUNIT, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_BOXICON, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags S_NULL // raisestate }, @@ -10315,7 +10323,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 62*FRACUNIT, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_BOXICON, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags S_NULL // raisestate }, @@ -10342,7 +10350,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 62*FRACUNIT, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_BOXICON, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags S_NULL // raisestate }, @@ -10369,7 +10377,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 62*FRACUNIT, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_BOXICON, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags S_NULL // raisestate }, @@ -10396,7 +10404,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 62*FRACUNIT, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_BOXICON, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags S_NULL // raisestate }, @@ -10423,7 +10431,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 62*FRACUNIT, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_BOXICON, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags S_NULL // raisestate }, @@ -10450,7 +10458,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 62*FRACUNIT, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_BOXICON, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags S_NULL // raisestate }, @@ -10477,7 +10485,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 62*FRACUNIT, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_BOXICON, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags S_NULL // raisestate }, @@ -23122,7 +23130,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 1, // damage sfx_peel, // activesound - MF_SHOOTABLE|MF_DONTENCOREMAP|MF_APPLYTERRAIN, // flags + MF_SHOOTABLE|MF_DONTENCOREMAP|MF_APPLYTERRAIN|MF_SLOPE, // flags S_NULL // raisestate }, @@ -23149,7 +23157,34 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 1, // damage sfx_None, // activesound - MF_SHOOTABLE|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP|MF_APPLYTERRAIN, // flags + MF_SHOOTABLE|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP|MF_APPLYTERRAIN|MF_SLOPE, // flags + S_NULL // raisestate + }, + + { // MT_BANANA_SPARK + -1, // doomednum + S_BANANA_SPARK, // 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_BANANA_SPARK2,// deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 4*FRACUNIT, // radius + 8*FRACUNIT, // height + 0, // display offset + 100, // mass + 1, // damage + sfx_None, // activesound + MF_DONTENCOREMAP|MF_NOCLIPTHING|MF_NOSQUISH, // flags S_NULL // raisestate }, @@ -23176,7 +23211,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 1, // damage sfx_s3k96, // activesound - MF_SHOOTABLE|MF_DONTENCOREMAP|MF_APPLYTERRAIN, // flags + MF_SHOOTABLE|MF_DONTENCOREMAP|MF_APPLYTERRAIN|MF_SLOPE, // flags S_NULL // raisestate }, @@ -23203,7 +23238,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 1, // damage sfx_None, // activesound - MF_SHOOTABLE|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP|MF_APPLYTERRAIN, // flags + MF_SHOOTABLE|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP|MF_APPLYTERRAIN|MF_SLOPE, // flags S_NULL // raisestate }, @@ -23230,7 +23265,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 1, // damage sfx_s3kc0s, // activesound - MF_SHOOTABLE|MF_DONTENCOREMAP|MF_APPLYTERRAIN, // flags + MF_SHOOTABLE|MF_DONTENCOREMAP|MF_APPLYTERRAIN|MF_SLOPE, // flags S_NULL // raisestate }, @@ -23257,7 +23292,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 1, // damage sfx_None, // activesound - MF_SHOOTABLE|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP|MF_APPLYTERRAIN, // flags + MF_SHOOTABLE|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP|MF_APPLYTERRAIN|MF_SLOPE, // flags S_NULL // raisestate }, @@ -23311,7 +23346,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 1, // damage sfx_s3k5c, // activesound - MF_SHOOTABLE|MF_DONTENCOREMAP|MF_APPLYTERRAIN, // flags + MF_SHOOTABLE|MF_DONTENCOREMAP|MF_APPLYTERRAIN|MF_SLOPE, // flags S_NULL // raisestate }, @@ -23338,7 +23373,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 1, // damage sfx_None, // activesound - MF_SHOOTABLE|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP|MF_APPLYTERRAIN, // flags + MF_SHOOTABLE|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP|MF_APPLYTERRAIN|MF_SLOPE, // flags S_NULL // raisestate }, @@ -23446,7 +23481,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 0, // mass 0, // damage sfx_s3k5c, // activesound - MF_SHOOTABLE|MF_DONTENCOREMAP|MF_APPLYTERRAIN, // flags + MF_SHOOTABLE|MF_DONTENCOREMAP|MF_APPLYTERRAIN|MF_SLOPE, // flags S_NULL // raisestate }, @@ -23473,7 +23508,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 1, // damage sfx_s3k96, // activesound - MF_SPECIAL|MF_DONTENCOREMAP|MF_APPLYTERRAIN, // flags + MF_SPECIAL|MF_DONTENCOREMAP|MF_APPLYTERRAIN|MF_SLOPE, // flags S_NULL // raisestate }, @@ -23500,7 +23535,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 1, // damage sfx_None, // activesound - MF_SPECIAL|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP|MF_APPLYTERRAIN, // flags + MF_SPECIAL|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP|MF_APPLYTERRAIN|MF_SLOPE, // flags S_NULL // raisestate }, @@ -24121,7 +24156,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 1, // damage sfx_None, // activesound - MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP|MF_APPLYTERRAIN, // flags + MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP|MF_APPLYTERRAIN|MF_SLOPE, // flags S_NULL // raisestate }, @@ -29158,7 +29193,21 @@ skincolor_t skincolors[MAXSKINCOLORS] = { {"Chaos Emerald 6", { 0, 208, 50, 32, 34, 37, 40, 44, 0, 0, 0, 0, 0, 0, 0, 0}, SKINCOLOR_NONE, 0, 0, false}, // SKINCOLOR_CHAOSEMERALD6 {"Chaos Emerald 7", { 0, 120, 121, 140, 133, 135, 149, 156, 0, 0, 0, 0, 0, 0, 0, 0}, SKINCOLOR_NONE, 0, 0, false}, // SKINCOLOR_CHAOSEMERALD7 - {"Invinc Flash", { 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, SKINCOLOR_NONE, 0, 0, false} // SKINCOLOR_INVINCFLASH + {"Invinc Flash", { 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, SKINCOLOR_NONE, 0, 0, false}, // SKINCOLOR_INVINCFLASH + + {"Position", { 8, 9, 11, 12, 14, 15, 17, 18, 20, 21, 23, 24, 26, 27, 29, 30}, SKINCOLOR_NONE, 0, 0, false}, // SKINCOLOR_POSNUM + {"Position Win 1", {152, 152, 153, 153, 154, 154, 155, 155, 156, 156, 157, 158, 159, 253, 254, 30}, SKINCOLOR_NONE, 0, 0, false}, // SKINCOLOR_POSNUM_WIN1 + {"Position Win 2", {134, 134, 135, 135, 135, 136, 136, 136, 137, 137, 138, 138, 139, 139, 254, 30}, SKINCOLOR_NONE, 0, 0, false}, // SKINCOLOR_POSNUM_WIN2 + {"Position Win 3", {255, 255, 122, 122, 123, 123, 141, 141, 142, 142, 143, 143, 138, 139, 254, 30}, SKINCOLOR_NONE, 0, 0, false}, // SKINCOLOR_POSNUM_WIN3 + {"Position Lose 1", { 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 71, 46, 47, 29, 30}, SKINCOLOR_NONE, 0, 0, false}, // SKINCOLOR_POSNUM_LOSE1 + {"Position Lose 2", { 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 63, 44, 45, 46, 47, 30}, SKINCOLOR_NONE, 0, 0, false}, // SKINCOLOR_POSNUM_LOSE2 + {"Position Lose 3", { 73, 74, 75, 76, 76, 77, 77, 78, 78, 79, 79, 236, 237, 238, 239, 30}, SKINCOLOR_NONE, 0, 0, false}, // SKINCOLOR_POSNUM_LOSE3 + {"Position Best 1", { 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 71, 46, 47, 29, 30}, SKINCOLOR_NONE, 0, 0, false}, // SKINCOLOR_POSNUM_BEST1 + {"Position Best 2", { 73, 74, 75, 76, 76, 77, 77, 78, 78, 79, 79, 236, 237, 238, 239, 30}, SKINCOLOR_NONE, 0, 0, false}, // SKINCOLOR_POSNUM_BEST2 + {"Position Best 3", {112, 112, 113, 114, 115, 115, 116, 116, 117, 117, 118, 118, 119, 110, 111, 30}, SKINCOLOR_NONE, 0, 0, false}, // SKINCOLOR_POSNUM_BEST3 + {"Position Best 4", {255, 255, 122, 122, 123, 123, 141, 141, 142, 142, 143, 143, 138, 139, 254, 30}, SKINCOLOR_NONE, 0, 0, false}, // SKINCOLOR_POSNUM_BEST4 + {"Position Best 5", {152, 152, 153, 153, 154, 154, 155, 155, 156, 156, 157, 158, 159, 253, 254, 30}, SKINCOLOR_NONE, 0, 0, false}, // SKINCOLOR_POSNUM_BEST5 + {"Position Best 6", {181, 181, 182, 182, 183, 183, 184, 184, 185, 185, 186, 186, 187, 187, 29, 30}, SKINCOLOR_NONE, 0, 0, false} // SKINCOLOR_POSNUM_BEST6 }; /** Patches the mobjinfo, state, and skincolor tables. diff --git a/src/info.h b/src/info.h index b272e5176..7dff6e2de 100644 --- a/src/info.h +++ b/src/info.h @@ -1077,6 +1077,7 @@ typedef enum sprite SPR_SBOX, // Sphere Box (for Battle) SPR_RPOP, // Random Item Box Pop SPR_ITRI, // Item Box Debris + SPR_ITPA, // Paper item backdrop SPR_SGNS, // Signpost sparkle SPR_FAST, // Speed boost trail SPR_DSHR, // Speed boost dust release @@ -1105,6 +1106,7 @@ typedef enum sprite SPR_RSHE, // Rocket sneaker SPR_FITM, // Eggman Monitor SPR_BANA, // Banana Peel + SPR_BAND, // Banana Peel death particles SPR_ORBN, // Orbinaut SPR_JAWZ, // Jawz SPR_SSMN, // SS Mine @@ -4285,6 +4287,7 @@ typedef enum state S_ITEM_DEBRIS_CLOUD_SPAWNER2, S_ITEMICON, + S_ITEMBACKDROP, // Item capsules S_ITEMCAPSULE, @@ -4505,6 +4508,11 @@ typedef enum state S_BANANA, S_BANANA_DEAD, + S_BANANA_SPARK, + S_BANANA_SPARK2, + S_BANANA_SPARK3, + S_BANANA_SPARK4, + //{ Orbinaut S_ORBINAUT1, S_ORBINAUT2, @@ -6365,6 +6373,7 @@ typedef enum mobj_type MT_BANANA, // Banana Stuff MT_BANANA_SHIELD, + MT_BANANA_SPARK, MT_ORBINAUT, // Orbinaut stuff MT_ORBINAUT_SHIELD, diff --git a/src/k_collide.c b/src/k_collide.c index b03cdfdec..d3b9bc60c 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -94,7 +94,7 @@ boolean K_BananaBallhogCollide(mobj_t *t1, mobj_t *t2) S_StartSound(t2, t2->info->deathsound); P_KillMobj(t2, t1, t1, DMG_NORMAL); - P_SetObjectMomZ(t2, 8*FRACUNIT, false); + P_SetObjectMomZ(t2, 24*FRACUNIT, false); P_InstaThrust(t2, bounceangle, 16*FRACUNIT); P_SpawnMobj(t2->x/2 + t1->x/2, t2->y/2 + t1->y/2, t2->z/2 + t1->z/2, MT_ITEMCLASH); @@ -122,7 +122,7 @@ boolean K_BananaBallhogCollide(mobj_t *t1, mobj_t *t2) S_StartSound(t1, t1->info->deathsound); P_KillMobj(t1, t2, t2, DMG_NORMAL); - P_SetObjectMomZ(t1, 8*FRACUNIT, false); + P_SetObjectMomZ(t1, 24*FRACUNIT, false); P_InstaThrust(t1, bounceangle, 16*FRACUNIT); } @@ -351,7 +351,7 @@ boolean K_MineCollide(mobj_t *t1, mobj_t *t2) S_StartSound(t2, t2->info->deathsound); P_KillMobj(t2, t1, t1, DMG_NORMAL); - P_SetObjectMomZ(t2, 8*FRACUNIT, false); + P_SetObjectMomZ(t2, 24*FRACUNIT, false); P_InstaThrust(t2, bounceangle, 16*FRACUNIT); } else if (t2->flags & MF_SHOOTABLE) @@ -414,7 +414,7 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2) S_StartSound(t2, t2->info->deathsound); P_KillMobj(t2, t1, t1, DMG_NORMAL); - P_SetObjectMomZ(t2, 8*FRACUNIT, false); + P_SetObjectMomZ(t2, 24*FRACUNIT, false); P_InstaThrust(t2, bounceangle, 16*FRACUNIT); P_SpawnMobj(t2->x/2 + t1->x/2, t2->y/2 + t1->y/2, t2->z/2 + t1->z/2, MT_ITEMCLASH); diff --git a/src/k_follower.c b/src/k_follower.c index 63ebda167..636620ec8 100644 --- a/src/k_follower.c +++ b/src/k_follower.c @@ -15,6 +15,9 @@ INT32 numfollowers = 0; follower_t followers[MAXSKINS]; +INT32 numfollowercategories; +followercategory_t followercategories[MAXFOLLOWERCATEGORIES]; + CV_PossibleValue_t Followercolor_cons_t[MAXSKINCOLORS+3]; // +3 to account for "Match", "Opposite" & NULL /*-------------------------------------------------- @@ -28,7 +31,7 @@ INT32 K_FollowerAvailable(const char *name) for (i = 0; i < numfollowers; i++) { - if (stricmp(followers[i].skinname, name) == 0) + if (stricmp(followers[i].name, name) == 0) return i; } @@ -54,7 +57,7 @@ boolean K_SetFollowerByName(INT32 playernum, const char *skinname) for (i = 0; i < numfollowers; i++) { // search in the skin list - if (stricmp(followers[i].skinname, skinname) == 0) + if (stricmp(followers[i].name, skinname) == 0) { K_SetFollowerByNum(playernum, i); return true; @@ -74,6 +77,31 @@ boolean K_SetFollowerByName(INT32 playernum, const char *skinname) return false; } +/*-------------------------------------------------- + void K_RemoveFollower(player_t *player) + + See header file for description. +--------------------------------------------------*/ +void K_RemoveFollower(player_t *player) +{ + mobj_t *bub, *tmp; + if (player->follower && !P_MobjWasRemoved(player->follower)) // this is also called when we change colour so don't respawn the follower unless we changed skins + { + // Remove follower's possible hnext list (bubble) + bub = player->follower->hnext; + + while (bub && !P_MobjWasRemoved(bub)) + { + tmp = bub->hnext; + P_RemoveMobj(bub); + bub = tmp; + } + + P_RemoveMobj(player->follower); + P_SetTarget(&player->follower, NULL); + } +} + /*-------------------------------------------------- void K_SetFollowerByNum(INT32 playernum, INT32 skinnum) @@ -82,8 +110,6 @@ boolean K_SetFollowerByName(INT32 playernum, const char *skinname) void K_SetFollowerByNum(INT32 playernum, INT32 skinnum) { player_t *player = &players[playernum]; - mobj_t *bub; - mobj_t *tmp; player->followerready = true; // we are ready to perform follower related actions in the player thinker, now. @@ -94,21 +120,8 @@ void K_SetFollowerByNum(INT32 playernum, INT32 skinnum) However, we will despawn it right here if there's any to make it easy for the player thinker to replace it or delete it. */ - if (player->follower && skinnum != player->followerskin) // this is also called when we change colour so don't respawn the follower unless we changed skins - { - // Remove follower's possible hnext list (bubble) - bub = player->follower->hnext; - - while (bub && !P_MobjWasRemoved(bub)) - { - tmp = bub->hnext; - P_RemoveMobj(bub); - bub = tmp; - } - - P_RemoveMobj(player->follower); - P_SetTarget(&player->follower, NULL); - } + if (skinnum != player->followerskin) + K_RemoveFollower(player); player->followerskin = skinnum; @@ -253,18 +266,16 @@ void K_HandleFollower(player_t *player) { //CONS_Printf("Follower skin invlaid. Setting to -1.\n"); player->followerskin = -1; - return; } // don't do anything if we can't have a follower to begin with. // (It gets removed under those conditions) - if (player->spectator) - { - return; - } - - if (player->followerskin < 0) + if (player->spectator || player->followerskin < 0) { + if (player->follower) + { + K_RemoveFollower(player); + } return; } diff --git a/src/k_follower.h b/src/k_follower.h index f9a16c971..3c1e2a7d1 100644 --- a/src/k_follower.h +++ b/src/k_follower.h @@ -45,10 +45,11 @@ typedef enum // typedef struct follower_s { - char skinname[SKINNAMESIZE+1]; // Skin Name. This is what to refer to when asking the commands anything. - char name[SKINNAMESIZE+1]; // Name. This is used for the menus. We'll just follow the same rules as skins for this. + char name[SKINNAMESIZE+1]; // Skin Name. This is what to refer to when asking the commands anything.. char icon[8+1]; // Lump names are only 8 characters. (+1 for \0) + UINT8 category; // Category + skincolornum_t defaultcolor; // default color for menus. followermode_t mode; // Follower behavior modifier. @@ -85,6 +86,18 @@ typedef struct follower_s extern INT32 numfollowers; extern follower_t followers[MAXSKINS]; +#define MAXFOLLOWERCATEGORIES 32 + +typedef struct followercategory_s +{ + char name[SKINNAMESIZE+1]; // Name. This is used for the menus. We'll just follow the same rules as skins for this. + char icon[8+1]; // Lump names are only 8 characters. (+1 for \0) + UINT8 numincategory; +} followercategory_t; + +extern INT32 numfollowercategories; +extern followercategory_t followercategories[MAXFOLLOWERCATEGORIES]; + /*-------------------------------------------------- INT32 K_FollowerAvailable(const char *name) @@ -168,5 +181,19 @@ UINT16 K_GetEffectiveFollowerColor(UINT16 followercolor, UINT16 playercolor); void K_HandleFollower(player_t *player); +/*-------------------------------------------------- + void K_RemoveFollower(player_t *player) + + Removes Follower object + + Input Arguments:- + player - The player who we want to remove the follower of. + + Return:- + None +--------------------------------------------------*/ + +void K_RemoveFollower(player_t *player); + #endif // __K_FOLLOWER__ diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 497909dc8..6149434c7 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -676,7 +676,7 @@ void K_PlayerLoseLife(player_t *player) return; } - if (player->spectator || player->exiting || player->bot || (player->pflags & PF_LOSTLIFE)) + if (player->spectator || player->exiting || player->bot || player->lives <= 0 || (player->pflags & PF_LOSTLIFE)) { return; } diff --git a/src/k_hud.c b/src/k_hud.c index 18d133844..29525a1c4 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -37,10 +37,6 @@ #include "r_fps.h" #include "m_random.h" -#define NUMPOSNUMS 10 -#define NUMPOSFRAMES 7 // White, three blues, three reds -#define NUMWINFRAMES 6 // Red, yellow, green, cyan, blue, purple - //{ Patch Definitions static patch_t *kp_nodraw; @@ -70,8 +66,7 @@ static patch_t *kp_startcountdown[20]; static patch_t *kp_racefault[6]; static patch_t *kp_racefinish[6]; -static patch_t *kp_positionnum[NUMPOSNUMS][NUMPOSFRAMES]; -static patch_t *kp_winnernum[NUMPOSFRAMES]; +static patch_t *kp_positionnum[10][2][2]; // number, overlay or underlay, splitscreen static patch_t *kp_facenum[MAXPLAYERS+1]; static patch_t *kp_facehighlight[8]; @@ -179,7 +174,7 @@ static patch_t *kp_trickcool[2]; void K_LoadKartHUDGraphics(void) { - INT32 i, j; + INT32 i, j, k; char buffer[9]; // Null Stuff @@ -280,23 +275,29 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_racefinish[5], "K_2PFINB"); // Position numbers - sprintf(buffer, "K_POSNxx"); - for (i = 0; i < NUMPOSNUMS; i++) + sprintf(buffer, "KRNKxyz"); + for (i = 0; i < 10; i++) { buffer[6] = '0'+i; - for (j = 0; j < NUMPOSFRAMES; j++) - { - //sprintf(buffer, "K_POSN%d%d", i, j); - buffer[7] = '0'+j; - HU_UpdatePatch(&kp_positionnum[i][j], "%s", buffer); - } - } - sprintf(buffer, "K_POSNWx"); - for (i = 0; i < NUMWINFRAMES; i++) - { - buffer[7] = '0'+i; - HU_UpdatePatch(&kp_winnernum[i], "%s", buffer); + for (j = 0; j < 2; j++) + { + buffer[5] = 'A'+j; + + for (k = 0; k < 2; k++) + { + if (k > 0) + { + buffer[4] = 'S'; + } + else + { + buffer[4] = 'B'; + } + + HU_UpdatePatch(&kp_positionnum[i][j][k], "%s", buffer); + } + } } sprintf(buffer, "OPPRNKxx"); @@ -659,7 +660,6 @@ const char *K_GetItemPatch(UINT8 item, boolean tiny) return (tiny ? "K_ISINV1" : "K_ITINV1"); case KITEM_BANANA: case KRITEM_TRIPLEBANANA: - case KRITEM_TENFOLDBANANA: return (tiny ? "K_ISBANA" : "K_ITBANA"); case KITEM_EGGMAN: return (tiny ? "K_ISEGGM" : "K_ITEGGM"); @@ -950,6 +950,11 @@ void K_ObjectTracking(trackingResult_t *result, vector3_t *point, boolean revers h = R_PointToDist2(point->x, point->y, viewx, viewy); da = AngleDeltaSigned(viewpointAngle, R_PointToAngle2(point->x, point->y, viewx, viewy)); + if (reverse) + { + da = -(da); + } + // Set results relative to top left! result->x = FixedMul(NEWTAN(da), fg); result->y = FixedMul((NEWTAN(viewpointAiming) - FixedDiv((viewz - point->z), 1 + FixedMul(NEWCOS(da), h))), fg); @@ -1545,146 +1550,187 @@ bademblem: } } -static void K_DrawKartPositionNum(INT32 num) +static fixed_t K_DrawKartPositionNumPatch(UINT8 num, UINT8 *color, fixed_t x, fixed_t y, fixed_t scale, INT32 flags) { - // POSI_X = BASEVIDWIDTH - 51; // 269 - // POSI_Y = BASEVIDHEIGHT- 64; // 136 + UINT8 splitIndex = (r_splitscreen > 0) ? 1 : 0; + fixed_t w = FRACUNIT; + fixed_t h = FRACUNIT; + INT32 overlayFlags[2]; + INT32 i; - boolean win = (stplyr->exiting && num == 1); - //INT32 X = POSI_X; - INT32 W = SHORT(kp_positionnum[0][0]->width); - fixed_t scale = FRACUNIT; - patch_t *localpatch = kp_positionnum[0][0]; - INT32 fx = 0, fy = 0, fflags = 0; - INT32 addOrSub = V_ADD; - boolean flipdraw = false; // flip the order we draw it in for MORE splitscreen bs. fun. - boolean flipvdraw = false; // used only for 2p splitscreen so overtaking doesn't make 1P's position fly off the screen. - boolean overtake = false; + if (num >= 10) + { + return x; // invalid input + } if ((mapheaderinfo[gamemap - 1]->levelflags & LF_SUBTRACTNUM) == LF_SUBTRACTNUM) { - addOrSub = V_SUBTRACT; + overlayFlags[0] = V_SUBTRACT; + overlayFlags[1] = V_ADD; + } + else + { + overlayFlags[0] = V_ADD; + overlayFlags[1] = V_SUBTRACT; + } + + w = kp_positionnum[num][0][splitIndex]->width * scale; + h = kp_positionnum[num][0][splitIndex]->height * scale; + + if (flags & V_SNAPTORIGHT) + { + x -= w; + } + + if (flags & V_SNAPTOBOTTOM) + { + y -= h; + } + + for (i = 1; i >= 0; i--) + { + V_DrawFixedPatch( + x, y, scale, + flags | overlayFlags[i], + kp_positionnum[num][i][splitIndex], + color + ); + } + + return (x - w); +} + +static void K_DrawKartPositionNum(INT32 num) +{ + const tic_t counter = (leveltime / 3); // Alternate colors every three frames + fixed_t scale = FRACUNIT; + fixed_t fx = 0, fy = 0; + transnum_t trans = 0; + INT32 fflags = 0; + UINT8 *color = NULL; + + if (leveltime < (starttime + NUMTRANSMAPS)) + { + trans = (starttime + NUMTRANSMAPS) - leveltime; + } + + if (trans >= NUMTRANSMAPS) + { + return; } if (stplyr->positiondelay || stplyr->exiting) { - scale *= 2; - overtake = true; // this is used for splitscreen stuff in conjunction with flipdraw. + const UINT8 delay = (stplyr->exiting) ? POS_DELAY_TIME : stplyr->positiondelay; + const fixed_t add = (scale * 3) >> ((r_splitscreen == 1) ? 1 : 2); + scale += min((add * (delay * delay)) / (POS_DELAY_TIME * POS_DELAY_TIME), add); } - if (r_splitscreen) - { - scale /= 2; - } - - W = FixedMul(W<>FRACBITS; - // pain and suffering defined below if (!r_splitscreen) { - fx = POSI_X; - fy = BASEVIDHEIGHT - 8; - fflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN; + fx = BASEVIDWIDTH << FRACBITS; + fy = BASEVIDHEIGHT << FRACBITS; + fflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT; } else if (r_splitscreen == 1) // for this splitscreen, we'll use case by case because it's a bit different. { - fx = POSI_X; - if (stplyr == &players[displayplayers[0]]) // for player 1: display this at the top right, above the minimap. + fx = BASEVIDWIDTH << FRACBITS; + + if (stplyr == &players[displayplayers[0]]) { - fy = 30; - fflags = V_SNAPTOTOP|V_SNAPTORIGHT|V_SPLITSCREEN; - if (overtake) - flipvdraw = true; // make sure overtaking doesn't explode us + // for player 1: display this at the top right, above the minimap. + fy = 0; + fflags = V_SNAPTOTOP|V_SNAPTORIGHT; } - else // if we're not p1, that means we're p2. display this at the bottom right, below the minimap. + else { - fy = (BASEVIDHEIGHT/2) - 8; - fflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN; + // if we're not p1, that means we're p2. display this at the bottom right, below the minimap. + fy = BASEVIDHEIGHT << FRACBITS; + fflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT; + } + + fy >>= 1; + } + else + { + fy = BASEVIDHEIGHT << FRACBITS; + + if (stplyr == &players[displayplayers[0]] + || stplyr == &players[displayplayers[2]]) + { + // If we are P1 or P3... + fx = 0; + fflags = V_SNAPTOLEFT|V_SNAPTOBOTTOM; + } + else + { + // else, that means we're P2 or P4. + fx = BASEVIDWIDTH << FRACBITS; + fflags = V_SNAPTORIGHT|V_SNAPTOBOTTOM; + } + + fx >>= 1; + fy >>= 1; + } + + if (trans > 0) + { + fflags |= (trans << V_ALPHASHIFT); + } + + if (stplyr->exiting && num == 1) + { + // 1st place winner? You get rainbows!! + color = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_POSNUM_BEST1 + (counter % 6), GTC_CACHE); + } + else if (stplyr->laps >= numlaps || stplyr->exiting) + { + // On the final lap, or already won. + boolean useRedNums = K_IsPlayerLosing(stplyr); + + if ((mapheaderinfo[gamemap - 1]->levelflags & LF_SUBTRACTNUM) == LF_SUBTRACTNUM) + { + // Subtracting RED will look BLUE, and vice versa. + useRedNums = !useRedNums; + } + + if (useRedNums == true) + { + color = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_POSNUM_LOSE1 + (counter % 3), GTC_CACHE); + } + else + { + color = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_POSNUM_WIN1 + (counter % 3), GTC_CACHE); } } else { - if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) // If we are P1 or P3... - { - fx = POSI_X; - fy = POSI_Y; - fflags = V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_SPLITSCREEN; - flipdraw = true; - if (num && num >= 10) - fx += W; // this seems dumb, but we need to do this in order for positions above 10 going off screen. - } - else // else, that means we're P2 or P4. - { - fx = POSI2_X; - fy = POSI2_Y; - fflags = V_SNAPTORIGHT|V_SNAPTOBOTTOM|V_SPLITSCREEN; - } + color = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_POSNUM, GTC_CACHE); } // Special case for 0 if (num <= 0) { - V_DrawFixedPatch(fx<laps >= numlaps || stplyr->exiting) // Check for the final lap, or won - { - boolean useRedNums = K_IsPlayerLosing(stplyr); + /* + + */ - if (addOrSub == V_SUBTRACT) - { - // Subtracting RED will look BLUE, and vice versa. - useRedNums = !useRedNums; - } - - // Alternate frame every three frames - switch ((leveltime % 9) / 3) - { - case 0: - if (useRedNums == true) - localpatch = kp_positionnum[num % 10][4]; - else - localpatch = kp_positionnum[num % 10][1]; - break; - case 1: - if (useRedNums == true) - localpatch = kp_positionnum[num % 10][5]; - else - localpatch = kp_positionnum[num % 10][2]; - break; - case 2: - if (useRedNums == true) - localpatch = kp_positionnum[num % 10][6]; - else - localpatch = kp_positionnum[num % 10][3]; - break; - default: - localpatch = kp_positionnum[num % 10][0]; - break; - } - } - else - { - localpatch = kp_positionnum[num % 10][0]; - } - - V_DrawFixedPatch( - (fx<width)*scale/2) : 0), - (fy<height)*scale/2) : 0), - scale, addOrSub|V_SLIDEIN|fflags, localpatch, NULL + fx = K_DrawKartPositionNumPatch( + (num % 10), color, + fx, fy, scale, V_SLIDEIN|V_SPLITSCREEN|fflags ); - // ^ if we overtake as p1 or p3 in splitscren, we shift it so that it doesn't go off screen. - // ^ if we overtake as p1 in 2p splits, shift vertically so that this doesn't happen either. - - fx -= W; num /= 10; } } @@ -4514,7 +4560,6 @@ static void K_drawDistributionDebugger(void) kp_sneaker[1], kp_sneaker[1], kp_banana[1], - kp_banana[1], kp_orbinaut[4], kp_orbinaut[4], kp_jawz[1] diff --git a/src/k_hud.h b/src/k_hud.h index 683425e8a..4f84f9dd3 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -19,6 +19,8 @@ #define RINGANIM_NUMFRAMES 10 #define RINGANIM_DELAYMAX 5 +#define POS_DELAY_TIME 10 + void K_AdjustXYWithSnap(INT32 *x, INT32 *y, UINT32 options, INT32 dupx, INT32 dupy); typedef struct trackingResult_s diff --git a/src/k_kart.c b/src/k_kart.c index d8fe83541..7e55c7c25 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -305,7 +305,6 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_dualsneaker); CV_RegisterVar(&cv_triplesneaker); CV_RegisterVar(&cv_triplebanana); - CV_RegisterVar(&cv_decabanana); CV_RegisterVar(&cv_tripleorbinaut); CV_RegisterVar(&cv_quadorbinaut); CV_RegisterVar(&cv_dualjawz); @@ -409,7 +408,6 @@ consvar_t *KartItemCVars[NUMKARTRESULTS-1] = &cv_dualsneaker, &cv_triplesneaker, &cv_triplebanana, - &cv_decabanana, &cv_tripleorbinaut, &cv_quadorbinaut, &cv_dualjawz @@ -428,7 +426,7 @@ static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = { 1, 2, 0, 0, 0, 0, 0, 0 }, // Eggman Monitor { 5, 5, 2, 2, 0, 0, 0, 0 }, // Orbinaut { 0, 4, 2, 1, 0, 0, 0, 0 }, // Jawz - { 0, 3, 3, 1, 0, 0, 0, 0 }, // Mine + { 0, 3, 3, 2, 0, 0, 0, 0 }, // Mine { 3, 0, 0, 0, 0, 0, 0, 0 }, // Land Mine { 0, 0, 2, 2, 0, 0, 0, 0 }, // Ballhog { 0, 0, 0, 0, 0, 2, 4, 0 }, // Self-Propelled Bomb @@ -446,7 +444,6 @@ static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = { 0, 0, 2, 2, 2, 0, 0, 0 }, // Sneaker x2 { 0, 0, 0, 0, 4, 4, 4, 0 }, // Sneaker x3 { 0, 1, 1, 0, 0, 0, 0, 0 }, // Banana x3 - { 0, 0, 0, 1, 0, 0, 0, 0 }, // Banana x10 { 0, 0, 1, 0, 0, 0, 0, 0 }, // Orbinaut x3 { 0, 0, 0, 2, 0, 0, 0, 0 }, // Orbinaut x4 { 0, 0, 1, 2, 1, 0, 0, 0 } // Jawz x2 @@ -480,7 +477,6 @@ static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = { 0, 0 }, // Sneaker x2 { 0, 1 }, // Sneaker x3 { 0, 0 }, // Banana x3 - { 1, 1 }, // Banana x10 { 2, 0 }, // Orbinaut x3 { 1, 1 }, // Orbinaut x4 { 5, 1 } // Jawz x2 @@ -516,7 +512,6 @@ static UINT8 K_KartItemOddsSpecial[NUMKARTRESULTS-1][4] = { 0, 1, 1, 0 }, // Sneaker x2 { 0, 0, 1, 1 }, // Sneaker x3 { 0, 0, 0, 0 }, // Banana x3 - { 0, 0, 0, 0 }, // Banana x10 { 0, 1, 1, 0 }, // Orbinaut x3 { 0, 0, 1, 1 }, // Orbinaut x4 { 0, 0, 1, 1 } // Jawz x2 @@ -577,7 +572,6 @@ SINT8 K_ItemResultToType(SINT8 getitem) return KITEM_SNEAKER; case KRITEM_TRIPLEBANANA: - case KRITEM_TENFOLDBANANA: return KITEM_BANANA; case KRITEM_TRIPLEORBINAUT: @@ -615,9 +609,6 @@ UINT8 K_ItemResultToAmount(SINT8 getitem) case KITEM_BALLHOG: // Not a special result, but has a special amount return 5; - case KRITEM_TENFOLDBANANA: - return 10; - default: return 1; } @@ -891,7 +882,6 @@ INT32 K_KartGetItemOdds( break; case KRITEM_TRIPLEBANANA: - case KRITEM_TENFOLDBANANA: powerItem = true; notNearEnd = true; break; @@ -3471,7 +3461,7 @@ boolean K_TripwirePass(player_t *player) boolean K_MovingHorizontally(mobj_t *mobj) { - return (P_AproxDistance(mobj->momx, mobj->momy) / 5 > abs(mobj->momz)); + return (P_AproxDistance(mobj->momx, mobj->momy) / 4 > abs(mobj->momz)); } boolean K_WaterRun(mobj_t *mobj) @@ -6174,6 +6164,7 @@ mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, { // Shoot forward mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, mapthing); + mo->angle = player->mo->angle; // These are really weird so let's make it a very specific case to make SURE it works... if (player->mo->eflags & MFE_VERTICALFLIP) @@ -6188,7 +6179,6 @@ mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, S_StartSound(player->mo, mo->info->seesound); - if (mo) { angle_t fa = player->mo->angle>>ANGLETOFINESHIFT; fixed_t HEIGHT = ((20 + (dir*10)) * FRACUNIT) + (FixedDiv(player->mo->momz, mapobjectscale)*P_MobjFlip(player->mo)); // Also intentionally not player scale @@ -6196,14 +6186,20 @@ mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, P_SetObjectMomZ(mo, HEIGHT, false); mo->momx = player->mo->momx + FixedMul(FINECOSINE(fa), PROJSPEED*dir); mo->momy = player->mo->momy + FixedMul(FINESINE(fa), PROJSPEED*dir); + } - mo->extravalue2 = dir; + mo->extravalue2 = dir; - if (mo->eflags & MFE_UNDERWATER) - mo->momz = (117 * mo->momz) / 200; + if (mo->eflags & MFE_UNDERWATER) + mo->momz = (117 * mo->momz) / 200; - P_SetScale(mo, finalscale); - mo->destscale = finalscale; + P_SetScale(mo, finalscale); + mo->destscale = finalscale; + + if (mapthing == MT_BANANA) + { + mo->angle = FixedAngle(P_RandomRange(PR_DECORATION, -180, 180) << FRACBITS); + mo->rollangle = FixedAngle(P_RandomRange(PR_DECORATION, -180, 180) << FRACBITS); } // this is the small graphic effect that plops in you when you throw an item: @@ -6288,6 +6284,8 @@ mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, if (player->mo->eflags & MFE_VERTICALFLIP) mo->eflags |= MFE_VERTICALFLIP; + mo->angle = newangle; + if (mapthing == MT_SSMINE) mo->extravalue1 = 49; // Pads the start-up length from 21 frames to a full 2 seconds else if (mapthing == MT_BUBBLESHIELDTRAP) @@ -6609,6 +6607,7 @@ void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound) mo->player->tricktime = 0; // Reset post-hitlag timer // Setup the boost for potential upwards trick, at worse, make it your regular max speed. (boost = curr speed*1.25) mo->player->trickboostpower = max(FixedDiv(mo->player->speed, K_GetKartSpeed(mo->player, false, false)) - FRACUNIT, 0)*125/100; + mo->player->trickboostpower = FixedDiv(mo->player->trickboostpower, K_GrowShrinkSpeedMul(mo->player)); //CONS_Printf("Got boost: %d%\n", mo->player->trickboostpower*100 / FRACUNIT); } @@ -6703,7 +6702,7 @@ killnext: S_StartSound(banana, banana->info->deathsound); P_KillMobj(banana, inflictor, source, DMG_NORMAL); - P_SetObjectMomZ(banana, 8*FRACUNIT, false); + P_SetObjectMomZ(banana, 24*FRACUNIT, false); if (inflictor) P_InstaThrust(banana, R_PointToAngle2(inflictor->x, inflictor->y, banana->x, banana->y)+ANGLE_90, 16*FRACUNIT); } @@ -6965,9 +6964,14 @@ void K_DropHnextList(player_t *player, boolean keepshields) mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT8 amount) { mobj_t *drop = P_SpawnMobj(x, y, z, MT_FLOATINGITEM); + mobj_t *backdrop = P_SpawnMobjFromMobj(drop, 0, 0, 0, MT_OVERLAY); + + P_SetTarget(&backdrop->target, drop); + P_SetMobjState(backdrop, S_ITEMBACKDROP); + P_SetScale(drop, drop->scale>>4); drop->destscale = (3*drop->destscale)/2; - + drop->angle = angle; P_Thrust(drop, FixedAngle(P_RandomFixed(PR_ITEM_ROULETTE) * 180) + angle, @@ -7043,6 +7047,9 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 } drop->flags |= MF_NOCLIPTHING; + backdrop->dispoffset = 1; + P_SetTarget(&backdrop->tracer, drop); + backdrop->flags2 |= MF2_LINKDRAW; return drop; } @@ -7096,7 +7103,7 @@ void K_DropRocketSneaker(player_t *player) flingangle = ANG60; S_StartSound(shoe, shoe->info->deathsound); - P_SetObjectMomZ(shoe, 8*FRACUNIT, false); + P_SetObjectMomZ(shoe, 24*FRACUNIT, false); P_InstaThrust(shoe, R_PointToAngle2(shoe->target->x, shoe->target->y, shoe->x, shoe->y)+flingangle, 16*FRACUNIT); shoe->momx += shoe->target->momx; shoe->momy += shoe->target->momy; @@ -9898,8 +9905,16 @@ void K_KartUpdatePosition(player_t *player) if (leveltime < starttime || oldposition == 0) oldposition = position; - if (oldposition != position) // Changed places? - player->positiondelay = 10; // Position number growth + if (position != oldposition) // Changed places? + { + if (position < oldposition && P_IsDisplayPlayer(player) == true) + { + // Play sound when getting closer to 1st. + S_StartSound(player->mo, sfx_mbs41); + } + + player->positiondelay = POS_DELAY_TIME + 4; // Position number growth + } /* except in FREE PLAY */ if (player->curshield == KSHIELD_TOP && @@ -11394,9 +11409,10 @@ void K_MoveKartPlayer(player_t *player, boolean onground) { const angle_t lr = ANGLE_45; fixed_t momz = FixedDiv(player->mo->momz, mapobjectscale); // bring momz back to scale... + fixed_t invertscale = FixedDiv(FRACUNIT, K_GrowShrinkSpeedMul(player)); fixed_t speedmult = max(0, FRACUNIT - abs(momz)/TRICKMOMZRAMP); // TRICKMOMZRAMP momz is minimum speed (Should be 20) - fixed_t basespeed = K_GetKartSpeed(player, false, false); // at WORSE, keep your normal speed when tricking. - fixed_t speed = FixedMul(speedmult, P_AproxDistance(player->mo->momx, player->mo->momy)); + fixed_t basespeed = FixedMul(invertscale, K_GetKartSpeed(player, false, false)); // at WORSE, keep your normal speed when tricking. + fixed_t speed = FixedMul(invertscale, FixedMul(speedmult, P_AproxDistance(player->mo->momx, player->mo->momy))); K_trickPanelTimingVisual(player, momz); @@ -11483,14 +11499,12 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } else if (cmd->throwdir < 0) { - boolean relative = true; - player->mo->momx /= 3; player->mo->momy /= 3; if (player->mo->momz * P_MobjFlip(player->mo) <= 0) { - relative = false; + player->mo->momz = 0; // relative = false; } // Calculate speed boost decay: @@ -11499,7 +11513,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->trickboostdecay = min(TICRATE*3/4, abs(momz/FRACUNIT)); //CONS_Printf("decay: %d\n", player->trickboostdecay); - P_SetObjectMomZ(player->mo, 48*FRACUNIT, relative); + player->mo->momz += P_MobjFlip(player->mo)*48*mapobjectscale; player->trickpanel = 4; } } diff --git a/src/k_menu.h b/src/k_menu.h index ba4458bcc..588542002 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -410,6 +410,8 @@ typedef enum { mpause_addons = 0, mpause_switchmap, + mpause_restartmap, + mpause_tryagain, #ifdef HAVE_DISCORDRPC mpause_discordrequests, #endif @@ -599,6 +601,7 @@ typedef enum CSSTEP_CHARS, CSSTEP_ALTS, CSSTEP_COLORS, + CSSTEP_FOLLOWERCATEGORY, CSSTEP_FOLLOWER, CSSTEP_FOLLOWERCOLORS, CSSTEP_READY @@ -614,6 +617,7 @@ typedef struct setup_player_s UINT8 delay; UINT16 color; UINT8 mdepth; + boolean hitlag; // Hack, save player 1's original device even if they init charsel with keyboard. // If they play ALONE, allow them to retain that original device, otherwise, ignore this. @@ -622,7 +626,8 @@ typedef struct setup_player_s UINT8 changeselect; - INT32 followern; + INT16 followercategory; + INT16 followern; UINT16 followercolor; tic_t follower_tics; tic_t follower_timer; @@ -975,6 +980,8 @@ extern consvar_t cv_dummymenuplayer; extern consvar_t cv_dummyspectator; // Bunch of funny functions for the pause menu...~ +void M_RestartMap(INT32 choice); // Restart level (MP) +void M_TryAgain(INT32 choice); // Try again (SP) void M_ConfirmSpectate(INT32 choice); // Spectate confirm when you're alone void M_ConfirmEnterGame(INT32 choice); // Enter game confirm when you're alone void M_ConfirmSpectateChange(INT32 choice); // Splitscreen spectate/play menu func diff --git a/src/k_menudef.c b/src/k_menudef.c index 66be81efb..4fb6b2262 100644 --- a/src/k_menudef.c +++ b/src/k_menudef.c @@ -1071,7 +1071,6 @@ menuitem_t OPTIONS_GameplayItems[] = {IT_KEYHANDLER | IT_NOTHING, NULL, "Bananas", NULL, {.routine = M_HandleItemToggles}, KITEM_BANANA, 0}, {IT_KEYHANDLER | IT_NOTHING, NULL, "Bananas x3", NULL, {.routine = M_HandleItemToggles}, KRITEM_TRIPLEBANANA, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Bananas x10", NULL, {.routine = M_HandleItemToggles}, KRITEM_TENFOLDBANANA, 0}, {IT_KEYHANDLER | IT_NOTHING, NULL, "Proximity Mines", NULL, {.routine = M_HandleItemToggles}, KITEM_MINE, 0}, {IT_KEYHANDLER | IT_NOTHING, NULL, "Orbinauts", NULL, {.routine = M_HandleItemToggles}, KITEM_ORBINAUT, 0}, @@ -1594,6 +1593,12 @@ menuitem_t PAUSE_Main[] = {IT_STRING | IT_SUBMENU, "CHANGE MAP", "M_ICOMAP", NULL, {.submenu = &PAUSE_GamemodesDef}, 0, 0}, + {IT_STRING | IT_CALL, "RESTART MAP", "M_ICORE", + NULL, {.routine = M_RestartMap}, 0, 0}, + + {IT_STRING | IT_CALL, "TRY AGAIN", "M_ICORE", + NULL, {.routine = M_TryAgain}, 0, 0}, + #ifdef HAVE_DISCORDRPC {IT_STRING | IT_CALL, "DISCORD REQUESTS", "M_ICODIS", NULL, {NULL}, 0, 0}, diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 0b0bc5661..8bfec4716 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -969,7 +969,8 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y) { angle_t angamt = ANGLE_MAX; UINT16 i, numoptions = 0; - UINT16 l = 0, r = 0; + INT16 l = 0, r = 0; + INT16 subtractcheck; switch (p->mdepth) { @@ -979,8 +980,11 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y) case CSSTEP_COLORS: numoptions = nummenucolors; break; + case CSSTEP_FOLLOWERCATEGORY: + numoptions = numfollowercategories+1; + break; case CSSTEP_FOLLOWER: - numoptions = numfollowers+1; + numoptions = followercategories[p->followercategory].numincategory; break; case CSSTEP_FOLLOWERCOLORS: numoptions = nummenucolors+2; @@ -994,17 +998,19 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y) return; } + subtractcheck = 1 ^ (numoptions & 1); + angamt /= numoptions; for (i = 0; i < numoptions; i++) { fixed_t cx = x << FRACBITS, cy = y << FRACBITS; - boolean subtract = (i & 1); + boolean subtract = (i & 1) == subtractcheck; angle_t ang = ((i+1)/2) * angamt; patch_t *patch = NULL; UINT8 *colormap = NULL; fixed_t radius = 28<mdepth) { @@ -1017,7 +1023,7 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y) n -= ((i+1)/2); else n += ((i+1)/2); - n %= numoptions; + n = (n + numoptions) % numoptions; skin = setup_chargrid[p->gridx][p->gridy].skinlist[n]; patch = faceprefix[skin][FACE_RANK]; @@ -1061,16 +1067,16 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y) break; } - case CSSTEP_FOLLOWER: + case CSSTEP_FOLLOWERCATEGORY: { - follower_t *fl = NULL; + followercategory_t *fc = NULL; - n = (p->followern + 1) + numoptions/2; + n = (p->followercategory + 1) + numoptions/2; if (subtract) n -= ((i+1)/2); else n += ((i+1)/2); - n %= numoptions; + n = (n + numoptions) % numoptions; if (n == 0) { @@ -1078,11 +1084,71 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y) } else { - fl = &followers[n - 1]; + fc = &followercategories[n - 1]; + patch = W_CachePatchName(fc->icon, PU_CACHE); + } + + radius = 24<width) << FRACBITS) >> 1; + cy -= (SHORT(patch->height) << FRACBITS) >> 1; + break; + } + + case CSSTEP_FOLLOWER: + { + follower_t *fl = NULL; + INT16 startfollowern = p->followern; + + if (i == 0) + { + n = p->followern; + r = (numoptions+1)/2; + while (r) + { + n--; + if (n < 0) + n = numfollowers-1; + if (n == startfollowern) + break; + if (followers[n].category == p->followercategory) + r--; + } + l = r = n; + } + else if (subtract) + { + do + { + l--; + if (l < 0) + l = numfollowers-1; + if (l == startfollowern) + break; + } + while (followers[l].category != p->followercategory); + n = l; + } + else + { + do + { + r++; + if (r >= numfollowers) + r = 0; + if (r == startfollowern) + break; + } + while (followers[r].category != p->followercategory); + n = r; + } + + { + fl = &followers[n]; patch = W_CachePatchName(fl->icon, PU_CACHE); colormap = R_GetTranslationColormap(TC_DEFAULT, - K_GetEffectiveFollowerColor(p->followercolor, p->color), + K_GetEffectiveFollowerColor(fl->defaultcolor, p->color), GTC_MENUCACHE ); } @@ -1142,7 +1208,12 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y) ang = (signed)(ang - (angamt/2)); if (p->rotate) - ang = (signed)(ang + ((angamt / CSROTATETICS) * p->rotate)); + { + SINT8 rotate = p->rotate; + if ((p->hitlag == true) && (setup_animcounter & 1)) + rotate = -rotate; + ang = (signed)(ang + ((angamt / CSROTATETICS) * rotate)); + } cx += FixedMul(radius, FINECOSINE(ang >> ANGLETOFINESHIFT)); cy -= FixedMul(radius, FINESINE(ang >> ANGLETOFINESHIFT)) / 3; @@ -1154,41 +1225,40 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y) } // returns false if the character couldn't be rendered -static boolean M_DrawCharacterSprite(INT16 x, INT16 y, INT16 skin, INT32 addflags, UINT8 *colormap) +static boolean M_DrawCharacterSprite(INT16 x, INT16 y, INT16 skin, boolean charflip, boolean animate, INT32 addflags, UINT8 *colormap) { UINT8 spr; spritedef_t *sprdef; spriteframe_t *sprframe; patch_t *sprpatch; + UINT8 rotation = (charflip ? 1 : 7); + UINT32 frame = animate ? setup_animcounter : 0; - UINT32 flags = 0; - UINT32 frame; - - spr = P_GetSkinSprite2(&skins[skin], SPR2_FSTN, NULL); + spr = P_GetSkinSprite2(&skins[skin], SPR2_STIN, NULL); sprdef = &skins[skin].sprites[spr]; if (!sprdef->numframes) // No frames ?? return false; // Can't render! - frame = states[S_KART_FAST].frame & FF_FRAMEMASK; - if (frame >= sprdef->numframes) // Walking animation missing - frame = 0; // Try to use standing frame + frame %= sprdef->numframes; sprframe = &sprdef->spriteframes[frame]; - sprpatch = W_CachePatchNum(sprframe->lumppat[1], PU_CACHE); + sprpatch = W_CachePatchNum(sprframe->lumppat[rotation], PU_CACHE); - if (sprframe->flip & 1) // Only for first sprite - flags |= V_FLIP; // This sprite is left/right flipped! + if (sprframe->flip & (1<followern; @@ -1238,14 +1309,11 @@ static boolean M_DrawFollowerSprite(INT16 x, INT16 y, INT32 num, INT32 addflags, useframe = 0; // frame doesn't exist, we went beyond it... what? sprframe = &sprdef->spriteframes[useframe]; - patch = W_CachePatchNum(sprframe->lumppat[1], PU_CACHE); + patch = W_CachePatchNum(sprframe->lumppat[rotation], PU_CACHE); - if (sprframe->flip & 2) // Only for first sprite + if (sprframe->flip & (1<follower_timer)>>ANGLETOFINESHIFT) & FINEMASK)); - color = K_GetEffectiveFollowerColor(p->followercolor, p->color); + color = K_GetEffectiveFollowerColor( + (p->mdepth < CSSTEP_FOLLOWERCOLORS) ? fl.defaultcolor : p->followercolor, + p->color); } colormap = R_GetTranslationColormap(TC_DEFAULT, color, GTC_MENUCACHE); - V_DrawFixedPatch((x)*FRACUNIT, ((y-12)*FRACUNIT) + sine - fl.zoffs, fl.scale, addflags, patch, colormap); + V_DrawFixedPatch((x*FRACUNIT), ((y-12)*FRACUNIT) + sine, fl.scale, addflags, patch, colormap); return true; } -static void M_DrawCharSelectSprite(UINT8 num, INT16 x, INT16 y) +static void M_DrawCharSelectSprite(UINT8 num, INT16 x, INT16 y, boolean charflip) { setup_player_t *p = &setup_player[num]; - UINT8 cnum = p->clonenum; + UINT8 color; + UINT8 *colormap; - // for p1 alone don't try to preview things on pages that don't exist lol. - if (p->mdepth == CSSTEP_CHARS && setup_numplayers == 1) - cnum = setup_page; + if (p->skin < 0) + return; - INT16 skin = setup_chargrid[p->gridx][p->gridy].skinlist[cnum]; - UINT8 color = p->color; - UINT8 *colormap = R_GetTranslationColormap(skin, color, GTC_MENUCACHE); - INT32 flags = 0; + if (p->mdepth < CSSTEP_COLORS) + color = skins[p->skin].prefcolor; + else + color = p->color; + colormap = R_GetTranslationColormap(p->skin, color, GTC_MENUCACHE); - // Flip for left-side players - if (!(num & 1)) - flags ^= V_FLIP; - - if (skin >= 0) - M_DrawCharacterSprite(x, y, skin, flags, colormap); + M_DrawCharacterSprite(x, y, p->skin, charflip, (p->mdepth == CSSTEP_READY), 0, colormap); } static void M_DrawCharSelectPreview(UINT8 num) @@ -1289,6 +1355,7 @@ static void M_DrawCharSelectPreview(UINT8 num) INT16 x = 11, y = 5; char letter = 'A' + num; setup_player_t *p = &setup_player[num]; + boolean charflip = !!(num & 1); if (num & 1) x += 233; @@ -1300,28 +1367,10 @@ static void M_DrawCharSelectPreview(UINT8 num) if (p->mdepth >= CSSTEP_CHARS) { - M_DrawCharSelectSprite(num, x+32, y+75); - - if (p->mdepth >= CSSTEP_FOLLOWER) - { - M_DrawFollowerSprite(x+16, y+75, -1, !(num & 1) ? V_FLIP : 0, 0, p); - } - + M_DrawCharSelectSprite(num, x+32, y+75, charflip); M_DrawCharSelectCircle(p, x+32, y+64); } - if ((setup_animcounter/10) & 1 && gamestate == GS_MENU) // Not drawn outside of GS_MENU. - { - if (p->mdepth == CSSTEP_NONE && num == setup_numplayers) - { - V_DrawScaledPatch(x+1, y+36, 0, W_CachePatchName("4PSTART", PU_CACHE)); - } - else if (p->mdepth >= CSSTEP_READY) - { - V_DrawScaledPatch(x+1, y+36, 0, W_CachePatchName("4PREADY", PU_CACHE)); - } - } - V_DrawScaledPatch(x+9, y+2, 0, W_CachePatchName("FILEBACK", PU_CACHE)); V_DrawScaledPatch(x, y+2, 0, W_CachePatchName(va("CHARSEL%c", letter), PU_CACHE)); if (p->mdepth > CSSTEP_PROFILE) @@ -1334,6 +1383,23 @@ static void M_DrawCharSelectPreview(UINT8 num) V_DrawFileString(x+16, y+2, 0, "PLAYER"); } + if (p->mdepth >= CSSTEP_FOLLOWER) + { + M_DrawFollowerSprite(x+32+((charflip ? 1 : -1)*16), y+75, -1, charflip, 0, 0, p); + } + + if ((setup_animcounter/10) & 1 && gamestate == GS_MENU) // Not drawn outside of GS_MENU. + { + if (p->mdepth == CSSTEP_NONE && num == setup_numplayers) + { + V_DrawScaledPatch(x+1, y+36, 0, W_CachePatchName("4PSTART", PU_CACHE)); + } + else if (p->mdepth >= CSSTEP_READY) + { + V_DrawScaledPatch(x+1, y+36, 0, W_CachePatchName("4PREADY", PU_CACHE)); + } + } + // Profile selection if (p->mdepth == CSSTEP_PROFILE) { @@ -1549,39 +1615,38 @@ static void M_DrawProfileCard(INT32 x, INT32 y, boolean greyedout, profile_t *p) // check what setup_player is doing in priority. if (sp->mdepth >= CSSTEP_CHARS) { - skinnum = setup_chargrid[sp->gridx][sp->gridy].skinlist[sp->clonenum]; if (skinnum >= 0) { - if (M_DrawCharacterSprite(x-22, y+119, skinnum, V_FLIP, colormap)) + if (M_DrawCharacterSprite(x-22, y+119, skinnum, false, false, 0, colormap)) V_DrawMappedPatch(x+14, y+66, 0, faceprefix[skinnum][FACE_RANK], colormap); - - if (sp->mdepth >= CSSTEP_FOLLOWER) - { - if (M_DrawFollowerSprite(x-44 +12, y+119, 0, V_FLIP, 0, sp)) - { - UINT16 col = K_GetEffectiveFollowerColor(sp->followercolor, sp->color);; - patch_t *ico = W_CachePatchName(followers[sp->followern].icon, PU_CACHE); - UINT8 *fcolormap; - - fcolormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); - V_DrawMappedPatch(x+14+18, y+66, 0, ico, fcolormap); - } - } } M_DrawCharSelectCircle(sp, x-22, y+104); + + if (sp->mdepth >= CSSTEP_FOLLOWER) + { + if (M_DrawFollowerSprite(x-22 - 16, y+119, 0, false, 0, 0, sp)) + { + UINT16 col = K_GetEffectiveFollowerColor(sp->followercolor, sp->color);; + patch_t *ico = W_CachePatchName(followers[sp->followern].icon, PU_CACHE); + UINT8 *fcolormap; + + fcolormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); + V_DrawMappedPatch(x+14+18, y+66, 0, ico, fcolormap); + } + } } else if (skinnum > -1) // otherwise, read from profile. { UINT16 col = K_GetEffectiveFollowerColor(p->followercolor, p->color);; UINT8 fln = K_FollowerAvailable(p->follower); - if (M_DrawCharacterSprite(x-22, y+119, skinnum, V_FLIP, colormap)) + if (M_DrawCharacterSprite(x-22, y+119, skinnum, false, false, 0, colormap)) V_DrawMappedPatch(x+14, y+66, 0, faceprefix[skinnum][FACE_RANK], colormap); - if (M_DrawFollowerSprite(x-44 +12, y+119, fln, V_FLIP, col, NULL)) + if (M_DrawFollowerSprite(x-22 - 16, y+119, fln, false, 0, col, NULL)) { patch_t *ico = W_CachePatchName(followers[fln].icon, PU_CACHE); UINT8 *fcolormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); @@ -3594,19 +3659,29 @@ void M_DrawPause(void) { case IT_STRING: { - - char iconame[9]; // 8 chars + \0 patch_t *pp; if (i == itemOn) { - strcpy(iconame, currentMenu->menuitems[i].tooltip); - iconame[7] = '2'; // Yes this is a stupid hack. Replace the last character with a 2 when we're selecting this graphic. + if (i == mpause_restartmap || i == mpause_tryagain) + { + pp = W_CachePatchName( + va("M_ICOR2%c", ('A'+(pausemenu.ticker & 1))), + PU_CACHE); + } + else + { + char iconame[9]; // 8 chars + \0 + strcpy(iconame, currentMenu->menuitems[i].tooltip); + iconame[7] = '2'; // Yes this is a stupid hack. Replace the last character with a 2 when we're selecting this graphic. - pp = W_CachePatchName(iconame, PU_CACHE); + pp = W_CachePatchName(iconame, PU_CACHE); + } } else + { pp = W_CachePatchName(currentMenu->menuitems[i].tooltip, PU_CACHE); + } // 294 - 261 = 33 // We need to move 33 px in 50 tics which means we move 33/50 = 0.66 px every tic = 2/3 of the offset. @@ -3898,6 +3973,18 @@ static void M_DrawReplayHutReplayInfo(menudemo_t *demoref) if (demoref->gametype == GT_RACE) { V_DrawThinString(x, y+39, V_SNAPTOTOP|highlightflags, "TIME"); + } + else + { + V_DrawThinString(x, y+39, V_SNAPTOTOP|highlightflags, "SCORE"); + } + + if (demoref->standings[0].timeorscore == (UINT32_MAX-1)) + { + V_DrawThinString(x+32, y+39, V_SNAPTOTOP, "NO CONTEST"); + } + else if (demoref->gametype == GT_RACE) + { V_DrawRightAlignedString(x+84, y+40, V_SNAPTOTOP, va("%d'%02d\"%02d", G_TicsToMinutes(demoref->standings[0].timeorscore, true), G_TicsToSeconds(demoref->standings[0].timeorscore), @@ -3906,7 +3993,6 @@ static void M_DrawReplayHutReplayInfo(menudemo_t *demoref) } else { - V_DrawThinString(x, y+39, V_SNAPTOTOP|highlightflags, "SCORE"); V_DrawString(x+32, y+40, V_SNAPTOTOP, va("%d", demoref->standings[0].timeorscore)); } @@ -3915,7 +4001,7 @@ static void M_DrawReplayHutReplayInfo(menudemo_t *demoref) // Lat: 08/06/2020: For some reason missing skins have their value set to 255 (don't even ask me why I didn't write this) // and for an even STRANGER reason this passes the first check below, so we're going to make sure that the skin here ISN'T 255 before we do anything stupid. - if (demoref->standings[0].skin != 0xFF) + if (demoref->standings[0].skin < numskins) { patch = faceprefix[demoref->standings[0].skin][FACE_WANTED]; colormap = R_GetTranslationColormap( @@ -4116,7 +4202,7 @@ void M_DrawReplayStartMenu(void) // Lat: 08/06/2020: For some reason missing skins have their value set to 255 (don't even ask me why I didn't write this) // and for an even STRANGER reason this passes the first check below, so we're going to make sure that the skin here ISN'T 255 before we do anything stupid. - if (demoref->standings[i].skin != 0xFF) + if (demoref->standings[i].skin < numskins) { patch = faceprefix[demoref->standings[i].skin][FACE_RANK]; colormap = R_GetTranslationColormap( diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 4babf145b..ab43217ff 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -357,7 +357,7 @@ static void M_EraseDataResponse(INT32 ch) void M_EraseData(INT32 choice) { - const char *eschoice, *esstr = M_GetText("Are you sure you want to erase\n%s?\n\n(Press A to confirm)\n"); + const char *eschoice, *esstr = M_GetText("Are you sure you want to erase\n%s?\n\nPress (A) to confirm or (B) to cancel\n"); optionsmenu.erasecontext = (UINT8)choice; @@ -1517,7 +1517,7 @@ static void M_HandleMenuInput(void) { if (((currentMenu->menuitems[itemOn].status & IT_CALLTYPE) & IT_CALL_NOTMODIFIED) && majormods) { - M_StartMessage(M_GetText("This cannot be done with complex addons\nor in a cheated game.\n\n(Press a key)\n"), NULL, MM_NOTHING); + M_StartMessage(M_GetText("This cannot be done with complex addons\nor in a cheated game.\n\nPress (B)\n"), NULL, MM_NOTHING); return; } } @@ -1583,6 +1583,12 @@ void M_Ticker(void) { INT32 i; + if (!menuactive) + { + noFurtherInput = false; + return; + } + if (menutransition.tics != 0 || menutransition.dest != 0) { noFurtherInput = true; @@ -2035,7 +2041,7 @@ void M_QuitSRB2(INT32 choice) // We pick index 0 which is language sensitive, or one at random, // between 1 and maximum number. (void)choice; - M_StartMessage("Are you sure you want to quit playing?\n\n(Press A to exit)", FUNCPTRCAST(M_QuitResponse), MM_YESNO); + M_StartMessage("Are you sure you want to quit playing?\n\nPress (A) to confirm or (B) to cancel", FUNCPTRCAST(M_QuitResponse), MM_YESNO); } // ========= @@ -2063,6 +2069,12 @@ static void M_SetupProfileGridPos(setup_player_t *p) // While we're here, read follower values. p->followern = K_FollowerAvailable(pr->follower); + + if (p->followern < 0 || p->followern >= numfollowers || followers[p->followern].category >= numfollowercategories) + p->followercategory = -1; + else + p->followercategory = followers[p->followern].category; + p->followercolor = pr->followercolor; // Now position the grid for skin @@ -2150,6 +2162,7 @@ void M_CharacterSelectInit(void) { // Default to no follower / match colour. setup_player[i].followern = -1; + setup_player[i].followercategory = -1; setup_player[i].followercolor = FOLLOWERCOLOR_MATCH; // Set default selected profile to the last used profile for each player: @@ -2486,6 +2499,16 @@ static boolean M_HandleCSelectProfile(setup_player_t *p, UINT8 num) S_StartSound(NULL, sfx_s3k63); } + else if (M_MenuExtraPressed(num)) + { + UINT8 yourprofile = min(cv_lastprofile[realnum].value, PR_GetNumProfiles()); + if (p->profilen == yourprofile) + p->profilen = PROFILE_GUEST; + else + p->profilen = yourprofile; + S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s + M_SetMenuDelay(num); + } return false; @@ -2578,6 +2601,15 @@ static boolean M_HandleCharacterGrid(setup_player_t *p, UINT8 num) S_StartSound(NULL, sfx_s3k5b); M_SetMenuDelay(num); } + else if (M_MenuExtraPressed(num)) + { + p->gridx /= 3; + p->gridx = (3*p->gridx) + 1; + p->gridy /= 3; + p->gridy = (3*p->gridy) + 1; + S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s + M_SetMenuDelay(num); + } // try to set the clone num to the page # if possible. p->clonenum = setup_page; @@ -2692,6 +2724,14 @@ static void M_HandleCharRotate(setup_player_t *p, UINT8 num) S_StartSound(NULL, sfx_s3k5b); M_SetMenuDelay(num); } + else if (M_MenuExtraPressed(num)) + { + p->clonenum = 0; + p->rotate = CSROTATETICS; + p->hitlag = true; + S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s + M_SetMenuDelay(num); + } } static void M_HandleColorRotate(setup_player_t *p, UINT8 num) @@ -2716,8 +2756,7 @@ static void M_HandleColorRotate(setup_player_t *p, UINT8 num) if (M_MenuConfirmPressed(num) /*|| M_MenuButtonPressed(num, MBT_START)*/) { - p->mdepth = CSSTEP_FOLLOWER; - M_GetFollowerState(p); + p->mdepth = CSSTEP_FOLLOWERCATEGORY; S_StartSound(NULL, sfx_s3k63); M_SetMenuDelay(num); } @@ -2734,6 +2773,17 @@ static void M_HandleColorRotate(setup_player_t *p, UINT8 num) S_StartSound(NULL, sfx_s3k5b); M_SetMenuDelay(num); } + else if (M_MenuExtraPressed(num)) + { + if (p->skin >= 0) + { + p->color = skins[p->skin].prefcolor; + p->rotate = CSROTATETICS; + p->hitlag = true; + S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s + M_SetMenuDelay(num); + } + } } static void M_AnimateFollower(setup_player_t *p) @@ -2764,16 +2814,103 @@ static void M_AnimateFollower(setup_player_t *p) p->follower_timer++; } -static void M_HandleFollowerRotate(setup_player_t *p, UINT8 num) +static void M_HandleFollowerCategoryRotate(setup_player_t *p, UINT8 num) { if (cv_splitdevice.value) num = 0; if (menucmd[num].dpad_lr > 0) { - p->followern++; - if (p->followern >= numfollowers) + p->followercategory++; + if (p->followercategory >= numfollowercategories) + p->followercategory = -1; + + p->rotate = CSROTATETICS; + p->delay = CSROTATETICS; + S_StartSound(NULL, sfx_s3kc3s); + } + else if (menucmd[num].dpad_lr < 0) + { + p->followercategory--; + if (p->followercategory < -1) + p->followercategory = numfollowercategories-1; + + p->rotate = -CSROTATETICS; + p->delay = CSROTATETICS; + S_StartSound(NULL, sfx_s3kc3s); + } + + if (M_MenuConfirmPressed(num) /*|| M_MenuButtonPressed(num, MBT_START)*/) + { + if (p->followercategory < 0) + { p->followern = -1; + p->mdepth = CSSTEP_READY; + p->delay = TICRATE; + M_SetupReadyExplosions(p); + S_StartSound(NULL, sfx_s3k4e); + } + else + { + if (p->followern < 0 || followers[p->followern].category != p->followercategory) + { + p->followern = 0; + while (p->followern < numfollowers && followers[p->followern].category != p->followercategory) + p->followern++; + } + + if (p->followern >= numfollowers) + { + p->followern = -1; + S_StartSound(NULL, sfx_s3kb2); + } + else + { + M_GetFollowerState(p); + p->mdepth = CSSTEP_FOLLOWER; + S_StartSound(NULL, sfx_s3k63); + } + } + + M_SetMenuDelay(num); + } + else if (M_MenuBackPressed(num)) + { + p->mdepth = CSSTEP_COLORS; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(num); + } + else if (M_MenuExtraPressed(num)) + { + if (p->followercategory >= 0 || p->followern < 0 || p->followern >= numfollowers || followers[p->followern].category >= numfollowercategories) + p->followercategory = -1; + else + p->followercategory = followers[p->followern].category; + p->rotate = CSROTATETICS; + p->hitlag = true; + S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s + M_SetMenuDelay(num); + } +} + +static void M_HandleFollowerRotate(setup_player_t *p, UINT8 num) +{ + INT16 startfollowern = p->followern; + + if (cv_splitdevice.value) + num = 0; + + if (menucmd[num].dpad_lr > 0) + { + do + { + p->followern++; + if (p->followern >= numfollowers) + p->followern = 0; + if (p->followern == startfollowern) + break; + } + while (followers[p->followern].category != p->followercategory); M_GetFollowerState(p); @@ -2783,9 +2920,17 @@ static void M_HandleFollowerRotate(setup_player_t *p, UINT8 num) } else if (menucmd[num].dpad_lr < 0) { - p->followern--; - if (p->followern < -1) - p->followern = numfollowers-1; + do + { + p->followern--; + if (p->followern < 0) + p->followern = numfollowers-1; + if (p->followern == startfollowern) + break; + } + while (followers[p->followern].category != p->followercategory); + + M_GetFollowerState(p); p->rotate = -CSROTATETICS; p->delay = CSROTATETICS; @@ -2811,10 +2956,19 @@ static void M_HandleFollowerRotate(setup_player_t *p, UINT8 num) } else if (M_MenuBackPressed(num)) { - p->mdepth = CSSTEP_COLORS; + p->mdepth = CSSTEP_FOLLOWERCATEGORY; S_StartSound(NULL, sfx_s3k5b); M_SetMenuDelay(num); } + else if (M_MenuExtraPressed(num)) + { + p->mdepth = CSSTEP_FOLLOWERCATEGORY; + p->followercategory = -1; + p->rotate = CSROTATETICS; + p->hitlag = true; + S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s + M_SetMenuDelay(num); + } } static void M_HandleFollowerColorRotate(setup_player_t *p, UINT8 num) @@ -2849,10 +3003,24 @@ static void M_HandleFollowerColorRotate(setup_player_t *p, UINT8 num) } else if (M_MenuBackPressed(num)) { + M_GetFollowerState(p); p->mdepth = CSSTEP_FOLLOWER; S_StartSound(NULL, sfx_s3k5b); M_SetMenuDelay(num); } + else if (M_MenuExtraPressed(num)) + { + if (p->followercolor == FOLLOWERCOLOR_MATCH) + p->followercolor = FOLLOWERCOLOR_OPPOSITE; + else if (p->followercolor == followers[p->followern].defaultcolor) + p->followercolor = FOLLOWERCOLOR_MATCH; + else + p->followercolor = followers[p->followern].defaultcolor; + p->rotate = CSROTATETICS; + p->hitlag = true; + S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s + M_SetMenuDelay(num); + } } boolean M_CharacterSelectHandler(INT32 choice) @@ -2901,6 +3069,9 @@ boolean M_CharacterSelectHandler(INT32 choice) case CSSTEP_COLORS: // Select color M_HandleColorRotate(p, i); break; + case CSSTEP_FOLLOWERCATEGORY: + M_HandleFollowerCategoryRotate(p, i); + break; case CSSTEP_FOLLOWER: M_HandleFollowerRotate(p, i); break; @@ -2956,33 +3127,27 @@ static void M_MPConfirmCharacterSelection(void) UINT8 i; INT16 col; - char commandnames[][MAXSTRINGLENGTH] = { "skin ", "skin2 ", "skin3 ", "skin4 "}; - // ^ laziness 100 (we append a space directly so that we don't have to do it later too!!!!) - for (i = 0; i < splitscreen +1; i++) { - char cmd[MAXSTRINGLENGTH]; - // colour // (convert the number that's saved to a string we can use) col = setup_player[i].color; CV_StealthSetValue(&cv_playercolor[i], col); // follower - CV_StealthSetValue(&cv_follower[i], setup_player[i].followern); - - // follower color - CV_StealthSetValue(&cv_followercolor[i], setup_player[i].followercolor); + if (setup_player[i].followern < 0) + CV_StealthSet(&cv_follower[i], "None"); + else + CV_StealthSet(&cv_follower[i], followers[setup_player[i].followern].name); // finally, call the skin[x] console command. // This will call SendNameAndColor which will synch everything we sent here and apply the changes! - // This is a hack to make sure we call Skin[x]_OnChange afterwards - CV_StealthSetValue(&cv_skin[i], -1); + CV_StealthSet(&cv_skin[i], skins[setup_player[i].skin].name); - strcpy(cmd, commandnames[i]); - strcat(cmd, skins[setup_player[i].skin].name); - COM_ImmedExecute(cmd); + // ...actually, let's do this last - Skin_OnChange has some return-early occasions + // follower color + CV_SetValue(&cv_followercolor[i], setup_player[i].followercolor); } M_ClearMenus(true); @@ -3004,6 +3169,8 @@ void M_CharacterSelectTick(void) setup_player[i].rotate--; else if (setup_player[i].rotate < 0) setup_player[i].rotate++; + else + setup_player[i].hitlag = false; if (i >= setup_numplayers) continue; @@ -3034,7 +3201,7 @@ void M_CharacterSelectTick(void) optionsmenu.profile->color = setup_player[0].color; // save follower - strcpy(optionsmenu.profile->follower, followers[setup_player[0].followern].skinname); + strcpy(optionsmenu.profile->follower, followers[setup_player[0].followern].name); optionsmenu.profile->followercolor = setup_player[0].followercolor; // reset setup_player @@ -3051,7 +3218,10 @@ void M_CharacterSelectTick(void) CV_StealthSet(&cv_skin[i], skins[setup_player[i].skin].name); CV_StealthSetValue(&cv_playercolor[i], setup_player[i].color); - CV_StealthSetValue(&cv_follower[i], setup_player[i].followern); + if (setup_player[i].followern < 0) + CV_StealthSet(&cv_follower[i], "None"); + else + CV_StealthSet(&cv_follower[i], followers[setup_player[i].followern].name); CV_StealthSetValue(&cv_followercolor[i], setup_player[i].followercolor); } @@ -3269,7 +3439,15 @@ static void M_LevelListFromGametype(INT16 gt) while (cup) { if (cup->unlockrequired == -1 || unlockables[cup->unlockrequired].unlocked) + { highestid = cup->id; + if (Playing() && mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == cup) + { + cupgrid.x = cup->id % CUPMENU_COLUMNS; + cupgrid.y = (cup->id / CUPMENU_COLUMNS) % CUPMENU_ROWS; + cupgrid.pageno = cup->id / (CUPMENU_COLUMNS * CUPMENU_ROWS); + } + } cup = cup->next; } @@ -4775,7 +4953,7 @@ void M_HandleProfileSelect(INT32 ch) #if 0 if (optionsmenu.profilen == 0) { - M_StartMessage(M_GetText("Are you sure you wish\nto use the Guest Profile?\nThis profile cannot be customised.\nIt is recommended to create\na new Profile instead.\n\n(Press A to confirm)"), FUNCPTRCAST(M_FirstPickProfile), MM_YESNO); + M_StartMessage(M_GetText("Are you sure you wish\nto use the Guest Profile?\nThis profile cannot be customised.\nIt is recommended to create\na new Profile instead.\n\nPress (A) to confirm or (B) to cancel"), FUNCPTRCAST(M_FirstPickProfile), MM_YESNO); return; } #endif @@ -4917,7 +5095,7 @@ void M_ConfirmProfile(INT32 choice) } else { - M_StartMessage(M_GetText("Are you sure you wish to\nselect this profile?\n\n(Press A to confirm)"), FUNCPTRCAST(M_FirstPickProfile), MM_YESNO); + M_StartMessage(M_GetText("Are you sure you wish to\nselect this profile?\n\nPress (A) to confirm or (B) to cancel"), FUNCPTRCAST(M_FirstPickProfile), MM_YESNO); M_SetMenuDelay(pid); } } @@ -5123,7 +5301,7 @@ void M_ProfileControlsConfirm(INT32 choice) { (void)choice; - //M_StartMessage(M_GetText("Exiting will save the control changes\nfor this Profile.\nIs this okay?\n\n(Press A to confirm)"), FUNCPTRCAST(M_ProfileControlSaveResponse), MM_YESNO); + //M_StartMessage(M_GetText("Exiting will save the control changes\nfor this Profile.\nIs this okay?\n\nPress (A) to confirm or (B) to cancel"), FUNCPTRCAST(M_ProfileControlSaveResponse), MM_YESNO); // TODO: Add a graphic for controls saving, instead of obnoxious prompt. M_ProfileControlSaveResponse(MA_YES); @@ -5500,7 +5678,7 @@ void M_CheckProfileData(INT32 choice) if (np < 2) { S_StartSound(NULL, sfx_s3k7b); - M_StartMessage("There are no custom profiles.\n\n(Press any button)", NULL, MM_NOTHING); + M_StartMessage("There are no custom profiles.\n\nPress (B)", NULL, MM_NOTHING); return; } @@ -5564,9 +5742,9 @@ void M_HandleProfileErase(INT32 choice) else if (M_MenuConfirmPressed(pid)) { if (optionsmenu.eraseprofilen == cv_currprofile.value) - M_StartMessage("Your ""\x85""current profile""\x80"" will be erased.\nAre you sure you want to proceed?\nDeleting this profile will also\nreturn you to the title screen.\n\n(Press A to confirm)", FUNCPTRCAST(M_EraseProfileResponse), MM_YESNO); + M_StartMessage("Your ""\x85""current profile""\x80"" will be erased.\nAre you sure you want to proceed?\nDeleting this profile will also\nreturn you to the title screen.\n\nPress (A) to confirm or (B) to cancel", FUNCPTRCAST(M_EraseProfileResponse), MM_YESNO); else - M_StartMessage("This profile will be erased.\nAre you sure you want to proceed?\n\n(Press A to confirm)", FUNCPTRCAST(M_EraseProfileResponse), MM_YESNO); + M_StartMessage("This profile will be erased.\nAre you sure you want to proceed?\n\nPress (A) to confirm or (B) to cancel", FUNCPTRCAST(M_EraseProfileResponse), MM_YESNO); M_SetMenuDelay(pid); } @@ -5700,6 +5878,8 @@ struct pausemenu_s pausemenu; // Pause menu! void M_OpenPauseMenu(void) { + INT32 i = 0; + currentMenu = &PAUSE_MainDef; // Ready the variables @@ -5716,6 +5896,8 @@ void M_OpenPauseMenu(void) PAUSE_Main[mpause_addons].status = IT_DISABLED; PAUSE_Main[mpause_switchmap].status = IT_DISABLED; + PAUSE_Main[mpause_restartmap].status = IT_DISABLED; + PAUSE_Main[mpause_tryagain].status = IT_DISABLED; #ifdef HAVE_DISCORDRPC PAUSE_Main[mpause_discordrequests].status = IT_DISABLED; #endif @@ -5735,9 +5917,36 @@ void M_OpenPauseMenu(void) if (server || IsPlayerAdmin(consoleplayer)) { PAUSE_Main[mpause_switchmap].status = IT_STRING | IT_SUBMENU; + for (i = 0; i < PAUSE_GamemodesDef.numitems; i++) + { + if (PAUSE_GamemodesMenu[i].mvar2 != gametype) + continue; + PAUSE_GamemodesDef.lastOn = i; + break; + } + PAUSE_Main[mpause_restartmap].status = IT_STRING | IT_CALL; PAUSE_Main[mpause_addons].status = IT_STRING | IT_CALL; } } + else if (!netgame && !demo.playback) + { + boolean retryallowed = (modeattacking != ATTACKING_NONE); + if (G_GametypeUsesLives()) + { + for (i = 0; i <= splitscreen; i++) + { + if (players[g_localplayers[i]].lives <= 1) + continue; + retryallowed = true; + break; + } + } + + if (retryallowed) + { + PAUSE_Main[mpause_tryagain].status = IT_STRING | IT_CALL; + } + } if (G_GametypeHasSpectators()) { @@ -5766,6 +5975,7 @@ void M_QuitPauseMenu(INT32 choice) void M_PauseTick(void) { pausemenu.offset /= 2; + pausemenu.ticker++; if (pausemenu.closing) { @@ -5814,6 +6024,37 @@ boolean M_PauseInputs(INT32 ch) return false; } +// Restart map +void M_RestartMap(INT32 choice) +{ + (void)choice; + M_ClearMenus(false); + COM_ImmedExecute("restartlevel"); +} + +// Try again +void M_TryAgain(INT32 choice) +{ + (void)choice; + if (demo.playback) + return; + + if (netgame || !Playing()) // Should never happen! + return; + + M_ClearMenus(false); + + if (modeattacking != ATTACKING_NONE) + { + G_CheckDemoStatus(); // Cancel recording + M_StartTimeAttack(-1); + } + else + { + G_SetRetryFlag(); + } +} + // Pause spectate / join functions void M_ConfirmSpectate(INT32 choice) { @@ -5828,7 +6069,7 @@ void M_ConfirmEnterGame(INT32 choice) (void)choice; if (!cv_allowteamchange.value) { - M_StartMessage(M_GetText("The server is not allowing\nteam changes at this time.\nPress a key.\n"), NULL, MM_NOTHING); + M_StartMessage(M_GetText("The server is not allowing\nteam changes at this time.\n\nPress (B)\n"), NULL, MM_NOTHING); return; } M_QuitPauseMenu(-1); @@ -5860,7 +6101,7 @@ void M_EndGame(INT32 choice) if (!Playing()) return; - M_StartMessage(M_GetText("Are you sure you want to return\nto the title screen?\n(Press A to confirm)\n"), FUNCPTRCAST(M_ExitGameResponse), MM_YESNO); + M_StartMessage(M_GetText("Are you sure you want to return\nto the title screen?\nPress (A) to confirm or (B) to cancel\n"), FUNCPTRCAST(M_ExitGameResponse), MM_YESNO); } @@ -6042,7 +6283,7 @@ void M_ReplayHut(INT32 choice) } if (!preparefilemenu(false, true)) { - M_StartMessage("No replays found.\n\n(Press a key)\n", NULL, MM_NOTHING); + M_StartMessage("No replays found.\n\nPress (B)\n", NULL, MM_NOTHING); return; } else if (!demo.inreplayhut) @@ -6118,7 +6359,7 @@ void M_HandleReplayHutList(INT32 choice) if (!preparefilemenu(false, true)) { S_StartSound(NULL, sfx_s224); - M_StartMessage(va("%c%s\x80\nThis folder is empty.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); + M_StartMessage(va("%c%s\x80\nThis folder is empty.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); menupath[menupathindex[++menudepthleft]] = 0; if (!preparefilemenu(true, true)) @@ -6137,7 +6378,7 @@ void M_HandleReplayHutList(INT32 choice) else { S_StartSound(NULL, sfx_s26d); - M_StartMessage(va("%c%s\x80\nThis folder is too deep to navigate to!\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); + M_StartMessage(va("%c%s\x80\nThis folder is too deep to navigate to!\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); menupath[menupathindex[menudepthleft]] = 0; } break; @@ -6264,7 +6505,7 @@ void M_Addons(INT32 choice) if (!preparefilemenu(false, false)) { - M_StartMessage(va("No files/folders found.\n\n%s\n\n(Press a key)\n", LOCATIONSTRING1),NULL,MM_NOTHING); + M_StartMessage(va("No files/folders found.\n\n%s\n\nPress (B)\n", LOCATIONSTRING1),NULL,MM_NOTHING); return; } else @@ -6297,7 +6538,7 @@ char *M_AddonsHeaderPath(void) #define UNEXIST S_StartSound(NULL, sfx_s26d);\ M_SetupNextMenu(MISC_AddonsDef.prevMenu, false);\ - M_StartMessage(va("\x82%s\x80\nThis folder no longer exists!\nAborting to main menu.\n\n(Press a key)\n", M_AddonsHeaderPath()),NULL,MM_NOTHING) + M_StartMessage(va("\x82%s\x80\nThis folder no longer exists!\nAborting to main menu.\n\nPress (B)\n", M_AddonsHeaderPath()),NULL,MM_NOTHING) #define CLEARNAME Z_Free(refreshdirname);\ refreshdirname = NULL @@ -6341,19 +6582,19 @@ void M_AddonsRefresh(void) { S_StartSound(NULL, sfx_s26d); if (refreshdirmenu & REFRESHDIR_MAX) - message = va("%c%s\x80\nMaximum number of addons reached.\nA file could not be loaded.\nIf you wish to play with this addon, restart the game to clear existing ones.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); + message = va("%c%s\x80\nMaximum number of addons reached.\nA file could not be loaded.\nIf you wish to play with this addon, restart the game to clear existing ones.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); else - message = va("%c%s\x80\nA file was not loaded.\nCheck the console log for more info.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); + message = va("%c%s\x80\nA file was not loaded.\nCheck the console log for more info.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); } else if (refreshdirmenu & (REFRESHDIR_WARNING|REFRESHDIR_ERROR)) { S_StartSound(NULL, sfx_s224); - message = va("%c%s\x80\nA file was loaded with %s.\nCheck the console log for more info.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname, ((refreshdirmenu & REFRESHDIR_ERROR) ? "errors" : "warnings")); + message = va("%c%s\x80\nA file was loaded with %s.\nCheck the console log for more info.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname, ((refreshdirmenu & REFRESHDIR_ERROR) ? "errors" : "warnings")); } else if (majormods && !prevmajormods) { S_StartSound(NULL, sfx_s221); - message = va("%c%s\x80\nYou've loaded a gameplay-modifying addon.\n\nRecord Attack has been disabled, but you\ncan still play alone in local Multiplayer.\n\nIf you wish to play Record Attack mode, restart the game to disable loaded addons.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); + message = va("%c%s\x80\nYou've loaded a gameplay-modifying addon.\n\nRecord Attack has been disabled, but you\ncan still play alone in local Multiplayer.\n\nIf you wish to play Record Attack mode, restart the game to disable loaded addons.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); prevmajormods = majormods; } @@ -6472,7 +6713,7 @@ void M_HandleAddons(INT32 choice) if (!preparefilemenu(false, false)) { S_StartSound(NULL, sfx_s224); - M_StartMessage(va("%c%s\x80\nThis folder is empty.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); + M_StartMessage(va("%c%s\x80\nThis folder is empty.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); menupath[menupathindex[++menudepthleft]] = 0; if (!preparefilemenu(true, false)) @@ -6491,7 +6732,7 @@ void M_HandleAddons(INT32 choice) else { S_StartSound(NULL, sfx_s26d); - M_StartMessage(va("%c%s\x80\nThis folder is too deep to navigate to!\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); + M_StartMessage(va("%c%s\x80\nThis folder is too deep to navigate to!\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); menupath[menupathindex[menudepthleft]] = 0; } break; @@ -6507,11 +6748,11 @@ void M_HandleAddons(INT32 choice) break; case EXT_TXT: - M_StartMessage(va("%c%s\x80\nThis file may not be a console script.\nAttempt to run anyways? \n\n(Press A to confirm)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), dirmenu[dir_on[menudepthleft]]+DIR_STRING),FUNCPTRCAST(M_AddonExec),MM_YESNO); + M_StartMessage(va("%c%s\x80\nThis file may not be a console script.\nAttempt to run anyways? \n\nPress (A) to confirm or (B) to cancel\n\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), dirmenu[dir_on[menudepthleft]]+DIR_STRING),FUNCPTRCAST(M_AddonExec),MM_YESNO); break; case EXT_CFG: - M_StartMessage(va("%c%s\x80\nThis file may modify your settings.\nAttempt to run anyways? \n\n(Press A to confirm)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), dirmenu[dir_on[menudepthleft]]+DIR_STRING),FUNCPTRCAST(M_AddonExec),MM_YESNO); + M_StartMessage(va("%c%s\x80\nThis file may modify your settings.\nAttempt to run anyways? \n\nPress (A) to confirm or (B) to cancel\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), dirmenu[dir_on[menudepthleft]]+DIR_STRING),FUNCPTRCAST(M_AddonExec),MM_YESNO); break; case EXT_LUA: diff --git a/src/lua_baselib.c b/src/lua_baselib.c index d73e7b073..fcdf86624 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -402,10 +402,7 @@ static int lib_pRandomByte(lua_State *L) static int lib_pRandomKey(lua_State *L) { INT32 a = (INT32)luaL_checkinteger(L, 1); - NOHUD - if (a > 65536) - LUA_UsageWarning(L, "P_RandomKey: range > 65536 is undefined behavior"); lua_pushinteger(L, P_RandomKey(PR_UNDEFINED, a)); demo_writerng = 2; return 1; @@ -415,15 +412,13 @@ static int lib_pRandomRange(lua_State *L) { INT32 a = (INT32)luaL_checkinteger(L, 1); INT32 b = (INT32)luaL_checkinteger(L, 2); - NOHUD - if (b < a) { + if (b < a) + { INT32 c = a; a = b; b = c; } - if ((b-a+1) > 65536) - LUA_UsageWarning(L, "P_RandomRange: range > 65536 is undefined behavior"); lua_pushinteger(L, P_RandomRange(PR_UNDEFINED, a, b)); demo_writerng = 2; return 1; diff --git a/src/m_cheat.c b/src/m_cheat.c index 9614c9b60..be4f231e1 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -762,6 +762,13 @@ void Command_Setlives_f(void) D_Cheat(consoleplayer, CHEAT_LIVES, atoi(COM_Argv(1))); } +void Command_Grayscale_f(void) +{ + REQUIRE_CHEATS; + + COM_ImmedExecute("toggle palette \"\" GRAYPAL"); +} + // // OBJECTPLACE (and related variables) // diff --git a/src/m_cheat.h b/src/m_cheat.h index 4d97cb8d9..c952721c3 100644 --- a/src/m_cheat.h +++ b/src/m_cheat.h @@ -75,6 +75,7 @@ void Command_Teleport_f(void); void Command_RTeleport_f(void); void Command_Skynum_f(void); void Command_Weather_f(void); +void Command_Grayscale_f(void); #ifdef _DEBUG void Command_CauseCfail_f(void); #endif diff --git a/src/m_random.c b/src/m_random.c index 166c6bf2e..6d8beeaa3 100644 --- a/src/m_random.c +++ b/src/m_random.c @@ -25,20 +25,56 @@ // RNG functions (not synched) // --------------------------- +ATTRINLINE static UINT32 FUNCINLINE __external_prng__(void) +{ + UINT32 rnd = rand(); + +#if RAND_MAX < 65535 + // Compensate for especially bad randomness. + UINT32 rndv = (rand() & 1) << 15; + rnd ^= rndv; +#endif + + // Shuffle like we do for our own PRNG, since RAND_MAX + // tends to be [0, INT32_MAX] instead of [0, UINT32_MAX]. + rnd ^= rnd >> 13; + rnd ^= rnd >> 11; + rnd ^= rnd << 21; + return (rnd * 36548569); +} + +ATTRINLINE static UINT32 FUNCINLINE __external_prng_bound__(UINT32 bound) +{ + // Do rejection sampling to remove the modulo bias. + UINT32 threshold = -bound % bound; + for (;;) + { + UINT32 r = __external_prng__(); + if (r >= threshold) + { + return r % bound; + } + } +} + +/** Provides a random 32-bit number. Distribution is uniform. + * As with all M_Random functions, not synched in netgames. + * + * \return A random 32-bit number. + */ +UINT32 M_Random(void) +{ + return __external_prng__(); +} + /** Provides a random fixed point number. Distribution is uniform. * As with all M_Random functions, not synched in netgames. * - * \return A random fixed point number from [0,1). + * \return A random fixed point number from [0,1]. */ fixed_t M_RandomFixed(void) { -#if RAND_MAX < 65535 - // Compensate for insufficient randomness. - fixed_t rndv = (rand()&1)<<15; - return rand()^rndv; -#else - return (rand() & 0xFFFF); -#endif + return (fixed_t)(__external_prng_bound__(FRACUNIT)); } /** Provides a random byte. Distribution is uniform. @@ -48,7 +84,7 @@ fixed_t M_RandomFixed(void) */ UINT8 M_RandomByte(void) { - return (rand() & 0xFF); + return (UINT8)(__external_prng_bound__(UINT8_MAX)); } /** Provides a random integer for picking random elements from an array. @@ -58,9 +94,9 @@ UINT8 M_RandomByte(void) * \param a Number of items in array. * \return A random integer from [0,a). */ -INT32 M_RandomKey(INT32 a) +UINT32 M_RandomKey(UINT32 a) { - return (INT32)((rand()/((unsigned)RAND_MAX+1.0f))*a); + return __external_prng_bound__(a); } /** Provides a random integer in a given range. @@ -73,7 +109,7 @@ INT32 M_RandomKey(INT32 a) */ INT32 M_RandomRange(INT32 a, INT32 b) { - return (INT32)((rand()/((unsigned)RAND_MAX+1.0f))*(b-a+1))+a; + return (INT32)(__external_prng_bound__((b - a) + 1)) + a; } @@ -92,23 +128,56 @@ typedef struct static rng_t rng; // The entire PRNG state -/** Provides a random fixed point number. +/** Provides a random 32 bit integer. * This is a variant of an xorshift PRNG; state fits in a 32 bit integer structure. * - * \return A random fixed point number from [0,1). + * \return A random, uniformly distributed number from [0,UINT32_MAX]. */ -ATTRINLINE static fixed_t FUNCINLINE __internal_prng__(pr_class_t pr_class) +ATTRINLINE static UINT32 FUNCINLINE __internal_prng__(pr_class_t pr_class) { rng.seed[pr_class] ^= rng.seed[pr_class] >> 13; rng.seed[pr_class] ^= rng.seed[pr_class] >> 11; rng.seed[pr_class] ^= rng.seed[pr_class] << 21; - return ( (rng.seed[pr_class] * 36548569) >> 4) & (FRACUNIT-1); + return (rng.seed[pr_class] * 36548569); +} + +/** Provides a random number within a specified range. + * + * \return A random, uniformly distributed number from [0,bound]. + */ +ATTRINLINE static UINT32 FUNCINLINE __internal_prng_bound__(pr_class_t pr_class, UINT32 bound) +{ + // Do rejection sampling to remove the modulo bias. + UINT32 threshold = -bound % bound; + for (;;) + { + UINT32 r = __internal_prng__(pr_class); + if (r >= threshold) + { + return r % bound; + } + } } /** Provides a random fixed point number. Distribution is uniform. * Literally a wrapper for the internal PRNG function. * - * \return A random fixed point number from [0,1). + * \return A random fixed point number from [0,UINT32_MAX]. + */ +#ifndef DEBUGRANDOM +UINT32 P_Random(pr_class_t pr_class) +{ +#else +UINT32 P_RandomD(const char *rfile, INT32 rline, pr_class_t pr_class) +{ + CONS_Printf("P_Random(%u) at: %sp %d\n", pr_class, rfile, rline); +#endif + return __internal_prng__(pr_class); +} + +/** Provides a random fixed point number. Distribution is uniform. + * + * \return A random fixed point number from [0,1]. */ #ifndef DEBUGRANDOM fixed_t P_RandomFixed(pr_class_t pr_class) @@ -118,14 +187,14 @@ fixed_t P_RandomFixedD(const char *rfile, INT32 rline, pr_class_t pr_class) { CONS_Printf("P_RandomFixed(%u) at: %sp %d\n", pr_class, rfile, rline); #endif - return __internal_prng__(pr_class); + return (fixed_t)(__internal_prng_bound__(pr_class, FRACUNIT)); } /** Provides a random byte. Distribution is uniform. * If you're curious, (&0xFF00) >> 8 gives the same result * as a fixed point multiplication by 256. * - * \return Random integer from [0, 255]. + * \return Random integer from [0,255]. * \sa __internal_prng__ */ #ifndef DEBUGRANDOM @@ -136,7 +205,7 @@ UINT8 P_RandomByteD(const char *rfile, INT32 rline, pr_class_t pr_class) { CONS_Printf("P_RandomByte(%u) at: %sp %d\n", pr_class, rfile, rline); #endif - return (UINT8)((__internal_prng__(pr_class) & 0xFF00) >> 8); + return (UINT8)(__internal_prng_bound__(pr_class, UINT8_MAX)); } /** Provides a random integer for picking random elements from an array. @@ -144,23 +213,22 @@ UINT8 P_RandomByteD(const char *rfile, INT32 rline, pr_class_t pr_class) * NOTE: Maximum range is 65536. * * \param a Number of items in array. - * \return A random integer from [0,a). + * \return A random integer from [0,a]. * \sa __internal_prng__ */ #ifndef DEBUGRANDOM -INT32 P_RandomKey(pr_class_t pr_class, INT32 a) +UINT32 P_RandomKey(pr_class_t pr_class, UINT32 a) { #else -INT32 P_RandomKeyD(const char *rfile, INT32 rline, pr_class_t pr_class, INT32 a) +UINT32 P_RandomKeyD(const char *rfile, INT32 rline, pr_class_t pr_class, UINT32 a) { CONS_Printf("P_RandomKey(%u) at: %sp %d\n", pr_class, rfile, rline); #endif - return (INT32)(((INT64)__internal_prng__(pr_class) * a) >> FRACBITS); + return __internal_prng_bound__(pr_class, a); } /** Provides a random integer in a given range. * Distribution is uniform. - * NOTE: Maximum range is 65536. * * \param a Lower bound. * \param b Upper bound. @@ -175,7 +243,7 @@ INT32 P_RandomRangeD(const char *rfile, INT32 rline, pr_class_t pr_class, INT32 { CONS_Printf("P_RandomRange(%u) at: %sp %d\n", pr_class, rfile, rline); #endif - return (INT32)(((INT64)__internal_prng__(pr_class) * (b - a + 1)) >> FRACBITS) + a; + return (INT32)(__internal_prng_bound__(pr_class, (b - a) + 1)) + a; } @@ -187,13 +255,13 @@ INT32 P_RandomRangeD(const char *rfile, INT32 rline, pr_class_t pr_class, INT32 /** Peeks to see what the next result from the PRNG will be. * Used for debugging. * - * \return A 'random' fixed point number from [0,1). + * \return A 'random' number from [0,UINT32_MAX] * \sa __internal_prng__ */ -fixed_t P_RandomPeek(pr_class_t pr_class) +UINT32 P_RandomPeek(pr_class_t pr_class) { UINT32 r = rng.seed[pr_class]; - fixed_t ret = __internal_prng__(pr_class); + UINT32 ret = __internal_prng__(pr_class); rng.seed[pr_class] = r; return ret; } diff --git a/src/m_random.h b/src/m_random.h index 34e4c34c8..ec1f643e1 100644 --- a/src/m_random.h +++ b/src/m_random.h @@ -70,37 +70,41 @@ typedef enum // P_Random functions pulls random bytes from a PRNG that is network synced. // RNG functions +UINT32 M_Random(void); fixed_t M_RandomFixed(void); UINT8 M_RandomByte(void); -INT32 M_RandomKey(INT32 a); +UINT32 M_RandomKey(UINT32 a); INT32 M_RandomRange(INT32 a, INT32 b); // PRNG functions #ifdef DEBUGRANDOM +#define P_Random(c) P_RandomD(__FILE__, __LINE__, c) #define P_RandomFixed(c) P_RandomFixedD(__FILE__, __LINE__, c) #define P_RandomByte(c) P_RandomByteD(__FILE__, __LINE__, c) #define P_RandomKey(c, d) P_RandomKeyD(__FILE__, __LINE__, c, d) #define P_RandomRange(c, d, e) P_RandomRangeD(__FILE__, __LINE__, c, d, e) +UINT32 P_RandomD(const char *rfile, INT32 rline, pr_class_t pr_class); fixed_t P_RandomFixedD(const char *rfile, INT32 rline, pr_class_t pr_class); UINT8 P_RandomByteD(const char *rfile, INT32 rline, pr_class_t pr_class); -INT32 P_RandomKeyD(const char *rfile, INT32 rline, pr_class_t pr_class, INT32 a); +UINT32 P_RandomKeyD(const char *rfile, INT32 rline, pr_class_t pr_class, UINT32 a); INT32 P_RandomRangeD(const char *rfile, INT32 rline, pr_class_t pr_class, INT32 a, INT32 b); #else +UINT32 P_Random(pr_class_t pr_class); fixed_t P_RandomFixed(pr_class_t pr_class); UINT8 P_RandomByte(pr_class_t pr_class); -INT32 P_RandomKey(pr_class_t pr_class, INT32 a); +UINT32 P_RandomKey(pr_class_t pr_class, UINT32 a); INT32 P_RandomRange(pr_class_t pr_class, INT32 a, INT32 b); #endif // Macros for other functions -#define M_SignedRandom() ((INT32)M_RandomByte() - 128) // [-128, 127] signed byte, originally a -#define P_SignedRandom(pr) ((INT32)P_RandomByte(pr) - 128) // function of its own, moved to a macro +#define M_SignedRandom() ((INT32)M_RandomByte() + INT8_MIN) // [-128, 127] signed byte, originally a +#define P_SignedRandom(pr) ((INT32)P_RandomByte(pr) + INT8_MIN) // function of its own, moved to a macro #define M_RandomChance(p) (M_RandomFixed() < p) // given fixed point probability, p, between 0 (0%) #define P_RandomChance(pr, p) (P_RandomFixed(pr) < p) // and FRACUNIT (100%), returns true p% of the time // Debugging -fixed_t P_RandomPeek(pr_class_t pr_class); +UINT32 P_RandomPeek(pr_class_t pr_class); // Working with the seed for PRNG #ifdef DEBUGRANDOM diff --git a/src/objects/orbinaut.c b/src/objects/orbinaut.c index 4d5738e00..ee6e72ed5 100644 --- a/src/objects/orbinaut.c +++ b/src/objects/orbinaut.c @@ -217,7 +217,7 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) S_StartSound(t2, t2->info->deathsound); P_KillMobj(t2, t1, t1, DMG_NORMAL); - P_SetObjectMomZ(t2, 8*FRACUNIT, false); + P_SetObjectMomZ(t2, 24*FRACUNIT, false); P_InstaThrust(t2, bounceangle, 16*FRACUNIT); P_SpawnMobj(t2->x/2 + t1->x/2, t2->y/2 + t1->y/2, t2->z/2 + t1->z/2, MT_ITEMCLASH); @@ -254,7 +254,7 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) S_StartSound(t1, t1->info->deathsound); P_KillMobj(t1, t2, t2, DMG_NORMAL); - P_SetObjectMomZ(t1, 8*FRACUNIT, false); + P_SetObjectMomZ(t1, 24*FRACUNIT, false); P_InstaThrust(t1, bounceangle, 16*FRACUNIT); } diff --git a/src/p_inter.c b/src/p_inter.c index da54636fb..e77a5d8ff 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1585,6 +1585,32 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget target->fuse = 1; break; + case MT_BANANA: + case MT_BANANA_SHIELD: + { + const UINT8 numParticles = 8; + const angle_t diff = ANGLE_MAX / numParticles; + UINT8 i; + + for (i = 0; i < numParticles; i++) + { + mobj_t *spark = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_BANANA_SPARK); + spark->angle = (diff * i) - (diff / 2); + + if (inflictor != NULL && P_MobjWasRemoved(inflictor) == false) + { + spark->angle += K_MomentumAngle(inflictor); + spark->momx += inflictor->momx / 2; + spark->momy += inflictor->momy / 2; + spark->momz += inflictor->momz / 2; + } + + P_SetObjectMomZ(spark, (12 + P_RandomRange(PR_DECORATION, -4, 4)) * FRACUNIT, true); + P_Thrust(spark, spark->angle, (12 + P_RandomRange(PR_DECORATION, -4, 4)) * spark->scale); + } + break; + } + default: break; } @@ -1997,9 +2023,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da // Check if the player is allowed to be damaged! // If not, then spawn the instashield effect instead. - // NB: "allowcombo", "hardhit" and related checks are here to disallow HITLAG COMBOS, not loss-of-control combos - // DMG_EXPLODE bypasses this check to prevent blocking eggbox/SPB with spinout flashtics - if (!force && (type != DMG_EXPLODE)) + if (!force) { if (gametyperules & GTR_BUMPERS) { @@ -2039,7 +2063,8 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da allowcombo = false; } - if ((target->hitlag == 0 || allowcombo == false) && player->flashing > 0) + // DMG_EXPLODE excluded from flashtic checks to prevent dodging eggbox/SPB with weak spinout + if ((target->hitlag == 0 || allowcombo == false) && player->flashing > 0 && type != DMG_EXPLODE) { // Post-hit invincibility K_DoInstashield(player); @@ -2257,6 +2282,9 @@ static void P_FlingBurst mo->fuse = objFuse; P_SetTarget(&mo->target, player->mo); + // We want everything from P_SpawnMobjFromMobj except scale. + objScale = FixedMul(objScale, FixedDiv(mapobjectscale, player->mo->scale)); + if (objScale != FRACUNIT) { P_SetScale(mo, FixedMul(objScale, mo->scale)); diff --git a/src/p_map.c b/src/p_map.c index e235cb9a4..677954b43 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -2618,7 +2618,7 @@ increment_move if (!(thing->flags & MF_NOCLIP)) { //All things are affected by their scale. - fixed_t maxstep = P_GetThingStepUp(thing, x, y); + fixed_t maxstep = P_GetThingStepUp(thing, tryx, tryy); if (tmceilingz - tmfloorz < thing->height) { diff --git a/src/p_mobj.c b/src/p_mobj.c index 05fd8669d..e3bf0054d 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1158,66 +1158,68 @@ fixed_t P_GetMobjGravity(mobj_t *mo) gravityadd = -gravityadd; } } - else //Otherwise, sort through the other exceptions. + + // Sort through the other exceptions. + switch (mo->type) { - switch (mo->type) - { - case MT_FLINGRING: - case MT_FLINGCOIN: - case MT_FLINGBLUESPHERE: - case MT_FLINGNIGHTSCHIP: - case MT_BOUNCERING: - case MT_RAILRING: - case MT_INFINITYRING: - case MT_AUTOMATICRING: - case MT_EXPLOSIONRING: - case MT_SCATTERRING: - case MT_GRENADERING: - case MT_BOUNCEPICKUP: - case MT_RAILPICKUP: - case MT_AUTOPICKUP: - case MT_EXPLODEPICKUP: - case MT_SCATTERPICKUP: - case MT_GRENADEPICKUP: - case MT_REDFLAG: - case MT_BLUEFLAG: - if (mo->target) + case MT_FLINGRING: + case MT_FLINGCOIN: + case MT_FLINGBLUESPHERE: + case MT_FLINGNIGHTSCHIP: + case MT_BOUNCERING: + case MT_RAILRING: + case MT_INFINITYRING: + case MT_AUTOMATICRING: + case MT_EXPLOSIONRING: + case MT_SCATTERRING: + case MT_GRENADERING: + case MT_BOUNCEPICKUP: + case MT_RAILPICKUP: + case MT_AUTOPICKUP: + case MT_EXPLODEPICKUP: + case MT_SCATTERPICKUP: + case MT_GRENADEPICKUP: + case MT_REDFLAG: + case MT_BLUEFLAG: + if (mo->target) + { + // Flung items copy the gravity of their tosser. + if ((mo->target->eflags & MFE_VERTICALFLIP) && !(mo->eflags & MFE_VERTICALFLIP)) { - // Flung items copy the gravity of their tosser. - if ((mo->target->eflags & MFE_VERTICALFLIP) && !(mo->eflags & MFE_VERTICALFLIP)) - { - gravityadd = -gravityadd; - mo->eflags |= MFE_VERTICALFLIP; - } + gravityadd = -gravityadd; + mo->eflags |= MFE_VERTICALFLIP; } - break; - case MT_WATERDROP: - case MT_BATTLEBUMPER: - gravityadd /= 2; - break; - case MT_BANANA: - case MT_EGGMANITEM: - case MT_SSMINE: - case MT_LANDMINE: - case MT_DROPTARGET: - case MT_SINK: - case MT_EMERALD: + } + break; + case MT_WATERDROP: + case MT_BATTLEBUMPER: + gravityadd /= 2; + break; + case MT_BANANA: + case MT_EGGMANITEM: + case MT_SSMINE: + case MT_LANDMINE: + case MT_DROPTARGET: + case MT_SINK: + case MT_EMERALD: + if (mo->health > 0) + { if (mo->extravalue2 > 0) { gravityadd *= mo->extravalue2; } gravityadd = (5*gravityadd)/2; - break; - case MT_KARMAFIREWORK: - gravityadd /= 3; - break; - case MT_ITEM_DEBRIS: - gravityadd *= 6; - break; - default: - break; - } + } + break; + case MT_KARMAFIREWORK: + gravityadd /= 3; + break; + case MT_ITEM_DEBRIS: + gravityadd *= 6; + break; + default: + break; } } @@ -1261,6 +1263,11 @@ void P_CheckGravity(mobj_t *mo, boolean affect) // void P_SetPitchRollFromSlope(mobj_t *mo, pslope_t *slope) { + if (!(mo->flags & MF_SLOPE)) + { + return; + } + if (slope) { fixed_t tempz = slope->normal.z; @@ -1764,7 +1771,7 @@ void P_XYMovement(mobj_t *mo) S_StartSound(mo, mo->info->deathsound); P_KillMobj(mo, NULL, NULL, DMG_NORMAL); - P_SetObjectMomZ(mo, 8*FRACUNIT, false); + P_SetObjectMomZ(mo, 24*FRACUNIT, false); P_InstaThrust(mo, R_PointToAngle2(mo->x, mo->y, mo->x + xmove, mo->y + ymove)+ANGLE_90, 16*FRACUNIT); } break; @@ -3119,16 +3126,14 @@ boolean P_CanRunOnWater(mobj_t *mobj, ffloor_t *rover) fixed_t surfaceheight = INT32_MAX; fixed_t surfDiff = INT32_MAX; - fixed_t floorheight = INT32_MAX; - fixed_t floorDiff = INT32_MAX; - fixed_t mobjbottom = INT32_MAX; fixed_t maxStep = INT32_MAX; boolean doifit = false; pslope_t *waterSlope = NULL; - angle_t ourZAng = 0; - angle_t waterZAng = 0; + angle_t moveDir = 0; + fixed_t ourZAng = 0; + fixed_t waterZAng = 0; if (rover == NULL) { @@ -3162,20 +3167,36 @@ boolean P_CanRunOnWater(mobj_t *mobj, ffloor_t *rover) return false; } - if (mobj->standingslope != NULL) + moveDir = K_MomentumAngle(mobj); + + if (mobj->standingslope != NULL && mobj->standingslope->zangle != 0) { - ourZAng = mobj->standingslope->zangle; + angle_t dir = mobj->standingslope->xydirection; + angle_t workang = mobj->standingslope->zangle; + if (workang >= ANGLE_180) + { + workang = InvAngle(workang); + dir = InvAngle(dir); + } + ourZAng = P_ReturnThrustX(mobj, dir - moveDir, AngleFixed(workang)); } waterSlope = (flip ? *rover->b_slope : *rover->t_slope); - if (waterSlope != NULL) + if (waterSlope != NULL && waterSlope->zangle != 0) { - waterZAng = waterSlope->zangle; + angle_t dir = waterSlope->xydirection; + angle_t workang = waterSlope->zangle; + if (workang >= ANGLE_180) + { + workang = InvAngle(workang); + dir = InvAngle(dir); + } + waterZAng = P_ReturnThrustX(mobj, dir - moveDir, AngleFixed(workang)); } - if (ourZAng != waterZAng) + if (abs(ourZAng - waterZAng) > 11*FRACUNIT) { - // The surface slopes are different. + // The surface slopes are too different. return false; } @@ -3193,16 +3214,20 @@ boolean P_CanRunOnWater(mobj_t *mobj, ffloor_t *rover) maxStep = P_GetThingStepUp(mobj, mobj->x, mobj->y); surfDiff = flip ? (surfaceheight - mobjbottom) : (mobjbottom - surfaceheight); + + // We start water run IF we can step onto it! if (surfDiff <= maxStep && surfDiff >= 0) { - // We start water run IF we can step-down! - floorheight = flip ? P_GetSectorCeilingZAt(mobj->subsector->sector, mobj->x, mobj->y) : P_GetSectorFloorZAt(mobj->subsector->sector, mobj->x, mobj->y); - floorDiff = flip ? (floorheight - mobjbottom) : (mobjbottom - floorheight); - if (floorDiff <= maxStep && floorDiff >= 0) + if (ourZAng < 0) { - // ... but NOT if real floor is in range. - // FIXME: Count solid FOFs in this check - return false; + fixed_t floorheight = flip ? P_GetSectorCeilingZAt(mobj->subsector->sector, mobj->x, mobj->y) : P_GetSectorFloorZAt(mobj->subsector->sector, mobj->x, mobj->y); + fixed_t floorDiff = flip ? (floorheight - mobjbottom) : (mobjbottom - floorheight); + if (floorDiff <= maxStep && floorDiff >= -maxStep) + { + // ... but NOT if going down and real floor is in range. + // FIXME: Count solid FOFs in this check + return false; + } } return true; @@ -5257,27 +5282,8 @@ void P_RunOverlays(void) continue; } - if (!r_splitscreen /*&& rendermode != render_soft*/) - { - angle_t viewingangle; - - if (players[displayplayers[0]].awayviewtics) - viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, players[displayplayers[0]].awayviewmobj->x, players[displayplayers[0]].awayviewmobj->y); - else if (!camera[0].chase && players[displayplayers[0]].mo) - viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, players[displayplayers[0]].mo->x, players[displayplayers[0]].mo->y); - else - viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, camera[0].x, camera[0].y); - - if (!(mo->state->frame & FF_ANIMATE) && mo->state->var1) - viewingangle += ANGLE_180; - destx = mo->target->x + P_ReturnThrustX(mo->target, viewingangle, FixedMul(FRACUNIT/4, mo->scale)); - desty = mo->target->y + P_ReturnThrustY(mo->target, viewingangle, FixedMul(FRACUNIT/4, mo->scale)); - } - else - { - destx = mo->target->x; - desty = mo->target->y; - } + destx = mo->target->x; + desty = mo->target->y; mo->eflags = (mo->eflags & ~MFE_VERTICALFLIP) | (mo->target->eflags & MFE_VERTICALFLIP); mo->scale = mo->destscale = FixedMul(mo->target->scale, mo->movefactor); @@ -5289,12 +5295,27 @@ void P_RunOverlays(void) if ((mo->flags & MF_DONTENCOREMAP) != (mo->target->flags & MF_DONTENCOREMAP)) mo->flags ^= MF_DONTENCOREMAP; + mo->dispoffset = mo->target->dispoffset + mo->info->dispoffset; + if (!(mo->state->frame & FF_ANIMATE)) + { zoffs = FixedMul(((signed)mo->state->var2)*FRACUNIT, mo->scale); - // if you're using FF_ANIMATE on an overlay, - // then you're on your own. + + if (mo->state->var1) + { + mo->dispoffset--; + } + else + { + mo->dispoffset++; + } + } else + { + // if you're using FF_ANIMATE on an overlay, + // then you're on your own. zoffs = 0; + } P_UnsetThingPosition(mo); mo->x = destx; @@ -6490,8 +6511,26 @@ static boolean P_MobjDeadThink(mobj_t *mobj) P_SetObjectMomZ(mobj, -2*FRACUNIT/3, true); } break; - case MT_ORBINAUT: case MT_BANANA: + { + angle_t spin = FixedMul(FixedDiv(abs(mobj->momz), 8 * mobj->scale), ANGLE_67h); + mobj->angle -= spin; + mobj->rollangle += spin; + + if (P_IsObjectOnGround(mobj) && mobj->momz * P_MobjFlip(mobj) <= 0) + { + P_RemoveMobj(mobj); + return false; + } + } + break; + case MT_BANANA_SPARK: + { + angle_t spin = FixedMul(FixedDiv(abs(mobj->momz), 8 * mobj->scale), ANGLE_22h); + mobj->rollangle += spin; + } + break; + case MT_ORBINAUT: case MT_EGGMANITEM: case MT_LANDMINE: //case MT_DROPTARGET: @@ -6786,6 +6825,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) break; case MT_FLOATINGITEM: { + mobj->pitch = mobj->roll = 0; if (mobj->flags & MF_NOCLIPTHING) { if (P_CheckDeathPitCollide(mobj)) @@ -6930,16 +6970,61 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } } - if (P_IsObjectOnGround(mobj) && mobj->health > 1) + if (P_IsObjectOnGround(mobj)) { - S_StartSound(mobj, mobj->info->activesound); - mobj->momx = mobj->momy = 0; - mobj->health = 1; + //mobj->rollangle = 0; + + if (mobj->health > 1) + { + S_StartSound(mobj, mobj->info->activesound); + mobj->momx = mobj->momy = 0; + mobj->health = 1; + } + } + else + { + // tilt n tumble + angle_t spin = FixedMul(FixedDiv(abs(mobj->momz), 8 * mobj->scale), ANGLE_67h); + mobj->angle += spin; + mobj->rollangle -= spin; } if (mobj->threshold > 0) mobj->threshold--; break; + case MT_BANANA_SPARK: + { + if (leveltime & 1) + { + mobj->spritexscale = mobj->spriteyscale = FRACUNIT; + } + else + { + if ((leveltime / 2) & 1) + { + mobj->spriteyscale = 3*FRACUNIT/2; + } + else + { + mobj->spritexscale = 3*FRACUNIT/2; + } + } + + if (P_IsObjectOnGround(mobj) == true && mobj->momz * P_MobjFlip(mobj) <= 0) + { + P_SetObjectMomZ(mobj, 8*FRACUNIT, false); + + if (mobj->health > 0) + { + mobj->tics = 1; + mobj->destscale = 0; + mobj->spritexscale = mobj->spriteyscale = FRACUNIT; + mobj->health = 0; + } + } + + break; + } case MT_SPB: { Obj_SPBThink(mobj); @@ -9579,7 +9664,7 @@ void P_MobjThinker(mobj_t *mobj) S_StartSound(mobj, mobj->info->deathsound); P_KillMobj(mobj, NULL, NULL, DMG_NORMAL); - P_SetObjectMomZ(mobj, 8*FRACUNIT, false); + P_SetObjectMomZ(mobj, 24*FRACUNIT, false); P_InstaThrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy) + ANGLE_90, 16*FRACUNIT); } @@ -9806,24 +9891,6 @@ void P_PushableThinker(mobj_t *mobj) // Quick, optimized function for scenery void P_SceneryThinker(mobj_t *mobj) { - if (mobj->flags & MF_BOXICON) - { - if (!(mobj->eflags & MFE_VERTICALFLIP)) - { - if (mobj->z < mobj->floorz + FixedMul(mobj->info->damage, mobj->scale)) - mobj->momz = FixedMul(mobj->info->speed, mobj->scale); - else - mobj->momz = 0; - } - else - { - if (mobj->z + FixedMul(mobj->info->height, mobj->scale) > mobj->ceilingz - FixedMul(mobj->info->damage, mobj->scale)) - mobj->momz = -FixedMul(mobj->info->speed, mobj->scale); - else - mobj->momz = 0; - } - } - // momentum movement if (mobj->momx || mobj->momy) { @@ -10872,21 +10939,41 @@ void P_RemovePrecipMobj(precipmobj_t *mobj) void P_RemoveSavegameMobj(mobj_t *mobj) { // unlink from sector and block lists - P_UnsetThingPosition(mobj); - - // Remove touching_sectorlist from mobj. - if (sector_list) + if (((thinker_t *)mobj)->function.acp1 == (actionf_p1)P_NullPrecipThinker) { - P_DelSeclist(sector_list); - sector_list = NULL; + P_UnsetPrecipThingPosition((precipmobj_t *)mobj); + + if (precipsector_list) + { + P_DelPrecipSeclist(precipsector_list); + precipsector_list = NULL; + } + } + else + { + // unlink from sector and block lists + P_UnsetThingPosition(mobj); + + // Remove touching_sectorlist from mobj. + if (sector_list) + { + P_DelSeclist(sector_list); + sector_list = NULL; + } } // stop any playing sound S_StopSound(mobj); + R_RemoveMobjInterpolator(mobj); // free block - P_RemoveThinker((thinker_t *)mobj); - R_RemoveMobjInterpolator(mobj); + // Here we use the same code as R_RemoveThinkerDelayed, but without reference counting (we're removing everything so it shouldn't matter) and without touching currentthinker since we aren't in P_RunThinkers + { + thinker_t *thinker = (thinker_t *)mobj; + thinker_t *next = thinker->next; + (next->prev = thinker->prev)->next = next; + Z_Free(thinker); + } } static CV_PossibleValue_t respawnitemtime_cons_t[] = {{1, "MIN"}, {300, "MAX"}, {0, NULL}}; @@ -13767,6 +13854,8 @@ mobj_t *P_SpawnMobjFromMobj(mobj_t *mobj, fixed_t xofs, fixed_t yofs, fixed_t zo if (!newmobj) return NULL; + newmobj->hitlag = mobj->hitlag; + newmobj->destscale = P_ScaleFromMap(mobj->destscale, newmobj->destscale); P_SetScale(newmobj, P_ScaleFromMap(mobj->scale, newmobj->scale)); diff --git a/src/p_mobj.h b/src/p_mobj.h index 923fc7f80..b725f2bf0 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -126,8 +126,8 @@ typedef enum MF_NOCLIP = 1<<12, // Allow moves to any height, no gravity. For active floaters. MF_FLOAT = 1<<13, - // Monitor powerup icon. These rise a bit. - MF_BOXICON = 1<<14, + // Change pitch/roll when touching slopes. + MF_SLOPE = 1<<14, // Don't hit same species, explode on block. // Player missiles as well as fireballs of various kinds. MF_MISSILE = 1<<15, @@ -163,7 +163,7 @@ typedef enum MF_NOSQUISH = 1<<30, // Disable hitlag for this object MF_NOHITLAGFORME = (INT32)(1U<<31), - // no more free slots, next up I suppose we can get rid of shit like MF_BOXICON? + // no more free slots, gotta get rid of more crusty base SRB2 flags } mobjflag_t; typedef enum diff --git a/src/p_saveg.c b/src/p_saveg.c index b75dfe591..1d58992f2 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -3345,6 +3345,19 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker) mobj->player->viewz = mobj->player->mo->z + mobj->player->viewheight; } + if (mobj->type == MT_SKYBOX) + { + mtag_t tag = mobj->movedir; + if (tag < 0 || tag > 15) + { + CONS_Debug(DBG_GAMELOGIC, "LoadMobjThinker: Skybox ID %d of netloaded object is not between 0 and 15!\n", tag); + } + else if (mobj->flags2 & MF2_AMBUSH) + skyboxcenterpnts[tag] = mobj; + else + skyboxviewpnts[tag] = mobj; + } + if (diff2 & MD2_WAYPOINTCAP) P_SetTarget(&waypointcap, mobj); @@ -3972,10 +3985,14 @@ static void P_NetUnArchiveThinkers(void) { next = currentthinker->next; - if (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker) + if (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker || currentthinker->function.acp1 == (actionf_p1)P_NullPrecipThinker) P_RemoveSavegameMobj((mobj_t *)currentthinker); // item isn't saved, don't remove it else + { + (next->prev = currentthinker->prev)->next = next; + R_DestroyLevelInterpolators(currentthinker); Z_Free(currentthinker); + } } } diff --git a/src/p_setup.c b/src/p_setup.c index 3de23a0d1..efadfedd9 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -170,6 +170,13 @@ mapthing_t *playerstarts[MAXPLAYERS]; mapthing_t *bluectfstarts[MAXPLAYERS]; mapthing_t *redctfstarts[MAXPLAYERS]; +// Global state for PartialAddWadFile/MultiSetupWadFiles +// Might be replacable with parameters, but non-trivial when the functions are called on separate tics +static SINT8 partadd_stage = -1; +static boolean partadd_important = false; +UINT16 partadd_earliestfile = UINT16_MAX; + + // Maintain *ZOOM TUBE* waypoints // Renamed because SRB2Kart owns real waypoints. mobj_t *tubewaypoints[NUMTUBEWAYPOINTSEQUENCES][TUBEWAYPOINTSEQUENCESIZE]; @@ -2624,6 +2631,7 @@ static void P_ProcessLinedefsAfterSidedefs(void) { size_t i = numlines; register line_t *ld = lines; + const boolean subtractTripwire = ((mapheaderinfo[gamemap - 1]->levelflags & LF_SUBTRACTNUM) == LF_SUBTRACTNUM); for (; i--; ld++) { @@ -2638,7 +2646,7 @@ static void P_ProcessLinedefsAfterSidedefs(void) if (ld->tripwire) { - ld->blendmode = AST_ADD; + ld->blendmode = (subtractTripwire ? AST_SUBTRACT : AST_ADD); ld->alpha = 0xff; } @@ -7350,7 +7358,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) // Reset the palette now all fades have been done if (rendermode != render_none) - V_SetPaletteLump(GetPalette()); // Set the level palette + V_ReloadPalette(); // Set the level palette if (!(reloadinggamestate || titlemapinaction)) { @@ -7745,7 +7753,7 @@ lumpnum_t wadnamelump = LUMPERROR; INT16 wadnamemap = 0; // gamemap based // Initialising map data (and catching replacements)... -UINT8 P_InitMapData(INT32 numexistingmapheaders) +UINT8 P_InitMapData(boolean existingmapheaders) { UINT8 ret = 0; INT32 i; @@ -7759,20 +7767,9 @@ UINT8 P_InitMapData(INT32 numexistingmapheaders) name = mapheaderinfo[i]->lumpname; maplump = W_CheckNumForMap(name); - // Doesn't exist? - if (maplump == INT16_MAX) - { -#ifndef DEVELOP - if (!numexistingmapheaders) - { - I_Error("P_InitMapData: Base map %s has a header but no level\n", name); - } -#endif - continue; - } - // Always check for cup cache reassociations. // (The core assumption is that cups < headers.) + if (maplump != LUMPERROR || mapheaderinfo[i]->lumpnum != LUMPERROR) { cupheader_t *cup = kartcupheaders; INT32 j; @@ -7807,6 +7804,18 @@ UINT8 P_InitMapData(INT32 numexistingmapheaders) } } + // Doesn't exist in this set of files? + if (maplump == LUMPERROR) + { +#ifndef DEVELOP + if (!existingmapheaders) + { + I_Error("P_InitMapData: Base map %s has a header but no level\n", name); + } +#endif + continue; + } + // No change? if (mapheaderinfo[i]->lumpnum == maplump) continue; @@ -7816,7 +7825,7 @@ UINT8 P_InitMapData(INT32 numexistingmapheaders) ret |= MAPRET_ADDED; CONS_Printf("%s\n", name); - if (numexistingmapheaders && mapheaderinfo[i]->lumpnum != LUMPERROR) + if (existingmapheaders && mapheaderinfo[i]->lumpnum != LUMPERROR) { G_SetGameModified(multiplayer, true); // oops, double-defined - no record attack privileges for you @@ -7866,23 +7875,32 @@ UINT8 P_InitMapData(INT32 numexistingmapheaders) return ret; } -UINT16 p_adding_file = INT16_MAX; - // // Add a wadfile to the active wad files, // replace sounds, musics, patches, textures, sprites and maps // boolean P_AddWadFile(const char *wadfilename) +{ + UINT16 wadnum; + + if ((wadnum = P_PartialAddWadFile(wadfilename)) == UINT16_MAX) + return false; + + P_MultiSetupWadFiles(true); + return true; +} + +// +// Add a WAD file and do the per-WAD setup stages. +// Call P_MultiSetupWadFiles as soon as possible after any number of these. +// +UINT16 P_PartialAddWadFile(const char *wadfilename) { size_t i, j, sreplaces = 0, mreplaces = 0, digmreplaces = 0; - INT32 numexistingmapheaders = nummapheaders; UINT16 numlumps, wadnum; char *name; lumpinfo_t *lumpinfo; - //boolean texturechange = false; ///\todo Useless; broken when back-frontporting PK3 changes? - UINT8 mapsadded = 0; - // Vars to help us with the position start and amount of each resource type. // Useful for PK3s since they use folders. // WADs use markers for some resources, but others such as sounds are checked lump-by-lump anyway. @@ -7902,10 +7920,19 @@ boolean P_AddWadFile(const char *wadfilename) refreshdirmenu |= REFRESHDIR_NOTLOADED; return false; } - else - wadnum = (UINT16)(numwadfiles-1); - p_adding_file = wadnum; + wadnum = (UINT16)(numwadfiles-1); + + // Init partadd. + if (wadfiles[wadnum]->important) + { + partadd_important = true; + } + if (partadd_stage != 0) + { + partadd_earliestfile = wadnum; + } + partadd_stage = 0; switch(wadfiles[wadnum]->type) { @@ -7939,8 +7966,6 @@ boolean P_AddWadFile(const char *wadfilename) // R_LoadSpritsRange(wadnum, sprPos, sprNum); // if (texNum) // Textures. TODO: R_LoadTextures() does the folder positioning once again. New function maybe? // R_LoadTextures(); -// if (mapNum) // Maps. TODO: Actually implement the map WAD loading code, lulz. -// P_LoadWadMapRange(wadnum, mapPos, mapNum); break; default: lumpinfo = wadfiles[wadnum]->lumpinfo; @@ -8005,23 +8030,14 @@ boolean P_AddWadFile(const char *wadfilename) // TEXTURES/etc. list. R_LoadTexturesPwad(wadnum); // numtexture changes - // Reload ANIMDEFS - P_InitPicAnims(); - // Reload BRIGHT K_InitBrightmapsPwad(wadnum); - // Flush and reload HUD graphics - //ST_UnloadGraphics(); - HU_LoadGraphics(); - ST_LoadGraphics(); - // // look for skins // R_AddSkins(wadnum); // faB: wadfile index in wadfiles[] R_PatchSkins(wadnum); // toast: PATCH PATCH - ST_ReloadSkinFaceGraphics(); // // edit music defs @@ -8029,37 +8045,99 @@ boolean P_AddWadFile(const char *wadfilename) S_LoadMusicDefs(wadnum); // - // search for maps + // extra sprite/skin data // - mapsadded = P_InitMapData(numexistingmapheaders); - - if (!mapsadded) - CONS_Printf(M_GetText("No maps added\n")); - R_LoadSpriteInfoLumps(wadnum, numlumps); -#ifdef HWRENDER - HWR_ReloadModels(); -#endif + // For anything that has to be done over every wadfile at once, see P_MultiSetupWadFiles. - // reload status bar (warning should have valid player!) - if (gamestate == GS_LEVEL) - ST_Start(); - - // Prevent savefile cheating - if (cursaveslot > 0) - cursaveslot = 0; - - if ((mapsadded & MAPRET_CURRENTREPLACED) && gamestate == GS_LEVEL && (netgame || multiplayer)) - { - CONS_Printf(M_GetText("Current map %d replaced by added file, ending the level to ensure consistency.\n"), gamemap); - if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0); - } - - refreshdirmenu &= ~REFRESHDIR_GAMEDATA; // Under usual circumstances we'd wait for REFRESHDIR_GAMEDATA to disappear the next frame, but it's a bit too dangerous for that... - - p_adding_file = INT16_MAX; + refreshdirmenu &= ~REFRESHDIR_GAMEDATA; // Under usual circumstances we'd wait for REFRESHDIR_ flags to disappear the next frame, but this one's a bit too dangerous for that... return true; } + +// Only exists to make sure there's no way to overwrite partadd_stage externally +// unless you really push yourself. +SINT8 P_PartialAddGetStage(void) +{ + return partadd_stage; +} + +// +// Set up a series of partially added WAD files. +// Setup functions that iterate over every loaded WAD go here. +// If fullsetup false, only do one stage per call. +// +boolean P_MultiSetupWadFiles(boolean fullsetup) +{ + if (partadd_stage < 0) + I_Error(M_GetText("P_MultiSetupWadFiles: Post-load addon setup attempted without loading any addons first")); + + if (partadd_stage == 0) + { + // Flush and reload HUD graphics + //ST_UnloadGraphics(); + HU_LoadGraphics(); + ST_LoadGraphics(); + ST_ReloadSkinFaceGraphics(); + + if (!partadd_important) + partadd_stage = -1; // everything done + else if (fullsetup) + partadd_stage++; + } + + if (partadd_stage == 1) + { + // Prevent savefile cheating + if (cursaveslot >= 0) + cursaveslot = 0; + + // Reload ANIMATED / ANIMDEFS + P_InitPicAnims(); + + // reload status bar (warning should have valid player!) + if (gamestate == GS_LEVEL) + ST_Start(); + +#ifdef HWRENDER + HWR_ReloadModels(); +#endif + + if (fullsetup) + partadd_stage++; + } + + if (partadd_stage == 2) + { + UINT8 mapsadded = P_InitMapData(true); + + if (!mapsadded) + CONS_Printf(M_GetText("No maps added\n")); + + if ((mapsadded & MAPRET_CURRENTREPLACED) + && (gamestate == GS_LEVEL) + && (netgame || multiplayer)) + { + CONS_Printf(M_GetText("Current map %d replaced by added file, ending the level to ensure consistency.\n"), gamemap); + if (server) + SendNetXCmd(XD_EXITLEVEL, NULL, 0); + } + + //if (fullsetup) + //partadd_stage++; + partadd_stage = -1; + } + + I_Assert(!fullsetup || partadd_stage < 0); + + if (partadd_stage < 0) + { + partadd_important = false; + partadd_earliestfile = UINT16_MAX; + return true; + } + + partadd_stage++; + return false; +} diff --git a/src/p_setup.h b/src/p_setup.h index d5d3aa7d0..04a3233e0 100644 --- a/src/p_setup.h +++ b/src/p_setup.h @@ -96,8 +96,6 @@ INT32 P_CheckLevelFlat(const char *flatname); extern size_t nummapthings; extern mapthing_t *mapthings; -extern UINT16 p_adding_file; - void P_SetupLevelSky(const char *skytexname, boolean global); void P_RespawnThings(void); boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate); @@ -108,11 +106,29 @@ boolean P_AddWadFile(const char *wadfilename); #define MAPRET_ADDED (1) #define MAPRET_CURRENTREPLACED (1<<1) -UINT8 P_InitMapData(INT32 numexistingmapheaders); +UINT8 P_InitMapData(boolean existingmapheaders); extern lumpnum_t wadnamelump; extern INT16 wadnamemap; #define WADNAMECHECK(name) (!strncmp(name, "WADNAME", 7)) +// WARNING: The following functions should be grouped as follows: +// any amount of PartialAdds followed by MultiSetups until returned true, +// as soon as possible. +UINT16 P_PartialAddWadFile(const char *wadfilename); +// Run a single stage of multisetup, or all of them if fullsetup set. +// fullsetup true: run everything +// otherwise multiple stages +// returns true if setup finished on this call, false otherwise (always true on fullsetup) +// throws I_Error if called without any partial adds started as a safeguard +boolean P_MultiSetupWadFiles(boolean fullsetup); +// Get the current setup stage. +// if negative, no PartialAdds done since last MultiSetup +// if 0, partial adds done but MultiSetup not called yet +// if positive, setup's partway done +SINT8 P_PartialAddGetStage(void); +extern UINT16 partadd_earliestfile; + + boolean P_RunSOC(const char *socfilename); void P_LoadSoundsRange(UINT16 wadnum, UINT16 first, UINT16 num); void P_LoadMusicsRange(UINT16 wadnum, UINT16 first, UINT16 num); diff --git a/src/p_spec.c b/src/p_spec.c index ca2f32009..7a1e4072a 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -148,7 +148,7 @@ static void GrowAnimDefs(void) // A prototype; here instead of p_spec.h, so they're "private" void P_ParseANIMDEFSLump(INT32 wadNum, UINT16 lumpnum); -void P_ParseAnimationDefintion(SINT8 istexture); +void P_ParseAnimationDefintion(void); /** Sets up texture and flat animations. * @@ -161,8 +161,6 @@ void P_ParseAnimationDefintion(SINT8 istexture); * \author Steven McGranahan (original), Shadow Hog (had to rewrite it to handle multiple WADs), JTE (had to rewrite it to handle multiple WADs _correctly_) */ -static boolean animdeftempflats = false; // only until ANIMDEFS flats are removed - void P_InitPicAnims(void) { // Init animation @@ -182,7 +180,6 @@ void P_InitPicAnims(void) while (animdefsLumpNum != INT16_MAX) { - animdeftempflats = ((p_adding_file == INT16_MAX) || p_adding_file == w); P_ParseANIMDEFSLump(w, animdefsLumpNum); animdefsLumpNum = W_CheckNumForNamePwad("ANIMDEFS", (UINT16)w, animdefsLumpNum + 1); } @@ -204,31 +201,14 @@ void P_InitPicAnims(void) lastanim = anims; for (i = 0; animdefs[i].istexture != -1; i++) { - if (animdefs[i].istexture == 1) - { - if (R_CheckTextureNumForName(animdefs[i].startname) == -1) - continue; - - lastanim->picnum = R_TextureNumForName(animdefs[i].endname); - lastanim->basepic = R_TextureNumForName(animdefs[i].startname); - } - else - { - if (animdefs[i].istexture == 2) - { - CONS_Alert(CONS_WARNING, "ANIMDEFS flats are disabled; flat support in general will be removed soon! (%s, %s)\n", animdefs[i].startname, animdefs[i].endname); - } + if (animdefs[i].istexture != 1) continue; - } -#if 0 - { - if ((W_CheckNumForName(animdefs[i].startname)) == LUMPERROR) - continue; - lastanim->picnum = R_GetFlatNumForName(animdefs[i].endname); - lastanim->basepic = R_GetFlatNumForName(animdefs[i].startname); - } -#endif + if (R_CheckTextureNumForName(animdefs[i].startname) == -1) + continue; + + lastanim->picnum = R_TextureNumForName(animdefs[i].endname); + lastanim->basepic = R_TextureNumForName(animdefs[i].startname); lastanim->istexture = animdefs[i].istexture; lastanim->numpics = lastanim->picnum - lastanim->basepic + 1; @@ -285,21 +265,20 @@ void P_ParseANIMDEFSLump(INT32 wadNum, UINT16 lumpnum) if (stricmp(animdefsToken, "TEXTURE") == 0) { Z_Free(animdefsToken); - P_ParseAnimationDefintion(1); + P_ParseAnimationDefintion(); } else if (stricmp(animdefsToken, "FLAT") == 0) { - Z_Free(animdefsToken); - P_ParseAnimationDefintion(0); + I_Error("Error parsing ANIMDEFS lump: FLats are no longer supported by Ring Racers"); } else if (stricmp(animdefsToken, "OSCILLATE") == 0) { // This probably came off the tail of an earlier definition. It's technically legal syntax, but we don't support it. - I_Error("Error parsing ANIMDEFS lump: Animation definitions utilizing \"OSCILLATE\" (the animation plays in reverse when it reaches the end) are not supported by SRB2"); + I_Error("Error parsing ANIMDEFS lump: Animation definitions utilizing \"OSCILLATE\" (the animation plays in reverse when it reaches the end) are not supported by Ring Racers"); } else { - I_Error("Error parsing ANIMDEFS lump: Expected \"TEXTURE\" or \"FLAT\", got \"%s\"",animdefsToken); + I_Error("Error parsing ANIMDEFS lump: Expected \"TEXTURE\", got \"%s\"",animdefsToken); } // parse next line while (*p != '\0' && *p != '\n') ++p; @@ -310,7 +289,7 @@ void P_ParseANIMDEFSLump(INT32 wadNum, UINT16 lumpnum) Z_Free((void *)animdefsText); } -void P_ParseAnimationDefintion(SINT8 istexture) +void P_ParseAnimationDefintion(void) { char *animdefsToken; size_t animdefsTokenLength; @@ -353,8 +332,7 @@ void P_ParseAnimationDefintion(SINT8 istexture) // Search for existing animdef for (i = 0; i < maxanims; i++) - if (animdefs[i].istexture == istexture // Check if it's the same type! - && stricmp(animdefsToken, animdefs[i].startname) == 0) + if (stricmp(animdefsToken, animdefs[i].startname) == 0) { //CONS_Alert(CONS_NOTICE, "Duplicate animation: %s\n", animdefsToken); @@ -376,10 +354,7 @@ void P_ParseAnimationDefintion(SINT8 istexture) Z_Free(animdefsToken); // set texture type - if (istexture) - animdefs[i].istexture = 1; - else - animdefs[i].istexture = (animdeftempflats ? 2 : 0); + animdefs[i].istexture = 1; // "RANGE" animdefsToken = M_GetToken(NULL); @@ -457,16 +432,6 @@ void P_ParseAnimationDefintion(SINT8 istexture) } animdefs[i].speed = animSpeed; Z_Free(animdefsToken); - -#ifdef WALLFLATS - // hehe... uhh..... - if (!istexture) - { - GrowAnimDefs(); - M_Memcpy(&animdefs[maxanims-1], &animdefs[i], sizeof(animdef_t)); - animdefs[maxanims-1].istexture = 1; - } -#endif } /** Checks for flats in levelflats that are part of a flat animation sequence @@ -2391,6 +2356,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) camera[i].y += y; camera[i].z += z; camera[i].subsector = R_PointInSubsector(camera[i].x, camera[i].y); + R_RelativeTeleportViewInterpolation(i, x, y, z, 0); break; } } diff --git a/src/p_telept.c b/src/p_telept.c index c3e36f3c7..9d3f792a3 100644 --- a/src/p_telept.c +++ b/src/p_telept.c @@ -17,6 +17,7 @@ #include "r_state.h" #include "s_sound.h" #include "r_main.h" +#include "r_fps.h" /** \brief The P_MixUp function @@ -73,8 +74,12 @@ void P_MixUp(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle, // move chasecam at new player location for (i = 0; i <= r_splitscreen; i++) { - if (thing->player == &players[displayplayers[i]] && camera[i].chase) + if (thing->player != &players[displayplayers[i]]) + continue; + if (camera[i].chase) P_ResetCamera(thing->player, &camera[i]); + R_ResetViewInterpolation(i + 1); + break; } // don't run in place after a teleport diff --git a/src/p_tick.c b/src/p_tick.c index a7f53b003..7cf1748b5 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -24,6 +24,7 @@ #include "lua_hook.h" #include "m_perfstats.h" #include "i_system.h" // I_GetPreciseTime +#include "i_video.h" // rendermode #include "r_main.h" #include "r_fps.h" @@ -787,15 +788,20 @@ void P_Ticker(boolean run) // Hack: ensure newview is assigned every tic. // Ensures view interpolation is T-1 to T in poor network conditions // We need a better way to assign view state decoupled from game logic - for (i = 0; i <= r_splitscreen; i++) + if (rendermode != render_none) { - player_t *player = &players[displayplayers[i]]; - const boolean skybox = (player->skybox.viewpoint && cv_skybox.value); // True if there's a skybox object and skyboxes are on - if (skybox) + for (i = 0; i <= r_splitscreen; i++) { - R_SkyboxFrame(i); + player_t *player = &players[displayplayers[i]]; + if (!player->mo) + continue; + const boolean skybox = (player->skybox.viewpoint && cv_skybox.value); // True if there's a skybox object and skyboxes are on + if (skybox) + { + R_SkyboxFrame(i); + } + R_SetupFrame(i); } - R_SetupFrame(i); } } diff --git a/src/r_fps.c b/src/r_fps.c index 2c01cc571..8f870abf2 100644 --- a/src/r_fps.c +++ b/src/r_fps.c @@ -216,6 +216,14 @@ void R_ResetViewInterpolation(UINT8 p) } } +void R_RelativeTeleportViewInterpolation(UINT8 p, fixed_t xdiff, fixed_t ydiff, fixed_t zdiff, angle_t angdiff) +{ + pview_old[p].x += xdiff; + pview_old[p].y += ydiff; + pview_old[p].z += zdiff; + pview_old[p].angle += angdiff; +} + void R_SetViewContext(enum viewcontext_e _viewcontext) { UINT8 i = 0; diff --git a/src/r_fps.h b/src/r_fps.h index 41fc65af0..c2bc05699 100644 --- a/src/r_fps.h +++ b/src/r_fps.h @@ -126,6 +126,8 @@ void R_InterpolateViewRollAngle(fixed_t frac); void R_UpdateViewInterpolation(void); // Reset the view states (e.g. after level load) so R_InterpolateView doesn't interpolate invalid data void R_ResetViewInterpolation(UINT8 p); +// Update old view for seamless relative teleport +void R_RelativeTeleportViewInterpolation(UINT8 p, fixed_t xdiff, fixed_t ydiff, fixed_t zdiff, angle_t angdiff); // Set the current view context (the viewvars pointed to by newview) void R_SetViewContext(enum viewcontext_e _viewcontext); diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c index a1948f2f1..5e97694ad 100644 --- a/src/sdl/i_video.c +++ b/src/sdl/i_video.c @@ -106,10 +106,9 @@ 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_CALL|CV_NOINIT, CV_OnOff, Impl_SetVsync); +consvar_t cv_vidwait = CVAR_INIT ("vid_wait", "On", CV_SAVE, CV_OnOff, NULL); 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); @@ -185,6 +184,20 @@ static SDL_bool Impl_CreateWindow(SDL_bool fullscreen); //static void Impl_SetWindowName(const char *title); static void Impl_SetWindowIcon(void); +static void Impl_SetSoftwareVsync(int vsync) +{ +#if SDL_VERSION_ATLEAST(2,0,18) + static int oldvsync = 0; + if (oldvsync != vsync) + { + SDL_RenderSetVSync(renderer, vsync); + } + oldvsync = vsync; +#else + (void)vsync; +#endif +} + static void SDLSetMode(INT32 width, INT32 height, SDL_bool fullscreen, SDL_bool reposition) { static SDL_bool wasfullscreen = SDL_FALSE; @@ -277,6 +290,7 @@ static void SDLSetMode(INT32 width, INT32 height, SDL_bool fullscreen, SDL_bool } SDL_PixelFormatEnumToMasks(sw_texture_format, &bpp, &rmask, &gmask, &bmask, &amask); vidSurface = SDL_CreateRGBSurface(0, width, height, bpp, rmask, gmask, bmask, amask); + Impl_SetSoftwareVsync(cv_vidwait.value); } } @@ -1254,6 +1268,7 @@ void I_FinishUpdate(void) SDL_RenderClear(renderer); SDL_RenderCopy(renderer, texture, &src_rect, NULL); SDL_RenderPresent(renderer); + Impl_SetSoftwareVsync(cv_vidwait.value); } #ifdef HWRENDER else if (rendermode == render_opengl) @@ -1478,15 +1493,6 @@ static SDL_bool Impl_CreateContext(void) int flags = 0; // Use this to set SDL_RENDERER_* flags now if (usesdl2soft) flags |= SDL_RENDERER_SOFTWARE; -#if 0 - // This shit is BROKEN. - // - The version of SDL we're using cannot toggle VSync at runtime. We'll need a new SDL version implemented to have this work properly. - // - cv_vidwait is initialized before config is loaded, so it's forced to default value at runtime, and forced off when switching. The config loading code would need restructured. - // - With both this & frame interpolation on, I_FinishUpdate takes x10 longer. At this point, it is simpler to use a standard FPS cap. - // So you can probably guess why I'm kinda over this, I'm just disabling it. - else if (cv_vidwait.value) - flags |= SDL_RENDERER_PRESENTVSYNC; -#endif // 3 August 2022 // Possibly a Windows 11 issue; the default @@ -2006,11 +2012,3 @@ 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 5abb9504b..e10adc82c 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -1089,10 +1089,10 @@ sfxinfo_t S_sfx[NUMSFX] = {"bhurry", false, 255, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // v1.0.2 Battle overtime {"bsnipe", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Banana sniping {"sploss", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Down to yellow sparks - {"join", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Player joined server - {"leave", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Player left server - {"requst", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Got a Discord join request - {"syfail", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Funny sync failure + {"join", true, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Player joined server + {"leave", true, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Player left server + {"requst", true, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Got a Discord join request + {"syfail", true, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Funny sync failure {"itfree", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // :shitsfree: {"dbgsal", false, 255, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Debug notification {"cock", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Hammer cocks, bang bang @@ -1353,7 +1353,7 @@ sfxenum_t S_AddSoundFx(const char *name, boolean singular, INT32 flags, boolean return i; } - CONS_Alert(CONS_WARNING, M_GetText("No more free sound slots\n")); + I_Error("Out of Sound Freeslots while allocating \"%s\"\nLoad less addons to fix this.", name); return 0; } diff --git a/src/st_stuff.c b/src/st_stuff.c index 676c3992a..5c2def212 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -411,12 +411,10 @@ static void ST_drawDebugInfo(void) // Figure out some other way to display all of the RNG classes. fixed_t peekres = P_RandomPeek(PR_UNDEFINED); - peekres *= 10000; // Change from fixed point - peekres >>= FRACBITS; // to displayable decimal V_DrawRightAlignedString(320, height - 16, V_MONOSPACE, va("Init: %08x", P_GetInitSeed(PR_UNDEFINED))); V_DrawRightAlignedString(320, height - 8, V_MONOSPACE, va("Seed: %08x", P_GetRandSeed(PR_UNDEFINED))); - V_DrawRightAlignedString(320, height, V_MONOSPACE, va("== : .%04d", peekres)); + V_DrawRightAlignedString(320, height, V_MONOSPACE, va("== : %08x", peekres)); height -= 32; } diff --git a/src/v_video.c b/src/v_video.c index 1369ca627..2df56c9a4 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -86,11 +86,16 @@ static CV_PossibleValue_t constextsize_cons_t[] = { static void CV_constextsize_OnChange(void); consvar_t cv_constextsize = CVAR_INIT ("con_textsize", "Medium", CV_SAVE|CV_CALL, constextsize_cons_t, CV_constextsize_OnChange); +consvar_t cv_palette = CVAR_INIT ("palette", "", CV_CHEAT|CV_CALL|CV_NOINIT, NULL, CV_palette_OnChange); +consvar_t cv_palettenum = CVAR_INIT ("palettenum", "0", CV_CHEAT|CV_CALL|CV_NOINIT, CV_Unsigned, CV_palette_OnChange); + // local copy of the palette for V_GetColor() RGBA_t *pLocalPalette = NULL; RGBA_t *pMasterPalette = NULL; RGBA_t *pGammaCorrectedPalette = NULL; +static size_t currentPaletteSize; + /* The following was an extremely helpful resource when developing my Colour Cube LUT. http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter24.html @@ -311,9 +316,12 @@ UINT32 V_GammaCorrect(UINT32 input, double power) static void LoadPalette(const char *lumpname) { lumpnum_t lumpnum = W_GetNumForName(lumpname); - size_t i, palsize = W_LumpLength(lumpnum)/3; + size_t i, palsize; UINT8 *pal; + currentPaletteSize = W_LumpLength(lumpnum); + palsize = currentPaletteSize / 3; + Cubeapply = InitCube(); if (pLocalPalette != pMasterPalette) @@ -400,8 +408,24 @@ const char *R_GetPalname(UINT16 num) const char *GetPalette(void) { + const char *user = cv_palette.string; + + if (user && user[0]) + { + if (W_CheckNumForName(user) == LUMPERROR) + { + CONS_Alert(CONS_WARNING, + "cv_palette %s lump does not exist\n", user); + } + else + { + return cv_palette.string; + } + } + if (gamestate == GS_LEVEL) return R_GetPalname((encoremode ? mapheaderinfo[gamemap-1]->encorepal : mapheaderinfo[gamemap-1]->palette)); + return "PLAYPAL"; } @@ -419,6 +443,19 @@ void V_SetPalette(INT32 palettenum) if (!pLocalPalette) V_ReloadPalette(); + if (palettenum == 0) + { + palettenum = cv_palettenum.value; + + if (palettenum * 256U > currentPaletteSize - 256) + { + CONS_Alert(CONS_WARNING, + "cv_palettenum %d out of range\n", + palettenum); + palettenum = 0; + } + } + #ifdef HWRENDER if (rendermode == render_opengl) HWR_SetPalette(&pLocalPalette[palettenum*256]); @@ -433,23 +470,12 @@ void V_SetPalette(INT32 palettenum) void V_SetPaletteLump(const char *pal) { LoadPalette(pal); -#ifdef HWRENDER - if (rendermode == render_opengl) - HWR_SetPalette(pLocalPalette); -#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL) - else -#endif -#endif - if (rendermode != render_none) - I_SetPalette(pLocalPalette); -#ifdef HASINVERT - R_MakeInvertmap(); -#endif + V_SetPalette(0); } static void CV_palette_OnChange(void) { - // reload palette + // recalculate Color Cube V_ReloadPalette(); V_SetPalette(0); } diff --git a/src/v_video.h b/src/v_video.h index c3467d31e..1803d0818 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -34,7 +34,8 @@ extern consvar_t cv_ticrate, cv_constextsize, cv_globalgamma, cv_globalsaturation, cv_rhue, cv_yhue, cv_ghue, cv_chue, cv_bhue, cv_mhue, cv_rgamma, cv_ygamma, cv_ggamma, cv_cgamma, cv_bgamma, cv_mgamma, -cv_rsaturation, cv_ysaturation, cv_gsaturation, cv_csaturation, cv_bsaturation, cv_msaturation; +cv_rsaturation, cv_ysaturation, cv_gsaturation, cv_csaturation, cv_bsaturation, cv_msaturation, +cv_palette, cv_palettenum; // Allocates buffer screens, call before R_Init. void V_Init(void); diff --git a/src/w_wad.c b/src/w_wad.c index 8edbe74b6..e81879114 100644 --- a/src/w_wad.c +++ b/src/w_wad.c @@ -1324,6 +1324,7 @@ lumpnum_t W_CheckNumForMap(const char *name) lumpnum_t check = INT16_MAX; UINT32 uhash, hash = quickncasehash(name, LUMPNUMCACHENAME); INT32 i; + UINT16 firstfile = (partadd_earliestfile == UINT16_MAX) ? 0 : partadd_earliestfile; // Check the lumpnumcache first. Loop backwards so that we check // most recent entries first @@ -1339,7 +1340,7 @@ lumpnum_t W_CheckNumForMap(const char *name) uhash = quickncasehash(name, 8); // Not a mistake, legacy system for short lumpnames - for (i = numwadfiles - 1; i >= 0; i--) + for (i = numwadfiles - 1; i >= firstfile; i--) { check = W_CheckNumForMapPwad(name, uhash, (UINT16)i, 0); diff --git a/src/w_wad.h b/src/w_wad.h index 361ee4e50..ec3013af7 100644 --- a/src/w_wad.h +++ b/src/w_wad.h @@ -99,9 +99,10 @@ void* vres_GetPatch(virtlump_t *vlump, INT32 tag); // ========================================================================= #define MAX_WADPATH 512 -#define MAX_WADFILES 127 // maximum of wad files used at the same time -// Replay code relies on it being an UINT8 and, just to be safe, in case some wad counter somewhere is a SINT8, you should NOT go above 127 here if you're lazy like me. -// Besides, are there truly 127 wads worth your interrest? +#define MAX_WADFILES 255 // maximum of wad files used at the same time +// Replay code relies on it being an UINT8. There are no SINT8s handling WAD indices, though. +// Can be set all the way up to 255 but not 256, +// because an UINT8 will never be >= 256, probably breaking some conditionals. #define lumpcache_t void *