diff --git a/cmake/Comptime.cmake b/cmake/Comptime.cmake index 3859c2aad..f50faf933 100644 --- a/cmake/Comptime.cmake +++ b/cmake/Comptime.cmake @@ -1,3 +1,5 @@ +cmake_minimum_required(VERSION 3.3 FATAL_ERROR) + set(CMAKE_BINARY_DIR "${BINARY_DIR}") set(CMAKE_CURRENT_BINARY_DIR "${BINARY_DIR}") @@ -7,7 +9,23 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Mo include(GitUtilities) git_current_branch(SRB2_COMP_BRANCH) -git_summary(SRB2_COMP_REVISION) git_working_tree_dirty(SRB2_COMP_UNCOMMITTED) +git_summary(revision) +string(REGEX REPLACE "([\"\\])" "\\\\\\1" SRB2_COMP_REVISION "${revision}") + +if("${CMAKE_BUILD_TYPE}" STREQUAL "") + set(CMAKE_BUILD_TYPE None) +endif() + +# These build types enable optimizations of some kind by default. +set(optimized_build_types "MINSIZEREL;RELEASE;RELWITHDEBINFO") + +string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type) +if("${build_type}" IN_LIST optimized_build_types) + set(SRB2_COMP_OPTIMIZED TRUE) +else() + set(SRB2_COMP_OPTIMIZED FALSE) +endif() + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/src/config.h") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 148d12ff4..7293393cb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -154,7 +154,7 @@ add_custom_command( # build time for accurate git information and before anything # that needs it, obviously. add_custom_target(_SRB2_reconf ALL - COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DBINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}/.. -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/Comptime.cmake + COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DBINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}/.. -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/Comptime.cmake WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.." ) add_dependencies(SRB2SDL2 _SRB2_reconf) diff --git a/src/comptime.c b/src/comptime.c index 2baef79d6..74b810062 100644 --- a/src/comptime.c +++ b/src/comptime.c @@ -11,6 +11,8 @@ #include "config.h" const char *compbranch = SRB2_COMP_BRANCH; const char *comprevision = SRB2_COMP_REVISION; +const char *comptype = CMAKE_BUILD_TYPE; +const int compoptimized = SRB2_COMP_OPTIMIZED; #elif (defined(COMPVERSION)) #include "comptime.h" diff --git a/src/config.h.in b/src/config.h.in index d7b67cdce..77a205a74 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -20,6 +20,9 @@ #define COMPVERSION_UNCOMMITTED #endif +#define CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" +#cmakedefine01 SRB2_COMP_OPTIMIZED + #endif /* Manually defined asset hashes for non-CMake builds diff --git a/src/console.c b/src/console.c index ede2f633a..10baa4254 100644 --- a/src/console.c +++ b/src/console.c @@ -358,7 +358,22 @@ static void CON_SetupColormaps(void) *memorysrc = (UINT8)(i & 0xFF); // remap each color to itself... purplemap[0] = (UINT8)163; + yellowmap[0] = (UINT8)73; + yellowmap[1] = (UINT8)73; + yellowmap[3] = (UINT8)74; + yellowmap[6] = (UINT8)74; + yellowmap[7] = (UINT8)190; + yellowmap[8] = (UINT8)190; + yellowmap[10] = (UINT8)190; + yellowmap[12] = (UINT8)190; + yellowmap[14] = (UINT8)149; + yellowmap[15] = (UINT8)149; + yellowmap[16] = (UINT8)149; + yellowmap[21] = (UINT8)152; + yellowmap[23] = (UINT8)173; + yellowmap[24] = (UINT8)167; + greenmap[0] = (UINT8)98; bluemap[0] = (UINT8)148; redmap[0] = (UINT8)34; // battle diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 00cff178e..1553f7f2c 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -54,7 +54,6 @@ #include "k_pwrlv.h" #include "k_bot.h" #include "k_grandprix.h" -#include "k_boss.h" #include "doomstat.h" #include "s_sound.h" // sfx_syfail #include "m_cond.h" // netUnlocked @@ -899,9 +898,6 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) UINT8 *p; size_t mirror_length; const char *httpurl = cv_httpsource.string; - UINT8 prefgametype = (cv_kartgametypepreference.value == -1) - ? gametype - : cv_kartgametypepreference.value; netbuffer->packettype = PT_SERVERINFO; netbuffer->u.serverinfo._255 = 255; @@ -933,7 +929,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) else netbuffer->u.serverinfo.refusereason = 0; - strncpy(netbuffer->u.serverinfo.gametypename, Gametype_Names[prefgametype], + strncpy(netbuffer->u.serverinfo.gametypename, gametypes[gametype]->name, sizeof netbuffer->u.serverinfo.gametypename); netbuffer->u.serverinfo.modifiedgame = (UINT8)modifiedgame; netbuffer->u.serverinfo.cheatsenabled = CV_CheatsEnabled(); @@ -2077,8 +2073,9 @@ static void CL_ConnectToServer(void) Y_EndVote(); DEBFILE(va("waiting %d nodes\n", doomcom->numnodes)); - M_ClearMenus(true); G_SetGamestate(GS_WAITINGPLAYERS); + if (wipegamestate == GS_MENU) + M_ClearMenus(true); wipegamestate = GS_WAITINGPLAYERS; ClearAdminPlayers(); @@ -3744,6 +3741,9 @@ static void Got_AddBot(UINT8 **p, INT32 playernum) sprintf(player_names[newplayernum], "%s", skins[skinnum].realname); SetPlayerSkinByNum(newplayernum, skinnum); + players[newplayernum].spectator = !(gametyperules & GTR_BOTS) + || (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE); + if (netgame) { HU_AddChatText(va("\x82*Bot %d has been added to the game", newplayernum+1), false); diff --git a/src/d_main.c b/src/d_main.c index 9c83f406d..8384de07c 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -72,7 +72,6 @@ // SRB2Kart #include "k_grandprix.h" -#include "k_boss.h" #include "doomstat.h" #include "m_random.h" // P_ClearRandom #include "k_specialstage.h" @@ -963,12 +962,6 @@ void D_StartTitle(void) // Reset GP memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); - // Reset boss info - K_ResetBossInfo(); - - // Reset Special Stage - K_ResetSpecialStage(); - // empty maptol so mario/etc sounds don't play in sound test when they shouldn't maptol = 0; @@ -1200,13 +1193,14 @@ D_ConvertVersionNumbers (void) // void D_SRB2Main(void) { - INT32 i, p; + INT32 i, j, p; #ifdef DEVELOP INT32 pstartmap = 1; // default to first loaded map (Test Run) #else INT32 pstartmap = 0; // default to random map (0 is not a valid map number) #endif boolean autostart = false; + INT32 newgametype = -1; /* break the version string into version numbers, for netplay */ D_ConvertVersionNumbers(); @@ -1519,11 +1513,6 @@ void D_SRB2Main(void) CON_SetLoadingProgress(LOADED_HUINIT); - memset(timelimits, 0, sizeof(timelimits)); - memset(pointlimits, 0, sizeof(pointlimits)); - - timelimits[GT_BATTLE] = 2; - D_RegisterServerCommands(); D_RegisterClientCommands(); // be sure that this is called before D_CheckNetGame R_RegisterEngineStuff(); @@ -1531,14 +1520,14 @@ void D_SRB2Main(void) I_RegisterSysCommands(); + M_Init(); + //--------------------------------------------------------- CONFIG.CFG M_FirstLoadConfig(); // WARNING : this do a "COM_BufExecute()" // Load Profiles now that default controls have been defined PR_LoadProfiles(); // load control profiles - M_Init(); - #if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL) VID_PrepareModeList(); // Regenerate Modelist according to cv_fullscreen #endif @@ -1796,8 +1785,6 @@ void D_SRB2Main(void) if (M_CheckParm("-gametype") && M_IsNextParm()) { // from Command_Map_f - INT32 j; - INT16 newgametype = -1; const char *sgametype = M_GetNextParm(); newgametype = G_GetGametypeByName(sgametype); @@ -1805,7 +1792,7 @@ void D_SRB2Main(void) if (newgametype == -1) // reached end of the list with no match { j = atoi(sgametype); // assume they gave us a gametype number, which is okay too - if (j >= 0 && j < gametypecount) + if (j >= 0 && j < numgametypes) newgametype = (INT16)j; } @@ -1819,7 +1806,6 @@ void D_SRB2Main(void) if (M_CheckParm("-skill") && M_IsNextParm()) { - INT32 j; INT16 newskill = -1; const char *sskill = M_GetNextParm(); @@ -1872,11 +1858,20 @@ void D_SRB2Main(void) if (grandprixinfo.gp == true && mapheaderinfo[pstartmap-1]) { - if (mapheaderinfo[pstartmap-1]->typeoflevel & TOL_SPECIAL) + if (newgametype == -1) { - specialStage.active = true; - specialStage.encore = grandprixinfo.encore; - grandprixinfo.eventmode = GPEVENT_SPECIAL; + newgametype = G_GuessGametypeByTOL(mapheaderinfo[pstartmap-1]->typeoflevel); + if (newgametype != -1) + { + j = gametype; + G_SetGametype(newgametype); + D_GameTypeChanged(j); + } + + if (gametyperules & (GTR_BOSS|GTR_CATCHER)) + grandprixinfo.eventmode = GPEVENT_SPECIAL; + else if (gametype != GT_RACE) + grandprixinfo.eventmode = GPEVENT_BONUS; } G_SetUsedCheats(); diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 61b737ce9..97b71478e 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -57,7 +57,6 @@ #include "k_color.h" #include "k_respawn.h" #include "k_grandprix.h" -#include "k_boss.h" #include "k_follower.h" #include "doomstat.h" #include "deh_tables.h" @@ -399,10 +398,6 @@ consvar_t cv_kartbumpers = CVAR_INIT ("battlebumpers", "3", CV_NETVAR, kartbumpe consvar_t cv_kartfrantic = CVAR_INIT ("franticitems", "Off", CV_NETVAR|CV_CALL|CV_NOINIT, CV_OnOff, KartFrantic_OnChange); static CV_PossibleValue_t kartencore_cons_t[] = {{-1, "Auto"}, {0, "Off"}, {1, "On"}, {0, NULL}}; consvar_t cv_kartencore = CVAR_INIT ("encore", "Auto", CV_NETVAR|CV_CALL|CV_NOINIT, kartencore_cons_t, KartEncore_OnChange); -static CV_PossibleValue_t kartvoterulechanges_cons_t[] = {{0, "Never"}, {1, "Sometimes"}, {2, "Frequent"}, {3, "Always"}, {0, NULL}}; -consvar_t cv_kartvoterulechanges = CVAR_INIT ("voterulechanges", "Frequent", CV_NETVAR, kartvoterulechanges_cons_t, NULL); -static CV_PossibleValue_t kartgametypepreference_cons_t[] = {{-1, "None"}, {GT_RACE, "Race"}, {GT_BATTLE, "Battle"}, {0, NULL}}; -consvar_t cv_kartgametypepreference = CVAR_INIT ("gametypepreference", "None", CV_NETVAR, kartgametypepreference_cons_t, NULL); static CV_PossibleValue_t kartspeedometer_cons_t[] = {{0, "Off"}, {1, "Percentage"}, {2, "Kilometers"}, {3, "Miles"}, {4, "Fracunits"}, {0, NULL}}; consvar_t cv_kartspeedometer = CVAR_INIT ("speedometer", "Percentage", CV_SAVE, kartspeedometer_cons_t, NULL); // use tics in display static CV_PossibleValue_t kartvoices_cons_t[] = {{0, "Never"}, {1, "Tasteful"}, {2, "Meme"}, {0, NULL}}; @@ -485,10 +480,6 @@ consvar_t cv_timelimit = CVAR_INIT ("timelimit", "None", CV_NETVAR|CV_CALL|CV_NO static CV_PossibleValue_t numlaps_cons_t[] = {{1, "MIN"}, {MAX_LAPS, "MAX"}, {0, "Map default"}, {0, NULL}}; consvar_t cv_numlaps = CVAR_INIT ("numlaps", "Map default", CV_SAVE|CV_NETVAR|CV_CALL|CV_CHEAT, numlaps_cons_t, NumLaps_OnChange); -// Point and time limits for every gametype -INT32 pointlimits[NUMGAMETYPES]; -INT32 timelimits[NUMGAMETYPES]; - consvar_t cv_forceskin = CVAR_INIT ("forceskin", "None", CV_NETVAR|CV_CALL|CV_CHEAT, NULL, ForceSkin_OnChange); consvar_t cv_downloading = CVAR_INIT ("downloading", "On", 0, CV_OnOff, NULL); @@ -557,13 +548,11 @@ char timedemo_csv_id[256]; boolean timedemo_quit; INT16 gametype = GT_RACE; -UINT32 gametyperules = 0; -INT16 gametypecount = GT_FIRSTFREESLOT; +INT16 numgametypes = GT_FIRSTFREESLOT; boolean forceresetplayers = false; boolean deferencoremode = false; UINT8 splitscreen = 0; -boolean circuitmap = false; INT32 adminplayers[MAXPLAYERS]; // Scheduled commands. @@ -2530,19 +2519,11 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean r CONS_Debug(DBG_GAMELOGIC, "Map change: mapnum=%d gametype=%d pencoremode=%d resetplayers=%d delay=%d skipprecutscene=%d\n", mapnum, newgametype, pencoremode, resetplayers, delay, skipprecutscene); - if ((netgame || multiplayer) && !((gametype == newgametype) && (gametypedefaultrules[newgametype] & GTR_CAMPAIGN))) + if ((netgame || multiplayer) && (grandprixinfo.gp != false)) FLS = false; // Too lazy to change the input value for every instance of this function....... - if (bossinfo.boss == true) - { - pencoremode = bossinfo.encore; - } - else if (specialStage.active == true) - { - pencoremode = specialStage.encore; - } - else if (grandprixinfo.gp == true) + if (grandprixinfo.gp == true) { pencoremode = grandprixinfo.encore; } @@ -2596,14 +2577,13 @@ void D_SetupVote(void) UINT8 buf[5*2]; // four UINT16 maps (at twice the width of a UINT8), and two gametypes UINT8 *p = buf; INT32 i; - UINT8 gt = (cv_kartgametypepreference.value == -1) ? gametype : cv_kartgametypepreference.value; - UINT8 secondgt = G_SometimesGetDifferentGametype(gt); + UINT8 secondgt = G_SometimesGetDifferentGametype(); INT16 votebuffer[4] = {-1,-1,-1,0}; - if ((cv_kartencore.value == 1) && (gametypedefaultrules[gt] & GTR_CIRCUIT)) - WRITEUINT8(p, (gt|VOTEMODIFIER_ENCORE)); + if ((cv_kartencore.value == 1) && (gametyperules & GTR_ENCORE)) + WRITEUINT8(p, (gametype|VOTEMODIFIER_ENCORE)); else - WRITEUINT8(p, gt); + WRITEUINT8(p, gametype); WRITEUINT8(p, secondgt); secondgt &= ~VOTEMODIFIER_ENCORE; @@ -2613,9 +2593,9 @@ void D_SetupVote(void) if (i == 2) // sometimes a different gametype m = G_RandMap(G_TOLFlag(secondgt), prevmap, ((secondgt != gametype) ? 2 : 0), 0, true, votebuffer); else if (i >= 3) // unknown-random and formerly force-unknown MAP HELL - m = G_RandMap(G_TOLFlag(gt), prevmap, 0, (i-2), (i < 4), votebuffer); + m = G_RandMap(G_TOLFlag(gametype), prevmap, 0, (i-2), (i < 4), votebuffer); else - m = G_RandMap(G_TOLFlag(gt), prevmap, 0, 0, true, votebuffer); + m = G_RandMap(G_TOLFlag(gametype), prevmap, 0, 0, true, votebuffer); if (i < 3) votebuffer[i] = m; WRITEUINT16(p, m); @@ -2758,13 +2738,7 @@ static void Command_Map_f(void) if (option_gametype) { - if (!multiplayer) - { - CONS_Printf(M_GetText( - "You can't switch gametypes in single player!\n")); - return; - } - else if (COM_Argc() < option_gametype + 2)/* no argument after? */ + if (COM_Argc() < option_gametype + 2)/* no argument after? */ { CONS_Alert(CONS_ERROR, "No gametype name follows parameter '%s'.\n", @@ -2822,7 +2796,7 @@ static void Command_Map_f(void) if (isdigit(gametypename[0])) { d = atoi(gametypename); - if (d >= 0 && d < gametypecount) + if (d >= 0 && d < numgametypes) newgametype = d; else { @@ -2830,7 +2804,7 @@ static void Command_Map_f(void) "Gametype number %d is out of range. Use a number between" " 0 and %d inclusive. ...Or just use the name. :v\n", d, - gametypecount-1); + numgametypes-1); Z_Free(realmapname); Z_Free(mapname); return; @@ -2839,13 +2813,23 @@ static void Command_Map_f(void) else { CONS_Alert(CONS_ERROR, - "'%s' is not a gametype.\n", + "'%s' is not a valid gametype.\n", gametypename); Z_Free(realmapname); Z_Free(mapname); return; } } + + if (Playing() && netgame && (gametypes[newgametype]->rules & GTR_FORBIDMP)) + { + CONS_Alert(CONS_ERROR, + "'%s' is not a net-compatible gametype.\n", + gametypename); + Z_Free(realmapname); + Z_Free(mapname); + return; + } } else if (!Playing()) { @@ -2853,7 +2837,15 @@ static void Command_Map_f(void) if (mapheaderinfo[newmapnum-1]) { // Let's just guess so we don't have to specify the gametype EVERY time... - newgametype = (mapheaderinfo[newmapnum-1]->typeoflevel & (TOL_BATTLE|TOL_BOSS)) ? GT_BATTLE : GT_RACE; + newgametype = G_GuessGametypeByTOL(mapheaderinfo[newmapnum-1]->typeoflevel); + + if (newgametype == -1) + { + CONS_Alert(CONS_WARNING, M_GetText("%s (%s) doesn't support any known gametype!\n"), realmapname, G_BuildMapName(newmapnum)); + Z_Free(realmapname); + Z_Free(mapname); + return; + } } } @@ -2865,6 +2857,8 @@ static void Command_Map_f(void) if (!M_SecretUnlocked(SECRET_ENCORE, false) && newencoremode == true && !usingcheats) { CONS_Alert(CONS_NOTICE, M_GetText("You haven't unlocked Encore Mode yet!\n")); + Z_Free(realmapname); + Z_Free(mapname); return; } } @@ -2885,8 +2879,7 @@ static void Command_Map_f(void) mapheaderinfo[newmapnum-1]->typeoflevel & G_TOLFlag(newgametype) )) { - CONS_Alert(CONS_WARNING, M_GetText("%s (%s) doesn't support %s mode!\n(Use -force to override)\n"), realmapname, G_BuildMapName(newmapnum), - (multiplayer ? gametype_cons_t[newgametype].strvalue : "Single Player")); + CONS_Alert(CONS_WARNING, M_GetText("%s (%s) doesn't support %s mode!\n(Use -force to override)\n"), realmapname, G_BuildMapName(newmapnum), gametypes[newgametype]->name); Z_Free(realmapname); Z_Free(mapname); return; @@ -2895,8 +2888,7 @@ static void Command_Map_f(void) { fromlevelselect = ( netgame || multiplayer ) && - newgametype == gametype && - gametypedefaultrules[newgametype] & GTR_CAMPAIGN; + grandprixinfo.gp != false; } } @@ -2952,35 +2944,18 @@ static void Command_Map_f(void) grandprixinfo.eventmode = GPEVENT_NONE; - if (newgametype == GT_BATTLE) + if (gametypes[newgametype]->rules & (GTR_BOSS|GTR_CATCHER)) + { + grandprixinfo.eventmode = GPEVENT_SPECIAL; + } + else if (newgametype != GT_RACE) { grandprixinfo.eventmode = GPEVENT_BONUS; - - if (mapheaderinfo[newmapnum-1] && - mapheaderinfo[newmapnum-1]->typeoflevel & TOL_BOSS) - { - bossinfo.boss = true; - bossinfo.encore = newencoremode; - } - else - { - bossinfo.boss = false; - K_ResetBossInfo(); - } } - else + + if (!Playing()) { - if (mapheaderinfo[newmapnum-1] && - mapheaderinfo[newmapnum-1]->typeoflevel & TOL_SPECIAL) // Special Stage - { - specialStage.active = true; - specialStage.encore = newencoremode; - grandprixinfo.eventmode = GPEVENT_SPECIAL; - } - else - { - specialStage.active = false; - } + multiplayer = true; } } @@ -3029,12 +3004,12 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) gametype = READUINT8(*cp); G_SetGametype(gametype); // I fear putting that macro as an argument - if (gametype < 0 || gametype >= gametypecount) + if (gametype < 0 || gametype >= numgametypes) gametype = lastgametype; else if (gametype != lastgametype) D_GameTypeChanged(lastgametype); // emulate consvar_t behavior for gametype - if (!(gametyperules & GTR_CIRCUIT) && !bossinfo.boss) + if (!(gametyperules & GTR_ENCORE)) pencoremode = false; skipprecutscene = ((flags & (1<<2)) != 0); @@ -3086,9 +3061,10 @@ static void Command_RandomMap(void) { INT32 oldmapnum; INT32 newmapnum; - INT32 newgametype; - boolean newencoremode; + INT32 newgametype = (Playing() ? gametype : menugametype); + boolean newencore = false; boolean newresetplayers; + size_t option_gametype; if (client && !IsPlayerAdmin(consoleplayer)) { @@ -3096,13 +3072,69 @@ static void Command_RandomMap(void) return; } + if ((option_gametype = COM_CheckPartialParm("-g"))) + { + const char *gametypename; + + if (COM_Argc() < option_gametype + 2)/* no argument after? */ + { + CONS_Alert(CONS_ERROR, + "No gametype name follows parameter '%s'.\n", + COM_Argv(option_gametype)); + return; + } + + // new gametype value + // use current one by default + gametypename = COM_Argv(option_gametype + 1); + + newgametype = G_GetGametypeByName(gametypename); + + if (newgametype == -1) // reached end of the list with no match + { + /* Did they give us a gametype number? That's okay too! */ + if (isdigit(gametypename[0])) + { + INT16 d = atoi(gametypename); + if (d >= 0 && d < numgametypes) + newgametype = d; + else + { + CONS_Alert(CONS_ERROR, + "Gametype number %d is out of range. Use a number between" + " 0 and %d inclusive. ...Or just use the name. :v\n", + d, + numgametypes-1); + return; + } + } + else + { + CONS_Alert(CONS_ERROR, + "'%s' is not a valid gametype.\n", + gametypename); + return; + } + } + + if (Playing() && netgame && (gametypes[newgametype]->rules & GTR_FORBIDMP)) + { + CONS_Alert(CONS_ERROR, + "'%s' is not a net-compatible gametype.\n", + gametypename); + return; + } + } + // TODO: Handle singleplayer conditions. // The existing ones are way too annoyingly complicated and "anti-cheat" for my tastes. if (Playing()) { - newgametype = gametype; - newencoremode = encoremode; + if (cv_kartencore.value == 1 && (gametypes[newgametype]->rules & GTR_ENCORE)) + { + newencore = true; + } newresetplayers = false; if (gamestate == GS_LEVEL) @@ -3116,14 +3148,12 @@ static void Command_RandomMap(void) } else { - newgametype = cv_dummygametype.value; // Changed from cv_newgametype to match newmenus - newencoremode = false; newresetplayers = true; oldmapnum = -1; } newmapnum = G_RandMap(G_TOLFlag(newgametype), oldmapnum, 0, 0, false, NULL) + 1; - D_MapChange(newmapnum, newgametype, newencoremode, newresetplayers, 0, false, false); + D_MapChange(newmapnum, newgametype, newencore, newresetplayers, 0, false, false); } static void Command_RestartLevel(void) @@ -3742,7 +3772,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum) // Clear player score and rings if a spectator. if (players[playernum].spectator) { - if (gametyperules & GTR_BUMPERS) // SRB2kart + if (gametyperules & GTR_POINTLIMIT) // SRB2kart { players[playernum].roundscore = 0; K_CalculateBattleWanted(); @@ -4774,16 +4804,13 @@ static void Command_Version_f(void) else // 16-bit? 128-bit? CONS_Printf("Bits Unknown "); + CONS_Printf("%s ", comptype); + // No ASM? #ifdef NOASM CONS_Printf("\x85" "NOASM " "\x80"); #endif - // Debug build -#ifdef _DEBUG - CONS_Printf("\x85" "DEBUG " "\x80"); -#endif - // DEVELOP build #if defined(TESTERS) CONS_Printf("\x88" "TESTERS " "\x80"); @@ -4812,15 +4839,9 @@ static void Command_ShowGametype_f(void) { const char *gametypestr = NULL; - if (!(netgame || multiplayer)) // print "Single player" instead of "Race" - { - CONS_Printf(M_GetText("Current gametype is %s\n"), "Single Player"); - return; - } - // get name string for current gametype - if (gametype >= 0 && gametype < gametypecount) - gametypestr = Gametype_Names[gametype]; + if (gametype >= 0 && gametype < numgametypes) + gametypestr = gametypes[gametype]->name; if (gametypestr) CONS_Printf(M_GetText("Current gametype is %s\n"), gametypestr); @@ -4999,10 +5020,10 @@ void D_GameTypeChanged(INT32 lastgametype) { const char *oldgt = NULL, *newgt = NULL; - if (lastgametype >= 0 && lastgametype < gametypecount) - oldgt = Gametype_Names[lastgametype]; - if (gametype >= 0 && lastgametype < gametypecount) - newgt = Gametype_Names[gametype]; + if (lastgametype >= 0 && lastgametype < numgametypes) + oldgt = gametypes[lastgametype]->name; + if (gametype >= 0 && gametype < numgametypes) + newgt = gametypes[gametype]->name; if (oldgt && newgt) CONS_Printf(M_GetText("Gametype was changed from %s to %s\n"), oldgt, newgt); @@ -5014,11 +5035,11 @@ void D_GameTypeChanged(INT32 lastgametype) { if (!cv_timelimit.changed) // user hasn't changed limits { - CV_SetValue(&cv_timelimit, timelimits[gametype]); + CV_SetValue(&cv_timelimit, gametypes[gametype]->timelimit); } if (!cv_pointlimit.changed) { - CV_SetValue(&cv_pointlimit, pointlimits[gametype]); + CV_SetValue(&cv_pointlimit, gametypes[gametype]->pointlimit); } } @@ -5313,11 +5334,29 @@ static void Got_SetupVotecmd(UINT8 **cp, INT32 playernum) // Strip illegal Encore flag. if ((gt & VOTEMODIFIER_ENCORE) - && !(gametypedefaultrules[(gt & ~VOTEMODIFIER_ENCORE)] & GTR_CIRCUIT)) + && !(gametypes[(gt & ~VOTEMODIFIER_ENCORE)]->rules & GTR_ENCORE)) { gt &= ~VOTEMODIFIER_ENCORE; } + if ((gt & ~VOTEMODIFIER_ENCORE) >= numgametypes) + { + gt &= ~VOTEMODIFIER_ENCORE; + if (server) + I_Error("Got_SetupVotecmd: Internal gametype ID %d not found (numgametypes = %d)", gt, numgametypes); + CONS_Alert(CONS_WARNING, M_GetText("Vote setup with bad gametype ID %d received from %s\n"), gt, player_names[playernum]); + return; + } + + if ((secondgt & ~VOTEMODIFIER_ENCORE) >= numgametypes) + { + secondgt &= ~VOTEMODIFIER_ENCORE; + if (server) + I_Error("Got_SetupVotecmd: Internal second gametype ID %d not found (numgametypes = %d)", secondgt, numgametypes); + CONS_Alert(CONS_WARNING, M_GetText("Vote setup with bad second gametype ID %d received from %s\n"), secondgt, player_names[playernum]); + return; + } + for (i = 0; i < 4; i++) { tempvotelevels[i][0] = (UINT16)READUINT16(*cp); @@ -5333,11 +5372,11 @@ static void Got_SetupVotecmd(UINT8 **cp, INT32 playernum) // If third entry has an illelegal Encore flag... (illelegal!?) if ((secondgt & VOTEMODIFIER_ENCORE) - && !(gametypedefaultrules[(secondgt & ~VOTEMODIFIER_ENCORE)] & GTR_CIRCUIT)) + && !(gametypes[(secondgt & ~VOTEMODIFIER_ENCORE)]->rules & GTR_ENCORE)) { secondgt &= ~VOTEMODIFIER_ENCORE; // Apply it to the second entry instead, gametype permitting! - if (gametypedefaultrules[gt] & GTR_CIRCUIT) + if (gametypes[gt]->rules & GTR_ENCORE) { tempvotelevels[1][1] |= VOTEMODIFIER_ENCORE; } @@ -5737,11 +5776,11 @@ void Command_Retry_f(void) { CONS_Printf(M_GetText("You must be in a level to use this.\n")); } - else if (grandprixinfo.gp == false && bossinfo.boss == false) + else if (grandprixinfo.gp == false) { CONS_Printf(M_GetText("This only works in singleplayer games.\n")); } - else if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE) + else if (grandprixinfo.eventmode == GPEVENT_BONUS) { CONS_Printf(M_GetText("You can't retry right now!\n")); } diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 8cf262df0..f0fbf59d8 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -83,8 +83,6 @@ extern consvar_t cv_kartspeed; extern consvar_t cv_kartbumpers; extern consvar_t cv_kartfrantic; extern consvar_t cv_kartencore; -extern consvar_t cv_kartvoterulechanges; -extern consvar_t cv_kartgametypepreference; extern consvar_t cv_kartspeedometer; extern consvar_t cv_kartvoices; extern consvar_t cv_kartbot; diff --git a/src/deh_lua.c b/src/deh_lua.c index 56bfdf4ea..f131617c6 100644 --- a/src/deh_lua.c +++ b/src/deh_lua.c @@ -309,11 +309,16 @@ static inline int lib_getenum(lua_State *L) } else if (fastncmp("GT_", word, 3)) { p = word; - for (i = 0; Gametype_ConstantNames[i]; i++) - if (fastcmp(p, Gametype_ConstantNames[i])) { + i = 0; + while (gametypes[i] != NULL) + { + if (fastcmp(p, gametypes[i]->constant)) + { lua_pushinteger(L, i); return 1; } + i++; + } if (mathlib) return luaL_error(L, "gametype '%s' could not be found.\n", word); return 0; } diff --git a/src/deh_soc.c b/src/deh_soc.c index 1d7b94214..4fb40b087 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -764,13 +764,13 @@ void readgametype(MYFILE *f, char *gtname) char *tmp; INT32 i, j; - INT16 newgtidx = 0; + gametype_t *newgametype = NULL; + UINT32 newgtrules = 0; UINT32 newgttol = 0; INT32 newgtpointlimit = 0; INT32 newgttimelimit = 0; - INT16 newgtrankingstype = -1; - int newgtinttype = 0; + UINT8 newgtinttype = 0; char gtconst[MAXLINELEN]; // Empty strings. @@ -821,12 +821,6 @@ void readgametype(MYFILE *f, char *gtname) newgtpointlimit = (INT32)i; else if (fastcmp(word, "DEFAULTTIMELIMIT")) newgttimelimit = (INT32)i; - // Rankings type - else if (fastcmp(word, "RANKINGTYPE")) - { - // Case insensitive - newgtrankingstype = (int)get_number(word2); - } // Intermission type else if (fastcmp(word, "INTERMISSIONTYPE")) { @@ -879,36 +873,54 @@ void readgametype(MYFILE *f, char *gtname) Z_Free(word2lwr); // Ran out of gametype slots - if (gametypecount == NUMGAMETYPEFREESLOTS) + if (numgametypes == GT_LASTFREESLOT) { - CONS_Alert(CONS_WARNING, "Ran out of free gametype slots!\n"); + I_Error("Out of Gametype Freeslots while allocating \"%s\"\nLoad less addons to fix this.", gtname); + } + + if (gtname[0] == '\0') + { + deh_warning("Custom gametype must have a name"); + return; + } + + if (strlen(gtname) >= MAXGAMETYPELENGTH) + { + deh_warning("Custom gametype \"%s\"'s name must be %d long at most", gtname, MAXGAMETYPELENGTH-1); + return; + } + + for (i = 0; i < numgametypes; i++) + if (fastcmp(gtname, gametypes[i]->name)) + break; + + if (i < numgametypes) + { + deh_warning("Custom gametype \"%s\"'s name is already in use", gtname); return; } // Add the new gametype - newgtidx = G_AddGametype(newgtrules); - G_AddGametypeTOL(newgtidx, newgttol); + newgametype = Z_Calloc(sizeof (gametype_t), PU_STATIC, NULL); + if (!newgametype) + { + I_Error("Out of memory allocating gametype \"%s\"", gtname); + } - // Not covered by G_AddGametype alone. - if (newgtrankingstype == -1) - newgtrankingstype = newgtidx; - gametyperankings[newgtidx] = newgtrankingstype; - intermissiontypes[newgtidx] = newgtinttype; - pointlimits[newgtidx] = newgtpointlimit; - timelimits[newgtidx] = newgttimelimit; - - // Write the new gametype name. - Gametype_Names[newgtidx] = Z_StrDup((const char *)gtname); - - // Write the constant name. if (gtconst[0] == '\0') strncpy(gtconst, gtname, MAXLINELEN); - G_AddGametypeConstant(newgtidx, (const char *)gtconst); - // Update gametype_cons_t accordingly. - G_UpdateGametypeSelections(); + newgametype->name = Z_StrDup((const char *)gtname); + newgametype->rules = newgtrules; + newgametype->constant = G_PrepareGametypeConstant((const char *)gtconst); + newgametype->tol = newgttol; + newgametype->intermission = newgtinttype; + newgametype->pointlimit = newgtpointlimit; + newgametype->timelimit = newgttimelimit; - CONS_Printf("Added gametype %s\n", Gametype_Names[newgtidx]); + gametypes[numgametypes++] = newgametype; + + CONS_Printf("Added gametype %s\n", gtname); } void readlevelheader(MYFILE *f, char * name) @@ -1128,7 +1140,7 @@ void readlevelheader(MYFILE *f, char * name) } else if (fastcmp(word, "TYPEOFLEVEL")) { - if (i) // it's just a number + if (i || isdigit(word2[0])) // it's just a number mapheaderinfo[num]->typeoflevel = (UINT32)i; else { @@ -1267,12 +1279,12 @@ void readlevelheader(MYFILE *f, char * name) else mapheaderinfo[num]->menuflags &= ~LF2_NOTIMEATTACK; } - else if (fastcmp(word, "VISITNEEDED")) + else if (fastcmp(word, "FINISHNEEDED")) { if (i || word2[0] == 'T' || word2[0] == 'Y') - mapheaderinfo[num]->menuflags |= LF2_VISITNEEDED; + mapheaderinfo[num]->menuflags |= LF2_FINISHNEEDED; else - mapheaderinfo[num]->menuflags &= ~LF2_VISITNEEDED; + mapheaderinfo[num]->menuflags &= ~LF2_FINISHNEEDED; } else if (fastcmp(word, "GRAVITY")) mapheaderinfo[num]->gravity = FLOAT_TO_FIXED(atof(word2)); @@ -2267,6 +2279,8 @@ void readunlockable(MYFILE *f, INT32 num) unlockables[num].type = SECRET_TIMEATTACK; else if (fastcmp(word2, "BREAKTHECAPSULES")) unlockables[num].type = SECRET_BREAKTHECAPSULES; + else if (fastcmp(word2, "SPECIALATTACK")) + unlockables[num].type = SECRET_SPECIALATTACK; else if (fastcmp(word2, "SOUNDTEST")) unlockables[num].type = SECRET_SOUNDTEST; else if (fastcmp(word2, "ALTTITLE")) @@ -3670,7 +3684,7 @@ sfxenum_t get_sfx(const char *word) return atoi(word); if (fastncmp("GT_",word,3)) word += 3; // take off the GT_ - for (i = 0; i < NUMGAMETYPES; i++) + for (i = 0; i < MAXGAMETYPES; i++) if (fastcmp(word, Gametype_ConstantNames[i]+3)) return i; deh_warning("Couldn't find gametype named 'GT_%s'",word); diff --git a/src/deh_tables.c b/src/deh_tables.c index 8e7bee9c1..0c369b92e 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -5791,38 +5791,36 @@ const char *const PLAYERFLAG_LIST[] = { }; const char *const GAMETYPERULE_LIST[] = { - "CAMPAIGN", - "RINGSLINGER", - "SPECTATORS", - "LIVES", - "TEAMS", - "FIRSTPERSON", + "CIRCUIT", + "BOTS", + + "BUMPERS", + "SPHERES", + "CLOSERPLAYERS", + + "BATTLESTARTS", + "PAPERITEMS", "POWERSTONES", - "TEAMFLAGS", - "FRIENDLY", - "SPECIALSTAGES", - "EMERALDTOKENS", - "EMERALDHUNT", - "RACE", - "TAG", + "KARMA", + "ITEMARROWS", + + "CAPSULES", + "CATCHER", + "ROLLINGSTART", + "SPECIALSTART", + "BOSS", + "POINTLIMIT", "TIMELIMIT", "OVERTIME", - "HURTMESSAGES", - "FRIENDLYFIRE", - "STARTCOUNTDOWN", - "HIDEFROZEN", - "BLINDFOLDED", - "RESPAWNDELAY", - "PITYSHIELD", - "DEATHPENALTY", - "NOSPECTATORSPAWN", - "DEATHMATCHSTARTS", - "SPAWNINVUL", - "SPAWNENEMIES", - "ALLOWEXIT", - "NOTITLECARD", - "CUTSCENES", + "ENCORE", + + "TEAMS", + "NOTEAMS", + "TEAMSTARTS", + + "NOMP", + "NOCUPSELECT", NULL }; @@ -6305,7 +6303,7 @@ struct int_const_s const INT_CONST[] = { {"LF2_HIDEINMENU",LF2_HIDEINMENU}, {"LF2_HIDEINSTATS",LF2_HIDEINSTATS}, {"LF2_NOTIMEATTACK",LF2_NOTIMEATTACK}, - {"LF2_VISITNEEDED",LF2_VISITNEEDED}, + {"LF2_FINISHNEEDED",LF2_FINISHNEEDED}, // Emeralds {"EMERALD_CHAOS1",EMERALD_CHAOS1}, @@ -6403,9 +6401,9 @@ struct int_const_s const INT_CONST[] = { // Intermission types {"int_none",int_none}, - {"int_race",int_race}, - {"int_battle",int_battle}, - {"int_battletime", int_battletime}, + {"int_time",int_time}, + {"int_score",int_score}, + {"int_scoreortimeattack", int_scoreortimeattack}, // Jingles (jingletype_t) {"JT_NONE",JT_NONE}, diff --git a/src/discord.c b/src/discord.c index 28381d250..9d6011c11 100644 --- a/src/discord.c +++ b/src/discord.c @@ -499,8 +499,8 @@ void DRPC_UpdatePresence(void) else { snprintf(detailstr, 48, "%s%s%s", - gametype_cons_t[gametype].strvalue, - (gametype == GT_RACE) ? va(" | %s", kartspeed_cons_t[gamespeed].strvalue) : "", + gametypes[gametype]->name, + (gametyperules & GTR_CIRCUIT) ? va(" | %s", kartspeed_cons_t[gamespeed].strvalue) : "", (encoremode == true) ? " | Encore" : "" ); discordPresence.details = detailstr; diff --git a/src/doomdef.h b/src/doomdef.h index 09a5efa4a..72ba17a84 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -664,7 +664,8 @@ UINT32 quickncasehash (const char *p, size_t n) // Compile date and time and revision. extern const char *compdate, *comptime, *comprevision, *compbranch; -extern int compuncommitted; +extern int compuncommitted, compoptimized; +extern const char *comptype; // Disabled code and code under testing // None of these that are disabled in the normal build are guaranteed to work perfectly diff --git a/src/doomstat.h b/src/doomstat.h index 3457dcd2c..39b5a73da 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -135,9 +135,9 @@ extern boolean usedCheats; extern boolean imcontinuing; // Temporary flag while continuing extern boolean metalrecording; -#define ATTACKING_NONE 0 -#define ATTACKING_TIME 1 -#define ATTACKING_CAPSULES 2 +#define ATTACKING_NONE 0 +#define ATTACKING_TIME 1 +#define ATTACKING_LAP (1<<1) extern UINT8 modeattacking; // menu demo things @@ -151,15 +151,9 @@ extern boolean addedtogame; // true after the server has added you // Only true if >1 player. netgame => multiplayer but not (multiplayer=>netgame) extern boolean multiplayer; -extern INT16 gametype; - -extern UINT32 gametyperules; -extern INT16 gametypecount; - extern UINT8 splitscreen; extern int r_splitscreen; -extern boolean circuitmap; // Does this level have 'circuit mode'? extern boolean fromlevelselect; extern boolean forceresetplayers, deferencoremode; @@ -449,71 +443,97 @@ struct mapheader_t #define LF_SECTIONRACE (1<<2) ///< Section race level #define LF_SUBTRACTNUM (1<<3) ///< Use subtractive position number (for bright levels) -#define LF2_HIDEINMENU (1<<0) ///< Hide in the multiplayer menu -#define LF2_HIDEINSTATS (1<<1) ///< Hide in the statistics screen -#define LF2_NOTIMEATTACK (1<<2) ///< Hide this map in Time Attack modes -#define LF2_VISITNEEDED (1<<3) ///< Not available in Time Attack modes until you visit the level +#define LF2_HIDEINMENU (1<<0) ///< Hide in the multiplayer menu +#define LF2_HIDEINSTATS (1<<1) ///< Hide in the statistics screen +#define LF2_NOTIMEATTACK (1<<2) ///< Hide this map in Time Attack modes +#define LF2_FINISHNEEDED (1<<3) ///< Not available in Time Attack modes until you beat the level extern mapheader_t** mapheaderinfo; extern INT32 nummapheaders, mapallocsize; // Gametypes -#define NUMGAMETYPEFREESLOTS 128 +#define NUMGAMETYPEFREESLOTS (MAXGAMETYPES-GT_FIRSTFREESLOT) +#define MAXGAMETYPELENGTH 32 enum GameType { GT_RACE = 0, GT_BATTLE, + GT_SPECIAL, + GT_VERSUS, GT_FIRSTFREESLOT, - GT_LASTFREESLOT = GT_FIRSTFREESLOT + NUMGAMETYPEFREESLOTS - 1, - NUMGAMETYPES + GT_LASTFREESLOT = 127, // Previously (GT_FIRSTFREESLOT + NUMGAMETYPEFREESLOTS - 1) - it would be necessary to rewrite VOTEMODIFIER_ENCORE to go higher than this. + MAXGAMETYPES }; -// If you alter this list, update deh_tables.c, MISC_ChangeGameTypeMenu in m_menu.c, and Gametype_Names in g_game.c +// If you alter this list, update defaultgametypes and *gametypes in g_game.c + +#define MAXTOL (1<<31) +#define NUMBASETOLNAMES (5) +#define NUMTOLNAMES (NUMBASETOLNAMES + NUMGAMETYPEFREESLOTS) + +struct gametype_t +{ + const char *name; + const char *constant; + UINT32 rules; + UINT32 tol; + UINT8 intermission; + INT32 pointlimit; + INT32 timelimit; +}; + +extern gametype_t *gametypes[MAXGAMETYPES+1]; +extern INT16 numgametypes; + +extern INT16 gametype; // Gametype rules enum GameTypeRules { // Race rules - GTR_CIRCUIT = 1, // Enables the finish line, laps, and the waypoint system. - GTR_BOTS = 1<<2, // Allows bots in this gametype. Combine with BotTiccmd hooks to make bots support your gametype. + GTR_CIRCUIT = 1, // Enables the finish line, laps, and the waypoint system. + GTR_BOTS = 1<<1, // Allows bots in this gametype. Combine with BotTiccmd hooks to make bots support your gametype. // Battle gametype rules - GTR_BUMPERS = 1<<3, // Enables the bumper health system - GTR_SPHERES = 1<<4, // Replaces rings with blue spheres - GTR_PAPERITEMS = 1<<5, // Replaces item boxes with paper item spawners - GTR_WANTED = 1<<6, // unused - GTR_KARMA = 1<<7, // Enables the Karma system if you're out of bumpers - GTR_ITEMARROWS = 1<<8, // Show item box arrows above players - GTR_CAPSULES = 1<<9, // Enables the wanted anti-camping system - GTR_BATTLESTARTS = 1<<10, // Use Battle Mode start positions. + GTR_BUMPERS = 1<<2, // Enables the bumper health system + GTR_SPHERES = 1<<3, // Replaces rings with blue spheres + GTR_CLOSERPLAYERS = 1<<4, // Buffs spindash and draft power to bring everyone together, nerfs invincibility and grow to prevent excessive combos - GTR_POINTLIMIT = 1<<11, // Reaching point limit ends the round - GTR_TIMELIMIT = 1<<12, // Reaching time limit ends the round - GTR_OVERTIME = 1<<13, // Allow overtime behavior + GTR_BATTLESTARTS = 1<<5, // Use Battle Mode start positions. + GTR_PAPERITEMS = 1<<6, // Replaces item boxes with paper item spawners + GTR_POWERSTONES = 1<<7, // Battle Emerald collectables. + GTR_KARMA = 1<<8, // Enables the Karma system if you're out of bumpers + GTR_ITEMARROWS = 1<<9, // Show item box arrows above players - // Custom gametype rules - GTR_TEAMS = 1<<14, // Teams are forced on - GTR_NOTEAMS = 1<<15, // Teams are forced off - GTR_TEAMSTARTS = 1<<16, // Use team-based start positions + // Bonus gametype rules + GTR_CAPSULES = 1<<10, // Can enter Break The Capsules mode + GTR_CATCHER = 1<<11, // UFO Catcher (only works with GTR_CIRCUIT) + GTR_ROLLINGSTART = 1<<12, // Rolling start (only works with GTR_CIRCUIT) + GTR_SPECIALSTART = 1<<13, // White fade instant start + GTR_BOSS = 1<<14, // Boss intro and spawning - // Grand Prix rules - GTR_CAMPAIGN = 1<<17, // Handles cup-based progression - GTR_LIVES = 1<<18, // Lives system, players are forced to spectate during Game Over. - GTR_SPECIALBOTS = 1<<19, // Bot difficulty gets stronger between rounds, and the rival system is enabled. + // General purpose rules + GTR_POINTLIMIT = 1<<15, // Reaching point limit ends the round + GTR_TIMELIMIT = 1<<16, // Reaching time limit ends the round + GTR_OVERTIME = 1<<17, // Allow overtime behavior + GTR_ENCORE = 1<<18, // Alternate Encore mirroring, scripting, and texture remapping - GTR_NOCUPSELECT = 1<<20, // Your maps are not selected via cup. ...mutually exclusive with GTR_CAMPAIGN. + GTR_TEAMS = 1<<19, // Teams are forced on + GTR_NOTEAMS = 1<<20, // Teams are forced off + GTR_TEAMSTARTS = 1<<21, // Use team-based start positions + + GTR_NOMP = 1<<22, // No multiplayer + GTR_NOCUPSELECT = 1<<23, // Your maps are not selected via cup. // free: to and including 1<<31 }; +// Remember to update GAMETYPERULE_LIST in deh_soc.c -// String names for gametypes -extern const char *Gametype_Names[NUMGAMETYPES]; -extern const char *Gametype_ConstantNames[NUMGAMETYPES]; +#define GTR_FORBIDMP (GTR_NOMP|GTR_CATCHER|GTR_BOSS) -// Point and time limits for every gametype -extern INT32 pointlimits[NUMGAMETYPES]; -extern INT32 timelimits[NUMGAMETYPES]; +// TODO: replace every instance +#define gametyperules (gametypes[gametype]->rules) // TypeOfLevel things enum TypeOfLevel @@ -527,9 +547,10 @@ enum TypeOfLevel // Modifiers TOL_TV = 0x0100 ///< Midnight Channel specific: draw TV like overlay on HUD }; +// Make sure to update TYPEOFLEVEL too #define MAXTOL (1<<31) -#define NUMBASETOLNAMES (4) +#define NUMBASETOLNAMES (5) #define NUMTOLNAMES (NUMBASETOLNAMES + NUMGAMETYPEFREESLOTS) struct tolinfo_t diff --git a/src/f_finale.c b/src/f_finale.c index e20deae1f..36ca452f9 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -1889,6 +1889,52 @@ void F_StartTitleScreen(void) F_CacheTitleScreen(); } +void F_VersionDrawer(void) +{ + // An adapted thing from old menus - most games have version info on the title screen now... + INT32 texty = vid.height - 10*vid.dupy; + +#define addtext(f, str) {\ + V_DrawThinString(vid.dupx, texty, V_NOSCALESTART|f, str);\ + texty -= 10*vid.dupy;\ +} + if (customversionstring[0] != '\0') + { + addtext(V_ALLOWLOWERCASE, customversionstring); + addtext(0, "Mod version:"); + } + else + { +// Development -- show revision / branch info +#if defined(TESTERS) + addtext(V_ALLOWLOWERCASE|V_SKYMAP, "Tester client"); + addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s", compdate)); +#elif defined(HOSTTESTERS) + addtext(V_ALLOWLOWERCASE|V_REDMAP, "Netgame host for testers"); + addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s", compdate)); +#elif defined(DEVELOP) + addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, comprevision); + addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, compbranch); +#else // Regular build + addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s", VERSIONSTRING)); +#endif + if (compoptimized) + { + addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s build", comptype)); + } + else + { + addtext(V_ALLOWLOWERCASE|V_ORANGEMAP, va("%s build (no optimizations)", comptype)); + } + + if (compuncommitted) + { + addtext(V_REDMAP|V_STRINGDANCE, "! UNCOMMITTED CHANGES !"); + } + } +#undef addtext +} + // (no longer) De-Demo'd Title Screen void F_TitleScreenDrawer(void) { @@ -1959,39 +2005,7 @@ void F_TitleScreenDrawer(void) V_DrawFixedPatch(0, 0, FRACUNIT, 0, kts_copyright, NULL); - // An adapted thing from old menus - most games have version info on the title screen now... - { - INT32 texty = vid.height - 10*vid.dupy; -#define addtext(f, str) {\ - V_DrawThinString(vid.dupx, texty, V_NOSCALESTART|f, str);\ - texty -= 10*vid.dupy;\ -} - if (customversionstring[0] != '\0') - { - addtext(V_ALLOWLOWERCASE, customversionstring); - addtext(0, "Mod version:"); - } - else - { -// Development -- show revision / branch info -#if defined(TESTERS) - addtext(V_ALLOWLOWERCASE|V_SKYMAP, "Tester client"); - addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s", compdate)); -#elif defined(HOSTTESTERS) - addtext(V_ALLOWLOWERCASE|V_REDMAP, "Netgame host for testers"); - addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s", compdate)); -#elif defined(DEVELOP) - addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, comprevision); - addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, compbranch); -#else // Regular build - addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s", VERSIONSTRING)); -#endif - if (compuncommitted) - addtext(V_REDMAP|V_STRINGDANCE, "! UNCOMMITTED CHANGES !"); - } -#undef addtext - } - + F_VersionDrawer(); break; } diff --git a/src/f_finale.h b/src/f_finale.h index a42228625..ca1108214 100644 --- a/src/f_finale.h +++ b/src/f_finale.h @@ -60,6 +60,8 @@ void F_EndingDrawer(void); void F_CreditTicker(void); void F_CreditDrawer(void); +void F_VersionDrawer(void); + void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean resetplayer); void F_CutsceneDrawer(void); void F_EndCutScene(void); diff --git a/src/g_demo.c b/src/g_demo.c index 5e39d83fc..6ef1a9ebb 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -119,18 +119,14 @@ demoghost *ghosts = NULL; #define DEMOVERSION 0x0007 #define DEMOHEADER "\xF0" "KartReplay" "\x0F" -#define DF_GHOST 0x01 // This demo contains ghost data too! -#define DF_TIMEATTACK 0x02 // This demo is from Time Attack and contains its final completion time & best lap! -#define DF_BREAKTHECAPSULES 0x04 // This demo is from Break the Capsules and contains its final completion time! -#define DF_ATTACKMASK 0x06 // This demo is from ??? attack and contains ??? +#define DF_ATTACKMASK (ATTACKING_TIME|ATTACKING_LAP) // This demo contains time/lap data -// 0x08 free +#define DF_GHOST 0x08 // This demo contains ghost data too! #define DF_NONETMP 0x10 // multiplayer but not netgame #define DF_LUAVARS 0x20 // this demo contains extra lua vars -#define DF_ATTACKSHIFT 1 #define DF_ENCORE 0x40 #define DF_MULTIPLAYER 0x80 // This demo was recorded in multiplayer mode! @@ -2341,10 +2337,19 @@ void G_BeginRecording(void) memset(name,0,sizeof(name)); demobuf.p = demobuf.buffer; - demoflags = DF_GHOST|(multiplayer ? DF_MULTIPLAYER : (modeattacking<name, MAXGAMETYPELENGTH); + WRITEUINT8(demobuf.p, numlaps); // file list @@ -2384,21 +2391,18 @@ void G_BeginRecording(void) // character list G_SaveDemoSkins(&demobuf.p); - switch ((demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT) + if ((demoflags & DF_ATTACKMASK)) { - case ATTACKING_NONE: // 0 - break; - case ATTACKING_TIME: // 1 - demotime_p = demobuf.p; + demotime_p = demobuf.p; + + if (demoflags & ATTACKING_TIME) WRITEUINT32(demobuf.p,UINT32_MAX); // time + if (demoflags & ATTACKING_LAP) WRITEUINT32(demobuf.p,UINT32_MAX); // lap - break; - case ATTACKING_CAPSULES: // 2 - demotime_p = demobuf.p; - WRITEUINT32(demobuf.p,UINT32_MAX); // time - break; - default: // 3 - break; + } + else + { + demotime_p = NULL; } for (i = 0; i < PRNUMCLASS; i++) @@ -2602,18 +2606,15 @@ void G_SetDemoTime(UINT32 ptime, UINT32 plap) { if (!demo.recording || !demotime_p) return; - if (demoflags & DF_TIMEATTACK) + if (demoflags & ATTACKING_TIME) { WRITEUINT32(demotime_p, ptime); + } + if (demoflags & ATTACKING_LAP) + { WRITEUINT32(demotime_p, plap); - demotime_p = NULL; - } - else if (demoflags & DF_BREAKTHECAPSULES) - { - WRITEUINT32(demotime_p, ptime); - (void)plap; - demotime_p = NULL; } + demotime_p = NULL; } // Returns bitfield: @@ -2624,7 +2625,8 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) { UINT8 *buffer,*p; UINT8 flags; - UINT32 oldtime, newtime, oldlap, newlap; + UINT32 oldtime = UINT32_MAX, newtime = UINT32_MAX; + UINT32 oldlap = UINT32_MAX, newlap = UINT32_MAX; UINT16 oldversion; size_t bufsize ATTRUNUSED; UINT8 c; @@ -2654,23 +2656,22 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) SKIPSTRING(p); // gamemap p += 16; // map md5 flags = READUINT8(p); // demoflags - p++; // gametype + SKIPSTRING(p); // gametype p++; // numlaps G_SkipDemoExtraFiles(&p); G_SkipDemoSkins(&p); - aflags = flags & (DF_TIMEATTACK|DF_BREAKTHECAPSULES); + aflags = flags & DF_ATTACKMASK; I_Assert(aflags); - if (flags & DF_TIMEATTACK) - uselaps = true; // get around uninitalized error + if (aflags & ATTACKING_LAP) + uselaps = true; - newtime = READUINT32(p); + if (aflags & ATTACKING_TIME) + newtime = READUINT32(p); if (uselaps) newlap = READUINT32(p); - else - newlap = UINT32_MAX; Z_Free(buffer); @@ -2714,7 +2715,7 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) SKIPSTRING(p); // gamemap p += 16; // mapmd5 flags = READUINT8(p); - p++; // gametype + SKIPSTRING(p); // gametype p++; // numlaps G_SkipDemoExtraFiles(&p); if (!(flags & aflags)) @@ -2726,11 +2727,10 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) G_SkipDemoSkins(&p); - oldtime = READUINT32(p); + if (flags & ATTACKING_TIME) + oldtime = READUINT32(p); if (uselaps) oldlap = READUINT32(p); - else - oldlap = 0; Z_Free(buffer); @@ -2760,7 +2760,7 @@ void G_LoadDemoInfo(menudemo_t *pdemo) UINT8 version, subversion, pdemoflags, worknumskins, skinid; democharlist_t *skinlist = NULL; UINT16 pdemoversion, count; - char mapname[MAXMAPLUMPNAME]; + char mapname[MAXMAPLUMPNAME],gtname[MAXGAMETYPELENGTH]; INT32 i; if (!FIL_ReadFile(pdemo->filepath, &infobuffer)) @@ -2824,7 +2824,9 @@ void G_LoadDemoInfo(menudemo_t *pdemo) return; } - pdemo->gametype = READUINT8(info_p); + READSTRINGN(info_p, gtname, sizeof(gtname)); // gametype + pdemo->gametype = G_GetGametypeByName(gtname); + pdemo->numlaps = READUINT8(info_p); pdemo->addonstatus = G_CheckDemoExtraFiles(&info_p, true); @@ -2936,9 +2938,11 @@ void G_DeferedPlayDemo(const char *name) void G_DoPlayDemo(char *defdemoname) { - UINT8 i, p, numslots = 0; + INT32 i; + UINT8 p, numslots = 0; lumpnum_t l; - char color[MAXCOLORNAME+1],follower[17],mapname[MAXMAPLUMPNAME],*n,*pdemoname; + char color[MAXCOLORNAME+1],follower[17],mapname[MAXMAPLUMPNAME],gtname[MAXGAMETYPELENGTH]; + char *n,*pdemoname; UINT8 availabilities[MAXPLAYERS][MAXAVAILABILITY]; UINT8 version,subversion; UINT32 randseed[PRNUMCLASS]; @@ -2955,6 +2959,7 @@ void G_DoPlayDemo(char *defdemoname) follower[16] = '\0'; color[MAXCOLORNAME] = '\0'; + gtname[MAXGAMETYPELENGTH-1] = '\0'; // No demo name means we're restarting the current demo if (defdemoname == NULL) @@ -3064,8 +3069,22 @@ void G_DoPlayDemo(char *defdemoname) demobuf.p += 16; // mapmd5 demoflags = READUINT8(demobuf.p); - gametype = READUINT8(demobuf.p); - G_SetGametype(gametype); + + READSTRINGN(demobuf.p, gtname, sizeof(gtname)); // gametype + i = G_GetGametypeByName(gtname); + if (i < 0) + { + snprintf(msg, 1024, M_GetText("%s is in a gametype that is not currently loaded and cannot be played.\n"), pdemoname); + CONS_Alert(CONS_ERROR, "%s", msg); + M_StartMessage(msg, NULL, MM_NOTHING); + Z_Free(pdemoname); + Z_Free(demobuf.buffer); + demo.playback = false; + demo.title = false; + return; + } + G_SetGametype(i); + numlaps = READUINT8(demobuf.p); if (demo.title) // Titledemos should always play and ought to always be compatible with whatever wadlist is running. @@ -3138,7 +3157,7 @@ void G_DoPlayDemo(char *defdemoname) return; } - modeattacking = (demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT; + modeattacking = (demoflags & DF_ATTACKMASK); multiplayer = !!(demoflags & DF_MULTIPLAYER); demo.netgame = (multiplayer && !(demoflags & DF_NONETMP)); CON_ToggleOff(); @@ -3146,21 +3165,10 @@ void G_DoPlayDemo(char *defdemoname) hu_demotime = UINT32_MAX; hu_demolap = UINT32_MAX; - switch (modeattacking) - { - case ATTACKING_NONE: // 0 - break; - case ATTACKING_TIME: // 1 - hu_demotime = READUINT32(demobuf.p); - hu_demolap = READUINT32(demobuf.p); - break; - case ATTACKING_CAPSULES: // 2 - hu_demotime = READUINT32(demobuf.p); - break; - default: // 3 - modeattacking = ATTACKING_NONE; - break; - } + if (modeattacking & ATTACKING_TIME) + hu_demotime = READUINT32(demobuf.p); + if (modeattacking & ATTACKING_LAP) + hu_demolap = READUINT32(demobuf.p); // Random seed for (i = 0; i < PRNUMCLASS; i++) @@ -3534,7 +3542,7 @@ void G_AddGhost(char *defdemoname) return; } - p++; // gametype + SKIPSTRING(p); // gametype p++; // numlaps G_SkipDemoExtraFiles(&p); // Don't wanna modify the file list for ghosts. @@ -3547,19 +3555,10 @@ void G_AddGhost(char *defdemoname) return; } - switch ((flags & DF_ATTACKMASK)>>DF_ATTACKSHIFT) - { - case ATTACKING_NONE: // 0 - break; - case ATTACKING_TIME: // 1 - p += 8; // demo time, lap - break; - case ATTACKING_CAPSULES: // 2 - p += 4; // demo time - break; - default: // 3 - break; - } + if (flags & ATTACKING_TIME) + p += 4; + if (flags & ATTACKING_LAP) + p += 4; for (i = 0; i < PRNUMCLASS; i++) { @@ -3589,9 +3588,10 @@ void G_AddGhost(char *defdemoname) p++; // player number - doesn't really need to be checked, TODO maybe support adding multiple players' ghosts at once // any invalidating flags? - if ((READUINT8(p) & (DEMO_SPECTATOR|DEMO_BOT)) != 0) + i = READUINT8(p); + if ((i & (DEMO_SPECTATOR|DEMO_BOT)) != 0) { - CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Invalid player slot.\n"), pdemoname); + CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Invalid player slot (spectator/bot)\n"), pdemoname); Z_Free(skinlist); Z_Free(pdemoname); Z_Free(buffer); @@ -3602,6 +3602,8 @@ void G_AddGhost(char *defdemoname) M_Memcpy(name, p, 16); p += 16; + p += MAXAVAILABILITY; + // Skin i = READUINT8(p); if (i < worknumskins) @@ -3622,7 +3624,7 @@ void G_AddGhost(char *defdemoname) if (READUINT8(p) != 0xFF) { - CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Invalid player slot.\n"), pdemoname); + CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Invalid player slot (bad terminator)\n"), pdemoname); Z_Free(skinlist); Z_Free(pdemoname); Z_Free(buffer); @@ -3757,25 +3759,16 @@ void G_UpdateStaffGhostName(lumpnum_t l) goto fail; // we don't NEED to do it here, but whatever } - p++; // Gametype + SKIPSTRING(p); // gametype p++; // numlaps G_SkipDemoExtraFiles(&p); G_SkipDemoSkins(&p); - switch ((flags & DF_ATTACKMASK)>>DF_ATTACKSHIFT) - { - case ATTACKING_NONE: // 0 - break; - case ATTACKING_TIME: // 1 - p += 8; // demo time, lap - break; - case ATTACKING_CAPSULES: // 2 - p += 4; // demo time - break; - default: // 3 - break; - } + if (flags & ATTACKING_TIME) + p += 4; + if (flags & ATTACKING_LAP) + p += 4; for (i = 0; i < PRNUMCLASS; i++) { diff --git a/src/g_demo.h b/src/g_demo.h index d24ed3bbb..17ea331cf 100644 --- a/src/g_demo.h +++ b/src/g_demo.h @@ -86,7 +86,7 @@ struct menudemo_t { char title[65]; // Null-terminated for string prints UINT16 map; UINT8 addonstatus; // What do we need to do addon-wise to play this demo? - UINT8 gametype; + INT16 gametype; SINT8 kartspeed; // Add OR DF_ENCORE for encore mode, idk UINT8 numlaps; diff --git a/src/g_game.c b/src/g_game.c index 3c11de495..6cea62b1a 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -317,9 +317,8 @@ typedef struct { INT16 *mapbuffer; // Pointer to zone memory INT32 lastnummapheaders; // Reset if nummapheaders != this - UINT8 counttogametype; // Time to gametype change event } randmaps_t; -static randmaps_t randmaps = {NULL, 0, 0}; +static randmaps_t randmaps = {NULL, 0}; static void G_ResetRandMapBuffer(void) { @@ -329,7 +328,6 @@ static void G_ResetRandMapBuffer(void) randmaps.mapbuffer = Z_Malloc(randmaps.lastnummapheaders * sizeof(INT16), PU_STATIC, NULL); for (i = 0; i < randmaps.lastnummapheaders; i++) randmaps.mapbuffer[i] = -1; - //intentionally not resetting randmaps.counttogametype here } typedef struct joystickvector2_s @@ -517,13 +515,18 @@ static void G_UpdateRecordReplays(void) players[consoleplayer].realtime = UINT32_MAX; } - if (((mapheaderinfo[gamemap-1]->mainrecord->time == 0) || (players[consoleplayer].realtime < mapheaderinfo[gamemap-1]->mainrecord->time)) - && (players[consoleplayer].realtime < UINT32_MAX)) // DNF + if (modeattacking & ATTACKING_TIME) { - mapheaderinfo[gamemap-1]->mainrecord->time = players[consoleplayer].realtime; + if (((mapheaderinfo[gamemap-1]->mainrecord->time == 0) || (players[consoleplayer].realtime < mapheaderinfo[gamemap-1]->mainrecord->time)) + && (players[consoleplayer].realtime < UINT32_MAX)) // DNF + mapheaderinfo[gamemap-1]->mainrecord->time = players[consoleplayer].realtime; + } + else + { + mapheaderinfo[gamemap-1]->mainrecord->time = 0; } - if (modeattacking == ATTACKING_TIME) + if (modeattacking & ATTACKING_LAP) { if ((mapheaderinfo[gamemap-1]->mainrecord->lap == 0) || (bestlap < mapheaderinfo[gamemap-1]->mainrecord->lap)) mapheaderinfo[gamemap-1]->mainrecord->lap = bestlap; @@ -546,27 +549,32 @@ static void G_UpdateRecordReplays(void) strcat(gpath, PATHSEP); strcat(gpath, G_BuildMapName(gamemap)); - snprintf(lastdemo, 255, "%s-%s-last.lmp", gpath, cv_chooseskin.string); + snprintf(lastdemo, 255, "%s-%s-last.lmp", gpath, cv_skin[0].string); - gpath = Z_StrDup(gpath); - - if (FIL_FileExists(lastdemo)) + if (modeattacking != ATTACKING_NONE && FIL_FileExists(lastdemo)) { UINT8 *buf; - size_t len = FIL_ReadFile(lastdemo, &buf); + size_t len; - snprintf(bestdemo, 255, "%s-%s-time-best.lmp", gpath, cv_chooseskin.string); - if (!FIL_FileExists(bestdemo) || G_CmpDemoTime(bestdemo, lastdemo) & 1) - { // Better time, save this demo. - if (FIL_FileExists(bestdemo)) - remove(bestdemo); - FIL_WriteFile(bestdemo, buf, len); - CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW RECORD TIME!"), M_GetText("Saved replay as"), bestdemo); + gpath = Z_StrDup(gpath); + + len = FIL_ReadFile(lastdemo, &buf); + + if (modeattacking & ATTACKING_TIME) + { + snprintf(bestdemo, 255, "%s-%s-time-best.lmp", gpath, cv_skin[0].string); + if (!FIL_FileExists(bestdemo) || G_CmpDemoTime(bestdemo, lastdemo) & 1) + { // Better time, save this demo. + if (FIL_FileExists(bestdemo)) + remove(bestdemo); + FIL_WriteFile(bestdemo, buf, len); + CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW RECORD TIME!"), M_GetText("Saved replay as"), bestdemo); + } } - if (modeattacking == ATTACKING_TIME) + if (modeattacking & ATTACKING_LAP) { - snprintf(bestdemo, 255, "%s-%s-lap-best.lmp", gpath, cv_chooseskin.string); + snprintf(bestdemo, 255, "%s-%s-lap-best.lmp", gpath, cv_skin[0].string); if (!FIL_FileExists(bestdemo) || G_CmpDemoTime(bestdemo, lastdemo) & (1<<1)) { // Better lap time, save this demo. if (FIL_FileExists(bestdemo)) @@ -579,9 +587,9 @@ static void G_UpdateRecordReplays(void) //CONS_Printf("%s '%s'\n", M_GetText("Saved replay as"), lastdemo); Z_Free(buf); - } - Z_Free(gpath); + Z_Free(gpath); + } // Check emblems when level data is updated if ((earnedEmblems = M_CheckLevelEmblems())) @@ -1409,7 +1417,7 @@ void G_StartTitleCard(void) { // The title card has been disabled for this map. // Oh well. - if (!G_IsTitleCardAvailable() || demo.rewinding) + if (demo.rewinding || !G_IsTitleCardAvailable()) { WipeStageTitle = false; return; @@ -1424,9 +1432,9 @@ void G_StartTitleCard(void) // play the sound { sfxenum_t kstart = sfx_kstart; - if (bossinfo.boss) + if (K_CheckBossIntro() == true) kstart = sfx_ssa021; - else if (encoremode) + else if (encoremode == true) kstart = sfx_ruby2; S_StartSound(NULL, kstart); } @@ -1473,11 +1481,17 @@ void G_PreLevelTitleCard(void) // boolean G_IsTitleCardAvailable(void) { -#if 0 + // Overwrites all other title card exceptions. + if (K_CheckBossIntro() == true) + return true; + // The current level has no name. if (!mapheaderinfo[gamemap-1]->lvlttl[0]) return false; -#endif + + // Instant white fade. + if (gametyperules & GTR_SPECIALSTART) + return false; // The title card is available. return true; @@ -2789,22 +2803,9 @@ mapthing_t *G_FindMapStart(INT32 playernum) if (!playeringame[playernum]) return NULL; - // -- Spectators -- - // Order in platform gametypes: Race->DM->CTF - // And, with deathmatch starts: DM->CTF->Race - if (players[playernum].spectator) - { - // In platform gametypes, spawn in Co-op starts first - // Overriden by GTR_BATTLESTARTS. - if (gametyperules & GTR_BATTLESTARTS && bossinfo.boss == false) - spawnpoint = G_FindBattleStartOrFallback(playernum); - else - spawnpoint = G_FindRaceStartOrFallback(playernum); - } - - // -- Grand Prix / Time Attack -- + // -- Time Attack -- // Order: Race->DM->CTF - else if (grandprixinfo.gp || modeattacking) + if (K_TimeAttackRules() == true) spawnpoint = G_FindRaceStartOrFallback(playernum); // -- CTF -- @@ -2909,17 +2910,20 @@ void G_ExitLevel(void) if (gamestate == GS_LEVEL) { UINT8 i; - boolean youlost = false; - if (bossinfo.boss == true) + boolean doretry = false; + + if (!G_GametypeUsesLives()) + ; // never force a retry + else if (specialstageinfo.valid == true || (gametyperules & GTR_BOSS)) { - youlost = true; + doretry = true; for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && !players[i].spectator && !players[i].bot) { - if (players[i].bumpers > 0) + if (!K_IsPlayerLosing(&players[i])) { - youlost = false; + doretry = false; break; } } @@ -2927,10 +2931,10 @@ void G_ExitLevel(void) } else if (grandprixinfo.gp == true && grandprixinfo.eventmode == GPEVENT_NONE) { - youlost = (grandprixinfo.wonround != true); + doretry = (grandprixinfo.wonround != true); } - if (youlost) + if (doretry) { // You didn't win... @@ -2997,28 +3001,98 @@ void G_ExitLevel(void) } } -// See also the enum GameType in doomstat.h -const char *Gametype_Names[NUMGAMETYPES] = +static gametype_t defaultgametypes[] = { - "Race", // GT_RACE - "Battle" // GT_BATTLE + // GT_RACE + { + "Race", + "GT_RACE", + GTR_CIRCUIT|GTR_BOTS|GTR_ENCORE, + TOL_RACE, + int_time, + 0, + 0, + }, + + // GT_BATTLE + { + "Battle", + "GT_BATTLE", + GTR_SPHERES|GTR_BUMPERS|GTR_PAPERITEMS|GTR_POWERSTONES|GTR_KARMA|GTR_ITEMARROWS|GTR_CAPSULES|GTR_BATTLESTARTS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_CLOSERPLAYERS, + TOL_BATTLE, + int_scoreortimeattack, + 0, + 2, + }, + + // GT_SPECIAL + { + "Special", + "GT_SPECIAL", + GTR_CATCHER|GTR_SPECIALSTART|GTR_ROLLINGSTART|GTR_CIRCUIT, + TOL_SPECIAL, + int_time, + 0, + 0, + }, + + // GT_VERSUS + { + "Versus", + "GT_VERSUS", + GTR_BOSS|GTR_SPHERES|GTR_BUMPERS|GTR_POINTLIMIT|GTR_CLOSERPLAYERS|GTR_NOCUPSELECT|GTR_ENCORE, + TOL_BOSS, + int_scoreortimeattack, + 0, + 0, + }, }; -// For dehacked -const char *Gametype_ConstantNames[NUMGAMETYPES] = +gametype_t *gametypes[MAXGAMETYPES+1] = { - "GT_RACE", // GT_RACE - "GT_BATTLE" // GT_BATTLE + &defaultgametypes[GT_RACE], + &defaultgametypes[GT_BATTLE], + &defaultgametypes[GT_SPECIAL], + &defaultgametypes[GT_VERSUS], }; -// Gametype rules -UINT32 gametypedefaultrules[NUMGAMETYPES] = +// +// G_GetGametypeByName +// +// Returns the number for the given gametype name string, or -1 if not valid. +// +INT32 G_GetGametypeByName(const char *gametypestr) { - // Race - GTR_CAMPAIGN|GTR_CIRCUIT|GTR_BOTS, - // Battle - GTR_SPHERES|GTR_BUMPERS|GTR_PAPERITEMS|GTR_KARMA|GTR_ITEMARROWS|GTR_CAPSULES|GTR_BATTLESTARTS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME -}; + INT32 i = 0; + + while (gametypes[i] != NULL) + { + if (!stricmp(gametypestr, gametypes[i]->name)) + return i; + i++; + } + + return -1; // unknown gametype +} + +// +// G_GuessGametypeByTOL +// +// Returns the first valid number for the given typeoflevel, or -1 if not valid. +// +INT32 G_GuessGametypeByTOL(UINT32 tol) +{ + INT32 i = 0; + + while (gametypes[i] != NULL) + { + if (tol & gametypes[i]->tol) + return i; + i++; + } + + return -1; // unknown gametype +} // // G_SetGametype @@ -3027,41 +3101,26 @@ UINT32 gametypedefaultrules[NUMGAMETYPES] = // void G_SetGametype(INT16 gtype) { + if (gtype < 0 || gtype > numgametypes) + { + I_Error("G_SetGametype: Bad gametype change %d (was %d/\"%s\")", gtype, gametype, gametypes[gametype]->name); + } + gametype = gtype; - gametyperules = gametypedefaultrules[gametype]; } // -// G_AddGametype -// -// Add a gametype. Returns the new gametype number. -// -INT16 G_AddGametype(UINT32 rules) -{ - INT16 newgtype = gametypecount; - gametypecount++; - - // Set gametype rules. - gametypedefaultrules[newgtype] = rules; - Gametype_Names[newgtype] = "???"; - - // Update gametype_cons_t accordingly. - G_UpdateGametypeSelections(); - - return newgtype; -} - -// -// G_AddGametypeConstant +// G_PrepareGametypeConstant // // Self-explanatory. Filters out "bad" characters. // -void G_AddGametypeConstant(INT16 gtype, const char *newgtconst) +char *G_PrepareGametypeConstant(const char *newgtconst) { size_t r = 0; // read size_t w = 0; // write - char *gtconst = Z_Calloc(strlen(newgtconst) + 4, PU_STATIC, NULL); - char *tmpconst = Z_Calloc(strlen(newgtconst) + 1, PU_STATIC, NULL); + size_t len = strlen(newgtconst); + char *gtconst = Z_Calloc(len + 4, PU_STATIC, NULL); + char *tmpconst = Z_Calloc(len + 1, PU_STATIC, NULL); // Copy the gametype name. strcpy(tmpconst, newgtconst); @@ -3121,42 +3180,10 @@ void G_AddGametypeConstant(INT16 gtype, const char *newgtconst) // Free the temporary string. Z_Free(tmpconst); - // Finally, set the constant string. - Gametype_ConstantNames[gtype] = gtconst; + // Finally, return the constant string. + return gtconst; } -// -// G_UpdateGametypeSelections -// -// Updates gametype_cons_t. -// -void G_UpdateGametypeSelections(void) -{ - INT32 i; - for (i = 0; i < gametypecount; i++) - { - gametype_cons_t[i].value = i; - gametype_cons_t[i].strvalue = Gametype_Names[i]; - } - gametype_cons_t[NUMGAMETYPES].value = 0; - gametype_cons_t[NUMGAMETYPES].strvalue = NULL; -} - -// Gametype rankings -INT16 gametyperankings[NUMGAMETYPES] = -{ - GT_RACE, - GT_BATTLE, -}; - -// Gametype to TOL (Type Of Level) -UINT32 gametypetol[NUMGAMETYPES] = -{ - TOL_RACE, // Race - TOL_BATTLE, // Battle - TOL_TV, // Midnight Channel effect -}; - tolinfo_t TYPEOFLEVEL[NUMTOLNAMES] = { {"RACE",TOL_RACE}, {"BATTLE",TOL_BATTLE}, @@ -3183,51 +3210,6 @@ void G_AddTOL(UINT32 newtol, const char *tolname) TYPEOFLEVEL[i].flag = newtol; } -// -// G_AddGametypeTOL -// -// Assigns a type of level to a gametype. -// -void G_AddGametypeTOL(INT16 gtype, UINT32 newtol) -{ - gametypetol[gtype] = newtol; -} - -// -// G_GetGametypeByName -// -// Returns the number for the given gametype name string, or -1 if not valid. -// -INT32 G_GetGametypeByName(const char *gametypestr) -{ - INT32 i; - - for (i = 0; i < gametypecount; i++) - if (!stricmp(gametypestr, Gametype_Names[i])) - return i; - - return -1; // unknown gametype -} - -// -// G_IsSpecialStage -// -// Returns TRUE if -// the given map is a special stage. -// -boolean G_IsSpecialStage(INT32 mapnum) -{ - mapnum--; // gamemap-based to 0 indexed - - if (mapnum > nummapheaders || !mapheaderinfo[mapnum]) - return false; - - if (!mapheaderinfo[mapnum]->cup || mapheaderinfo[mapnum]->cup->cachedlevels[CUPCACHE_SPECIAL] != mapnum) - return false; - - return true; -} - // // G_GametypeUsesLives // @@ -3240,13 +3222,7 @@ boolean G_GametypeUsesLives(void) return false; if ((grandprixinfo.gp == true) // In Grand Prix - && (gametype == GT_RACE) // NOT in bonus round - && grandprixinfo.eventmode == GPEVENT_NONE) // NOT in bonus - { - return true; - } - - if (bossinfo.boss == true) // Fighting a boss? + && grandprixinfo.eventmode != GPEVENT_BONUS) // NOT in bonus round { return true; } @@ -3291,100 +3267,30 @@ boolean G_GametypeHasSpectators(void) // // G_SometimesGetDifferentGametype // -// Oh, yeah, and we sometimes flip encore mode on here too. +// Because gametypes are no longer on the vote screen, all this does is sometimes flip encore mode. +// However, it remains a seperate function for long-term possibility. // -INT16 G_SometimesGetDifferentGametype(UINT8 prefgametype) +INT16 G_SometimesGetDifferentGametype(void) { - // Most of the gametype references in this condition are intentionally not prefgametype. - // This is so a server CAN continue playing a gametype if they like the taste of it. - // The encore check needs prefgametype so can't use G_RaceGametype... boolean encorepossible = ((M_SecretUnlocked(SECRET_ENCORE, false) || encorescramble == 1) - && ((gametyperules|gametypedefaultrules[prefgametype]) & GTR_CIRCUIT)); + && (gametyperules & GTR_ENCORE)); UINT8 encoremodifier = 0; // -- the below is only necessary if you want to use randmaps.mapbuffer here //if (randmaps.lastnummapheaders != nummapheaders) //G_ResetRandMapBuffer(); - if (encorepossible) + // FORCE to what was scrambled on intermission? + if (encorepossible && encorescramble != -1) { - if (encorescramble != -1) + // FORCE to what was scrambled on intermission + if ((encorescramble != 0) != (cv_kartencore.value == 1)) { - encorepossible = (boolean)encorescramble; // FORCE to what was scrambled on intermission - } - else - { - switch (cv_kartvoterulechanges.value) - { - case 3: // always - encorepossible = true; - break; - case 2: // frequent - encorepossible = M_RandomChance(FRACUNIT>>1); - break; - case 1: // sometimes - encorepossible = M_RandomChance(FRACUNIT>>2); - break; - default: - break; - } - } - if (encorepossible != (cv_kartencore.value == 1)) encoremodifier = VOTEMODIFIER_ENCORE; + } } - if (!cv_kartvoterulechanges.value) // never - return (gametype|encoremodifier); - - if (randmaps.counttogametype > 0 && (cv_kartvoterulechanges.value != 3)) - { - randmaps.counttogametype--; - return (gametype|encoremodifier); - } - - switch (cv_kartvoterulechanges.value) // okay, we're having a gametype change! when's the next one, luv? - { - case 1: // sometimes - randmaps.counttogametype = 5; // per "cup" - break; - default: - // fallthrough - happens when clearing buffer, but needs a reasonable countdown if cvar is modified - case 2: // frequent - randmaps.counttogametype = 2; // ...every 1/2th-ish cup? - break; - } - - // Only this response is prefgametype-based. - // todo custom gametypes - if (prefgametype == GT_BATTLE) - { - // Intentionally does not use encoremodifier! - if (cv_kartencore.value == 1) - return (GT_RACE|VOTEMODIFIER_ENCORE); - return (GT_RACE); - } - // This might appear wrong HERE, but the game will display the Encore possibility on the second voting choice instead. - return (GT_BATTLE|encoremodifier); -} - -// -// G_GetGametypeColor -// -// Pretty and consistent ^u^ -// See also M_GetGametypeColor (if that still exists). -// -UINT8 G_GetGametypeColor(INT16 gt) -{ - if (modeattacking) // == ATTACKING_RECORD - return orangemap[0]; - - if (gt == GT_BATTLE) - return redmap[0]; - - if (gt == GT_RACE) - return skymap[0]; - - return 255; // FALLBACK + return (gametype|encoremodifier); } /** Get the typeoflevel flag needed to indicate support of a gametype. @@ -3394,7 +3300,9 @@ UINT8 G_GetGametypeColor(INT16 gt) */ UINT32 G_TOLFlag(INT32 pgametype) { - return gametypetol[pgametype]; + if (pgametype >= 0 && pgametype < numgametypes) + return gametypes[pgametype]->tol; + return 0; } INT16 G_GetFirstMapOfGametype(UINT8 pgametype) @@ -3405,7 +3313,7 @@ INT16 G_GetFirstMapOfGametype(UINT8 pgametype) templevelsearch.cup = NULL; templevelsearch.typeoflevel = G_TOLFlag(pgametype); - templevelsearch.cupmode = (!(gametypedefaultrules[pgametype] & GTR_NOCUPSELECT)); + templevelsearch.cupmode = (!(gametypes[pgametype]->rules & GTR_NOCUPSELECT)); templevelsearch.timeattack = false; templevelsearch.checklocked = true; @@ -3497,7 +3405,7 @@ tryagain: if (!mapheaderinfo[ix] || mapheaderinfo[ix]->lumpnum == LUMPERROR) continue; - if ((mapheaderinfo[ix]->typeoflevel & tolflags) != tolflags + if (!(mapheaderinfo[ix]->typeoflevel & tolflags) || ix == pprevmap || M_MapLocked(ix+1) || (usehellmaps != (mapheaderinfo[ix]->menuflags & LF2_HIDEINMENU))) // this is bad @@ -3716,7 +3624,6 @@ static void G_HandleSaveLevel(void) static void G_GetNextMap(void) { - boolean spec = G_IsSpecialStage(prevmap+1); INT32 i; // go to next level @@ -3733,7 +3640,7 @@ static void G_GetNextMap(void) } else { - INT32 lastgametype = gametype; + INT32 lastgametype = gametype, newgametype = GT_RACE; // 5 levels, 2 bonus stages: after rounds 2 and 4 (but flexible enough to accomodate other solutions) UINT8 bonusmodulo = (grandprixinfo.cup->numlevels+1)/(grandprixinfo.cup->numbonus+1); UINT8 bonusindex = (grandprixinfo.roundnum / bonusmodulo) - 1; @@ -3750,9 +3657,6 @@ static void G_GetNextMap(void) G_SetGametype(GT_RACE); if (gametype != lastgametype) D_GameTypeChanged(lastgametype); - - specialStage.active = false; - bossinfo.boss = false; } // Special stage else if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) @@ -3773,11 +3677,11 @@ static void G_GetNextMap(void) if (totaltotalring >= 50) { const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_SPECIAL]; - if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum] - && mapheaderinfo[cupLevelNum]->typeoflevel & (TOL_SPECIAL|TOL_BOSS|TOL_BATTLE)) + if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum]) { grandprixinfo.eventmode = GPEVENT_SPECIAL; nextmap = cupLevelNum; + newgametype = G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel); } } } @@ -3788,37 +3692,27 @@ static void G_GetNextMap(void) // todo any other condition? { const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_BONUS + bonusindex]; - if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum] - && mapheaderinfo[cupLevelNum]->typeoflevel & (TOL_BOSS|TOL_BATTLE)) + if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum]) { grandprixinfo.eventmode = GPEVENT_BONUS; nextmap = cupLevelNum; + newgametype = G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel); } } } + if (newgametype == -1) + { + // Don't permit invalid changes. + grandprixinfo.eventmode = GPEVENT_NONE; + newgametype = gametype; + } + if (grandprixinfo.eventmode != GPEVENT_NONE) { - // nextmap is set above - const INT32 newtol = mapheaderinfo[nextmap]->typeoflevel; - - if (newtol & TOL_SPECIAL) - { - specialStage.active = true; - specialStage.encore = grandprixinfo.encore; - } - else //(if newtol & (TOL_BATTLE|TOL_BOSS)) -- safe to assume?? - { - G_SetGametype(GT_BATTLE); - if (gametype != lastgametype) - D_GameTypeChanged(lastgametype); - if (newtol & TOL_BOSS) - { - K_ResetBossInfo(); - bossinfo.boss = true; - bossinfo.encore = grandprixinfo.encore; - } - } + G_SetGametype(newgametype); + if (gametype != lastgametype) + D_GameTypeChanged(lastgametype); } else if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) // On final map { @@ -3842,10 +3736,6 @@ static void G_GetNextMap(void) } } } - else if (bossinfo.boss == true) - { - nextmap = NEXTMAP_TITLE; // temporary - } else { UINT32 tolflag = G_TOLFlag(gametype); @@ -3984,7 +3874,9 @@ static void G_GetNextMap(void) if (nextmap == NEXTMAP_INVALID || (nextmap < NEXTMAP_SPECIAL && (nextmap >= nummapheaders || !mapheaderinfo[nextmap] || mapheaderinfo[nextmap]->lumpnum == LUMPERROR))) I_Error("G_GetNextMap: Internal map ID %d not found (nummapheaders = %d)\n", nextmap, nummapheaders); +#if 0 // This is a surprise tool that will help us later. if (!spec) +#endif //#if 0 lastmap = nextmap; } @@ -4108,7 +4000,7 @@ void G_AfterIntermission(void) G_HandleSaveLevel(); } - if ((gametyperules & GTR_CAMPAIGN) && mapheaderinfo[prevmap]->cutscenenum && !modeattacking && skipstats <= 1 && (gamecomplete || !(marathonmode & MA_NOCUTSCENES))) // Start a custom cutscene. + if ((grandprixinfo.gp == true) && mapheaderinfo[prevmap]->cutscenenum && !modeattacking && skipstats <= 1 && (gamecomplete || !(marathonmode & MA_NOCUTSCENES))) // Start a custom cutscene. F_StartCustomCutscene(mapheaderinfo[prevmap]->cutscenenum-1, false, false); else { @@ -4245,7 +4137,7 @@ void G_EndGame(void) } // Only do evaluation and credits in singleplayer contexts - if (!netgame && (gametyperules & GTR_CAMPAIGN)) + if (!netgame && grandprixinfo.gp == true) { if (nextmap == NEXTMAP_CEREMONY) // end game with ceremony { @@ -4883,7 +4775,6 @@ cleanup: void G_DeferedInitNew(boolean pencoremode, INT32 map, INT32 pickedchar, UINT8 ssplayers, boolean FLS) { UINT16 color = SKINCOLOR_NONE; - INT32 dogametype; paused = false; @@ -4894,17 +4785,8 @@ void G_DeferedInitNew(boolean pencoremode, INT32 map, INT32 pickedchar, UINT8 ss G_ResetRandMapBuffer(); - if ((modeattacking == ATTACKING_CAPSULES) || (bossinfo.boss == true)) - { - dogametype = GT_BATTLE; - } - else - { - dogametype = GT_RACE; - } - // this leave the actual game if needed - SV_StartSinglePlayerServer(dogametype, false); + SV_StartSinglePlayerServer(gametype, false); if (splitscreen != ssplayers) { @@ -5004,7 +4886,7 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr automapactive = false; imcontinuing = false; - if ((gametyperules & GTR_CAMPAIGN) && !skipprecutscene && mapheaderinfo[gamemap-1]->precutscenenum && !modeattacking && !(marathonmode & MA_NOCUTSCENES)) // Start a custom cutscene. + if ((grandprixinfo.gp == true) && !skipprecutscene && mapheaderinfo[gamemap-1]->precutscenenum && !modeattacking && !(marathonmode & MA_NOCUTSCENES)) // Start a custom cutscene. F_StartCustomCutscene(mapheaderinfo[gamemap-1]->precutscenenum-1, true, resetplayer); else { diff --git a/src/g_game.h b/src/g_game.h index cc4787ea9..de60bee49 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -183,24 +183,17 @@ void G_SaveGame(UINT32 slot, INT16 mapnum); void G_SaveGameOver(UINT32 slot, boolean modifylives); -extern UINT32 gametypedefaultrules[NUMGAMETYPES]; -extern UINT32 gametypetol[NUMGAMETYPES]; -extern INT16 gametyperankings[NUMGAMETYPES]; - void G_SetGametype(INT16 gametype); -INT16 G_AddGametype(UINT32 rules); -void G_AddGametypeConstant(INT16 gtype, const char *newgtconst); -void G_UpdateGametypeSelections(void); +char *G_PrepareGametypeConstant(const char *newgtconst); void G_AddTOL(UINT32 newtol, const char *tolname); -void G_AddGametypeTOL(INT16 gtype, UINT32 newtol); INT32 G_GetGametypeByName(const char *gametypestr); -boolean G_IsSpecialStage(INT32 mapnum); +INT32 G_GuessGametypeByTOL(UINT32 tol); + boolean G_GametypeUsesLives(void); boolean G_GametypeHasTeams(void); boolean G_GametypeHasSpectators(void); #define VOTEMODIFIER_ENCORE 0x80 -INT16 G_SometimesGetDifferentGametype(UINT8 prefgametype); -UINT8 G_GetGametypeColor(INT16 gt); +INT16 G_SometimesGetDifferentGametype(void); void G_ExitLevel(void); void G_NextLevel(void); void G_Continue(void); diff --git a/src/hu_stuff.c b/src/hu_stuff.c index bbccb232a..ed0ab076f 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -16,7 +16,7 @@ #include "hu_stuff.h" #include "font.h" -#include "k_menu.h" // gametype_cons_t +#include "k_menu.h" // highlightflags #include "m_cond.h" // emblems #include "m_misc.h" // word jumping @@ -55,7 +55,8 @@ // SRB2Kart #include "s_sound.h" // song credits #include "k_kart.h" -#include "k_boss.h" +#include "k_battle.h" +#include "k_grandprix.h" #include "k_color.h" #include "k_hud.h" #include "r_fps.h" @@ -2392,60 +2393,110 @@ static inline void HU_DrawSpectatorTicker(void) static void HU_DrawRankings(void) { playersort_t tab[MAXPLAYERS]; - INT32 i, j, scorelines, hilicol, numplayersingame = 0; + INT32 i, j, scorelines, numplayersingame = 0, hilicol = highlightflags; boolean completed[MAXPLAYERS]; UINT32 whiteplayer = MAXPLAYERS; + boolean timedone = false, pointsdone = false; V_DrawFadeScreen(0xFF00, 16); // A little more readable, and prevents cheating the fades under other circumstances. - if (modeattacking) - hilicol = V_ORANGEMAP; - else - hilicol = ((gametype == GT_RACE) ? V_SKYMAP : V_REDMAP); - // draw the current gametype in the lower right - if (modeattacking) - V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, "Record Attack"); - else - V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, Gametype_Names[gametype]); + if (grandprixinfo.gp == true) + V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, "Grand Prix"); + else if (battlecapsules) + V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, "Capsules"); + else if (gametype >= 0 && gametype < numgametypes) + V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, gametypes[gametype]->name); - if ((gametyperules & (GTR_TIMELIMIT|GTR_POINTLIMIT)) && !bossinfo.boss) + // Left hand side + if (grandprixinfo.gp == true) { - if ((gametyperules & GTR_TIMELIMIT) && timelimitintics > 0) + const char *roundstr = NULL; + V_DrawCenteredString(64, 8, 0, "ROUND"); + switch (grandprixinfo.eventmode) { - UINT32 timeval = (timelimitintics + starttime + 1 - leveltime); - if (timeval > timelimitintics+1) - timeval = timelimitintics+1; - timeval /= TICRATE; + case GPEVENT_BONUS: + roundstr = "BONUS"; + break; + case GPEVENT_SPECIAL: + roundstr = "SPECIAL"; + break; + default: + roundstr = va("%d", grandprixinfo.roundnum); + break; + } + V_DrawCenteredString(64, 16, hilicol, roundstr); + } + else if ((gametyperules & GTR_TIMELIMIT) && timelimitintics > 0) + { + UINT32 timeval = (timelimitintics + starttime + 1 - leveltime); + if (timeval > timelimitintics+1) + timeval = timelimitintics+1; + timeval /= TICRATE; - if (leveltime <= (timelimitintics + starttime)) - { - V_DrawCenteredString(64, 8, 0, "TIME LEFT"); - V_DrawCenteredString(64, 16, hilicol, va("%u", timeval)); - } - - // overtime - if (!players[consoleplayer].exiting && (leveltime > (timelimitintics + starttime + TICRATE/2)) && cv_overtime.value) - { - V_DrawCenteredString(64, 8, 0, "TIME LEFT"); - V_DrawCenteredString(64, 16, hilicol, "OVERTIME"); - } + if (leveltime <= (timelimitintics + starttime)) + { + V_DrawCenteredString(64, 8, 0, "TIME LEFT"); + V_DrawCenteredString(64, 16, hilicol, va("%u", timeval)); } - if ((gametyperules & GTR_POINTLIMIT) && cv_pointlimit.value > 0) + // overtime + if (!players[consoleplayer].exiting && (leveltime > (timelimitintics + starttime + TICRATE/2)) && cv_overtime.value) { - V_DrawCenteredString(256, 8, 0, "POINT LIMIT"); - V_DrawCenteredString(256, 16, hilicol, va("%d", cv_pointlimit.value)); + V_DrawCenteredString(64, 8, 0, "TIME LEFT"); + V_DrawCenteredString(64, 16, hilicol, "OVERTIME"); + } + + timedone = true; + } + else if ((gametyperules & GTR_POINTLIMIT) && cv_pointlimit.value > 0) + { + V_DrawCenteredString(64, 8, 0, "POINT LIMIT"); + V_DrawCenteredString(64, 16, hilicol, va("%d", cv_pointlimit.value)); + pointsdone = true; + } + else if (gametyperules & GTR_CIRCUIT) + { + V_DrawCenteredString(64, 8, 0, "LAPS"); + V_DrawCenteredString(64, 16, hilicol, va("%d", numlaps)); + } + + // Right hand side + if (battlecapsules == true) + { + if (numtargets < maptargets) + { + V_DrawCenteredString(256, 8, 0, "CAPSULES"); + V_DrawCenteredString(256, 16, hilicol, va("%d", maptargets - numtargets)); } } - else + else if (!timedone && (gametyperules & GTR_TIMELIMIT) && timelimitintics > 0) { - if (circuitmap) + UINT32 timeval = (timelimitintics + starttime + 1 - leveltime); + if (timeval > timelimitintics+1) + timeval = timelimitintics+1; + timeval /= TICRATE; + + if (leveltime <= (timelimitintics + starttime)) { - V_DrawCenteredString(64, 8, 0, "LAP COUNT"); - V_DrawCenteredString(64, 16, hilicol, va("%d", numlaps)); + V_DrawCenteredString(256, 8, 0, "TIME LEFT"); + V_DrawCenteredString(256, 16, hilicol, va("%u", timeval)); } + // overtime + if (!players[consoleplayer].exiting && (leveltime > (timelimitintics + starttime + TICRATE/2)) && cv_overtime.value) + { + V_DrawCenteredString(256, 8, 0, "TIME LEFT"); + V_DrawCenteredString(256, 16, hilicol, "OVERTIME"); + } + } + else if (!pointsdone && (gametyperules & GTR_POINTLIMIT) && cv_pointlimit.value > 0) + { + V_DrawCenteredString(256, 8, 0, "POINT LIMIT"); + V_DrawCenteredString(256, 16, hilicol, va("%d", cv_pointlimit.value)); + } + else if (gametyperules & GTR_CIRCUIT) + { V_DrawCenteredString(256, 8, 0, "GAME SPEED"); V_DrawCenteredString(256, 16, hilicol, kartspeed_cons_t[1+gamespeed].strvalue); } @@ -2494,13 +2545,12 @@ static void HU_DrawRankings(void) if ((gametyperules & GTR_CIRCUIT)) { - if (circuitmap) - tab[scorelines].count = players[i].laps; - else - tab[scorelines].count = players[i].realtime; + tab[scorelines].count = players[i].laps; } else + { tab[scorelines].count = players[i].roundscore; + } scorelines++; diff --git a/src/k_battle.c b/src/k_battle.c index 15b81f81c..e05b003a4 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -2,7 +2,6 @@ /// \brief SRB2Kart Battle Mode specific code #include "k_battle.h" -#include "k_boss.h" #include "k_kart.h" #include "doomtype.h" #include "doomdata.h" @@ -146,7 +145,7 @@ void K_CheckBumpers(void) } else if (numingame <= 1) { - if (!battlecapsules) + if ((gametyperules & GTR_CAPSULES) && !battlecapsules) { // Reset map to turn on battle capsules if (server) @@ -196,6 +195,11 @@ void K_CheckEmeralds(player_t *player) { UINT8 i; + if (!(gametyperules & GTR_POWERSTONES)) + { + return; + } + if (!ALLCHAOSEMERALDS(player->emeralds)) { return; @@ -221,6 +225,29 @@ void K_CheckEmeralds(player_t *player) K_CheckBumpers(); } +UINT16 K_GetChaosEmeraldColor(UINT32 emeraldType) +{ + switch (emeraldType) + { + case EMERALD_CHAOS1: + return SKINCOLOR_CHAOSEMERALD1; + case EMERALD_CHAOS2: + return SKINCOLOR_CHAOSEMERALD2; + case EMERALD_CHAOS3: + return SKINCOLOR_CHAOSEMERALD3; + case EMERALD_CHAOS4: + return SKINCOLOR_CHAOSEMERALD4; + case EMERALD_CHAOS5: + return SKINCOLOR_CHAOSEMERALD5; + case EMERALD_CHAOS6: + return SKINCOLOR_CHAOSEMERALD6; + case EMERALD_CHAOS7: + return SKINCOLOR_CHAOSEMERALD7; + default: + return SKINCOLOR_NONE; + } +} + mobj_t *K_SpawnChaosEmerald(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT32 emeraldType) { boolean validEmerald = true; @@ -240,25 +267,13 @@ mobj_t *K_SpawnChaosEmerald(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT switch (emeraldType) { case EMERALD_CHAOS1: - emerald->color = SKINCOLOR_CHAOSEMERALD1; - break; case EMERALD_CHAOS2: - emerald->color = SKINCOLOR_CHAOSEMERALD2; - break; case EMERALD_CHAOS3: - emerald->color = SKINCOLOR_CHAOSEMERALD3; - break; case EMERALD_CHAOS4: - emerald->color = SKINCOLOR_CHAOSEMERALD4; - break; case EMERALD_CHAOS5: - emerald->color = SKINCOLOR_CHAOSEMERALD5; - break; case EMERALD_CHAOS6: - emerald->color = SKINCOLOR_CHAOSEMERALD6; - break; case EMERALD_CHAOS7: - emerald->color = SKINCOLOR_CHAOSEMERALD7; + emerald->color = K_GetChaosEmeraldColor(emeraldType); break; default: CONS_Printf("Invalid emerald type %d\n", emeraldType); @@ -336,12 +351,17 @@ UINT8 K_NumEmeralds(player_t *player) return num; } +static inline boolean IsOnInterval(tic_t interval) +{ + return ((leveltime - starttime) % interval) == 0; +} + void K_RunPaperItemSpawners(void) { const boolean overtime = (battleovertime.enabled >= 10*TICRATE); - tic_t interval = 8*TICRATE; + const tic_t interval = BATTLE_SPAWN_INTERVAL; - const boolean canmakeemeralds = true; //(!(battlecapsules || bossinfo.boss)); + const boolean canmakeemeralds = (gametyperules & GTR_POWERSTONES); UINT32 emeraldsSpawned = 0; UINT32 firstUnspawnedEmerald = 0; @@ -352,7 +372,7 @@ void K_RunPaperItemSpawners(void) UINT8 pcount = 0; INT16 i; - if (battlecapsules || bossinfo.boss) + if (battlecapsules) { // Gametype uses paper items, but this specific expression doesn't return; @@ -364,13 +384,7 @@ void K_RunPaperItemSpawners(void) return; } - if (overtime == true) - { - // Double frequency of items - interval /= 2; - } - - if (((leveltime - starttime) % interval) != 0) + if (!IsOnInterval(interval)) { return; } @@ -546,7 +560,7 @@ void K_RunPaperItemSpawners(void) } else { - if (gametyperules & GTR_SPHERES) + if ((gametyperules & GTR_SPHERES) && IsOnInterval(2 * interval)) { drop = K_SpawnSphereBox( spotList[r]->x, spotList[r]->y, spotList[r]->z + (128 * mapobjectscale * flip), @@ -789,7 +803,7 @@ void K_BattleInit(boolean singleplayercontext) { size_t i; - if ((gametyperules & GTR_CAPSULES) && singleplayercontext && !battlecapsules && !bossinfo.boss) + if ((gametyperules & GTR_CAPSULES) && singleplayercontext && !battlecapsules) { mapthing_t *mt = mapthings; for (i = 0; i < nummapthings; i++, mt++) diff --git a/src/k_battle.h b/src/k_battle.h index 42d92cf33..f64cfa967 100644 --- a/src/k_battle.h +++ b/src/k_battle.h @@ -8,6 +8,9 @@ extern "C" { #endif +#define BATTLE_SPAWN_INTERVAL (4*TICRATE) +#define BATTLE_DESPAWN_TIME (15*TICRATE) + extern struct battleovertime { UINT16 enabled; ///< Has this been initalized yet? @@ -25,6 +28,7 @@ boolean K_IsPlayerWanted(player_t *player); void K_SpawnBattlePoints(player_t *source, player_t *victim, UINT8 amount); void K_CheckBumpers(void); void K_CheckEmeralds(player_t *player); +UINT16 K_GetChaosEmeraldColor(UINT32 emeraldType); mobj_t *K_SpawnChaosEmerald(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT32 emeraldType); mobj_t *K_SpawnSphereBox(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 amount); void K_DropEmeraldsFromPlayer(player_t *player, UINT32 emeraldType); diff --git a/src/k_boss.c b/src/k_boss.c index 490adb27e..ff10231ea 100644 --- a/src/k_boss.c +++ b/src/k_boss.c @@ -23,7 +23,7 @@ struct bossinfo bossinfo; /*-------------------------------------------------- - void K_ClearBossInfo(void) + void K_ResetBossInfo(void) See header file for description. --------------------------------------------------*/ @@ -32,6 +32,8 @@ void K_ResetBossInfo(void) Z_Free(bossinfo.enemyname); Z_Free(bossinfo.subtitle); memset(&bossinfo, 0, sizeof(struct bossinfo)); + bossinfo.barlen = BOSSHEALTHBARLEN; + bossinfo.titlesound = sfx_typri1; } /*-------------------------------------------------- @@ -43,7 +45,7 @@ void K_BossInfoTicker(void) { UINT8 i; - if (bossinfo.boss == false) + if (bossinfo.valid == false) return; // Update healthbar data. (only if the hud is visible) @@ -55,7 +57,7 @@ void K_BossInfoTicker(void) bossinfo.visualbar--; // If the boss is dying, start shrinking the healthbar. if (bossinfo.visualbar == 0) - bossinfo.barlen-= 2; + bossinfo.barlen -= 2; } // Less than the actual health? else if (bossinfo.visualbar < bossinfo.healthbar) @@ -108,6 +110,18 @@ void K_BossInfoTicker(void) void K_InitBossHealthBar(const char *enemyname, const char *subtitle, sfxenum_t titlesound, fixed_t pinchmagnitude, UINT8 divisions) { + if (!(gametyperules & GTR_BOSS)) + { + return; + } + + bossinfo.valid = true; + + if (!leveltime) + { + bossinfo.coolintro = true; + } + if (enemyname && enemyname[0]) { Z_Free(bossinfo.enemyname); @@ -158,6 +172,9 @@ void K_InitBossHealthBar(const char *enemyname, const char *subtitle, sfxenum_t void K_UpdateBossHealthBar(fixed_t magnitude, tic_t jitterlen) { + if (bossinfo.valid == false) + return; + if (magnitude > FRACUNIT) magnitude = FRACUNIT; else if (magnitude < 0) @@ -177,6 +194,9 @@ void K_DeclareWeakspot(mobj_t *spot, spottype_t spottype, UINT16 color, boolean { UINT8 i; + if (bossinfo.valid == false) + return; + // First check whether the spot is already in the list and simply redeclaring weakness (for example, a vulnerable moment in the pattern). for (i = 0; i < NUMWEAKSPOTS; i++) if (bossinfo.weakspots[i].spot == spot) @@ -206,3 +226,16 @@ void K_DeclareWeakspot(mobj_t *spot, spottype_t spottype, UINT16 color, boolean bossinfo.doweakspotsound = spottype; } } + +/*-------------------------------------------------- + boolean K_CheckBossIntro(void); + + See header file for description. +--------------------------------------------------*/ + +boolean K_CheckBossIntro(void) +{ + if (bossinfo.valid == false) + return false; + return bossinfo.coolintro; +} diff --git a/src/k_boss.h b/src/k_boss.h index 9986d3e36..2ce43adea 100644 --- a/src/k_boss.h +++ b/src/k_boss.h @@ -43,7 +43,8 @@ struct weakspot_t extern struct bossinfo { - boolean boss; ///< If true, then we are fighting a boss + boolean valid; ///< If true, then data in this struct is valid + UINT8 healthbar; ///< Actual health bar fill amount UINT8 visualbar; ///< Tracks above, but with delay fixed_t visualdiv; ///< How far apart health bar divisions should appear @@ -52,7 +53,7 @@ extern struct bossinfo UINT8 barlen; ///< The length of the bar (only reduced when a boss is deceased) char *enemyname; ///< The name next to the bar weakspot_t weakspots[NUMWEAKSPOTS]; ///< Array of weak spots (for minimap/object tracking) - boolean encore; ///< Copy of encore, just to make sure you can't cheat it with cvars + boolean coolintro; ///< Determines whether the map start(s/ed) with a boss-specific intro. spottype_t doweakspotsound; ///< If nonzero, at least one weakspot was declared this tic tic_t titleshow; ///< Show this many letters on the titlecard sfxenum_t titlesound; ///< Sound to play when title typing @@ -116,6 +117,18 @@ void K_UpdateBossHealthBar(fixed_t magnitude, tic_t jitterlen); void K_DeclareWeakspot(mobj_t *spot, spottype_t spottype, UINT16 color, boolean minimap); +/*-------------------------------------------------- + boolean K_CheckBossIntro(void); + + Checks whether the Versus-specific intro is playing for this map start. + + Return:- + true if cool intro in action, + otherwise false. +--------------------------------------------------*/ + +boolean K_CheckBossIntro(void); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_bot.c b/src/k_bot.c index caf6aa55f..e6604d338 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -27,7 +27,6 @@ #include "m_random.h" #include "r_things.h" // numskins #include "k_race.h" // finishBeamLine -#include "k_boss.h" #include "m_perfstats.h" @@ -168,7 +167,7 @@ void K_UpdateMatchRaceBots(void) } } - if (difficulty == 0 || !(gametyperules & GTR_BOTS) || bossinfo.boss == true) + if (difficulty == 0 || !(gametyperules & GTR_BOTS)) { wantedbots = 0; } diff --git a/src/k_collide.c b/src/k_collide.c index 06d6900e6..e9e832b3d 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -324,6 +324,11 @@ tic_t K_MineExplodeAttack(mobj_t *actor, fixed_t size, boolean spin) if (!spin) { + if (minehitlag == 0) + { + minehitlag = actor->hitlag; + } + Obj_SpawnBrolyKi(actor, minehitlag); return minehitlag; diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 221e9d10c..6d879054b 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -11,7 +11,6 @@ /// \brief Grand Prix mode game logic & bot behaviors #include "k_grandprix.h" -#include "k_boss.h" #include "k_specialstage.h" #include "doomdef.h" #include "d_player.h" @@ -339,7 +338,7 @@ void K_UpdateGrandPrixBots(void) continue; } - players[i].spectator = (grandprixinfo.eventmode != GPEVENT_NONE); + players[i].spectator = !(gametyperules & GTR_BOTS) || (grandprixinfo.eventmode != GPEVENT_NONE); } // Find the rival. @@ -529,7 +528,7 @@ void K_RetireBots(void) UINT8 i; if (grandprixinfo.gp == true - && ((grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) + && ((grandprixinfo.cup != NULL && grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) || grandprixinfo.eventmode != GPEVENT_NONE)) { // No replacement. @@ -676,7 +675,7 @@ void K_PlayerLoseLife(player_t *player) return; } - if (player->spectator || player->exiting || player->bot || player->lives <= 0 || (player->pflags & PF_LOSTLIFE)) + if (player->spectator || (player->exiting && !(player->pflags & PF_NOCONTEST)) || player->bot || player->lives <= 0 || (player->pflags & PF_LOSTLIFE)) { return; } @@ -703,24 +702,12 @@ void K_PlayerLoseLife(player_t *player) --------------------------------------------------*/ boolean K_CanChangeRules(boolean allowdemos) { - if (grandprixinfo.gp == true && grandprixinfo.roundnum > 0) + if (grandprixinfo.gp == true /*&& grandprixinfo.roundnum > 0*/) { // Don't cheat the rules of the GP! return false; } - if (bossinfo.boss == true) - { - // Don't cheat the boss! - return false; - } - - if (specialStage.active == true) - { - // Don't cheat special stages! - return false; - } - if (marathonmode) { // Don't cheat the endurance challenge! diff --git a/src/k_hud.c b/src/k_hud.c index 8da9ca089..742d0506b 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1863,7 +1863,7 @@ static boolean K_drawKartPositionFaces(void) ranklines--; i = ranklines; - if (gametype == GT_BATTLE || strank <= 2) // too close to the top, or playing battle, or a spectator? would have had (strank == -1) called out, but already caught by (strank <= 2) + if ((gametyperules & GTR_POINTLIMIT) || strank <= 2) // too close to the top, or playing battle, or a spectator? would have had (strank == -1) called out, but already caught by (strank <= 2) { if (i > 4) // could be both... i = 4; @@ -1951,7 +1951,7 @@ static boolean K_drawKartPositionFaces(void) if (i == strank) V_DrawScaledPatch(FACE_X, Y, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_facehighlight[(leveltime / 4) % 8]); - if (gametype == GT_BATTLE && players[rankplayer[i]].bumpers <= 0) + if ((gametyperules & GTR_BUMPERS) && players[rankplayer[i]].bumpers <= 0) V_DrawScaledPatch(FACE_X-4, Y-3, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_ranknobumpers); else { @@ -1973,7 +1973,7 @@ static void K_drawBossHealthBar(void) UINT8 i = 0, barstatus = 1, randlen = 0, darken = 0; const INT32 startx = BASEVIDWIDTH - 23; INT32 starty = BASEVIDHEIGHT - 25; - INT32 rolrand = 0; + INT32 rolrand = 0, randtemp = 0; boolean randsign = false; if (bossinfo.barlen <= 1) @@ -2019,7 +2019,9 @@ static void K_drawBossHealthBar(void) barstatus = 2; } - randlen = M_RandomKey(bossinfo.visualbar-(bossinfo.visualdiv/(2*FRACUNIT)))+1; + randtemp = bossinfo.visualbar-(bossinfo.visualdiv/(2*FRACUNIT)); + if (randtemp > 0) + randlen = M_RandomKey(randtemp)+1; randsign = M_RandomChance(FRACUNIT/2); // Right wing. @@ -2034,7 +2036,9 @@ static void K_drawBossHealthBar(void) randlen--; if (!randlen) { - randlen = M_RandomKey(bossinfo.visualbar-(bossinfo.visualdiv/(2*FRACUNIT)))+1; + randtemp = bossinfo.visualbar-(bossinfo.visualdiv/(2*FRACUNIT)); + if (randtemp > 0) + randlen = M_RandomKey(randtemp)+1; if (barstatus > 1) { rolrand = M_RandomKey(barstatus)+1; @@ -2265,7 +2269,7 @@ void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, IN colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE); V_DrawMappedPatch(x, y-4, 0, faceprefix[players[tab[i].num].skin][FACE_RANK], colormap); - /*if (gametype == GT_BATTLE && players[tab[i].num].bumpers > 0) -- not enough space for this + /*if ((gametyperules & GTR_BUMPERS) && players[tab[i].num].bumpers > 0) -- not enough space for this { INT32 bumperx = x+19; V_DrawMappedPatch(bumperx-2, y-4, 0, kp_tinybumper[0], colormap); @@ -2280,7 +2284,7 @@ void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, IN if (tab[i].num == whiteplayer) V_DrawScaledPatch(x, y-4, 0, kp_facehighlight[(leveltime / 4) % 8]); - if (gametype == GT_BATTLE && players[tab[i].num].bumpers <= 0) + if ((gametyperules & GTR_BUMPERS) && players[tab[i].num].bumpers <= 0) V_DrawScaledPatch(x-4, y-7, 0, kp_ranknobumpers); else { @@ -2291,7 +2295,7 @@ void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, IN V_DrawScaledPatch(x-5, y+6, 0, kp_facenum[pos]); } - if (gametype == GT_RACE) + if ((gametyperules & GTR_CIRCUIT)) { #define timestring(time) va("%i'%02i\"%02i", G_TicsToMinutes(time, true), G_TicsToSeconds(time), G_TicsToCentiseconds(time)) if (scorelines >= 8) @@ -2300,7 +2304,7 @@ void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, IN V_DrawRightAlignedThinString(x+rightoffset, y-1, hilicol|V_6WIDTHSPACE, timestring(players[tab[i].num].realtime)); else if (players[tab[i].num].pflags & PF_NOCONTEST) V_DrawRightAlignedThinString(x+rightoffset, y-1, V_6WIDTHSPACE, "NO CONTEST."); - else if (circuitmap) + else V_DrawRightAlignedThinString(x+rightoffset, y-1, V_6WIDTHSPACE, va("Lap %d", tab[i].count)); } else @@ -2309,7 +2313,7 @@ void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, IN V_DrawRightAlignedString(x+rightoffset, y, hilicol, timestring(players[tab[i].num].realtime)); else if (players[tab[i].num].pflags & PF_NOCONTEST) V_DrawRightAlignedThinString(x+rightoffset, y-1, 0, "NO CONTEST."); - else if (circuitmap) + else V_DrawRightAlignedString(x+rightoffset, y, 0, va("Lap %d", tab[i].count)); } #undef timestring @@ -2326,40 +2330,13 @@ void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, IN } } -#define RINGANIM_FLIPFRAME (RINGANIM_NUMFRAMES/2) - -static void K_drawKartLapsAndRings(void) +static void K_drawKartLaps(void) { - const boolean uselives = G_GametypeUsesLives(); - SINT8 ringanim_realframe = stplyr->karthud[khud_ringframe]; INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN; - UINT8 rn[2]; - INT32 ringflip = 0; - UINT8 *ringmap = NULL; - boolean colorring = false; - INT32 ringx = 0; - - rn[0] = ((abs(stplyr->rings) / 10) % 10); - rn[1] = (abs(stplyr->rings) % 10); - - if (stplyr->rings <= 0 && (leveltime/5 & 1)) // In debt - { - ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_CRIMSON, GTC_CACHE); - colorring = true; - } - else if (stplyr->rings >= 20) // Maxed out - ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_YELLOW, GTC_CACHE); - - if (stplyr->karthud[khud_ringframe] > RINGANIM_FLIPFRAME) - { - ringflip = V_FLIP; - ringanim_realframe = RINGANIM_NUMFRAMES-stplyr->karthud[khud_ringframe]; - ringx += SHORT((r_splitscreen > 1) ? kp_smallring[ringanim_realframe]->width : kp_ring[ringanim_realframe]->width); - } if (r_splitscreen > 1) { - INT32 fx = 0, fy = 0, fr = 0; + INT32 fx = 0, fy = 0; INT32 flipflag = 0; // pain and suffering defined below @@ -2385,8 +2362,6 @@ static void K_drawKartLapsAndRings(void) } } - fr = fx; - // Laps V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[1]->width) - 3) : 0), fy, V_HUDTRANS|V_SLIDEIN|splitflags|flipflag, kp_ringstickersplit[0]); @@ -2413,6 +2388,75 @@ static void K_drawKartLapsAndRings(void) V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(stplyr->laps) % 10]); V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(numlaps) % 10]); } + } + else + { + // Laps + V_DrawScaledPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_lapsticker); + V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d", min(stplyr->laps, numlaps), numlaps)); + } +} + +#define RINGANIM_FLIPFRAME (RINGANIM_NUMFRAMES/2) + +static void K_drawRingCounter(void) +{ + const boolean uselives = G_GametypeUsesLives(); + SINT8 ringanim_realframe = stplyr->karthud[khud_ringframe]; + INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN; + UINT8 rn[2]; + INT32 ringflip = 0; + UINT8 *ringmap = NULL; + boolean colorring = false; + INT32 ringx = 0, fy = 0; + + rn[0] = ((abs(stplyr->rings) / 10) % 10); + rn[1] = (abs(stplyr->rings) % 10); + + if (stplyr->rings <= 0 && (leveltime/5 & 1)) // In debt + { + ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_CRIMSON, GTC_CACHE); + colorring = true; + } + else if (stplyr->rings >= 20) // Maxed out + ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_YELLOW, GTC_CACHE); + + if (stplyr->karthud[khud_ringframe] > RINGANIM_FLIPFRAME) + { + ringflip = V_FLIP; + ringanim_realframe = RINGANIM_NUMFRAMES-stplyr->karthud[khud_ringframe]; + ringx += SHORT((r_splitscreen > 1) ? kp_smallring[ringanim_realframe]->width : kp_ring[ringanim_realframe]->width); + } + + if (r_splitscreen > 1) + { + INT32 fx = 0, fr = 0; + INT32 flipflag = 0; + + // pain and suffering defined below + if (r_splitscreen < 2) // don't change shit for THIS splitscreen. + { + fx = LAPS_X; + fy = LAPS_Y; + } + else + { + if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) // If we are P1 or P3... + { + fx = LAPS_X; + fy = LAPS_Y; + splitflags = V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_SPLITSCREEN; + } + else // else, that means we're P2 or P4. + { + fx = LAPS2_X; + fy = LAPS2_Y; + splitflags = V_SNAPTORIGHT|V_SNAPTOBOTTOM|V_SPLITSCREEN; + flipflag = V_FLIP; // make the string right aligned and other shit + } + } + + fr = fx; // Rings if (!uselives) @@ -2447,41 +2491,42 @@ static void K_drawKartLapsAndRings(void) } else { - // Laps - V_DrawScaledPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_lapsticker); - V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d", min(stplyr->laps, numlaps), numlaps)); + fy = LAPS_Y-11; + + if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) + fy -= 4; // Rings if (!uselives) - V_DrawScaledPatch(LAPS_X, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringsticker[1]); + V_DrawScaledPatch(LAPS_X, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringsticker[1]); else - V_DrawScaledPatch(LAPS_X, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringsticker[0]); + V_DrawScaledPatch(LAPS_X, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringsticker[0]); - V_DrawMappedPatch(LAPS_X+ringx+7, LAPS_Y-16, V_HUDTRANS|V_SLIDEIN|splitflags|ringflip, kp_ring[ringanim_realframe], (colorring ? ringmap : NULL)); + V_DrawMappedPatch(LAPS_X+ringx+7, fy-5, V_HUDTRANS|V_SLIDEIN|splitflags|ringflip, kp_ring[ringanim_realframe], (colorring ? ringmap : NULL)); if (stplyr->rings < 0) // Draw the minus for ring debt { - V_DrawMappedPatch(LAPS_X+23, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringdebtminus, ringmap); - V_DrawMappedPatch(LAPS_X+29, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[0]], ringmap); - V_DrawMappedPatch(LAPS_X+35, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[1]], ringmap); + V_DrawMappedPatch(LAPS_X+23, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringdebtminus, ringmap); + V_DrawMappedPatch(LAPS_X+29, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[0]], ringmap); + V_DrawMappedPatch(LAPS_X+35, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[1]], ringmap); } else { - V_DrawMappedPatch(LAPS_X+23, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[0]], ringmap); - V_DrawMappedPatch(LAPS_X+29, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[1]], ringmap); + V_DrawMappedPatch(LAPS_X+23, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[0]], ringmap); + V_DrawMappedPatch(LAPS_X+29, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[1]], ringmap); } // SPB ring lock if (stplyr->pflags & PF_RINGLOCK) - V_DrawScaledPatch(LAPS_X-5, LAPS_Y-28, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringspblock[stplyr->karthud[khud_ringspblock]]); + V_DrawScaledPatch(LAPS_X-5, fy-17, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringspblock[stplyr->karthud[khud_ringspblock]]); // Lives if (uselives) { UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE); - V_DrawMappedPatch(LAPS_X+46, LAPS_Y-16, V_HUDTRANS|V_SLIDEIN|splitflags, faceprefix[stplyr->skin][FACE_RANK], colormap); + V_DrawMappedPatch(LAPS_X+46, fy-5, V_HUDTRANS|V_SLIDEIN|splitflags, faceprefix[stplyr->skin][FACE_RANK], colormap); if (stplyr->lives >= 0) - V_DrawScaledPatch(LAPS_X+63, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(stplyr->lives % 10)]); // make sure this doesn't overflow OR underflow + V_DrawScaledPatch(LAPS_X+63, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(stplyr->lives % 10)]); // make sure this doesn't overflow OR underflow } } } @@ -2498,7 +2543,7 @@ static void K_drawKartAccessibilityIcons(INT32 fx) if (r_splitscreen < 2) // adjust to speedometer height { - if (gametype == GT_BATTLE) + if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) fy -= 4; } else @@ -2588,7 +2633,7 @@ static void K_drawKartSpeedometer(void) numbers[1] = ((convSpeed / 10) % 10); numbers[2] = (convSpeed % 10); - if (gametype == GT_BATTLE) + if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) battleoffset = -4; V_DrawScaledPatch(LAPS_X, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_speedometersticker); @@ -2793,14 +2838,15 @@ static void K_drawKartBumpersOrKarma(void) else V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_bumpersticker, colormap); - if (bossinfo.boss) - V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d", stplyr->bumpers, maxbumper)); - else // TODO BETTER HUD + if (gametyperules & GTR_KARMA) // TODO BETTER HUD V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d %d", stplyr->bumpers, maxbumper, stplyr->overtimekarma / TICRATE)); + else + V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d", stplyr->bumpers, maxbumper)); } } } +#if 0 static void K_drawKartWanted(void) { UINT8 i, numwanted = 0; @@ -2875,6 +2921,7 @@ static void K_drawKartWanted(void) } } } +#endif //if 0 static void K_drawKartPlayerCheck(void) { @@ -3358,7 +3405,7 @@ static void K_drawKartNameTags(void) c.z = viewz; // Maybe shouldn't be handling this here... but the camera info is too good. - if (bossinfo.boss) + if (bossinfo.valid == true) { weakspotdraw_t weakspotdraw[NUMWEAKSPOTS]; UINT8 numdraw = 0; @@ -3810,7 +3857,7 @@ static void K_drawKartMinimap(void) y -= SHORT(AutomapPic->topoffset); // Draw the super item in Battle - if (gametype == GT_BATTLE && battleovertime.enabled) + if ((gametyperules & GTR_OVERTIME) && battleovertime.enabled) { if (battleovertime.enabled >= 10*TICRATE || (battleovertime.enabled & 1)) { @@ -3920,8 +3967,8 @@ static void K_drawKartMinimap(void) K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, faceprefix[skin][FACE_MINIMAP], colormap, AutomapPic); // Target reticule - if ((gametype == GT_RACE && players[i].position == spbplace) - || (gametype == GT_BATTLE && K_IsPlayerWanted(&players[i]))) + if (((gametyperules & GTR_CIRCUIT) && players[i].position == spbplace) + || ((gametyperules & (GTR_BOSS|GTR_POINTLIMIT)) == GTR_POINTLIMIT && K_IsPlayerWanted(&players[i]))) { K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, kp_wantedreticle, NULL, AutomapPic); } @@ -3979,7 +4026,7 @@ static void K_drawKartMinimap(void) } // ...but first, any boss targets. - if (bossinfo.boss) + if (bossinfo.valid == true) { for (i = 0; i < NUMWEAKSPOTS; i++) { @@ -4047,8 +4094,8 @@ static void K_drawKartMinimap(void) K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, workingPic, colormap, AutomapPic); // Target reticule - if ((gametype == GT_RACE && players[localplayers[i]].position == spbplace) - || (gametype == GT_BATTLE && K_IsPlayerWanted(&players[localplayers[i]]))) + if (((gametyperules & GTR_CIRCUIT) && players[localplayers[i]].position == spbplace) + || ((gametyperules & (GTR_BOSS|GTR_POINTLIMIT)) == GTR_POINTLIMIT && K_IsPlayerWanted(&players[localplayers[i]]))) { K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, kp_wantedreticle, NULL, AutomapPic); } @@ -4836,7 +4883,7 @@ void K_drawKartFreePlay(void) if (!LUA_HudEnabled(hud_freeplay)) return; - if (modeattacking || grandprixinfo.gp || bossinfo.boss || stplyr->spectator) + if (modeattacking || grandprixinfo.gp || bossinfo.valid || stplyr->spectator) return; if (lt_exitticker < TICRATE/2) @@ -5024,10 +5071,10 @@ void K_drawKartHUD(void) return; } - battlefullscreen = ((gametyperules & (GTR_BUMPERS|GTR_KARMA)) == (GTR_BUMPERS|GTR_KARMA) + battlefullscreen = (!(gametyperules & GTR_CIRCUIT) && (stplyr->exiting - || (stplyr->bumpers <= 0 - && stplyr->karmadelay > 0 + || ((gametyperules & GTR_BUMPERS) && (stplyr->bumpers <= 0) + && ((gametyperules & GTR_KARMA) && (stplyr->karmadelay > 0)) && !(stplyr->pflags & PF_ELIMINATED) && stplyr->playerstate == PST_LIVE))); @@ -5043,11 +5090,13 @@ void K_drawKartHUD(void) K_drawKartNameTags(); // Draw WANTED status +#if 0 if (gametype == GT_BATTLE) { if (LUA_HudEnabled(hud_wanted)) K_drawKartWanted(); } +#endif if (LUA_HudEnabled(hud_minimap)) K_drawKartMinimap(); @@ -5094,32 +5143,26 @@ void K_drawKartHUD(void) { if (LUA_HudEnabled(hud_position)) { - if (bossinfo.boss) + if (bossinfo.valid) { K_drawBossHealthBar(); } - else if (gametype == GT_RACE) // Race-only elements (not currently gametyperuleable) + else if (freecam) + ; + else if ((gametyperules & GTR_POWERSTONES)) { - if (!islonesome) - { - // Draw the numerical position - K_DrawKartPositionNum(stplyr->position); - } - } - else if (gametype == GT_BATTLE) // Battle-only (ditto) - { - if (!freecam && !battlecapsules) - { + if (!battlecapsules) K_drawKartEmeralds(); - } } + else if (!islonesome) + K_DrawKartPositionNum(stplyr->position); } if (LUA_HudEnabled(hud_gametypeinfo)) { if (gametyperules & GTR_CIRCUIT) { - K_drawKartLapsAndRings(); + K_drawKartLaps(); } else if (gametyperules & GTR_BUMPERS) { @@ -5141,8 +5184,12 @@ void K_drawKartHUD(void) { K_drawBlueSphereMeter(); } + else + { + K_drawRingCounter(); + } - if (modeattacking && !bossinfo.boss) + if (modeattacking && !bossinfo.valid) { // Draw the input UI if (LUA_HudEnabled(hud_position)) @@ -5172,10 +5219,12 @@ void K_drawKartHUD(void) } // Race overlays - if (gametype == GT_RACE && !freecam) + if (!freecam) { if (stplyr->exiting) K_drawKartFinish(true); + else if (!(gametyperules & GTR_CIRCUIT)) + ; else if (stplyr->karthud[khud_lapanimation] && !r_splitscreen) K_drawLapStartAnim(); } @@ -5187,7 +5236,7 @@ void K_drawKartHUD(void) if (modeattacking || freecam) // everything after here is MP and debug only return; - if (gametype == GT_BATTLE && !r_splitscreen && (stplyr->karthud[khud_yougotem] % 2)) // * YOU GOT EM * + if ((gametyperules & GTR_KARMA) && !r_splitscreen && (stplyr->karthud[khud_yougotem] % 2)) // * YOU GOT EM * V_DrawScaledPatch(BASEVIDWIDTH/2 - (SHORT(kp_yougotem->width)/2), 32, V_HUDTRANS, kp_yougotem); // Draw FREE PLAY. diff --git a/src/k_kart.c b/src/k_kart.c index f1c0b8be2..43675a539 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -6,7 +6,6 @@ #include "k_kart.h" #include "k_battle.h" -#include "k_boss.h" #include "k_pwrlv.h" #include "k_color.h" #include "k_respawn.h" @@ -41,6 +40,7 @@ #include "k_follower.h" #include "k_objects.h" #include "k_grandprix.h" +#include "k_boss.h" #include "k_specialstage.h" #include "k_roulette.h" @@ -109,11 +109,43 @@ void K_TimerInit(void) boolean domodeattack = ((modeattacking != ATTACKING_NONE) || (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE)); - if (specialStage.active == true) + // Rooooooolllling staaaaaaart + if ((gametyperules & (GTR_ROLLINGSTART|GTR_CIRCUIT)) == (GTR_ROLLINGSTART|GTR_CIRCUIT)) + { + S_StartSound(NULL, sfx_s25f); + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *player = NULL; + + if (playeringame[i] == false) + { + continue; + } + + player = &players[i]; + if (player->spectator == true) + { + continue; + } + + if (player->mo == NULL || P_MobjWasRemoved(player->mo) == true) + { + continue; + } + + // Rolling start? lol + P_InstaThrust(player->mo, player->mo->angle, K_GetKartSpeed(player, false, false)); + } + } + + if ((gametyperules & (GTR_CATCHER|GTR_CIRCUIT)) == (GTR_CATCHER|GTR_CIRCUIT)) { K_InitSpecialStage(); } - else if (bossinfo.boss == false) + else if (K_CheckBossIntro() == true) + ; + else { if (!domodeattack) { @@ -152,7 +184,7 @@ void K_TimerInit(void) K_BattleInit(domodeattack); - if ((gametyperules & GTR_TIMELIMIT) && !bossinfo.boss && !modeattacking) + if ((gametyperules & GTR_TIMELIMIT) && !modeattacking) { if (!K_CanChangeRules(true)) { @@ -162,7 +194,7 @@ void K_TimerInit(void) } else { - timelimitintics = timelimits[gametype] * (60*TICRATE); + timelimitintics = gametypes[gametype]->timelimit * (60*TICRATE); } } else @@ -310,8 +342,6 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartbumpers); CV_RegisterVar(&cv_kartfrantic); CV_RegisterVar(&cv_kartencore); - CV_RegisterVar(&cv_kartvoterulechanges); - CV_RegisterVar(&cv_kartgametypepreference); CV_RegisterVar(&cv_kartspeedometer); CV_RegisterVar(&cv_kartvoices); CV_RegisterVar(&cv_kartbot); @@ -342,15 +372,21 @@ boolean K_IsPlayerLosing(player_t *player) INT32 winningpos = 1; UINT8 i, pcount = 0; + if (player->pflags & PF_NOCONTEST) + return true; + if (battlecapsules && numtargets == 0) return true; // Didn't even TRY? - if (battlecapsules || bossinfo.boss) + if (battlecapsules || (gametyperules & GTR_BOSS)) return (player->bumpers <= 0); // anything short of DNF is COOL if (player->position == 1) return false; + if (specialstageinfo.valid == true) + return false; // anything short of DNF is COOL + for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) @@ -515,7 +551,7 @@ boolean K_TimeAttackRules(void) UINT8 playing = 0; UINT8 i; - if (specialStage.active == true) + if ((gametyperules & (GTR_CATCHER|GTR_CIRCUIT)) == (GTR_CATCHER|GTR_CIRCUIT)) { // Kind of a hack -- Special Stages // are expected to be 1-player, so @@ -1272,10 +1308,9 @@ static boolean K_TryDraft(player_t *player, mobj_t *dest, fixed_t minDist, fixed player->draftpower += add; } - if (gametype == GT_BATTLE) + if (gametyperules & GTR_CLOSERPLAYERS) { - // TODO: gametyperules - // Double speed in Battle + // Double speed in smaller environments player->draftpower += add; } } @@ -1304,8 +1339,7 @@ static boolean K_TryDraft(player_t *player, mobj_t *dest, fixed_t minDist, fixed */ static void K_UpdateDraft(player_t *player) { - const boolean addUfo = ((specialStage.active == true) - && (specialStage.ufo != NULL && P_MobjWasRemoved(specialStage.ufo) == false)); + mobj_t *addUfo = K_GetPossibleSpecialTarget(); fixed_t topspd = K_GetKartSpeed(player, false, false); fixed_t draftdistance; @@ -1335,9 +1369,8 @@ static void K_UpdateDraft(player_t *player) minDist = 640 * player->mo->scale; - if (gametype == GT_BATTLE) + if (gametyperules & GTR_CLOSERPLAYERS) { - // TODO: gametyperules minDist /= 4; draftdistance *= 2; leniency *= 4; @@ -1346,10 +1379,10 @@ static void K_UpdateDraft(player_t *player) // Not enough speed to draft. if (player->speed >= 20 * player->mo->scale) { - if (addUfo == true) + if (addUfo != NULL) { // Tether off of the UFO! - if (K_TryDraft(player, specialStage.ufo, minDist, draftdistance, leniency) == true) + if (K_TryDraft(player, addUfo, minDist, draftdistance, leniency) == true) { return; // Finished doing our draft. } @@ -1403,11 +1436,11 @@ static void K_UpdateDraft(player_t *player) fixed_t dist = P_AproxDistance(P_AproxDistance(victim->mo->x - player->mo->x, victim->mo->y - player->mo->y), victim->mo->z - player->mo->z); K_DrawDraftCombiring(player, victim->mo, dist, draftdistance, true); } - else if (addUfo == true) + else if (addUfo != NULL) { // kind of a hack to not have to mess with how lastdraft works - fixed_t dist = P_AproxDistance(P_AproxDistance(specialStage.ufo->x - player->mo->x, specialStage.ufo->y - player->mo->y), specialStage.ufo->z - player->mo->z); - K_DrawDraftCombiring(player, specialStage.ufo, dist, draftdistance, true); + fixed_t dist = P_AproxDistance(P_AproxDistance(addUfo->x - player->mo->x, addUfo->y - player->mo->y), addUfo->z - player->mo->z); + K_DrawDraftCombiring(player, addUfo, dist, draftdistance, true); } } else // Remove draft speed boost. @@ -2454,7 +2487,7 @@ void K_PlayOvertakeSound(mobj_t *source) { boolean tasteful = (!source->player || !source->player->karthud[khud_voices]); - if (!gametype == GT_RACE) // Only in race + if (!(gametyperules & GTR_CIRCUIT)) // Only in race return; // 4 seconds from before race begins, 10 seconds afterwards @@ -2963,8 +2996,7 @@ fixed_t K_GetSpindashChargeSpeed(player_t *player) // (can be higher than this value when overcharged) const fixed_t val = (10*FRACUNIT/277) + (((player->kartspeed + player->kartweight) + 2) * FRACUNIT) / 45; - // TODO: gametyperules - return (gametype == GT_BATTLE) ? (4 * val) : val; + return (gametyperules & GTR_CLOSERPLAYERS) ? (4 * val) : val; } // sets boostpower, speedboost, accelboost, and handleboost to whatever we need it to be @@ -3099,9 +3131,8 @@ static void K_GetKartBoostPower(player_t *player) // 30% - 44%, each point of speed adds 1.75% fixed_t draftspeed = ((3*FRACUNIT)/10) + ((player->kartspeed-1) * ((7*FRACUNIT)/400)); - if (gametype == GT_BATTLE) + if (gametyperules & GTR_CLOSERPLAYERS) { - // TODO: gametyperules draftspeed *= 2; } @@ -3224,7 +3255,7 @@ fixed_t K_GetKartAccel(player_t *player) k_accel += 17 * (9 - player->kartspeed); // 121 - 257 // karma bomb gets 2x acceleration - if (gametype == GT_BATTLE && player->bumpers <= 0) + if ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0) k_accel *= 2; // Marble Garden Top gets 1200% accel @@ -3238,9 +3269,8 @@ UINT16 K_GetKartFlashing(player_t *player) { UINT16 tics = flashingtics; - if (gametype == GT_BATTLE) + if (gametyperules & GTR_BUMPERS) { - // TODO: gametyperules return 1; } @@ -3303,7 +3333,8 @@ SINT8 K_GetForwardMove(player_t *player) return 0; } - if (player->sneakertimer || player->spindashboost) + if (player->sneakertimer || player->spindashboost + || (((gametyperules & (GTR_ROLLINGSTART|GTR_CIRCUIT)) == (GTR_ROLLINGSTART|GTR_CIRCUIT)) && (leveltime < TICRATE/2))) { return MAXPLMOVE; } @@ -3545,6 +3576,12 @@ void K_BattleAwardHit(player_t *player, player_t *victim, mobj_t *inflictor, UIN UINT8 points = 1; boolean trapItem = false; + if (!(gametyperules & GTR_POINTLIMIT)) + { + // No points in this gametype. + return; + } + if (player == NULL || victim == NULL) { // Invalid player or victim @@ -3580,11 +3617,8 @@ void K_BattleAwardHit(player_t *player, player_t *victim, mobj_t *inflictor, UIN } } - if (gametyperules & GTR_POINTLIMIT) - { - P_AddPlayerScore(player, points); - K_SpawnBattlePoints(player, victim, points); - } + P_AddPlayerScore(player, points); + K_SpawnBattlePoints(player, victim, points); } void K_SpinPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 type) @@ -4132,7 +4166,7 @@ void K_HandleBumperChanges(player_t *player, UINT8 prevBumpers) player->karmadelay = comebacktime; - if (bossinfo.boss) + if (gametyperules & GTR_BOSS) { P_DoTimeOver(player); } @@ -5056,7 +5090,7 @@ void K_SpawnDraftDust(mobj_t *mo) { UINT8 leniency = (3*TICRATE)/4 + ((mo->player->kartweight-1) * (TICRATE/4)); - if (gametype == GT_BATTLE) + if (gametyperules & GTR_CLOSERPLAYERS) leniency *= 4; ang = mo->player->drawangle; @@ -6183,17 +6217,48 @@ void K_DropHnextList(player_t *player, boolean keepshields) } } +SINT8 K_GetTotallyRandomResult(UINT8 useodds) +{ + itemroulette_t rouletteData = {0}; + INT32 spawnchance[NUMKARTRESULTS]; + INT32 totalspawnchance = 0; + INT32 i; + + memset(spawnchance, 0, sizeof (spawnchance)); + + K_FillItemRouletteData(NULL, &rouletteData); + + for (i = 1; i < NUMKARTRESULTS; i++) + { + spawnchance[i] = ( + totalspawnchance += K_KartGetItemOdds(NULL, &rouletteData, useodds, i) + ); + } + + if (totalspawnchance > 0) + { + totalspawnchance = P_RandomKey(PR_ITEM_ROULETTE, totalspawnchance); + for (i = 0; i < NUMKARTRESULTS && spawnchance[i] <= totalspawnchance; i++); + } + else + { + i = KITEM_SAD; + } + + return i; +} + 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, @@ -6205,62 +6270,31 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 if (type == 0) { - itemroulette_t rouletteData = {0}; - UINT8 useodds = 0; - INT32 spawnchance[NUMKARTRESULTS]; - INT32 totalspawnchance = 0; - INT32 i; + const SINT8 i = K_GetTotallyRandomResult(amount); - memset(spawnchance, 0, sizeof (spawnchance)); + // TODO: this is bad! + // K_KartGetItemResult requires a player + // but item roulette will need rewritten to change this - useodds = amount; + const SINT8 newType = K_ItemResultToType(i); + const UINT8 newAmount = K_ItemResultToAmount(i); - K_FillItemRouletteData(NULL, &rouletteData); - - for (i = 1; i < NUMKARTRESULTS; i++) + if (newAmount > 1) { - spawnchance[i] = ( - totalspawnchance += K_KartGetItemOdds(NULL, &rouletteData, useodds, i) - ); - } + UINT8 j; - if (totalspawnchance > 0) - { - UINT8 newType; - UINT8 newAmount; - - totalspawnchance = P_RandomKey(PR_ITEM_ROULETTE, totalspawnchance); - for (i = 0; i < NUMKARTRESULTS && spawnchance[i] <= totalspawnchance; i++); - - // TODO: this is bad! - // K_KartGetItemResult requires a player - // but item roulette will need rewritten to change this - - newType = K_ItemResultToType(i); - newAmount = K_ItemResultToAmount(i); - - if (newAmount > 1) + for (j = 0; j < newAmount-1; j++) { - UINT8 j; - - for (j = 0; j < newAmount-1; j++) - { - K_CreatePaperItem( - x, y, z, - angle, flip, - newType, 1 - ); - } + K_CreatePaperItem( + x, y, z, + angle, flip, + newType, 1 + ); } + } - drop->threshold = newType; - drop->movecount = 1; - } - else - { - drop->threshold = 1; - drop->movecount = 1; - } + drop->threshold = newType; + drop->movecount = 1; } else { @@ -6273,6 +6307,11 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 P_SetTarget(&backdrop->tracer, drop); backdrop->flags2 |= MF2_LINKDRAW; + if (gametyperules & GTR_BUMPERS) + { + drop->fuse = BATTLE_DESPAWN_TIME; + } + return drop; } @@ -6793,10 +6832,10 @@ mobj_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range) mobj_t *wtarg = NULL; INT32 i; - if (specialStage.active == true) + if (specialstageinfo.valid == true) { - // Always target the UFO. - return specialStage.ufo; + // Always target the UFO (but not the emerald) + return K_GetPossibleSpecialTarget(); } for (i = 0; i < MAXPLAYERS; i++) @@ -7472,7 +7511,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) K_SpawnGrowShrinkParticles(player->mo, player->growshrinktimer); } - if (gametype == GT_RACE && player->rings <= 0) // spawn ring debt indicator + if (!(gametyperules & GTR_SPHERES) && player->rings <= 0) // spawn ring debt indicator { mobj_t *debtflag = P_SpawnMobj(player->mo->x + player->mo->momx, player->mo->y + player->mo->momy, player->mo->z + P_GetMobjZMovement(player->mo) + player->mo->height + (24*player->mo->scale), MT_THOK); @@ -7538,8 +7577,6 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) //CONS_Printf("cam: %d, dest: %d\n", player->karthud[khud_boostcam], player->karthud[khud_destboostcam]); } - player->karthud[khud_timeovercam] = 0; - // Make ABSOLUTELY SURE that your flashing tics don't get set WHILE you're still in hit animations. if (player->spinouttimer != 0 || player->wipeoutslow != 0) { @@ -7790,7 +7827,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->eggmanexplode) { - if (player->spectator || (gametype == GT_BATTLE && !player->bumpers)) + if (player->spectator || ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0)) player->eggmanexplode = 0; else { @@ -8032,10 +8069,11 @@ void K_KartPlayerAfterThink(player_t *player) mobj_t *ret = NULL; - if (specialStage.active == true && lastTargID == MAXPLAYERS) + if (specialstageinfo.valid == true + && lastTargID == MAXPLAYERS) { - // Aiming at the UFO. - lastTarg = specialStage.ufo; + // Aiming at the UFO (but never the emerald). + lastTarg = K_GetPossibleSpecialTarget(); } else if ((lastTargID >= 0 && lastTargID <= MAXPLAYERS) && playeringame[lastTargID] == true) @@ -9332,15 +9370,18 @@ static INT32 K_FlameShieldMax(player_t *player) UINT8 numplayers = 0; UINT8 i; - for (i = 0; i < MAXPLAYERS; i++) + if (gametyperules & GTR_CIRCUIT) { - if (playeringame[i] && !players[i].spectator) - numplayers++; - if (players[i].position == 1) - disttofinish = players[i].distancetofinish; + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator) + numplayers++; + if (players[i].position == 1) + disttofinish = players[i].distancetofinish; + } } - if (numplayers <= 1 || gametype == GT_BATTLE) + if (numplayers <= 1) { return 16; // max when alone, for testing // and when in battle, for chaos @@ -9594,8 +9635,7 @@ static void K_KartSpindash(player_t *player) { fixed_t thrust = FixedMul(player->mo->scale, player->spindash*FRACUNIT/5); - // TODO: gametyperules - if (gametype == GT_BATTLE) + if (gametyperules & GTR_CLOSERPLAYERS) thrust *= 2; // Give a bit of a boost depending on charge. @@ -10402,8 +10442,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->mo->destscale = FixedMul(player->mo->destscale, SHRINK_SCALE); } - // TODO: gametyperules - player->growshrinktimer = max(player->growshrinktimer, (gametype == GT_BATTLE ? 8 : 12) * TICRATE); + player->growshrinktimer = max(player->growshrinktimer, ((gametyperules & GTR_CLOSERPLAYERS) ? 8 : 12) * TICRATE); if (player->invincibilitytimer > 0) { @@ -10561,8 +10600,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if ((cmd->buttons & BT_ATTACK) && (player->pflags & PF_HOLDREADY)) { - // TODO: gametyperules - const INT32 incr = gametype == GT_BATTLE ? 4 : 2; + const INT32 incr = (gametyperules & GTR_CLOSERPLAYERS) ? 4 : 2; if (player->flamedash == 0) { @@ -10598,8 +10636,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) { player->pflags |= PF_HOLDREADY; - // TODO: gametyperules - if (gametype != GT_BATTLE || leveltime % 6 == 0) + if (!(gametyperules & GTR_CLOSERPLAYERS) || leveltime % 6 == 0) { if (player->flamemeter > 0) player->flamemeter--; @@ -10721,18 +10758,13 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (player->hyudorotimer > 0) { - INT32 hyu = hyudorotime; - - if (gametype == GT_RACE) - hyu *= 2; // double in race - if (leveltime & 1) { player->mo->renderflags |= RF_DONTDRAW; } else { - if (player->hyudorotimer >= (TICRATE/2) && player->hyudorotimer <= hyu-(TICRATE/2)) + if (player->hyudorotimer >= (TICRATE/2) && player->hyudorotimer <= hyudorotime-(TICRATE/2)) player->mo->renderflags &= ~K_GetPlayerDontDrawFlag(player); else player->mo->renderflags &= ~RF_DONTDRAW; @@ -10745,17 +10777,17 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->mo->renderflags &= ~RF_DONTDRAW; } - if (gametype == GT_BATTLE && player->bumpers <= 0) // dead in match? you da bomb + if (!(gametyperules & GTR_BUMPERS) || player->bumpers > 0) + { + player->mo->renderflags &= ~(RF_TRANSMASK|RF_BRIGHTMASK); + } + else // dead in match? you da bomb { K_DropItems(player); //K_StripItems(player); K_StripOther(player); player->mo->renderflags |= RF_GHOSTLY; player->flashing = player->karmadelay; } - else if (gametype == GT_RACE || player->bumpers > 0) - { - player->mo->renderflags &= ~(RF_TRANSMASK|RF_BRIGHTMASK); - } if (player->trickpanel == 1) { @@ -10968,7 +11000,7 @@ void K_CheckSpectateStatus(void) continue; if (leveltime > (starttime + 20*TICRATE)) // DON'T allow if the match is 20 seconds in return; - if (gametype == GT_RACE && players[i].laps >= 2) // DON'T allow if the race is at 2 laps + if ((gametyperules & GTR_CIRCUIT) && players[i].laps >= 2) // DON'T allow if the race is at 2 laps return; continue; } @@ -11164,4 +11196,27 @@ void K_HandleDirectionalInfluence(player_t *player) player->mo->momy = FixedMul(speed, finalY); } +void K_UpdateMobjItemOverlay(mobj_t *part, SINT8 itemType, UINT8 itemCount) +{ + switch (itemType) + { + case KITEM_ORBINAUT: + part->sprite = SPR_ITMO; + part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetOrbinautItemFrame(itemCount); + break; + case KITEM_INVINCIBILITY: + part->sprite = SPR_ITMI; + part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetInvincibilityItemFrame(); + break; + case KITEM_SAD: + part->sprite = SPR_ITEM; + part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE; + break; + default: + part->sprite = SPR_ITEM; + part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|(itemType); + break; + } +} + //} diff --git a/src/k_kart.h b/src/k_kart.h index f74f14e12..76043919b 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -138,6 +138,7 @@ INT32 K_GetKartDriftSparkValueForStage(player_t *player, UINT8 stage); void K_SpawnDriftBoostExplosion(player_t *player, int stage); void K_SpawnDriftElectricSparks(player_t *player, int color, boolean shockwave); void K_KartUpdatePosition(player_t *player); +SINT8 K_GetTotallyRandomResult(UINT8 useodds); mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT8 amount); void K_DropItems(player_t *player); void K_DropRocketSneaker(player_t *player); @@ -197,6 +198,8 @@ fixed_t K_ItemScaleForPlayer(player_t *player); void K_SetItemOut(player_t *player); void K_UnsetItemOut(player_t *player); +void K_UpdateMobjItemOverlay(mobj_t *part, SINT8 itemType, UINT8 itemCount); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_menu.h b/src/k_menu.h index cfb325fd6..f7a9c9725 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -119,7 +119,11 @@ struct menucolor_t { extern menucolor_t *menucolorhead, *menucolortail; -extern CV_PossibleValue_t gametype_cons_t[]; +extern INT16 menugametype; +void M_NextMenuGametype(UINT32 forbidden); +void M_PrevMenuGametype(UINT32 forbidden); +void M_HandleHostMenuGametype(INT32 choice); +void M_HandlePauseMenuGametype(INT32 choice); // // MENU TYPEDEFS @@ -417,6 +421,7 @@ extern menu_t MISC_StatisticsDef; typedef enum { mpause_addons = 0, + mpause_changegametype, mpause_switchmap, mpause_restartmap, mpause_tryagain, @@ -435,9 +440,6 @@ typedef enum mpause_title, } mpause_e; -extern menuitem_t PAUSE_GamemodesMenu[]; -extern menu_t PAUSE_GamemodesDef; - extern menuitem_t PAUSE_PlaybackMenu[]; extern menu_t PAUSE_PlaybackMenuDef; @@ -696,7 +698,6 @@ extern struct cupgrid_s { size_t cappages; tic_t previewanim; boolean grandprix; // Setup grand prix server after picking - boolean netgame; // Start the game in an actual server } cupgrid; typedef struct levelsearch_s { @@ -713,6 +714,7 @@ extern struct levellist_s { UINT16 dest; INT16 choosemap; UINT8 newgametype; + UINT8 guessgt; levelsearch_t levelsearch; boolean netgame; // Start the game in an actual server } levellist; @@ -773,7 +775,6 @@ void M_MPOptSelect(INT32 choice); void M_MPOptSelectInit(INT32 choice); void M_MPOptSelectTick(void); boolean M_MPResetOpts(void); -extern consvar_t cv_dummygametype; // lazy hack to allow us to select the GT on the server host submenu extern consvar_t cv_dummyip; // I HAVE // HAVE YOUR IP ADDRESS (This just the hack Cvar we'll type into and then it apends itself to "connect" in the console for IP join) diff --git a/src/k_menudef.c b/src/k_menudef.c index 99903d788..a4b8de7bd 100644 --- a/src/k_menudef.c +++ b/src/k_menudef.c @@ -89,12 +89,15 @@ menuitem_t PLAY_GamemodesMenu[] = {IT_STRING | IT_CALL, "Race", "A contest to see who's the fastest of them all!", NULL, {.routine = M_SetupRaceMenu}, 0, 0}, - {IT_STRING | IT_CALL, "Battle", "It's last hedgehog standing in this free-for-all!", + {IT_STRING | IT_CALL, "Battle", "It's last kart standing in this free-for-all!", "MENIMG00", {.routine = M_LevelSelectInit}, 0, GT_BATTLE}, {IT_STRING | IT_CALL, "Capsules", "Bust up all of the capsules in record time!", NULL, {.routine = M_LevelSelectInit}, 1, GT_BATTLE}, + {IT_STRING | IT_CALL, "Special", "Strike your target and secure the prize!", + NULL, {.routine = M_LevelSelectInit}, 1, GT_SPECIAL}, + {IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0}, }; @@ -359,8 +362,8 @@ menuitem_t PLAY_MP_Host[] = {IT_STRING | IT_CVAR, "Max. Players", "Set how many players can play at once. Others will spectate.", NULL, {.cvar = &cv_maxplayers}, 0, 0}, - {IT_STRING | IT_CVAR, "Gamemode", "Are we racing? Or perhaps battling?", - NULL, {.cvar = &cv_dummygametype}, 0, 0}, + {IT_STRING | IT_KEYHANDLER, "Gamemode", "Choose the type of play on your server.", + NULL, {.routine = M_HandleHostMenuGametype}, 0, 0}, {IT_STRING | IT_CALL, "GO", "Select a map with the currently selected gamemode", NULL, {.routine = M_MPSetupNetgameMapSelect}, 0, 0}, @@ -1132,9 +1135,6 @@ menuitem_t OPTIONS_Server[] = {IT_STRING | IT_CVAR, "Vote Timer", "Set how long players have to vote.", NULL, {.cvar = &cv_votetime}, 0, 0}, - {IT_STRING | IT_CVAR, "Vote Mode Change", "Set how often voting proposes a different gamemode.", - NULL, {.cvar = &cv_kartvoterulechanges}, 0, 0}, - {IT_SPACE | IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0}, @@ -1593,8 +1593,11 @@ menuitem_t PAUSE_Main[] = {IT_STRING | IT_CALL, "ADDONS", "M_ICOADD", NULL, {.routine = M_Addons}, 0, 0}, - {IT_STRING | IT_SUBMENU, "CHANGE MAP", "M_ICOMAP", - NULL, {.submenu = &PAUSE_GamemodesDef}, 0, 0}, + {IT_STRING | IT_KEYHANDLER, "GAMETYPE", "M_ICOGAM", + NULL, {.routine = M_HandlePauseMenuGametype}, 0, 0}, + + {IT_STRING | IT_CALL, "CHANGE MAP", "M_ICOMAP", + NULL, {.routine = M_LevelSelectInit}, 0, -1}, {IT_STRING | IT_CALL, "RESTART MAP", "M_ICORE", NULL, {.routine = M_RestartMap}, 0, 0}, @@ -1647,20 +1650,6 @@ menu_t PAUSE_MainDef = { M_PauseInputs }; -// PAUSE : Map switching gametype selection (In case you want to pick from battle / race...) -menuitem_t PAUSE_GamemodesMenu[] = -{ - {IT_STRING | IT_CALL, "Race", "Select which gamemode to choose a new map from.", - NULL, {.routine = M_LevelSelectInit}, 0, GT_RACE}, - - {IT_STRING | IT_CALL, "Battle", "Select which gamemode to choose a new map from.", - NULL, {.routine = M_LevelSelectInit}, 0, GT_BATTLE}, - - {IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0}, -}; - -menu_t PAUSE_GamemodesDef = KARTGAMEMODEMENU(PAUSE_GamemodesMenu, &PAUSE_MainDef); - // Replay popup menu menuitem_t PAUSE_PlaybackMenu[] = { diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 9071907bc..0f1514cba 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -541,12 +541,7 @@ void M_Drawer(void) } else { -#ifdef DEVELOP // Development -- show revision / branch info - V_DrawThinString(vid.dupx, vid.height - 20*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT|V_ALLOWLOWERCASE, compbranch); - V_DrawThinString(vid.dupx, vid.height - 10*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT|V_ALLOWLOWERCASE, comprevision); -#else // Regular build - V_DrawThinString(vid.dupx, vid.height - 10*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT|V_ALLOWLOWERCASE, va("%s", VERSIONSTRING)); -#endif + F_VersionDrawer(); } @@ -1933,7 +1928,7 @@ static void M_DrawCupPreview(INT16 y, levelsearch_t *levelsearch) V_DrawFill(0, y, BASEVIDWIDTH, 54, 31); - if (levelsearch->cup && !M_CupLocked(levelsearch->cup)) + if (levelsearch->cup && maxlevels > 0) { add = (cupgrid.previewanim / 82) % maxlevels; map = start; @@ -1979,16 +1974,18 @@ static void M_DrawCupPreview(INT16 y, levelsearch_t *levelsearch) } } -static void M_DrawCupTitle(INT16 y, cupheader_t *cup) +static void M_DrawCupTitle(INT16 y, levelsearch_t *levelsearch) { + UINT8 temp = 0; + V_DrawScaledPatch(0, y, 0, W_CachePatchName("MENUHINT", PU_CACHE)); - if (cup) + if (levelsearch->cup) { - boolean unlocked = !M_CupLocked(cup); + boolean unlocked = (M_GetFirstLevelInList(&temp, levelsearch) != NEXTMAP_INVALID); UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE); - patch_t *icon = W_CachePatchName(cup->icon, PU_CACHE); - const char *str = (unlocked ? va("%s Cup", cup->name) : "???"); + patch_t *icon = W_CachePatchName(levelsearch->cup->icon, PU_CACHE); + const char *str = (unlocked ? va("%s Cup", levelsearch->cup->name) : "???"); INT16 offset = V_LSTitleLowStringWidth(str, 0) / 2; V_DrawLSTitleLowString(BASEVIDWIDTH/2 - offset, y+6, 0, str); @@ -2002,32 +1999,35 @@ static void M_DrawCupTitle(INT16 y, cupheader_t *cup) else { if (currentMenu == &PLAY_LevelSelectDef) - V_DrawCenteredLSTitleLowString(BASEVIDWIDTH/2, y+6, 0, va("%s Mode", Gametype_Names[levellist.newgametype])); + { + UINT8 namedgt = (levellist.guessgt != MAXGAMETYPES) ? levellist.guessgt : levellist.newgametype; + V_DrawCenteredLSTitleLowString(BASEVIDWIDTH/2, y+6, 0, va("%s Mode", gametypes[namedgt]->name)); + } } } void M_DrawCupSelect(void) { - UINT8 i, j; + UINT8 i, j, temp = 0; levelsearch_t templevelsearch = levellist.levelsearch; // full copy - templevelsearch.cup = cupgrid.builtgrid[CUPMENU_CURSORID]; for (i = 0; i < CUPMENU_COLUMNS; i++) { for (j = 0; j < CUPMENU_ROWS; j++) { size_t id = (i + (j * CUPMENU_COLUMNS)) + (cupgrid.pageno * (CUPMENU_COLUMNS * CUPMENU_ROWS)); - cupheader_t *iconcup = cupgrid.builtgrid[id]; patch_t *patch = NULL; INT16 x, y; INT16 icony = 7; - if (!iconcup) + if (!cupgrid.builtgrid[id]) break; - /*if (iconcup->emeraldnum == 0) + templevelsearch.cup = cupgrid.builtgrid[id]; + + /*if (templevelsearch.cup->emeraldnum == 0) patch = W_CachePatchName("CUPMON3A", PU_CACHE); - else*/ if (iconcup->emeraldnum > 7) + else*/ if (templevelsearch.cup->emeraldnum > 7) { patch = W_CachePatchName("CUPMON2A", PU_CACHE); icony = 5; @@ -2040,14 +2040,14 @@ void M_DrawCupSelect(void) V_DrawScaledPatch(x, y, 0, patch); - if (M_CupLocked(iconcup)) + if (M_GetFirstLevelInList(&temp, &templevelsearch) == NEXTMAP_INVALID) { patch_t *st = W_CachePatchName(va("ICONST0%d", (cupgrid.previewanim % 4) + 1), PU_CACHE); V_DrawScaledPatch(x + 8, y + icony, 0, st); } else { - V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName(iconcup->icon, PU_CACHE)); + V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName(templevelsearch.cup->icon, PU_CACHE)); V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName("CUPBOX", PU_CACHE)); } } @@ -2058,8 +2058,10 @@ void M_DrawCupSelect(void) 0, W_CachePatchName("CUPCURS", PU_CACHE) ); + templevelsearch.cup = cupgrid.builtgrid[CUPMENU_CURSORID]; + M_DrawCupPreview(146 + (24*menutransition.tics), &templevelsearch); - M_DrawCupTitle(120 - (24*menutransition.tics), templevelsearch.cup); + M_DrawCupTitle(120 - (24*menutransition.tics), &templevelsearch); } static void M_DrawHighLowLevelTitle(INT16 x, INT16 y, INT16 map) @@ -2225,7 +2227,7 @@ void M_DrawLevelSelect(void) map = M_GetNextLevelInList(map, &j, &levellist.levelsearch); } - M_DrawCupTitle(tay, levellist.levelsearch.cup); + M_DrawCupTitle(tay, &levellist.levelsearch); } void M_DrawTimeAttack(void) @@ -2261,7 +2263,8 @@ void M_DrawTimeAttack(void) laprec = mapheaderinfo[map]->mainrecord->lap; } - if (levellist.newgametype != GT_BATTLE) + if ((gametypes[levellist.newgametype]->rules & GTR_CIRCUIT) + && (mapheaderinfo[map]->numlaps != 1)) { V_DrawRightAlignedString(rightedge-12, timeheight, highlightflags, "BEST LAP:"); K_drawKartTimestamp(laprec, 162+t, timeheight+6, 0, 2); @@ -2462,6 +2465,20 @@ void M_DrawMPHost(void) } break; } + case IT_KEYHANDLER: + { + if (currentMenu->menuitems[i].itemaction.routine != M_HandleHostMenuGametype) + break; + + w = V_ThinStringWidth(gametypes[menugametype]->name, V_6WIDTHSPACE); + V_DrawThinString(xp + 138 - w, yp, highlightflags|V_6WIDTHSPACE, gametypes[menugametype]->name); + if (i == itemOn) + { + V_DrawCharacter(xp + 138 - 10 - w - (skullAnimCounter/5), yp, '\x1C' | highlightflags, false); // left arrow + V_DrawCharacter(xp + 138 + 2 + (skullAnimCounter/5), yp, '\x1D' | highlightflags, false); // right arrow + } + break; + } } xp += 5; @@ -3680,27 +3697,19 @@ void M_DrawPause(void) case IT_STRING: { patch_t *pp; + UINT8 *colormap = NULL; - if (i == itemOn) + if (i == itemOn && (i == mpause_restartmap || i == mpause_tryagain)) { - 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( + va("M_ICOR2%c", ('A'+(pausemenu.ticker & 1))), + PU_CACHE); } else { pp = W_CachePatchName(currentMenu->menuitems[i].tooltip, PU_CACHE); + if (i == itemOn) + colormap = yellowmap; } // 294 - 261 = 33 @@ -3711,7 +3720,7 @@ void M_DrawPause(void) // This double ternary is awful, yes. dypos = ypos + pausemenu.offset; - V_DrawFixedPatch( ((i == itemOn ? (294 - pausemenu.offset*2/3 * (dypos > 100 ? 1 : -1)) : 261) + offset) << FRACBITS, (dypos)*FRACUNIT, FRACUNIT, 0, pp, NULL); + V_DrawFixedPatch( ((i == itemOn ? (294 - pausemenu.offset*2/3 * (dypos > 100 ? 1 : -1)) : 261) + offset) << FRACBITS, (dypos)*FRACUNIT, FRACUNIT, 0, pp, colormap); ypos += 50; itemsdrawn++; // We drew that! @@ -3760,12 +3769,26 @@ void M_DrawPause(void) word1[word1len] = '\0'; word2[word2len] = '\0'; - // If there's no 2nd word, take this opportunity to center this line of text. - if (word1len) - V_DrawCenteredLSTitleHighString(220 + offset*2, 75 + (!word2len ? 10 : 0), 0, word1); + if (itemOn == mpause_changegametype) + { + INT32 w = V_LSTitleLowStringWidth(gametypes[menugametype]->name, 0)/2; - if (word2len) - V_DrawCenteredLSTitleLowString(220 + offset*2, 103, 0, word2); + if (word1len) + V_DrawCenteredLSTitleHighString(220 + offset*2, 75, 0, word1); + + V_DrawLSTitleLowString(220-w + offset*2, 103, V_YELLOWMAP, gametypes[menugametype]->name); + V_DrawCharacter(220-w + offset*2 - 8 - (skullAnimCounter/5), 103+6, '\x1C' | V_YELLOWMAP, false); // left arrow + V_DrawCharacter(220+w + offset*2 + 4 + (skullAnimCounter/5), 103+6, '\x1D' | V_YELLOWMAP, false); // right arrow + } + else + { + // If there's no 2nd word, take this opportunity to center this line of text. + if (word1len) + V_DrawCenteredLSTitleHighString(220 + offset*2, 75 + (!word2len ? 10 : 0), 0, word1); + + if (word2len) + V_DrawCenteredLSTitleLowString(220 + offset*2, 103, 0, word2); + } } tic_t playback_last_menu_interaction_leveltime = 0; @@ -3964,9 +3987,22 @@ static void M_DrawReplayHutReplayInfo(menudemo_t *demoref) if (demoref->numlaps) V_DrawThinString(x, y+9, V_SNAPTOTOP|V_ALLOWLOWERCASE, va("(%d laps)", demoref->numlaps)); - V_DrawString(x, y+20, V_SNAPTOTOP|V_ALLOWLOWERCASE, demoref->gametype == GT_RACE ? - va("Race (%s speed)", kartspeed_cons_t[(demoref->kartspeed & ~DF_ENCORE) + 1].strvalue) : - "Battle Mode"); + { + const char *gtstring; + if (demoref->gametype < 0) + { + gtstring = "Custom (not loaded)"; + } + else + { + gtstring = gametypes[demoref->gametype]->name; + + if ((gametypes[demoref->gametype]->rules & GTR_CIRCUIT)) + gtstring = va("%s (%s)", gtstring, kartspeed_cons_t[(demoref->kartspeed & ~DF_ENCORE) + 1].strvalue); + } + + V_DrawString(x, y+20, V_SNAPTOTOP|V_ALLOWLOWERCASE, gtstring); + } if (!demoref->standings[0].ranking) { @@ -3979,30 +4015,33 @@ static void M_DrawReplayHutReplayInfo(menudemo_t *demoref) V_DrawThinString(x, y+29, V_SNAPTOTOP|highlightflags, "WINNER"); V_DrawString(x+38, y+30, V_SNAPTOTOP|V_ALLOWLOWERCASE, demoref->standings[0].name); - if (demoref->gametype == GT_RACE) + if (demoref->gametype >= 0) { - V_DrawThinString(x, y+39, V_SNAPTOTOP|highlightflags, "TIME"); - } - else - { - V_DrawThinString(x, y+39, V_SNAPTOTOP|highlightflags, "SCORE"); - } + if (gametypes[demoref->gametype]->rules & GTR_POINTLIMIT) + { + V_DrawThinString(x, y+39, V_SNAPTOTOP|highlightflags, "SCORE"); + } + else + { + V_DrawThinString(x, y+39, V_SNAPTOTOP|highlightflags, "TIME"); + } - 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), - G_TicsToCentiseconds(demoref->standings[0].timeorscore) - )); - } - else - { - V_DrawString(x+32, y+40, V_SNAPTOTOP, va("%d", demoref->standings[0].timeorscore)); + if (demoref->standings[0].timeorscore == (UINT32_MAX-1)) + { + V_DrawThinString(x+32, y+39, V_SNAPTOTOP, "NO CONTEST"); + } + else if (gametypes[demoref->gametype]->rules & GTR_POINTLIMIT) + { + V_DrawString(x+32, y+40, V_SNAPTOTOP, va("%d", demoref->standings[0].timeorscore)); + } + else + { + 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), + G_TicsToCentiseconds(demoref->standings[0].timeorscore) + )); + } } // Character face! @@ -4197,14 +4236,16 @@ void M_DrawReplayStartMenu(void) if (demoref->standings[i].timeorscore == UINT32_MAX-1) V_DrawThinString(BASEVIDWIDTH-92, STARTY + i*20 + 9, V_SNAPTOTOP, "NO CONTEST"); - else if (demoref->gametype == GT_RACE) + else if (demoref->gametype < 0) + ; + else if (gametypes[demoref->gametype]->rules & GTR_POINTLIMIT) + V_DrawString(BASEVIDWIDTH-92, STARTY + i*20 + 9, V_SNAPTOTOP, va("%d", demoref->standings[i].timeorscore)); + else V_DrawRightAlignedString(BASEVIDWIDTH-40, STARTY + i*20 + 9, V_SNAPTOTOP, va("%d'%02d\"%02d", G_TicsToMinutes(demoref->standings[i].timeorscore, true), G_TicsToSeconds(demoref->standings[i].timeorscore), G_TicsToCentiseconds(demoref->standings[i].timeorscore) )); - else - V_DrawString(BASEVIDWIDTH-92, STARTY + i*20 + 9, V_SNAPTOTOP, va("%d", demoref->standings[i].timeorscore)); // Character face! @@ -4692,6 +4733,16 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) specialmap = btcmapcache; break; } + case SECRET_SPECIALATTACK: + { + static UINT16 sscmapcache = NEXTMAP_INVALID; + if (sscmapcache > nummapheaders) + { + sscmapcache = G_RandMap(G_TOLFlag(GT_SPECIAL), -1, 2, 0, false, NULL); + } + specialmap = sscmapcache; + break; + } case SECRET_HARDSPEED: { static UINT16 hardmapcache = NEXTMAP_INVALID; diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 71a447a9e..191542ca7 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -147,10 +147,6 @@ consvar_t cv_menujam_update = CVAR_INIT ("menujam_update", "Off", CV_SAVE, CV_On static CV_PossibleValue_t menujam_cons_t[] = {{0, "menu"}, {1, "menu2"}, {2, "menu3"}, {0, NULL}}; static consvar_t cv_menujam = CVAR_INIT ("menujam", "0", CV_SAVE, menujam_cons_t, NULL); -// This gametype list is integral for many different reasons. -// When you add gametypes here, don't forget to update them in dehacked.c and doomstat.h! -CV_PossibleValue_t gametype_cons_t[NUMGAMETYPES+1]; - static CV_PossibleValue_t serversort_cons_t[] = { {0,"Ping"}, {1,"AVG. Power Level"}, @@ -189,18 +185,18 @@ static CV_PossibleValue_t dummyteam_cons_t[] = {{0, "Spectator"}, {1, "Red"}, {2 static CV_PossibleValue_t dummyspectate_cons_t[] = {{0, "Spectator"}, {1, "Playing"}, {0, NULL}}; static CV_PossibleValue_t dummyscramble_cons_t[] = {{0, "Random"}, {1, "Points"}, {0, NULL}}; static CV_PossibleValue_t dummystaff_cons_t[] = {{0, "MIN"}, {100, "MAX"}, {0, NULL}}; -static CV_PossibleValue_t dummygametype_cons_t[] = {{0, "Race"}, {1, "Battle"}, {0, NULL}}; //static consvar_t cv_dummymenuplayer = CVAR_INIT ("dummymenuplayer", "P1", CV_HIDDEN|CV_CALL, dummymenuplayer_cons_t, Dummymenuplayer_OnChange); static consvar_t cv_dummyteam = CVAR_INIT ("dummyteam", "Spectator", CV_HIDDEN, dummyteam_cons_t, NULL); //static cv_dummyspectate = CVAR_INITconsvar_t ("dummyspectate", "Spectator", CV_HIDDEN, dummyspectate_cons_t, NULL); static consvar_t cv_dummyscramble = CVAR_INIT ("dummyscramble", "Random", CV_HIDDEN, dummyscramble_cons_t, NULL); static consvar_t cv_dummystaff = CVAR_INIT ("dummystaff", "0", CV_HIDDEN|CV_CALL, dummystaff_cons_t, Dummystaff_OnChange); -consvar_t cv_dummygametype = CVAR_INIT ("dummygametype", "Race", CV_HIDDEN, dummygametype_cons_t, NULL); consvar_t cv_dummyip = CVAR_INIT ("dummyip", "", CV_HIDDEN, NULL, NULL); consvar_t cv_dummymenuplayer = CVAR_INIT ("dummymenuplayer", "P1", CV_HIDDEN|CV_CALL, dummymenuplayer_cons_t, Dummymenuplayer_OnChange); consvar_t cv_dummyspectate = CVAR_INIT ("dummyspectate", "Spectator", CV_HIDDEN, dummyspectate_cons_t, NULL); +INT16 menugametype = GT_RACE; + consvar_t cv_dummyprofilename = CVAR_INIT ("dummyprofilename", "", CV_HIDDEN, NULL, NULL); consvar_t cv_dummyprofileplayername = CVAR_INIT ("dummyprofileplayername", "", CV_HIDDEN, NULL, NULL); consvar_t cv_dummyprofilekickstart = CVAR_INIT ("dummyprofilekickstart", "Off", CV_HIDDEN, CV_OnOff, NULL); @@ -1009,6 +1005,8 @@ void M_ClearMenus(boolean callexitmenufunc) if (!menuactive) return; + CON_ClearHUD(); + if (currentMenu->quitroutine && callexitmenufunc && !currentMenu->quitroutine()) return; // we can't quit this menu (also used to set parameter from the menu) @@ -1713,7 +1711,6 @@ void M_Init(void) CV_RegisterVar(&cv_dummyspectate); CV_RegisterVar(&cv_dummyscramble); CV_RegisterVar(&cv_dummystaff); - CV_RegisterVar(&cv_dummygametype); CV_RegisterVar(&cv_dummyip); CV_RegisterVar(&cv_dummyprofilename); @@ -3295,25 +3292,40 @@ void M_SetupGametypeMenu(INT32 choice) PLAY_GamemodesDef.prevMenu = currentMenu; - // Battle and Capsules disabled + // Battle and Capsules (and Special) disabled PLAY_GamemodesMenu[1].status = IT_DISABLED; PLAY_GamemodesMenu[2].status = IT_DISABLED; + PLAY_GamemodesMenu[3].status = IT_DISABLED; if (cv_splitplayers.value > 1) { // Re-add Battle PLAY_GamemodesMenu[1].status = IT_STRING | IT_CALL; } - else if (M_SecretUnlocked(SECRET_BREAKTHECAPSULES, true)) - { - // Re-add Capsules - PLAY_GamemodesMenu[2].status = IT_STRING | IT_CALL; - } else { - // Only one non-Back entry, let's skip straight to Race. - M_SetupRaceMenu(-1); - return; + boolean anyunlocked = false; + + if (M_SecretUnlocked(SECRET_BREAKTHECAPSULES, true)) + { + // Re-add Capsules + PLAY_GamemodesMenu[2].status = IT_STRING | IT_CALL; + anyunlocked = true; + } + + if (M_SecretUnlocked(SECRET_SPECIALATTACK, true)) + { + // Re-add Special + PLAY_GamemodesMenu[3].status = IT_STRING | IT_CALL; + anyunlocked = true; + } + + if (!anyunlocked) + { + // Only one non-Back entry, let's skip straight to Race. + M_SetupRaceMenu(-1); + return; + } } M_SetupNextMenu(&PLAY_GamemodesDef, false); @@ -3406,9 +3418,6 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch) if (mapheaderinfo[mapnum]->lumpnum == LUMPERROR) return false; - if (levelsearch->checklocked && M_MapLocked(mapnum+1)) - return false; // not unlocked - // Check for TOL if (!(mapheaderinfo[mapnum]->typeoflevel & levelsearch->typeoflevel)) return false; @@ -3427,6 +3436,19 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch) && mapheaderinfo[mapnum]->cup != levelsearch->cup) return false; + // Finally, the most complex check: does the map have lock conditions? + if (levelsearch->checklocked) + { + // Check for completion + if ((mapheaderinfo[mapnum]->menuflags & LF2_FINISHNEEDED) + && !(mapheaderinfo[mapnum]->mapvisited & MV_BEATEN)) + return false; + + // Check for unlock + if (M_MapLocked(mapnum+1)) + return false; + } + // Survived our checks. return true; } @@ -3440,6 +3462,9 @@ UINT16 M_CountLevelsToShowInList(levelsearch_t *levelsearch) if (levelsearch->cup) { + if (levelsearch->checklocked && M_CupLocked(levelsearch->cup)) + return 0; + for (i = 0; i < CUPCACHE_MAX; i++) { if (!M_CanShowLevelInList(levelsearch->cup->cachedlevels[i], levelsearch)) @@ -3466,6 +3491,12 @@ UINT16 M_GetFirstLevelInList(UINT8 *i, levelsearch_t *levelsearch) if (levelsearch->cup) { + if (levelsearch->checklocked && M_CupLocked(levelsearch->cup)) + { + *i = CUPCACHE_MAX; + return NEXTMAP_INVALID; + } + *i = 0; mapnum = NEXTMAP_INVALID; for (; *i < CUPCACHE_MAX; (*i)++) @@ -3530,20 +3561,39 @@ static void M_LevelSelectScrollDest(void) } // Builds the level list we'll be using from the gametype we're choosing and send us to the apropriate menu. -static void M_LevelListFromGametype(INT16 gt) +static boolean M_LevelListFromGametype(INT16 gt) { static boolean first = true; - if (first || gt != levellist.newgametype) + UINT8 temp = 0; + + if (first || gt != levellist.newgametype || levellist.guessgt != MAXGAMETYPES) { + if (first) + { + cupgrid.cappages = 0; + cupgrid.builtgrid = NULL; + } + levellist.newgametype = gt; + levellist.levelsearch.typeoflevel = G_TOLFlag(gt); - levellist.levelsearch.cupmode = (!(gametypedefaultrules[gt] & GTR_NOCUPSELECT)); + if (levellist.levelsearch.timeattack == true && gt == GT_SPECIAL) + { + // Sneak in an extra. + levellist.levelsearch.typeoflevel |= G_TOLFlag(GT_VERSUS); + levellist.guessgt = gt; + } + else + { + levellist.guessgt = MAXGAMETYPES; + } + + levellist.levelsearch.cupmode = (!(gametypes[gt]->rules & GTR_NOCUPSELECT)); levellist.levelsearch.cup = NULL; + first = false; } - PLAY_CupSelectDef.prevMenu = currentMenu; - // Obviously go to Cup Select in gametypes that have cups. // Use a really long level select in gametypes that don't use cups. @@ -3551,32 +3601,35 @@ static void M_LevelListFromGametype(INT16 gt) { levelsearch_t templevelsearch = levellist.levelsearch; // full copy size_t currentid = 0, highestunlockedid = 0; - const size_t unitlen = sizeof(cupheader_t*) * (CUPMENU_COLUMNS * CUPMENU_ROWS); + const size_t pagelen = sizeof(cupheader_t*) * (CUPMENU_COLUMNS * CUPMENU_ROWS); + boolean foundany = false; templevelsearch.cup = kartcupheaders; - templevelsearch.checklocked = false; - // Make sure there's valid cups before going to this menu. +#if 0 + // Make sure there's valid cups before going to this menu. -- rip sweet prince if (templevelsearch.cup == NULL) I_Error("Can you really call this a racing game, I didn't recieve any Cups on my pillow or anything"); +#endif - if (!cupgrid.builtgrid) + if (cupgrid.cappages == 0) { cupgrid.cappages = 2; cupgrid.builtgrid = Z_Calloc( - cupgrid.cappages * unitlen, + cupgrid.cappages * pagelen, PU_STATIC, - cupgrid.builtgrid); + NULL); if (!cupgrid.builtgrid) { I_Error("M_LevelListFromGametype: Not enough memory to allocate builtgrid"); } } - memset(cupgrid.builtgrid, 0, cupgrid.cappages * unitlen); + memset(cupgrid.builtgrid, 0, cupgrid.cappages * pagelen); while (templevelsearch.cup) { + templevelsearch.checklocked = false; if (!M_CountLevelsToShowInList(&templevelsearch)) { // No valid maps, skip. @@ -3584,10 +3637,12 @@ static void M_LevelListFromGametype(INT16 gt) continue; } - if ((currentid * sizeof(cupheader_t*)) >= cupgrid.cappages * unitlen) + foundany = true; + + if ((currentid * sizeof(cupheader_t*)) >= cupgrid.cappages * pagelen) { // Double the size of the buffer, and clear the other stuff. - const size_t firstlen = cupgrid.cappages * unitlen; + const size_t firstlen = cupgrid.cappages * pagelen; cupgrid.builtgrid = Z_Realloc(cupgrid.builtgrid, firstlen * 2, PU_STATIC, NULL); @@ -3603,7 +3658,8 @@ static void M_LevelListFromGametype(INT16 gt) cupgrid.builtgrid[currentid] = templevelsearch.cup; - if (!M_CupLocked(templevelsearch.cup)) + templevelsearch.checklocked = true; + if (M_GetFirstLevelInList(&temp, &templevelsearch) != NEXTMAP_INVALID) { highestunlockedid = currentid; if (Playing() && mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == templevelsearch.cup) @@ -3618,16 +3674,29 @@ static void M_LevelListFromGametype(INT16 gt) templevelsearch.cup = templevelsearch.cup->next; } + if (foundany == false) + { + return false; + } + cupgrid.numpages = (highestunlockedid / (CUPMENU_COLUMNS * CUPMENU_ROWS)) + 1; if (cupgrid.pageno >= cupgrid.numpages) { cupgrid.pageno = 0; } + PLAY_CupSelectDef.prevMenu = currentMenu; PLAY_LevelSelectDef.prevMenu = &PLAY_CupSelectDef; M_SetupNextMenu(&PLAY_CupSelectDef, false); - return; + return true; + } + + // Okay, just a list of maps then. + + if (M_GetFirstLevelInList(&temp, &levellist.levelsearch) == NEXTMAP_INVALID) + { + return false; } // Reset position properly if you go back & forth between gametypes @@ -3643,6 +3712,7 @@ static void M_LevelListFromGametype(INT16 gt) PLAY_LevelSelectDef.prevMenu = currentMenu; M_SetupNextMenu(&PLAY_LevelSelectDef, false); + return true; } // Init level select for use in local play using the last choice we made. @@ -3651,10 +3721,11 @@ static void M_LevelListFromGametype(INT16 gt) void M_LevelSelectInit(INT32 choice) { + INT32 gt = currentMenu->menuitems[itemOn].mvar2; + (void)choice; // Make sure this is reset as we'll only be using this function for offline games! - cupgrid.netgame = false; levellist.netgame = false; levellist.levelsearch.checklocked = true; @@ -3677,7 +3748,111 @@ void M_LevelSelectInit(INT32 choice) return; } - M_LevelListFromGametype(currentMenu->menuitems[itemOn].mvar2); + if (gt == -1) + { + gt = menugametype; + } + + if (!M_LevelListFromGametype(gt)) + { + S_StartSound(NULL, sfx_s3kb2); + M_StartMessage(va("No levels available for\n%s Mode!\n\nPress (B)\n", gametypes[gt]->name), NULL, MM_NOTHING); + } +} + +static void M_LevelSelected(INT16 add) +{ + UINT8 i = 0; + INT16 map = M_GetFirstLevelInList(&i, &levellist.levelsearch); + + while (add > 0) + { + map = M_GetNextLevelInList(map, &i, &levellist.levelsearch); + + if (map >= nummapheaders) + { + break; + } + + add--; + } + + if (map >= nummapheaders) + { + // This shouldn't happen + return; + } + + levellist.choosemap = map; + + if (levellist.levelsearch.timeattack) + { + S_StartSound(NULL, sfx_s3k63); + + if (levellist.guessgt != MAXGAMETYPES) + levellist.newgametype = G_GuessGametypeByTOL(levellist.levelsearch.typeoflevel); + + PLAY_TimeAttackDef.lastOn = ta_start; + PLAY_TimeAttackDef.prevMenu = currentMenu; + M_SetupNextMenu(&PLAY_TimeAttackDef, false); + } + else + { + if (gamestate == GS_MENU) + { + UINT8 ssplayers = cv_splitplayers.value-1; + + netgame = false; + multiplayer = true; + + strncpy(connectedservername, cv_servername.string, MAXSERVERNAME); + + // Still need to reset devmode + cht_debug = 0; + + if (demo.playback) + G_StopDemo(); + if (metalrecording) + G_StopMetalDemo(); + + /*if (levellist.choosemap == 0) + levellist.choosemap = G_RandMap(G_TOLFlag(levellist.newgametype), -1, 0, 0, false, NULL);*/ + + if (cv_maxconnections.value < ssplayers+1) + CV_SetValue(&cv_maxconnections, ssplayers+1); + + if (splitscreen != ssplayers) + { + splitscreen = ssplayers; + SplitScreen_OnChange(); + } + + S_StartSound(NULL, sfx_s3k63); + + paused = false; + + // Early fadeout to let the sound finish playing + F_WipeStartScreen(); + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); + F_WipeEndScreen(); + F_RunWipe(wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); + + SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); + + CV_StealthSet(&cv_kartbot, cv_dummymatchbots.string); + CV_StealthSet(&cv_kartencore, (cv_dummygpencore.value == 1) ? "On" : "Auto"); + CV_StealthSet(&cv_kartspeed, (cv_dummykartspeed.value == KARTSPEED_NORMAL) ? "Auto" : cv_dummykartspeed.string); + + D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); + } + else + { + // directly do the map change + D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); + } + + M_ClearMenus(true); + } } void M_CupSelectHandler(INT32 choice) @@ -3733,13 +3908,18 @@ void M_CupSelectHandler(INT32 choice) if (M_MenuConfirmPressed(pid) /*|| M_MenuButtonPressed(pid, MBT_START)*/) { + INT16 count; cupheader_t *newcup = cupgrid.builtgrid[CUPMENU_CURSORID]; + cupheader_t *oldcup = levellist.levelsearch.cup; M_SetMenuDelay(pid); + levellist.levelsearch.cup = newcup; + count = M_CountLevelsToShowInList(&levellist.levelsearch); + if ((!newcup) - || (M_CupLocked(newcup)) - || (newcup->cachedlevels[0] == NEXTMAP_INVALID)) + || (count <= 0) + || (cupgrid.grandprix == true && newcup->cachedlevels[0] == NEXTMAP_INVALID)) { S_StartSound(NULL, sfx_s3kb2); return; @@ -3803,13 +3983,17 @@ void M_CupSelectHandler(INT32 choice) M_ClearMenus(true); } + else if (count == 1) + { + PLAY_TimeAttackDef.transitionID = currentMenu->transitionID+1; + M_LevelSelected(0); + } else { // Keep cursor position if you select the same cup again, reset if it's a different cup - if (levellist.levelsearch.cup != newcup) + if (oldcup != newcup || levellist.cursor >= count) { levellist.cursor = 0; - levellist.levelsearch.cup = newcup; } M_LevelSelectScrollDest(); @@ -3868,94 +4052,10 @@ void M_LevelSelectHandler(INT32 choice) if (M_MenuConfirmPressed(pid) /*|| M_MenuButtonPressed(pid, MBT_START)*/) { - UINT8 i = 0; - INT16 map = M_GetFirstLevelInList(&i, &levellist.levelsearch); - INT16 add = levellist.cursor; - M_SetMenuDelay(pid); - while (add > 0) - { - map = M_GetNextLevelInList(map, &i, &levellist.levelsearch); - - if (map >= nummapheaders) - { - break; - } - - add--; - } - - if (map >= nummapheaders) - { - // This shouldn't happen - return; - } - - levellist.choosemap = map; - - if (levellist.levelsearch.timeattack) - { - M_SetupNextMenu(&PLAY_TimeAttackDef, false); - S_StartSound(NULL, sfx_s3k63); - } - else - { - if (gamestate == GS_MENU) - { - UINT8 ssplayers = cv_splitplayers.value-1; - - netgame = false; - multiplayer = true; - - strncpy(connectedservername, cv_servername.string, MAXSERVERNAME); - - // Still need to reset devmode - cht_debug = 0; - - if (demo.playback) - G_StopDemo(); - if (metalrecording) - G_StopMetalDemo(); - - /*if (levellist.choosemap == 0) - levellist.choosemap = G_RandMap(G_TOLFlag(levellist.newgametype), -1, 0, 0, false, NULL);*/ - - if (cv_maxconnections.value < ssplayers+1) - CV_SetValue(&cv_maxconnections, ssplayers+1); - - if (splitscreen != ssplayers) - { - splitscreen = ssplayers; - SplitScreen_OnChange(); - } - - S_StartSound(NULL, sfx_s3k63); - - paused = false; - - // Early fadeout to let the sound finish playing - F_WipeStartScreen(); - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); - F_WipeEndScreen(); - F_RunWipe(wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); - - SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); - - CV_StealthSet(&cv_kartbot, cv_dummymatchbots.string); - CV_StealthSet(&cv_kartencore, (cv_dummygpencore.value == 1) ? "On" : "Auto"); - CV_StealthSet(&cv_kartspeed, (cv_dummykartspeed.value == KARTSPEED_NORMAL) ? "Auto" : cv_dummykartspeed.string); - - D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); - } - else - { - // directly do the map change - D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); - } - - M_ClearMenus(true); - } + PLAY_TimeAttackDef.transitionID = currentMenu->transitionID; + M_LevelSelected(levellist.cursor); } else if (M_MenuBackPressed(pid)) { @@ -4006,14 +4106,12 @@ void M_StartTimeAttack(INT32 choice) (void)choice; - switch (levellist.newgametype) + modeattacking = ATTACKING_TIME; + + if ((gametypes[levellist.newgametype]->rules & GTR_CIRCUIT) + && (mapheaderinfo[levellist.choosemap]->numlaps != 1)) { - case GT_BATTLE: - modeattacking = ATTACKING_CAPSULES; - break; - default: - modeattacking = ATTACKING_TIME; - break; + modeattacking |= ATTACKING_LAP; } // Still need to reset devmode @@ -4078,6 +4176,7 @@ void M_MPOptSelectInit(INT32 choice) { INT16 arrcpy[3][3] = {{0,68,0}, {0,12,0}, {0,74,0}}; UINT8 i = 0, j = 0; // To copy the array into the struct + const UINT32 forbidden = GTR_FORBIDMP; (void)choice; @@ -4088,6 +4187,10 @@ void M_MPOptSelectInit(INT32 choice) for (j = 0; j < 3; j++) mpmenu.modewinextend[i][j] = arrcpy[i][j]; // I miss Lua already + // Guarantee menugametype is good + M_NextMenuGametype(forbidden); + M_PrevMenuGametype(forbidden); + M_SetupNextMenu(&PLAY_MP_OptSelectDef, false); } @@ -4119,41 +4222,93 @@ void M_MPHostInit(INT32 choice) itemOn = mhost_go; } +void M_NextMenuGametype(UINT32 forbidden) +{ + const INT16 currentmenugametype = menugametype; + do + { + menugametype++; + if (menugametype >= numgametypes) + menugametype = 0; + + if (!(gametypes[menugametype]->rules & forbidden)) + break; + } while (menugametype != currentmenugametype); +} + +void M_PrevMenuGametype(UINT32 forbidden) +{ + const INT16 currentmenugametype = menugametype; + do + { + if (menugametype == 0) + menugametype = numgametypes; + menugametype--; + + if (!(gametypes[menugametype]->rules & forbidden)) + break; + } while (menugametype != currentmenugametype); +} + +void M_HandleHostMenuGametype(INT32 choice) +{ + const UINT8 pid = 0; + const UINT32 forbidden = GTR_FORBIDMP; + + (void)choice; + + if (M_MenuBackPressed(pid)) + { + M_GoBack(0); + M_SetMenuDelay(pid); + return; + } + else if (menucmd[pid].dpad_lr > 0 || M_MenuConfirmPressed(pid)) + { + M_NextMenuGametype(forbidden); + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + else if (menucmd[pid].dpad_lr < 0) + { + M_PrevMenuGametype(forbidden); + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + + if (menucmd[pid].dpad_ud > 0) + { + M_NextOpt(); + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + else if (menucmd[pid].dpad_ud < 0) + { + M_PrevOpt(); + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } +} + void M_MPSetupNetgameMapSelect(INT32 choice) { - - INT16 gt = GT_RACE; (void)choice; // Yep, we'll be starting a netgame. levellist.netgame = true; - cupgrid.netgame = true; // Make sure we reset those levellist.levelsearch.timeattack = false; levellist.levelsearch.checklocked = true; cupgrid.grandprix = false; - // In case we ever want to add new gamemodes there somehow, have at it! - switch (cv_dummygametype.value) - { - case 1: // Battle - { - gt = GT_BATTLE; - break; - } - - default: - { - gt = GT_RACE; - break; - } - } - // okay this is REALLY stupid but this fixes the host menu re-folding on itself when we go back. mpmenu.modewinextend[0][0] = 1; - M_LevelListFromGametype(gt); // Setup the level select. - // (This will also automatically send us to the apropriate menu) + if (!M_LevelListFromGametype(menugametype)) + { + S_StartSound(NULL, sfx_s3kb2); + M_StartMessage(va("No levels available for\n%s Mode!\n\nPress (B)\n", gametypes[menugametype]->name), NULL, MM_NOTHING); + } } // MULTIPLAYER JOIN BY IP @@ -6063,6 +6218,7 @@ void M_OpenPauseMenu(void) // By default, disable anything sensitive: PAUSE_Main[mpause_addons].status = IT_DISABLED; + PAUSE_Main[mpause_changegametype].status = IT_DISABLED; PAUSE_Main[mpause_switchmap].status = IT_DISABLED; PAUSE_Main[mpause_restartmap].status = IT_DISABLED; PAUSE_Main[mpause_tryagain].status = IT_DISABLED; @@ -6084,14 +6240,10 @@ 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_changegametype].status = IT_STRING | IT_KEYHANDLER; + menugametype = gametype; + + PAUSE_Main[mpause_switchmap].status = IT_STRING | IT_CALL; PAUSE_Main[mpause_restartmap].status = IT_STRING | IT_CALL; PAUSE_Main[mpause_addons].status = IT_STRING | IT_CALL; } @@ -6192,6 +6344,46 @@ boolean M_PauseInputs(INT32 ch) return false; } +// Change gametype +void M_HandlePauseMenuGametype(INT32 choice) +{ + const UINT8 pid = 0; + const UINT32 forbidden = GTR_FORBIDMP; + + (void)choice; + + if (M_MenuConfirmPressed(pid)) + { + if (menugametype != gametype) + { + M_ClearMenus(true); + COM_ImmedExecute(va("randommap -gt %s", gametypes[menugametype]->name)); + return; + } + + M_SetMenuDelay(pid); + S_StartSound(NULL, sfx_s3k7b); + } + else if (M_MenuExtraPressed(pid)) + { + menugametype = gametype; + M_SetMenuDelay(pid); + S_StartSound(NULL, sfx_s3k7b); + } + else if (menucmd[pid].dpad_lr > 0) + { + M_NextMenuGametype(forbidden); + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + else if (menucmd[pid].dpad_lr < 0) + { + M_PrevMenuGametype(forbidden); + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } +} + // Restart map void M_RestartMap(INT32 choice) { @@ -6269,7 +6461,7 @@ void M_EndGame(INT32 choice) if (!Playing()) return; - 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); + M_StartMessage(M_GetText("Are you sure you want to\nreturn to the menu?\nPress (A) to confirm or (B) to cancel\n"), FUNCPTRCAST(M_ExitGameResponse), MM_YESNO); } @@ -7546,9 +7738,16 @@ void M_Statistics(INT32 choice) if (!mapheaderinfo[i]) continue; + // Check for no visibility + legacy box if (mapheaderinfo[i]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINSTATS|LF2_HIDEINMENU)) continue; + // Check for completion + if ((mapheaderinfo[i]->menuflags & LF2_FINISHNEEDED) + && !(mapheaderinfo[i]->mapvisited & MV_BEATEN)) + continue; + + // Check for unlock if (M_MapLocked(i+1)) continue; diff --git a/src/k_objects.h b/src/k_objects.h index dbe3b6ffc..0078aeae6 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -60,7 +60,7 @@ void Obj_DuelBombInit(mobj_t *bomb); /* Broly Ki */ mobj_t *Obj_SpawnBrolyKi(mobj_t *source, tic_t duration); -void Obj_BrolyKiThink(mobj_t *ki); +boolean Obj_BrolyKiThink(mobj_t *ki); /* Special Stage UFO */ waypoint_t *K_GetSpecialUFOWaypoint(mobj_t *ufo); diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index 6ad088cae..db46a9266 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -16,7 +16,6 @@ #include "m_cond.h" // M_UpdateUnlockablesAndExtraEmblems #include "p_tick.h" // leveltime #include "k_grandprix.h" -#include "k_boss.h" #include "k_profiles.h" // Client-sided calculations done for Power Levels. @@ -38,7 +37,7 @@ SINT8 K_UsingPowerLevels(void) { SINT8 pt = PWRLV_DISABLED; - if (!cv_kartusepwrlv.value || !(netgame || (demo.playback && demo.netgame)) || grandprixinfo.gp == true || bossinfo.boss == true) + if (!cv_kartusepwrlv.value || !(netgame || (demo.playback && demo.netgame)) || grandprixinfo.gp == true) { return PWRLV_DISABLED; } diff --git a/src/k_race.c b/src/k_race.c index 2e7f477e9..0bbe0bfe0 100644 --- a/src/k_race.c +++ b/src/k_race.c @@ -400,7 +400,7 @@ static void K_DrawFinishLineBeamForLine(fixed_t offset, angle_t aiming, line_t * void K_RunFinishLineBeam(void) { - if (!(leveltime < starttime || rainbowstartavailable == true)) + if ((gametyperules & GTR_ROLLINGSTART) || !(leveltime < starttime || rainbowstartavailable == true)) { return; } diff --git a/src/k_respawn.c b/src/k_respawn.c index 0b4c02ccf..5e3a3ecc6 100644 --- a/src/k_respawn.c +++ b/src/k_respawn.c @@ -175,14 +175,14 @@ void K_DoIngameRespawn(player_t *player) mapthing_t *beststart = NULL; UINT8 numstarts = 0; - if (gametype == GT_RACE) - { - numstarts = numcoopstarts; - } - else if (gametype == GT_BATTLE) + if (gametyperules & GTR_BATTLESTARTS) { numstarts = numdmstarts; } + else + { + numstarts = numcoopstarts; + } if (numstarts > 0) { @@ -193,17 +193,13 @@ void K_DoIngameRespawn(player_t *player) UINT32 dist = UINT32_MAX; mapthing_t *checkstart = NULL; - if (gametype == GT_RACE) - { - checkstart = playerstarts[i]; - } - else if (gametype == GT_BATTLE) + if (gametyperules & GTR_BATTLESTARTS) { checkstart = deathmatchstarts[i]; } else { - break; + checkstart = playerstarts[i]; } dist = (UINT32)P_AproxDistance((player->mo->x >> FRACBITS) - checkstart->x, diff --git a/src/k_roulette.c b/src/k_roulette.c index 4f3de015f..da12d5209 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -176,6 +176,12 @@ static UINT8 K_KartItemOddsSpecial[NUMKARTRESULTS-1][4] = { 0, 0, 0, 0 } // Gachabom x3 }; +static kartitems_t K_KartItemReelSpecialEnd[] = +{ + KITEM_SUPERRING, + KITEM_NONE +}; + static kartitems_t K_KartItemReelTimeAttack[] = { KITEM_SNEAKER, @@ -359,7 +365,7 @@ static UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers return 0; } - if (specialStage.active == true) + if (specialstageinfo.valid == true) { UINT32 ufoDis = K_GetSpecialUFODistance(); @@ -506,7 +512,7 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, I_Assert(pos < 2); // DO NOT allow positions past the bounds of the table newOdds = K_KartItemOddsBattle[item-1][pos]; } - else if (specialStage.active == true) + else if (specialstageinfo.valid == true) { I_Assert(pos < 4); // Ditto newOdds = K_KartItemOddsSpecial[item-1][pos]; @@ -573,7 +579,7 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, return 0; } - if (specialStage.active == false) + if (specialstageinfo.valid == false) { if (roulette->firstDist < ENDDIST*2 // No SPB when 1st is almost done || position == 1) // No SPB for 1st ever @@ -705,7 +711,7 @@ static UINT8 K_FindUseodds(const player_t *player, itemroulette_t *const roulett oddsValid[i] = false; continue; } - else if (specialStage.active == true && i > 3) + else if (specialstageinfo.valid == true && i > 3) { oddsValid[i] = false; continue; @@ -734,7 +740,7 @@ static UINT8 K_FindUseodds(const player_t *player, itemroulette_t *const roulett } else { - if (specialStage.active == true) // Special Stages + if (specialstageinfo.valid == true) // Special Stages { SETUPDISTTABLE(0,2); SETUPDISTTABLE(1,2); @@ -808,7 +814,7 @@ static boolean K_ForcedSPB(const player_t *player, itemroulette_t *const roulett return false; } - if (specialStage.active == true) + if (specialstageinfo.valid == true) { return false; } @@ -904,7 +910,7 @@ static void K_InitRoulette(itemroulette_t *const roulette) roulette->exiting++; } - if (specialStage.active == true) + if (specialstageinfo.valid == true) { UINT32 dis = K_UndoMapScaling(players[i].distancetofinish); if (dis < roulette->secondDist) @@ -926,7 +932,7 @@ static void K_InitRoulette(itemroulette_t *const roulette) } } - if (specialStage.active == true) + if (specialstageinfo.valid == true) { roulette->firstDist = K_UndoMapScaling(K_GetSpecialUFODistance()); } @@ -1113,8 +1119,19 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet } // SPECIAL CASE No. 2: - // Use a special, pre-determined item reel for Time Attack / Free Play - if (bossinfo.boss == true) + // Use a special, pre-determined item reel for Time Attack / Free Play / End of Sealed Stars + if (specialstageinfo.valid) + { + if (K_GetPossibleSpecialTarget() == NULL) + { + for (i = 0; K_KartItemReelSpecialEnd[i] != KITEM_NONE; i++) + { + K_PushToRouletteItemList(roulette, K_KartItemReelSpecialEnd[i]); + } + return; + } + } + else if (gametyperules & GTR_BOSS) { for (i = 0; K_KartItemReelBoss[i] != KITEM_NONE; i++) { @@ -1125,25 +1142,17 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet } else if (K_TimeAttackRules() == true) { - switch (gametype) + kartitems_t *presetlist = K_KartItemReelTimeAttack; + + // If the objective is not to go fast, it's to cause serious damage. + if (gametyperules & GTR_CAPSULES) { - case GT_RACE: - default: - { - for (i = 0; K_KartItemReelTimeAttack[i] != KITEM_NONE; i++) - { - K_PushToRouletteItemList(roulette, K_KartItemReelTimeAttack[i]); - } - break; - } - case GT_BATTLE: - { - for (i = 0; K_KartItemReelBreakTheCapsules[i] != KITEM_NONE; i++) - { - K_PushToRouletteItemList(roulette, K_KartItemReelBreakTheCapsules[i]); - } - break; - } + presetlist = K_KartItemReelBreakTheCapsules; + } + + for (i = 0; presetlist[i] != KITEM_NONE; i++) + { + K_PushToRouletteItemList(roulette, presetlist[i]); } return; diff --git a/src/k_specialstage.c b/src/k_specialstage.c index e0551bef1..b0defccb9 100644 --- a/src/k_specialstage.c +++ b/src/k_specialstage.c @@ -22,7 +22,7 @@ #include "k_waypoint.h" #include "k_objects.h" -struct specialStage specialStage; +struct specialstageinfo specialstageinfo; /*-------------------------------------------------- void K_ResetSpecialStage(void) @@ -31,7 +31,8 @@ struct specialStage specialStage; --------------------------------------------------*/ void K_ResetSpecialStage(void) { - memset(&specialStage, 0, sizeof(struct specialStage)); + memset(&specialstageinfo, 0, sizeof(struct specialstageinfo)); + specialstageinfo.beamDist = UINT32_MAX; } /*-------------------------------------------------- @@ -41,34 +42,15 @@ void K_ResetSpecialStage(void) --------------------------------------------------*/ void K_InitSpecialStage(void) { - INT32 i; - - specialStage.beamDist = UINT32_MAX; // TODO: make proper value - P_SetTarget(&specialStage.ufo, Obj_CreateSpecialUFO()); - - for (i = 0; i < MAXPLAYERS; i++) + if ((gametyperules & (GTR_CATCHER|GTR_CIRCUIT)) != (GTR_CATCHER|GTR_CIRCUIT)) { - player_t *player = NULL; - - if (playeringame[i] == false) - { - continue; - } - - player = &players[i]; - if (player->spectator == true) - { - continue; - } - - if (player->mo == NULL || P_MobjWasRemoved(player->mo) == true) - { - continue; - } - - // Rolling start? lol - P_InstaThrust(player->mo, player->mo->angle, K_GetKartSpeed(player, false, false)); + return; } + + specialstageinfo.valid = true; + + specialstageinfo.beamDist = UINT32_MAX; // TODO: make proper value + P_SetTarget(&specialstageinfo.ufo, Obj_CreateSpecialUFO()); } /*-------------------------------------------------- @@ -88,15 +70,15 @@ static void K_MoveExitBeam(void) moveDist = (8 * mapobjectscale) / FRACUNIT; - if (specialStage.beamDist <= moveDist) + if (specialstageinfo.beamDist <= moveDist) { - specialStage.beamDist = 0; + specialstageinfo.beamDist = 0; // TODO: Fail Special Stage } else { - specialStage.beamDist -= moveDist; + specialstageinfo.beamDist -= moveDist; } // Find players who are now outside of the level. @@ -118,7 +100,7 @@ static void K_MoveExitBeam(void) continue; } - if (player->distancetofinish > specialStage.beamDist) + if (player->distancetofinish > specialstageinfo.beamDist) { P_DoTimeOver(player); } @@ -132,10 +114,30 @@ static void K_MoveExitBeam(void) --------------------------------------------------*/ void K_TickSpecialStage(void) { - if (specialStage.active == false) + if (specialstageinfo.valid == false) { return; } + if (P_MobjWasRemoved(specialstageinfo.ufo)) + { + P_SetTarget(&specialstageinfo.ufo, NULL); + } + K_MoveExitBeam(); } + +mobj_t *K_GetPossibleSpecialTarget(void) +{ + if (specialstageinfo.valid == false) + return NULL; + + if (specialstageinfo.ufo == NULL + || P_MobjWasRemoved(specialstageinfo.ufo)) + return NULL; + + if (specialstageinfo.ufo->health <= 1) //UFOEmeraldChase(specialstageinfo.ufo) + return NULL; + + return specialstageinfo.ufo; +} diff --git a/src/k_specialstage.h b/src/k_specialstage.h index 34df449fa..6b20ca220 100644 --- a/src/k_specialstage.h +++ b/src/k_specialstage.h @@ -20,14 +20,13 @@ extern "C" { #endif -extern struct specialStage +extern struct specialstageinfo { - boolean active; ///< If true, then we are in a special stage - boolean encore; ///< Copy of encore, just to make sure you can't cheat it with cvars + boolean valid; ///< If true, then data in this struct is valid UINT32 beamDist; ///< Where the exit beam is. mobj_t *ufo; ///< The Chaos Emerald capsule. -} specialStage; +} specialstageinfo; /*-------------------------------------------------- void K_ResetSpecialStage(void); @@ -55,6 +54,18 @@ void K_InitSpecialStage(void); void K_TickSpecialStage(void); +/*-------------------------------------------------- + mobj_t *K_GetPossibleSpecialTarget(void) + + Gets the global special stage target if valid + (for Jawz, tether, etc) + + Return:- + Target or NULL +--------------------------------------------------*/ + +mobj_t *K_GetPossibleSpecialTarget(void); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 87e720bea..54f3bacf1 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -2971,15 +2971,16 @@ static int lib_gAddGametype(lua_State *L) const char *k; lua_Integer i; + gametype_t *newgametype = NULL; + const char *gtname = NULL; const char *gtconst = NULL; - INT16 newgtidx = 0; UINT32 newgtrules = 0; UINT32 newgttol = 0; INT32 newgtpointlimit = 0; INT32 newgttimelimit = 0; - INT16 newgtrankingstype = -1; - int newgtinttype = 0; + UINT8 newgtinttype = 0; + INT16 j; luaL_checktype(L, 1, LUA_TTABLE); lua_settop(L, 1); // Clear out all other possible arguments, leaving only the first one. @@ -2988,8 +2989,10 @@ static int lib_gAddGametype(lua_State *L) return luaL_error(L, "This function cannot be called from within a hook or coroutine!"); // Ran out of gametype slots - if (gametypecount == NUMGAMETYPEFREESLOTS) - return luaL_error(L, "Ran out of free gametype slots!"); + if (numgametypes == GT_LASTFREESLOT) + { + I_Error("Out of Gametype Freeslots while allocating \"%s\"\nLoad less addons to fix this.", gtname); + } #define FIELDERROR(f, e) luaL_error(L, "bad value for " LUA_QL(f) " in table passed to " LUA_QL("G_AddGametype") " (%s)", e); #define TYPEERROR(f, t) FIELDERROR(f, va("%s expected, got %s", lua_typename(L, t), luaL_typename(L, -1))) @@ -3022,19 +3025,15 @@ static int lib_gAddGametype(lua_State *L) if (!lua_isnumber(L, 3)) TYPEERROR("typeoflevel", LUA_TNUMBER) newgttol = (UINT32)lua_tointeger(L, 3); - } else if (i == 5 || (k && fasticmp(k, "rankingtype"))) { - if (!lua_isnumber(L, 3)) - TYPEERROR("rankingtype", LUA_TNUMBER) - newgtrankingstype = (INT16)lua_tointeger(L, 3); - } else if (i == 6 || (k && fasticmp(k, "intermissiontype"))) { + } else if (i == 5 || (k && fasticmp(k, "intermissiontype"))) { if (!lua_isnumber(L, 3)) TYPEERROR("intermissiontype", LUA_TNUMBER) newgtinttype = (int)lua_tointeger(L, 3); - } else if (i == 7 || (k && fasticmp(k, "defaultpointlimit"))) { + } else if (i == 6 || (k && fasticmp(k, "defaultpointlimit"))) { if (!lua_isnumber(L, 3)) TYPEERROR("defaultpointlimit", LUA_TNUMBER) newgtpointlimit = (INT32)lua_tointeger(L, 3); - } else if (i == 8 || (k && fasticmp(k, "defaulttimelimit"))) { + } else if (i == 7 || (k && fasticmp(k, "defaulttimelimit"))) { if (!lua_isnumber(L, 3)) TYPEERROR("defaulttimelimit", LUA_TNUMBER) newgttimelimit = (INT32)lua_tointeger(L, 3); @@ -3045,38 +3044,44 @@ static int lib_gAddGametype(lua_State *L) #undef FIELDERROR #undef TYPEERROR + if (gtname == NULL) + return luaL_error(L, "Custom gametype must have a name"); + + if (strlen(gtname) >= MAXGAMETYPELENGTH) + return luaL_error(L, "Custom gametype \"%s\"'s name must be %d long at most", gtname, MAXGAMETYPELENGTH-1); + + for (j = 0; j < numgametypes; j++) + if (!strcmp(gtname, gametypes[j]->name)) + break; + + if (j < numgametypes) + return luaL_error(L, "Custom gametype \"%s\"'s name is already in use", gtname); + // pop gametype table lua_pop(L, 1); - // Set defaults - if (gtname == NULL) - gtname = Z_StrDup("Unnamed gametype"); - // Add the new gametype - newgtidx = G_AddGametype(newgtrules); - G_AddGametypeTOL(newgtidx, newgttol); + newgametype = Z_Calloc(sizeof (gametype_t), PU_STATIC, NULL); + if (!newgametype) + { + I_Error("Out of memory allocating gametype \"%s\"", gtname); + } - // Not covered by G_AddGametype alone. - if (newgtrankingstype == -1) - newgtrankingstype = newgtidx; - gametyperankings[newgtidx] = newgtrankingstype; - intermissiontypes[newgtidx] = newgtinttype; - pointlimits[newgtidx] = newgtpointlimit; - timelimits[newgtidx] = newgttimelimit; - - // Write the new gametype name. - Gametype_Names[newgtidx] = gtname; - - // Write the constant name. if (gtconst == NULL) gtconst = gtname; - G_AddGametypeConstant(newgtidx, gtconst); - // Update gametype_cons_t accordingly. - G_UpdateGametypeSelections(); + newgametype->name = gtname; + newgametype->rules = newgtrules; + newgametype->constant = G_PrepareGametypeConstant(gtconst); + newgametype->tol = newgttol; + newgametype->intermission = newgtinttype; + newgametype->pointlimit = newgtpointlimit; + newgametype->timelimit = newgttimelimit; + + gametypes[numgametypes++] = newgametype; // done - CONS_Printf("Added gametype %s\n", Gametype_Names[newgtidx]); + CONS_Printf("Added gametype %s\n", gtname); return 0; } @@ -3284,15 +3289,6 @@ static int lib_gExitLevel(lua_State *L) return 0; } -static int lib_gIsSpecialStage(lua_State *L) -{ - INT32 mapnum = luaL_optinteger(L, 1, gamemap); - //HUDSAFE - INLEVEL - lua_pushboolean(L, G_IsSpecialStage(mapnum)); - return 1; -} - static int lib_gGametypeUsesLives(lua_State *L) { //HUDSAFE @@ -4101,7 +4097,6 @@ static luaL_Reg lib[] = { {"G_DoReborn",lib_gDoReborn}, {"G_SetCustomExitVars",lib_gSetCustomExitVars}, {"G_ExitLevel",lib_gExitLevel}, - {"G_IsSpecialStage",lib_gIsSpecialStage}, {"G_GametypeUsesLives",lib_gGametypeUsesLives}, {"G_GametypeHasTeams",lib_gGametypeHasTeams}, {"G_GametypeHasSpectators",lib_gGametypeHasSpectators}, diff --git a/src/lua_script.c b/src/lua_script.c index 3a6d53c98..be6fed523 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -162,9 +162,6 @@ int LUA_PushGlobals(lua_State *L, const char *word) } else if (fastcmp(word,"maptol")) { lua_pushinteger(L, maptol); return 1; - } else if (fastcmp(word,"circuitmap")) { - lua_pushboolean(L, circuitmap); - return 1; } else if (fastcmp(word,"stoppedclock")) { lua_pushboolean(L, stoppedclock); return 1; diff --git a/src/m_cond.c b/src/m_cond.c index 052f23dc0..14ad2b6b4 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -570,7 +570,10 @@ static char *M_BuildConditionTitle(UINT16 map) { char *title, *ref; - if (M_MapLocked(map+1)) + if (((mapheaderinfo[map]->menuflags & LF2_FINISHNEEDED) + // the following is intentionally not MV_BEATEN, just in case the title is for "Finish a round on X" + && !(mapheaderinfo[map]->mapvisited & MV_VISITED)) + || M_MapLocked(map+1)) return Z_StrDup("???"); title = ref = G_BuildMapTitle(map+1); @@ -629,7 +632,7 @@ static const char *M_GetConditionString(condition_t *cn) title = BUILDCONDITIONTITLE(cn->requirement); work = va("%s %s%s", - (cn->type == UC_MAPVISITED) ? "Visit" : "Beat", + (cn->type == UC_MAPVISITED) ? "Visit" : "Finish a round on", title, (cn->type == UC_MAPENCORE) ? " in Encore Mode" : ""); Z_Free(title); diff --git a/src/m_cond.h b/src/m_cond.h index 5ab291c35..735894c71 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -123,7 +123,8 @@ typedef enum // Menu restrictions SECRET_TIMEATTACK, // Permit Time attack - SECRET_BREAKTHECAPSULES, // Permit SP Capsules + SECRET_BREAKTHECAPSULES, // Permit SP Capsule attack + SECRET_SPECIALATTACK, // Permit Special attack (You're blue now!) SECRET_SOUNDTEST, // Permit Sound Test SECRET_ALTTITLE, // Permit alternate titlescreen diff --git a/src/objects/broly.c b/src/objects/broly.c index d041c23b7..4c283a175 100644 --- a/src/objects/broly.c +++ b/src/objects/broly.c @@ -34,14 +34,16 @@ Obj_SpawnBrolyKi ( mobj_t * source, tic_t duration) { - mobj_t *x = P_SpawnMobjFromMobj( - source, 0, 0, 0, MT_BROLY); + mobj_t *x; - if (duration == 0) + if (duration <= 0) { - return x; + return NULL; } + x = P_SpawnMobjFromMobj( + source, 0, 0, 0, MT_BROLY); + // Shrink into center of source object. x->z = (source->z + source->height / 2); @@ -61,12 +63,20 @@ Obj_SpawnBrolyKi return x; } -void +boolean Obj_BrolyKiThink (mobj_t *x) { + if (broly_duration(x) <= 0) + { + P_RemoveMobj(x); + return false; + } + const fixed_t t = get_unit_linear(x), n = Easing_OutSine(t, 0, broly_maxscale(x)); P_InstaScale(x, n); + + return true; } diff --git a/src/objects/spb.c b/src/objects/spb.c index b76dfd4f8..af1041049 100644 --- a/src/objects/spb.c +++ b/src/objects/spb.c @@ -549,7 +549,7 @@ static void SPBSeek(mobj_t *spb, mobj_t *bestMobj) SetSPBSpeed(spb, xySpeed, zSpeed); - if (specialStage.active == false) + if (specialstageinfo.valid == false) { // see if a player is near us, if they are, try to hit them by slightly thrusting towards them, otherwise, bleh! steerDist = 1536 * mapobjectscale; @@ -857,6 +857,27 @@ void Obj_SPBThink(mobj_t *spb) ghost->colorized = true; } + if (spb_nothink(spb) <= 1) + { + if (specialstageinfo.valid == true) + { + bestRank = 0; + + if ((bestMobj = K_GetPossibleSpecialTarget()) == NULL) + { + // experimental - I think it's interesting IMO + Obj_MantaRingCreate( + spb, + spb_owner(spb), + NULL + ); + + spb->fuse = TICRATE/3; + spb_nothink(spb) = spb->fuse + 2; + } + } + } + if (spb_nothink(spb) > 0) { // Init values, don't think yet. @@ -874,15 +895,6 @@ void Obj_SPBThink(mobj_t *spb) } else { - if (specialStage.active == true) - { - if (specialStage.ufo != NULL && P_MobjWasRemoved(specialStage.ufo) == false) - { - bestRank = 1; - bestMobj = specialStage.ufo; - } - } - // Find the player with the best rank for (i = 0; i < MAXPLAYERS; i++) { diff --git a/src/objects/ufo.c b/src/objects/ufo.c index 5eaf7f645..f9a44127b 100644 --- a/src/objects/ufo.c +++ b/src/objects/ufo.c @@ -239,9 +239,9 @@ static void UFOUpdateAngle(mobj_t *ufo) waypoint_t *K_GetSpecialUFOWaypoint(mobj_t *ufo) { - if ((ufo == NULL) && (specialStage.active == true)) + if ((ufo == NULL) && (specialstageinfo.valid == true)) { - ufo = specialStage.ufo; + ufo = specialstageinfo.ufo; } if (ufo != NULL && P_MobjWasRemoved(ufo) == false @@ -281,10 +281,11 @@ static void UFOMove(mobj_t *ufo) if (curWaypoint == NULL || destWaypoint == NULL) { // Waypoints aren't valid. - // Just stand still. + // Just go straight up. + // :japanese_ogre: : "Abrupt and funny is the funniest way to end the special stage anyways" ufo->momx = 0; ufo->momy = 0; - ufo->momz = 0; + ufo->momz = ufo_speed(ufo); return; } @@ -365,8 +366,23 @@ static void UFOMove(mobj_t *ufo) if (reachedEnd == true) { - CONS_Printf("You lost...\n"); - ufo_waypoint(ufo) = -1; // Invalidate + UINT8 i; + + // Invalidate UFO/emerald + ufo_waypoint(ufo) = -1; + ufo->flags &= ~(MF_SPECIAL|MF_PICKUPFROMBELOW); + + // Disable player + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + if (players[i].spectator) + continue; + + players[i].pflags |= PF_NOCONTEST; + P_DoPlayerExit(&players[i]); + } } if (pathfindsuccess == true) @@ -591,6 +607,8 @@ boolean Obj_SpecialUFODamage(mobj_t *ufo, mobj_t *inflictor, mobj_t *source, UIN ufo->flags = (ufo->flags & ~MF_SHOOTABLE) | (MF_SPECIAL|MF_PICKUPFROMBELOW); ufo->shadowscale = FRACUNIT/3; + P_LinedefExecute(LE_PINCHPHASE, ufo, NULL); + ufo_speed(ufo) += addSpeed; // Even more speed! return true; } @@ -655,7 +673,10 @@ void Obj_UFOPieceThink(mobj_t *piece) fixed_t sc = FixedDiv(FixedDiv(ufo->ceilingz - stemZ, piece->scale), 15 * FRACUNIT); UFOMoveTo(piece, ufo->x, ufo->y, stemZ); - piece->spriteyscale = sc; + if (sc > 0) + { + piece->spriteyscale = sc; + } break; } default: @@ -820,11 +841,11 @@ mobj_t *Obj_CreateSpecialUFO(void) UINT32 K_GetSpecialUFODistance(void) { - if (specialStage.active == true) + if (specialstageinfo.valid == true) { - if (specialStage.ufo != NULL && P_MobjWasRemoved(specialStage.ufo) == false) + if (specialstageinfo.ufo != NULL && P_MobjWasRemoved(specialstageinfo.ufo) == false) { - return (UINT32)ufo_distancetofinish(specialStage.ufo); + return (UINT32)ufo_distancetofinish(specialstageinfo.ufo); } } diff --git a/src/p_enemy.c b/src/p_enemy.c index 2e1ed8557..fc67cc882 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -13053,7 +13053,7 @@ void A_ItemPop(mobj_t *actor) } // Here at mapload in battle? - if ((gametyperules & GTR_BUMPERS) && (actor->flags2 & MF2_BOSSNOTRAP)) + if (!(gametyperules & GTR_CIRCUIT) && (actor->flags2 & MF2_BOSSNOTRAP)) { numgotboxes++; diff --git a/src/p_inter.c b/src/p_inter.c index ff6ba99b0..9887b8bb7 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -32,9 +32,9 @@ // SRB2kart #include "k_kart.h" #include "k_battle.h" +#include "k_specialstage.h" #include "k_pwrlv.h" #include "k_grandprix.h" -#include "k_boss.h" #include "k_respawn.h" #include "p_spec.h" #include "k_objects.h" @@ -414,6 +414,8 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (toucher->hitlag > 0) return; + P_LinedefExecute(LE_BOSSDEAD, toucher, NULL); + CONS_Printf("You win!\n"); break; /* @@ -859,7 +861,7 @@ boolean P_CheckRacers(void) // Check if all the players in the race have finished. If so, end the level. for (i = 0; i < MAXPLAYERS; i++) { - if (!playeringame[i] || players[i].spectator || players[i].lives <= 0) + if (!playeringame[i] || players[i].spectator || (players[i].lives <= 0 && !players[i].exiting)) { // Y'all aren't even playing continue; @@ -893,7 +895,7 @@ boolean P_CheckRacers(void) } } - if (numPlaying <= 1) + if (numPlaying <= 1 || specialstageinfo.valid == true) { // Never do this without enough players. eliminateLast = false; @@ -1097,7 +1099,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget { P_SetTarget(&target->target, source); - if (gametyperules & GTR_BUMPERS) + if (!(gametyperules & GTR_CIRCUIT)) { target->fuse = 2; } @@ -2198,7 +2200,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da { tic_t kinvextend; - if (gametype == GT_BATTLE) + if (gametyperules & GTR_CLOSERPLAYERS) kinvextend = 2*TICRATE; else kinvextend = 5*TICRATE; diff --git a/src/p_mobj.c b/src/p_mobj.c index 26b38d631..230454a6c 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -39,7 +39,6 @@ // SRB2kart #include "k_kart.h" #include "k_battle.h" -#include "k_boss.h" #include "k_color.h" #include "k_respawn.h" #include "k_bot.h" @@ -4346,25 +4345,7 @@ static void P_RefreshItemCapsuleParts(mobj_t *mobj) part->threshold = mobj->threshold; part->movecount = mobj->movecount; - switch (itemType) - { - case KITEM_ORBINAUT: - part->sprite = SPR_ITMO; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetOrbinautItemFrame(mobj->movecount); - break; - case KITEM_INVINCIBILITY: - part->sprite = SPR_ITMI; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetInvincibilityItemFrame(); - break; - case KITEM_SAD: - part->sprite = SPR_ITEM; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE; - break; - default: - part->sprite = SPR_ITEM; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|(itemType); - break; - } + K_UpdateMobjItemOverlay(part, itemType, mobj->movecount); // update number frame if (K_GetShieldFromItem(itemType) != KSHIELD_NONE) // shields don't stack, so don't show a number @@ -6163,7 +6144,7 @@ static void P_MobjSceneryThink(mobj_t *mobj) mobj->color = mobj->target->color; K_MatchGenericExtraFlags(mobj, mobj->target); - if ((gametype == GT_RACE || mobj->target->player->bumpers <= 0) + if ((!(gametyperules & GTR_BUMPERS) || mobj->target->player->bumpers <= 0) #if 1 // Set to 0 to test without needing to host || (P_IsDisplayPlayer(mobj->target->player)) #endif @@ -6534,7 +6515,10 @@ static void P_MobjSceneryThink(mobj_t *mobj) mobj->renderflags ^= RF_DONTDRAW; break; case MT_BROLY: - Obj_BrolyKiThink(mobj); + if (Obj_BrolyKiThink(mobj) == false) + { + return; + } break; case MT_VWREF: case MT_VWREB: @@ -8362,7 +8346,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) statenum_t state = (mobj->state-states); if (!mobj->target || !mobj->target->health || !mobj->target->player || mobj->target->player->spectator - || (gametype == GT_RACE || mobj->target->player->bumpers)) + || (!(gametyperules & GTR_BUMPERS) || mobj->target->player->bumpers)) { P_RemoveMobj(mobj); return false; @@ -9366,7 +9350,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) { if (gametyperules & GTR_PAPERITEMS) { - if (battlecapsules == true || bossinfo.boss == true) + if (battlecapsules == true) { ; } @@ -9389,12 +9373,12 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } // FALLTHRU case MT_SPHEREBOX: - if (gametype == GT_BATTLE && mobj->threshold == 70) + if (mobj->threshold == 70) { mobj->color = K_RainbowColor(leveltime); mobj->colorized = true; - if (battleovertime.enabled) + if ((gametyperules & GTR_OVERTIME) && battleovertime.enabled) { angle_t ang = FixedAngle((leveltime % 360) << FRACBITS); fixed_t z = battleovertime.z; @@ -9528,13 +9512,39 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s P_RemoveMobj(mobj); // make sure they disappear } +static boolean P_CanFlickerFuse(mobj_t *mobj) +{ + switch (mobj->type) + { + case MT_SNAPPER_HEAD: + case MT_SNAPPER_LEG: + case MT_MINECARTSEG: + return true; + + case MT_RANDOMITEM: + case MT_EGGMANITEM: + case MT_FALLINGROCK: + case MT_FLOATINGITEM: + if (mobj->fuse <= TICRATE) + { + return true; + } + break; + + default: + break; + } + + return false; + +} + static boolean P_FuseThink(mobj_t *mobj) { - if (mobj->type == MT_SNAPPER_HEAD || mobj->type == MT_SNAPPER_LEG || mobj->type == MT_MINECARTSEG) - mobj->renderflags ^= RF_DONTDRAW; - - if (mobj->fuse <= TICRATE && (mobj->type == MT_RANDOMITEM || mobj->type == MT_EGGMANITEM || mobj->type == MT_FALLINGROCK)) + if (P_CanFlickerFuse(mobj)) + { mobj->renderflags ^= RF_DONTDRAW; + } mobj->fuse--; @@ -9582,7 +9592,7 @@ static boolean P_FuseThink(mobj_t *mobj) { ; } - else if ((gametyperules & GTR_BUMPERS) && (mobj->state == &states[S_INVISIBLE])) + else if (!(gametyperules & GTR_CIRCUIT) && (mobj->state == &states[S_INVISIBLE])) { break; } @@ -11420,7 +11430,7 @@ void P_RespawnBattleBoxes(void) { thinker_t *th; - if (!(gametyperules & GTR_BUMPERS)) + if (gametyperules & GTR_CIRCUIT) return; for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) @@ -11685,13 +11695,16 @@ void P_SpawnPlayer(INT32 playernum) K_InitStumbleIndicator(p); - if (gametyperules & GTR_BUMPERS) + if (gametyperules & GTR_ITEMARROWS) { mobj_t *overheadarrow = P_SpawnMobj(mobj->x, mobj->y, mobj->z + mobj->height + 16*FRACUNIT, MT_PLAYERARROW); P_SetTarget(&overheadarrow->target, mobj); overheadarrow->renderflags |= RF_DONTDRAW; P_SetScale(overheadarrow, mobj->destscale); + } + if (gametyperules & GTR_BUMPERS) + { if (p->spectator) { // HEY! No being cheap... @@ -12075,7 +12088,7 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i) // No bosses outside of a combat situation. // (just in case we want boss arenas to do double duty as battle maps) - if (!bossinfo.boss && (mobjinfo[i].flags & MF_BOSS)) + if (!(gametyperules & GTR_BOSS) && (mobjinfo[i].flags & MF_BOSS)) { return false; } @@ -12096,7 +12109,7 @@ static mobjtype_t P_GetMobjtypeSubstitute(mapthing_t *mthing, mobjtype_t i) if ((i == MT_RING) && (gametyperules & GTR_SPHERES)) return MT_BLUESPHERE; - if ((i == MT_RANDOMITEM) && (gametyperules & (GTR_PAPERITEMS|GTR_CIRCUIT)) == (GTR_PAPERITEMS|GTR_CIRCUIT) && !bossinfo.boss) + if ((i == MT_RANDOMITEM) && (gametyperules & (GTR_PAPERITEMS|GTR_CIRCUIT)) == (GTR_PAPERITEMS|GTR_CIRCUIT)) return MT_PAPERITEMSPOT; return i; diff --git a/src/p_setup.c b/src/p_setup.c index 407f9f42f..37ca56328 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -6820,7 +6820,6 @@ static void P_InitLevelSettings(void) rflagpoint = bflagpoint = NULL; // circuit, race and competition stuff - circuitmap = false; numstarposts = 0; timeinmap = 0; @@ -6842,7 +6841,7 @@ static void P_InitLevelSettings(void) if (playeringame[i] && !players[i].spectator) p++; - if (grandprixinfo.gp == false && bossinfo.boss == false) + if (grandprixinfo.gp == false) players[i].lives = 3; G_PlayerReborn(i, true); @@ -6852,39 +6851,27 @@ static void P_InitLevelSettings(void) racecountdown = exitcountdown = exitfadestarted = 0; curlap = bestlap = 0; // SRB2Kart - // SRB2Kart: map load variables + // Gamespeed and frantic items + gamespeed = KARTSPEED_EASY; + franticitems = false; + if (grandprixinfo.gp == true) { - if ((gametyperules & GTR_BUMPERS)) - { - gamespeed = KARTSPEED_EASY; - } - else + if (gametyperules & GTR_CIRCUIT) { gamespeed = grandprixinfo.gamespeed; } - - franticitems = false; - } - else if (bossinfo.boss) - { - gamespeed = KARTSPEED_EASY; - franticitems = false; } else if (modeattacking) { - // Just play it safe and set everything - if ((gametyperules & GTR_BUMPERS)) - gamespeed = KARTSPEED_EASY; - else + if (gametyperules & GTR_CIRCUIT) + { gamespeed = KARTSPEED_HARD; - franticitems = false; + } } else { - if ((gametyperules & GTR_BUMPERS)) - gamespeed = KARTSPEED_EASY; - else + if (gametyperules & GTR_CIRCUIT) { if (cv_kartspeed.value == KARTSPEED_AUTO) gamespeed = ((speedscramble == -1) ? KARTSPEED_NORMAL : (UINT8)speedscramble); @@ -6899,6 +6886,9 @@ static void P_InitLevelSettings(void) memset(&battleovertime, 0, sizeof(struct battleovertime)); speedscramble = encorescramble = -1; + + K_ResetSpecialStage(); + K_ResetBossInfo(); } #if 0 @@ -7000,20 +6990,23 @@ static void P_LoadRecordGhosts(void) gpath = Z_StrDup(va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap))); // Best Time ghost - if (cv_ghost_besttime.value) + if (modeattacking & ATTACKING_TIME) { - for (i = 0; i < numskins; ++i) + if (cv_ghost_besttime.value) { - if (cv_ghost_besttime.value == 1 && players[consoleplayer].skin != i) - continue; + for (i = 0; i < numskins; ++i) + { + if (cv_ghost_besttime.value == 1 && players[consoleplayer].skin != i) + continue; - if (FIL_FileExists(va("%s-%s-time-best.lmp", gpath, skins[i].name))) - G_AddGhost(va("%s-%s-time-best.lmp", gpath, skins[i].name)); + if (FIL_FileExists(va("%s-%s-time-best.lmp", gpath, skins[i].name))) + G_AddGhost(va("%s-%s-time-best.lmp", gpath, skins[i].name)); + } } } // Best Lap ghost - if (modeattacking != ATTACKING_CAPSULES) + if (modeattacking & ATTACKING_LAP) { if (cv_ghost_bestlap.value) { @@ -7220,7 +7213,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) // This is needed. Don't touch. maptol = mapheaderinfo[gamemap-1]->typeoflevel; - gametyperules = gametypedefaultrules[gametype]; CON_Drawer(); // let the user know what we are going to do I_FinishUpdate(); // page flip or blit buffer @@ -7334,13 +7326,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) } G_ClearModeAttackRetryFlag(); } - /* - else if (rendermode != render_none && G_IsSpecialStage(gamemap)) - { - P_RunSpecialStageWipe(); - ranspecialwipe = 1; - } - */ // Make sure all sounds are stopped before Z_FreeTags. S_StopSounds(); @@ -7375,7 +7360,20 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) S_Start(); } - levelfadecol = (encoremode ? 0 : 31); + if (gametyperules & GTR_SPECIALSTART) + { + if (ranspecialwipe != 2) + S_StartSound(NULL, sfx_s3kaf); + levelfadecol = 0; + } + else if (encoremode) + { + levelfadecol = 0; + } + else + { + levelfadecol = 31; + } if (rendermode != render_none) { @@ -7611,19 +7609,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) K_UpdateMatchRaceBots(); } - if (bossinfo.boss) - { - // Reset some pesky boss state that can't be handled elsewhere. - bossinfo.barlen = BOSSHEALTHBARLEN; - bossinfo.visualbar = 0; - Z_Free(bossinfo.enemyname); - Z_Free(bossinfo.subtitle); - bossinfo.enemyname = bossinfo.subtitle = NULL; - bossinfo.titleshow = 0; - bossinfo.titlesound = sfx_typri1; - memset(&(bossinfo.weakspots), 0, sizeof(weakspot_t)*NUMWEAKSPOTS); - } - if (!fromnetsave) // uglier hack { // to make a newly loaded level start on the second frame. INT32 buf = gametic % BACKUPTICS; @@ -7769,7 +7754,7 @@ UINT8 P_InitMapData(boolean existingmapheaders) for (i = 0; i < nummapheaders; ++i) { name = mapheaderinfo[i]->lumpname; - maplump = W_CheckNumForMap(name); + maplump = W_CheckNumForMap(name, (mapheaderinfo[i]->lumpnum == LUMPERROR)); // Always check for cup cache reassociations. // (The core assumption is that cups < headers.) diff --git a/src/p_spec.c b/src/p_spec.c index 4988ad881..48d38a125 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -43,6 +43,7 @@ // SRB2kart #include "k_kart.h" +#include "k_specialstage.h" #include "console.h" // CON_LogMessage #include "k_respawn.h" #include "k_terrain.h" @@ -1843,7 +1844,7 @@ static void K_HandleLapIncrement(player_t *player) { if (player) { - if (leveltime < starttime) + if (leveltime < starttime && !(gametyperules & GTR_ROLLINGSTART)) { // Will fault the player K_DoIngameRespawn(player); @@ -1902,7 +1903,7 @@ static void K_HandleLapIncrement(player_t *player) if (P_IsDisplayPlayer(player)) { - if (player->laps == numlaps) // final lap + if (numlaps > 1 && player->laps == numlaps) // final lap S_StartSound(NULL, sfx_s3k68); else if ((player->laps > 1) && (player->laps < numlaps)) // non-final lap S_StartSound(NULL, sfx_s221); @@ -1925,6 +1926,14 @@ static void K_HandleLapIncrement(player_t *player) // finished race exit setup if (player->laps > numlaps) { + if (specialstageinfo.valid == true) + { + // Don't permit a win just by sneaking ahead of the UFO/emerald. + if (!(specialstageinfo.ufo == NULL || P_MobjWasRemoved(specialstageinfo.ufo))) + { + player->pflags |= PF_NOCONTEST; + } + } P_DoPlayerExit(player); P_SetupSignExit(player); } @@ -6662,12 +6671,6 @@ void P_SpawnSpecials(boolean fromnetsave) break; } - // SRB2Kart - case 2001: // Finish Line - if ((gametyperules & GTR_CIRCUIT)) - circuitmap = true; - break; - default: break; } diff --git a/src/p_tick.c b/src/p_tick.c index 8312a18a8..ce390bd1a 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -361,7 +361,7 @@ static inline void P_RunThinkers(void) if (gametyperules & GTR_PAPERITEMS) K_RunPaperItemSpawners(); - if ((gametyperules & GTR_BUMPERS) && battleovertime.enabled) + if ((gametyperules & GTR_OVERTIME) && battleovertime.enabled) K_RunBattleOvertime(); } @@ -646,7 +646,7 @@ void P_Ticker(boolean run) P_PlayerAfterThink(&players[i]); // Bosses have a punchy start, so no position. - if (bossinfo.boss == true) + if (K_CheckBossIntro() == true) { if (leveltime == 3) { @@ -719,7 +719,7 @@ void P_Ticker(boolean run) K_TickSpecialStage(); - if ((gametyperules & GTR_BUMPERS)) + if ((gametyperules & GTR_POINTLIMIT)) { if (wantedcalcdelay && --wantedcalcdelay <= 0) K_CalculateBattleWanted(); diff --git a/src/p_user.c b/src/p_user.c index ac96ea345..be70fd0e0 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -54,6 +54,7 @@ #include "k_bot.h" #include "k_grandprix.h" #include "k_boss.h" +#include "k_specialstage.h" #include "k_terrain.h" // K_SpawnSplashForMobj #include "k_color.h" #include "k_follower.h" @@ -501,7 +502,7 @@ INT32 P_GivePlayerRings(player_t *player, INT32 num_rings) if (!player->mo) return 0; - if ((gametyperules & GTR_BUMPERS)) // No rings in Battle Mode + if ((gametyperules & GTR_SPHERES)) // No rings in Battle Mode return 0; test = player->rings + num_rings; @@ -519,6 +520,9 @@ INT32 P_GivePlayerSpheres(player_t *player, INT32 num_spheres) { num_spheres += player->spheres; + if (!(gametyperules & GTR_SPHERES)) // No spheres in Race mode) + return 0; + // Not alive if ((gametyperules & GTR_BUMPERS) && (player->bumpers <= 0)) return 0; @@ -554,7 +558,7 @@ void P_GivePlayerLives(player_t *player, INT32 numlives) // Adds to the player's score void P_AddPlayerScore(player_t *player, UINT32 amount) { - if (!((gametyperules & GTR_BUMPERS))) + if (!((gametyperules & GTR_POINTLIMIT))) return; if (player->exiting) // srb2kart @@ -720,6 +724,7 @@ boolean P_EndingMusic(player_t *player) { char buffer[9]; boolean looping = true; + boolean racetracks = !!(gametyperules & GTR_CIRCUIT); INT32 bestlocalpos, test; player_t *bestlocalplayer; @@ -773,7 +778,7 @@ boolean P_EndingMusic(player_t *player) #undef getplayerpos - if ((gametyperules & GTR_CIRCUIT) && bestlocalpos == MAXPLAYERS+1) + if (racetracks == true && bestlocalpos == MAXPLAYERS+1) sprintf(buffer, "k*fail"); // F-Zero death results theme else { @@ -787,9 +792,11 @@ boolean P_EndingMusic(player_t *player) S_SpeedMusic(1.0f); - if ((gametyperules & GTR_CIRCUIT)) + if (racetracks == true) + { buffer[1] = 'r'; - else if ((gametyperules & GTR_BUMPERS)) + } + else { buffer[1] = 'b'; looping = false; @@ -828,7 +835,8 @@ void P_RestoreMusic(player_t *player) return; // Event - Level Start - if (bossinfo.boss == false && (leveltime < (starttime + (TICRATE/2)))) // see also where time overs are handled + if ((K_CheckBossIntro() == false) + && (leveltime < (starttime + (TICRATE/2)))) // see also where time overs are handled return; { @@ -1303,7 +1311,16 @@ void P_DoPlayerExit(player_t *player) P_EndingMusic(player); if (P_CheckRacers() && !exitcountdown) - exitcountdown = raceexittime+1; + { + if (specialstageinfo.valid == true && losing == true) + { + exitcountdown = (5*TICRATE)/2; + } + else + { + exitcountdown = raceexittime+1; + } + } } else if ((gametyperules & GTR_BUMPERS)) // Battle Mode exiting { @@ -2622,7 +2639,7 @@ static void P_DeathThink(player_t *player) player->realtime = leveltime - starttime; if (player == &players[consoleplayer]) { - if (player->spectator || !circuitmap) + if (player->spectator) curlap = 0; else if (curlap != UINT32_MAX) curlap++; // This is too complicated to sync to realtime, just sorta hope for the best :V @@ -3044,7 +3061,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall return true; } - if ((player->pflags & PF_NOCONTEST) && (gametyperules & GTR_CIRCUIT)) // 1 for momentum keep, 2 for turnaround + if ((player->pflags & PF_NOCONTEST) && (gametyperules & GTR_CIRCUIT) && player->karthud[khud_timeovercam] != 0) // 1 for momentum keep, 2 for turnaround timeover = (player->karthud[khud_timeovercam] > 2*TICRATE ? 2 : 1); else timeover = 0; @@ -3612,7 +3629,7 @@ void P_DoTimeOver(player_t *player) legitimateexit = true; // SRB2kart: losing a race is still seeing it through to the end :p } - if (netgame && !player->bot && !bossinfo.boss) + if (netgame && !player->bot && !(gametyperules & GTR_BOSS)) { CON_LogMessage(va(M_GetText("%s ran out of time.\n"), player_names[player-players])); } @@ -3981,7 +3998,7 @@ void P_PlayerThink(player_t *player) player->realtime = leveltime - starttime; if (player == &players[consoleplayer]) { - if (player->spectator || !circuitmap) + if (player->spectator) curlap = 0; else if (curlap != UINT32_MAX) curlap++; // This is too complicated to sync to realtime, just sorta hope for the best :V diff --git a/src/s_sound.c b/src/s_sound.c index 96211d653..a721a9ff4 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -30,7 +30,6 @@ #include "m_misc.h" // for tunes command #include "m_cond.h" // for conditionsets #include "lua_hook.h" // MusicChange hook -#include "k_boss.h" // bossinfo #include "byteptr.h" #ifdef HW3SOUND diff --git a/src/st_stuff.c b/src/st_stuff.c index 5c2def212..3f7e793eb 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -597,7 +597,7 @@ void ST_runTitleCard(void) // SRB2KART // side Zig-Zag positions... - if (bossinfo.boss == true) + if (K_CheckBossIntro() == true) { // Handle name info... if (bossinfo.enemyname) @@ -792,7 +792,7 @@ void ST_drawTitleCard(void) if (lt_ticker < TTANIMSTART) V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol); - if (bossinfo.boss == true) + if (K_CheckBossIntro() == true) { // WARNING! // https://twitter.com/matthewseiji/status/1485003284196716544 @@ -802,7 +802,7 @@ void ST_drawTitleCard(void) #define HITIME 15 patch_t *localwarn = (encoremode ? twarn2 : twarn); INT32 transp = (lt_ticker+HITIME) % (LOTIME+HITIME); - boolean encorehack = (encoremode && lt_ticker <= PRELEVELTIME+4); + boolean encorehack = ((levelfadecol == 0) && lt_ticker <= PRELEVELTIME+4); if ((localwarn->width > 0) && (lt_ticker + (HITIME-transp) <= lt_endtime)) { @@ -1253,15 +1253,15 @@ void ST_Drawer(void) switch (demo.savemode) { case DSM_NOTSAVING: - V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|((gametyperules & GTR_BUMPERS) ? V_REDMAP : V_SKYMAP), "(B) or (X): Save replay"); + V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|V_YELLOWMAP, "(B) or (X): Save replay"); break; case DSM_WILLAUTOSAVE: - V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|((gametyperules & GTR_BUMPERS) ? V_REDMAP : V_SKYMAP), "Replay will be saved. (Look Backward: Change title)"); + V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|V_YELLOWMAP, "Replay will be saved. (Look Backward: Change title)"); break; case DSM_WILLSAVE: - V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|((gametyperules & GTR_BUMPERS) ? V_REDMAP : V_SKYMAP), "Replay will be saved."); + V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|V_YELLOWMAP, "Replay will be saved."); break; case DSM_TITLEENTRY: diff --git a/src/typedef.h b/src/typedef.h index 57958b34e..5515a4f36 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -111,6 +111,7 @@ TYPEDEF (textpage_t); TYPEDEF (textprompt_t); TYPEDEF (mappoint_t); TYPEDEF (customoption_t); +TYPEDEF (gametype_t); TYPEDEF (mapheader_t); TYPEDEF (tolinfo_t); TYPEDEF (cupheader_t); diff --git a/src/v_video.c b/src/v_video.c index d65b34cea..e633b4e3f 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -585,16 +585,16 @@ void V_AdjustXYWithSnap(INT32 *x, INT32 *y, UINT32 options, INT32 dupx, INT32 du } } - if (options & V_SLIDEIN) + if ((options & V_SLIDEIN)) { const tic_t length = TICRATE/4; tic_t timer = lt_exitticker; - if (bossinfo.boss == true) + if (K_CheckBossIntro() == true || G_IsTitleCardAvailable() == false) { - if (leveltime <= 3) + if (leveltime <= 16) timer = 0; else - timer = leveltime-3; + timer = leveltime-16; } if (timer < length) diff --git a/src/w_wad.c b/src/w_wad.c index cef5615cc..ad630a6fb 100644 --- a/src/w_wad.c +++ b/src/w_wad.c @@ -1309,12 +1309,12 @@ lumpnum_t W_CheckNumForLongName(const char *name) // Look for valid map data through all added files in descendant order. // Get a map marker for WADs, and a standalone WAD file lump inside PK3s. -lumpnum_t W_CheckNumForMap(const char *name) +lumpnum_t W_CheckNumForMap(const char *name, boolean checktofirst) { lumpnum_t check = INT16_MAX; UINT32 uhash, hash = quickncasehash(name, LUMPNUMCACHENAME); INT32 i; - UINT16 firstfile = (partadd_earliestfile == UINT16_MAX) ? 0 : partadd_earliestfile; + UINT16 firstfile = (checktofirst || (partadd_earliestfile == UINT16_MAX)) ? 0 : partadd_earliestfile; // Check the lumpnumcache first. Loop backwards so that we check // most recent entries first diff --git a/src/w_wad.h b/src/w_wad.h index 24c0ac74b..31ca26e69 100644 --- a/src/w_wad.h +++ b/src/w_wad.h @@ -171,7 +171,7 @@ UINT16 W_CheckNumForFullNamePK3(const char *name, UINT16 wad, UINT16 startlump); UINT16 W_CheckNumForFolderStartPK3(const char *name, UINT16 wad, UINT16 startlump); UINT16 W_CheckNumForFolderEndPK3(const char *name, UINT16 wad, UINT16 startlump); -lumpnum_t W_CheckNumForMap(const char *name); +lumpnum_t W_CheckNumForMap(const char *name, boolean checktofirst); lumpnum_t W_CheckNumForName(const char *name); lumpnum_t W_CheckNumForLongName(const char *name); lumpnum_t W_GetNumForName(const char *name); // like W_CheckNumForName but I_Error on LUMPERROR diff --git a/src/y_inter.c b/src/y_inter.c index 8cb21fe04..902966ca4 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -98,7 +98,6 @@ static INT32 endtic = -1; static INT32 sorttic = -1; intertype_t intertype = int_none; -intertype_t intermissiontypes[NUMGAMETYPES]; static huddrawlist_h luahuddrawlist_intermission; @@ -210,7 +209,7 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) else { // set up the levelstring - if (bossinfo.boss == true && bossinfo.enemyname) + if (bossinfo.valid == true && bossinfo.enemyname) { snprintf(data.levelstring, sizeof data.levelstring, @@ -335,7 +334,7 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) // void Y_IntermissionDrawer(void) { - INT32 i, whiteplayer = MAXPLAYERS, x = 4, hilicol = V_YELLOWMAP; // fallback + INT32 i, whiteplayer = MAXPLAYERS, x = 4, hilicol = highlightflags; if (intertype == int_none || rendermode == render_none) return; @@ -358,11 +357,6 @@ void Y_IntermissionDrawer(void) if (!r_splitscreen) whiteplayer = demo.playback ? displayplayers[0] : consoleplayer; - if (modeattacking) - hilicol = V_ORANGEMAP; - else - hilicol = ((intertype == int_race) ? V_SKYMAP : V_REDMAP); - if (sorttic != -1 && intertic > sorttic) { INT32 count = (intertic - sorttic); @@ -375,7 +369,7 @@ void Y_IntermissionDrawer(void) x += (((16 - count) * vid.width) / (8 * vid.dupx)); } - if (intertype == int_race || intertype == int_battle || intertype == int_battletime) + if (intertype == int_time || intertype == int_score) { #define NUMFORNEWCOLUMN 8 INT32 y = 41, gutter = ((data.numplayers > NUMFORNEWCOLUMN) ? 0 : (BASEVIDWIDTH/2)); @@ -398,7 +392,7 @@ void Y_IntermissionDrawer(void) { switch (intertype) { - case int_battle: + case int_score: timeheader = "SCORE"; break; default: @@ -533,7 +527,7 @@ void Y_IntermissionDrawer(void) V_DrawRightAlignedThinString(x+152+gutter, y-1, (data.numplayers > NUMFORNEWCOLUMN ? V_6WIDTHSPACE : 0), "NO CONTEST."); else { - if (intertype == int_race || intertype == int_battletime) + if (intertype == int_time) { snprintf(strtime, sizeof strtime, "%i'%02i\"%02i", G_TicsToMinutes(data.val[i], true), G_TicsToSeconds(data.val[i]), G_TicsToCentiseconds(data.val[i])); @@ -575,7 +569,7 @@ skiptallydrawer: if (!LUA_HudEnabled(hud_intermissionmessages)) return; - if (timer && grandprixinfo.gp == false && bossinfo.boss == false && !modeattacking) + if (timer && grandprixinfo.gp == false && !modeattacking) { char *string; INT32 tickdown = (timer+1)/TICRATE; @@ -669,7 +663,7 @@ void Y_Ticker(void) if (intertic < TICRATE || intertic & 1 || endtic != -1) return; - if (intertype == int_race || intertype == int_battle || intertype == int_battletime) + if (intertype == int_time || intertype == int_score) { { if (!data.rankingsmode && sorttic != -1 && (intertic >= sorttic + 8)) @@ -751,29 +745,28 @@ void Y_Ticker(void) // void Y_DetermineIntermissionType(void) { - // set to int_none initially - intertype = int_none; - - if (gametype == GT_RACE) - intertype = int_race; - else if (gametype == GT_BATTLE) + // no intermission for GP events + if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE) { - if (grandprixinfo.gp == true && bossinfo.boss == false) - intertype = int_none; - else - { - UINT8 i = 0, nump = 0; - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - continue; - nump++; - } - intertype = (nump < 2 ? int_battletime : int_battle); - } + intertype = int_none; + return; + } + + // set initially + intertype = gametypes[gametype]->intermission; + + // special cases + if (intertype == int_scoreortimeattack) + { + UINT8 i = 0, nump = 0; + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + nump++; + } + intertype = (nump < 2 ? int_time : int_score); } - else //if (intermissiontypes[gametype] != int_none) - intertype = intermissiontypes[gametype]; } // @@ -830,9 +823,6 @@ void Y_StartIntermission(void) sorttic = max((timer/2) - 2*TICRATE, 2*TICRATE); } - if (intermissiontypes[gametype] != int_none) - intertype = intermissiontypes[gametype]; - // We couldn't display the intermission even if we wanted to. // But we still need to give the players their score bonuses, dummy. //if (dedicated) return; @@ -841,23 +831,18 @@ void Y_StartIntermission(void) if (prevmap >= nummapheaders || !mapheaderinfo[prevmap]) I_Error("Y_StartIntermission: Internal map ID %d not found (nummapheaders = %d)", prevmap, nummapheaders); + if (!(gametyperules & GTR_CIRCUIT) && (timer > 1)) + S_ChangeMusicInternal("racent", true); // loop it + switch (intertype) { - case int_battle: - case int_battletime: + case int_score: { - if (timer > 1) - S_ChangeMusicInternal("racent", true); // loop it - // Calculate who won - if (intertype == int_battle) - { - Y_CalculateMatchData(0, Y_CompareScore); - break; - } + Y_CalculateMatchData(0, Y_CompareScore); + break; } - // FALLTHRU - case int_race: + case int_time: { // Calculate who won Y_CalculateMatchData(0, Y_CompareTime); @@ -1224,12 +1209,8 @@ void Y_VoteDrawer(void) if (timer) { - INT32 hilicol, tickdown = (timer+1)/TICRATE; - if (gametype == GT_RACE) - hilicol = V_SKYMAP; - else //if (gametype == GT_BATTLE) - hilicol = V_REDMAP; - V_DrawCenteredString(BASEVIDWIDTH/2, 188, hilicol, + INT32 tickdown = (timer+1)/TICRATE; + V_DrawCenteredString(BASEVIDWIDTH/2, 188, V_YELLOWMAP, va("Vote ends in %d", tickdown)); } } @@ -1569,9 +1550,9 @@ void Y_StartVote(void) levelinfo[i].str[sizeof levelinfo[i].str - 1] = '\0'; // set up the gtc and gts - levelinfo[i].gtc = G_GetGametypeColor(votelevels[i][1]); + levelinfo[i].gtc = 73; // yellowmap[0] -- TODO rewrite vote screen if (i == 2 && votelevels[i][1] != votelevels[0][1]) - levelinfo[i].gts = Gametype_Names[votelevels[i][1]]; + levelinfo[i].gts = gametypes[votelevels[i][1]]->name; else levelinfo[i].gts = NULL; } diff --git a/src/y_inter.h b/src/y_inter.h index f7a0960e8..4b73bf560 100644 --- a/src/y_inter.h +++ b/src/y_inter.h @@ -33,13 +33,12 @@ void Y_SetupVoteFinish(SINT8 pick, SINT8 level); typedef enum { int_none, - int_race, // Race - int_battle, // Battle (score-based) - int_battletime, // Battle (time-based) + int_time, // Always time + int_score, // Always score + int_scoreortimeattack, // Score unless 1P } intertype_t; extern intertype_t intertype; -extern intertype_t intermissiontypes[NUMGAMETYPES]; #ifdef __cplusplus } // extern "C"