diff --git a/src/Sourcefile b/src/Sourcefile index ea5f2d0d5..f2aa652b1 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -124,3 +124,4 @@ k_terrain.c k_director.c k_follower.c k_profiles.c +k_specialstage.c diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 5cad5e080..417fbade4 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -3877,31 +3877,23 @@ void SV_StopServer(void) } // called at singleplayer start and stopdemo -void SV_StartSinglePlayerServer(void) +void SV_StartSinglePlayerServer(INT32 dogametype, boolean donetgame) { INT32 lastgametype = gametype; server = true; - netgame = false; - multiplayer = false; + multiplayer = (modeattacking == ATTACKING_NONE); joinedIP[0] = '\0'; // Make sure to empty this so that we don't save garbage when we start our own game. (because yes we use this for netgames too....) - if ((modeattacking == ATTACKING_CAPSULES) || (bossinfo.boss == true)) - { - G_SetGametype(GT_BATTLE); - } - else - { - G_SetGametype(GT_RACE); - } + netgame = false; // so setting timelimit works... (XD_NETVAR doesn't play nice with SV_StopServer) + G_SetGametype(dogametype); if (gametype != lastgametype) D_GameTypeChanged(lastgametype); + netgame = donetgame; + // no more tic the game with this settings! SV_StopServer(); - - if (splitscreen) - multiplayer = true; } static void SV_SendRefuse(INT32 node, const char *reason) diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 354e62486..eb11ca04a 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -473,7 +473,7 @@ void SendKick(UINT8 playernum, UINT8 msg); void NetKeepAlive(void); void NetUpdate(void); -void SV_StartSinglePlayerServer(void); +void SV_StartSinglePlayerServer(INT32 dogametype, boolean donetgame); boolean SV_SpawnServer(void); void SV_StopServer(void); void SV_ResetServer(void); diff --git a/src/d_main.c b/src/d_main.c index a98eba963..7f1a40ca6 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -75,6 +75,7 @@ #include "k_boss.h" #include "doomstat.h" #include "m_random.h" // P_ClearRandom +#include "k_specialstage.h" #ifdef CMAKECONFIG #include "config.h" @@ -983,6 +984,9 @@ void D_StartTitle(void) // 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; @@ -1807,29 +1811,20 @@ void D_SRB2Main(void) INT16 newskill = -1; const char *sskill = M_GetNextParm(); - const char *masterstr = "Master"; - - if (!strcasecmp(masterstr, sskill)) + for (j = 0; gpdifficulty_cons_t[j].strvalue; j++) { - newskill = KARTGP_MASTER; + if (!strcasecmp(gpdifficulty_cons_t[j].strvalue, sskill)) + { + newskill = (INT16)gpdifficulty_cons_t[j].value; + break; + } } - else - { - for (j = 0; kartspeed_cons_t[j].strvalue; j++) - { - if (!strcasecmp(kartspeed_cons_t[j].strvalue, sskill)) - { - newskill = (INT16)kartspeed_cons_t[j].value; - break; - } - } - if (!kartspeed_cons_t[j].strvalue) // reached end of the list with no match - { - j = atoi(sskill); // assume they gave us a skill number, which is okay too - if (j >= KARTSPEED_EASY && j <= KARTGP_MASTER) - newskill = (INT16)j; - } + if (!gpdifficulty_cons_t[j].strvalue) // reached end of the list with no match + { + j = atoi(sskill); // assume they gave us a skill number, which is okay too + if (j >= KARTSPEED_EASY && j <= KARTGP_MASTER) + newskill = (INT16)j; } if (grandprixinfo.gp == true) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 1d7a2db4f..92586cab5 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -62,6 +62,7 @@ #include "doomstat.h" #include "deh_tables.h" #include "m_perfstats.h" +#include "k_specialstage.h" #ifdef HAVE_DISCORDRPC #include "discord.h" @@ -448,6 +449,7 @@ consvar_t cv_kartdebugnodes = CVAR_INIT ("debugnodes", "Off", CV_CHEAT, CV_OnOff consvar_t cv_kartdebugcolorize = CVAR_INIT ("debugcolorize", "Off", CV_CHEAT, CV_OnOff, NULL); consvar_t cv_kartdebugdirector = CVAR_INIT ("debugdirector", "Off", CV_CHEAT, CV_OnOff, NULL); consvar_t cv_spbtest = CVAR_INIT ("spbtest", "Off", CV_CHEAT|CV_NETVAR, CV_OnOff, NULL); +consvar_t cv_gptest = CVAR_INIT ("gptest", "Off", CV_CHEAT|CV_NETVAR, CV_OnOff, NULL); static CV_PossibleValue_t votetime_cons_t[] = {{10, "MIN"}, {3600, "MAX"}, {0, NULL}}; consvar_t cv_votetime = CVAR_INIT ("votetime", "20", CV_NETVAR, votetime_cons_t, NULL); @@ -471,9 +473,9 @@ consvar_t cv_overtime = CVAR_INIT ("overtime", "Yes", CV_NETVAR, CV_YesNo, NULL) consvar_t cv_rollingdemos = CVAR_INIT ("rollingdemos", "On", CV_SAVE, CV_OnOff, NULL); static CV_PossibleValue_t pointlimit_cons_t[] = {{1, "MIN"}, {MAXSCORE, "MAX"}, {0, "None"}, {0, NULL}}; -consvar_t cv_pointlimit = CVAR_INIT ("pointlimit", "None", CV_SAVE|CV_NETVAR|CV_CALL|CV_NOINIT, pointlimit_cons_t, PointLimit_OnChange); +consvar_t cv_pointlimit = CVAR_INIT ("pointlimit", "None", CV_NETVAR|CV_CALL|CV_NOINIT, pointlimit_cons_t, PointLimit_OnChange); static CV_PossibleValue_t timelimit_cons_t[] = {{1, "MIN"}, {30, "MAX"}, {0, "None"}, {0, NULL}}; -consvar_t cv_timelimit = CVAR_INIT ("timelimit", "None", CV_SAVE|CV_NETVAR|CV_CALL|CV_NOINIT, timelimit_cons_t, TimeLimit_OnChange); +consvar_t cv_timelimit = CVAR_INIT ("timelimit", "None", CV_NETVAR|CV_CALL|CV_NOINIT, timelimit_cons_t, TimeLimit_OnChange); 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); @@ -2570,15 +2572,19 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean r if ((netgame || multiplayer) && !((gametype == newgametype) && (gametypedefaultrules[newgametype] & GTR_CAMPAIGN))) FLS = false; - if (grandprixinfo.gp == true) - { - // Too lazy to change the input value for every instance of this function....... - pencoremode = grandprixinfo.encore; - } - else if (bossinfo.boss == true) + // 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) + { + pencoremode = grandprixinfo.encore; + } if (delay != 2) { @@ -2938,6 +2944,7 @@ static void Command_Map_f(void) if (newgametype == GT_BATTLE) { grandprixinfo.gp = false; + specialStage.active = false; K_ResetBossInfo(); if (mapheaderinfo[newmapnum-1] && @@ -2947,66 +2954,71 @@ static void Command_Map_f(void) bossinfo.encore = newencoremode; } } - else // default GP + else { - grandprixinfo.gamespeed = (cv_kartspeed.value == KARTSPEED_AUTO ? KARTSPEED_NORMAL : cv_kartspeed.value); - grandprixinfo.masterbots = false; - - if (option_skill) + if (mapheaderinfo[newmapnum-1] && + mapheaderinfo[newmapnum-1]->typeoflevel & TOL_SPECIAL) // Special Stage { - const char *masterstr = "Master"; - const char *skillname = COM_Argv(option_skill + 1); - INT32 newskill = -1; - INT32 j; + grandprixinfo.gp = false; + bossinfo.boss = false; - if (!strcasecmp(masterstr, skillname)) + specialStage.active = true; + specialStage.encore = newencoremode; + } + else // default GP + { + grandprixinfo.gamespeed = (cv_kartspeed.value == KARTSPEED_AUTO ? KARTSPEED_NORMAL : cv_kartspeed.value); + grandprixinfo.masterbots = false; + + if (option_skill) { - newskill = KARTGP_MASTER; - } - else - { - for (j = 0; kartspeed_cons_t[j].strvalue; j++) + const char *skillname = COM_Argv(option_skill + 1); + INT32 newskill = -1; + INT32 j; + + for (j = 0; gpdifficulty_cons_t[j].strvalue; j++) { - if (!strcasecmp(kartspeed_cons_t[j].strvalue, skillname)) + if (!strcasecmp(gpdifficulty_cons_t[j].strvalue, skillname)) { - newskill = (INT16)kartspeed_cons_t[j].value; + newskill = (INT16)gpdifficulty_cons_t[j].value; break; } } - if (!kartspeed_cons_t[j].strvalue) // reached end of the list with no match + if (!gpdifficulty_cons_t[j].strvalue) // reached end of the list with no match { INT32 num = atoi(COM_Argv(option_skill + 1)); // assume they gave us a skill number, which is okay too if (num >= KARTSPEED_EASY && num <= KARTGP_MASTER) newskill = (INT16)num; } + + if (newskill != -1) + { + if (newskill == KARTGP_MASTER) + { + grandprixinfo.gamespeed = KARTSPEED_HARD; + grandprixinfo.masterbots = true; + } + else + { + grandprixinfo.gamespeed = newskill; + grandprixinfo.masterbots = false; + } + } } - if (newskill != -1) - { - if (newskill == KARTGP_MASTER) - { - grandprixinfo.gamespeed = KARTSPEED_HARD; - grandprixinfo.masterbots = true; - } - else - { - grandprixinfo.gamespeed = newskill; - grandprixinfo.masterbots = false; - } - } + grandprixinfo.encore = newencoremode; + + grandprixinfo.gp = true; + grandprixinfo.roundnum = 0; + grandprixinfo.cup = NULL; + grandprixinfo.wonround = false; + + bossinfo.boss = false; + specialStage.active = false; + + grandprixinfo.initalize = true; } - - grandprixinfo.encore = newencoremode; - - grandprixinfo.gp = true; - grandprixinfo.roundnum = 0; - grandprixinfo.cup = NULL; - grandprixinfo.wonround = false; - - bossinfo.boss = false; - - grandprixinfo.initalize = true; } } @@ -4020,7 +4032,7 @@ void Schedule_Run(void) return; } - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { // Don't engage in automation while in a restricted context. return; @@ -4156,7 +4168,7 @@ void Automate_Run(automateEvents_t type) return; } - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { // Don't engage in automation while in a restricted context. return; @@ -4900,7 +4912,12 @@ void ItemFinder_OnChange(void) */ static void PointLimit_OnChange(void) { - // Don't allow pointlimit in Single Player/Co-Op/Race! + if (K_CanChangeRules(false) == false) + { + return; + } + + // Don't allow pointlimit in non-pointlimited gametypes! if (server && Playing() && !(gametyperules & GTR_POINTLIMIT)) { if (cv_pointlimit.value) @@ -4915,7 +4932,7 @@ static void PointLimit_OnChange(void) cv_pointlimit.value, cv_pointlimit.value > 1 ? "s" : ""); } - else if (netgame || multiplayer) + else CONS_Printf(M_GetText("Point limit disabled\n")); } @@ -4938,6 +4955,8 @@ Lagless_OnChange (void) } UINT32 timelimitintics = 0; +UINT32 extratimeintics = 0; +UINT32 secretextratime = 0; /** Deals with a timelimit change by printing the change to the console. * If the gametype is single player, cooperative, or race, the timelimit is @@ -4949,26 +4968,40 @@ UINT32 timelimitintics = 0; */ static void TimeLimit_OnChange(void) { - // Don't allow timelimit in Single Player/Co-Op/Race! - if (server && Playing() && cv_timelimit.value != 0 && (bossinfo.boss || !(gametyperules & GTR_TIMELIMIT))) + if (K_CanChangeRules(false) == false) { - CV_SetValue(&cv_timelimit, 0); return; } - if (cv_timelimit.value != 0) + if (gamestate == GS_LEVEL && leveltime < starttime) { - CONS_Printf(M_GetText("Rounds will end after %d minute%s.\n"),cv_timelimit.value,cv_timelimit.value == 1 ? "" : "s"); // Graue 11-17-2003 - timelimitintics = cv_timelimit.value * (60*TICRATE); + if (cv_timelimit.value) + { + CONS_Printf(M_GetText("Time limit has been set to %d minute%s.\n"), cv_timelimit.value,cv_timelimit.value == 1 ? "" : "s"); + } + else + { + CONS_Printf(M_GetText("Time limit has been disabled.\n")); + } - // Note the deliberate absence of any code preventing - // pointlimit and timelimit from being set simultaneously. - // Some people might like to use them together. It works. - } + timelimitintics = cv_timelimit.value * (60*TICRATE); + extratimeintics = secretextratime = 0; #ifdef HAVE_DISCORDRPC - DRPC_UpdatePresence(); + DRPC_UpdatePresence(); #endif + } + else + { + if (cv_timelimit.value) + { + CONS_Printf(M_GetText("Time limit will be %d minute%s next round.\n"), cv_timelimit.value,cv_timelimit.value == 1 ? "" : "s"); + } + else + { + CONS_Printf(M_GetText("Time limit will be disabled next round.\n")); + } + } } /** Adjusts certain settings to match a changed gametype. @@ -4995,7 +5028,7 @@ void D_GameTypeChanged(INT32 lastgametype) // Only do the following as the server, not as remote admin. // There will always be a server, and this only needs to be done once. - if (server && (multiplayer || netgame)) + if (server && multiplayer) { if (!cv_timelimit.changed) // user hasn't changed limits { @@ -5006,27 +5039,6 @@ void D_GameTypeChanged(INT32 lastgametype) CV_SetValue(&cv_pointlimit, pointlimits[gametype]); } } - /* -- no longer useful - else if (!multiplayer && !netgame) - { - G_SetGametype(GT_RACE); - } - */ - - // reset timelimit and pointlimit in race/coop, prevent stupid cheats - if (server) - { - if (!(gametyperules & GTR_TIMELIMIT)) - { - if (cv_timelimit.value) - CV_SetValue(&cv_timelimit, 0); - } - if (!(gametyperules & GTR_POINTLIMIT)) - { - if (cv_pointlimit.value) - CV_SetValue(&cv_pointlimit, 0); - } - } // don't retain teams in other modes or between changes from ctf to team match. // also, stop any and all forms of team scrambling that might otherwise take place. @@ -5752,6 +5764,10 @@ void Command_Retry_f(void) { CONS_Printf(M_GetText("This only works in singleplayer games.\n")); } + else if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE) + { + CONS_Printf(M_GetText("You can't retry right now!\n")); + } else { M_ClearMenus(true); @@ -6560,12 +6576,12 @@ static void Command_ShowTime_f(void) // SRB2Kart: On change messages static void NumLaps_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } - if (leveltime < starttime) + if (gamestate == GS_LEVEL && leveltime < starttime) { CONS_Printf(M_GetText("Number of laps have been set to %d.\n"), cv_numlaps.value); numlaps = (UINT8)cv_numlaps.value; @@ -6578,12 +6594,12 @@ static void NumLaps_OnChange(void) static void KartFrantic_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } - if (leveltime < starttime) + if (gamestate == GS_LEVEL && leveltime < starttime) { CONS_Printf(M_GetText("Frantic items has been set to %s.\n"), cv_kartfrantic.value ? M_GetText("on") : M_GetText("off")); franticitems = (boolean)cv_kartfrantic.value; @@ -6596,12 +6612,12 @@ static void KartFrantic_OnChange(void) static void KartSpeed_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } - if (leveltime < starttime && cv_kartspeed.value != KARTSPEED_AUTO) + if (gamestate == GS_LEVEL && leveltime < starttime && cv_kartspeed.value != KARTSPEED_AUTO) { CONS_Printf(M_GetText("Game speed has been changed to \"%s\".\n"), cv_kartspeed.string); gamespeed = (UINT8)cv_kartspeed.value; @@ -6614,7 +6630,7 @@ static void KartSpeed_OnChange(void) static void KartEncore_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -6624,7 +6640,7 @@ static void KartEncore_OnChange(void) static void KartEliminateLast_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { CV_StealthSet(&cv_karteliminatelast, cv_karteliminatelast.defaultvalue); } diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 0355722d6..266d73395 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -55,7 +55,7 @@ extern consvar_t cv_itemrespawn; extern consvar_t cv_pointlimit; extern consvar_t cv_timelimit; extern consvar_t cv_numlaps; -extern UINT32 timelimitintics; +extern UINT32 timelimitintics, extratimeintics, secretextratime; extern consvar_t cv_allowexitlevel; extern consvar_t cv_autobalance; @@ -120,7 +120,8 @@ extern consvar_t cv_kartusepwrlv; extern consvar_t cv_votetime; extern consvar_t cv_kartdebugitem, cv_kartdebugamount, cv_kartdebugdistribution, cv_kartdebughuddrop; -extern consvar_t cv_kartdebugnodes, cv_kartdebugcolorize, cv_kartdebugdirector, cv_spbtest; +extern consvar_t cv_kartdebugnodes, cv_kartdebugcolorize, cv_kartdebugdirector; +extern consvar_t cv_spbtest, cv_gptest; extern consvar_t cv_kartdebugwaypoints, cv_kartdebugbotpredict; extern consvar_t cv_itemfinder; diff --git a/src/d_player.h b/src/d_player.h index ef3fd5193..d61360ded 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -237,7 +237,8 @@ typedef enum khud_lapanimation, // Used to show the lap start wing logo animation khud_laphand, // Lap hand gfx to use; 0 = none, 1 = :ok_hand:, 2 = :thumbs_up:, 3 = :thumps_down: - // Start + // Big text + khud_finish, // Set when completing a round khud_fault, // Set when faulting during the starting countdown // Camera diff --git a/src/deh_soc.c b/src/deh_soc.c index 427493f28..66b05db79 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -3052,7 +3052,15 @@ void readcupheader(MYFILE *f, cupheader_t *cup) } else if (fastcmp(word, "LEVELLIST")) { - cup->numlevels = 0; + while (cup->numlevels > 0) + { + cup->numlevels--; + Z_Free(cup->levellist[cup->numlevels]); + cup->levellist[cup->numlevels] = NULL; + if (cup->cachedlevels[cup->numlevels] == NEXTMAP_INVALID) + continue; + mapheaderinfo[cup->cachedlevels[cup->numlevels]]->cup = NULL; + } tmp = strtok(word2,","); do { @@ -3069,11 +3077,15 @@ void readcupheader(MYFILE *f, cupheader_t *cup) } else if (fastcmp(word, "BONUSGAME")) { + Z_Free(cup->levellist[CUPCACHE_BONUS]); cup->levellist[CUPCACHE_BONUS] = Z_StrDup(word2); + cup->cachedlevels[CUPCACHE_BONUS] = NEXTMAP_INVALID; } else if (fastcmp(word, "SPECIALSTAGE")) { + Z_Free(cup->levellist[CUPCACHE_SPECIAL]); cup->levellist[CUPCACHE_SPECIAL] = Z_StrDup(word2); + cup->cachedlevels[CUPCACHE_SPECIAL] = NEXTMAP_INVALID; } else if (fastcmp(word, "EMERALDNUM")) { diff --git a/src/doomdef.h b/src/doomdef.h index 3492179b4..0d759f3bd 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -670,6 +670,11 @@ extern int compuncommitted; /// Experimental attempts at preventing MF_PAPERCOLLISION objects from getting stuck in walls. //#define PAPER_COLLISIONCORRECTION +#ifdef DEVELOP +// Easily make it so that overtime works offline +#define TESTOVERTIMEINFREEPLAY +#endif + /// FINALLY some real clipping that doesn't make walls dissappear AND speeds the game up /// (that was the original comment from SRB2CB, sadly it is a lie and actually slows game down) /// on the bright side it fixes some weird issues with translucent walls diff --git a/src/doomstat.h b/src/doomstat.h index f7c7f08ff..bf9e7b17e 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -518,6 +518,7 @@ enum TypeOfLevel TOL_RACE = 0x0001, ///< Race TOL_BATTLE = 0x0002, ///< Battle TOL_BOSS = 0x0004, ///< Boss (variant of battle, but forbidden) + TOL_SPECIAL = 0x0008, ///< Special Stage (variant of race, but forbidden) // Modifiers TOL_TV = 0x0100 ///< Midnight Channel specific: draw TV like overlay on HUD diff --git a/src/f_finale.c b/src/f_finale.c index 1903eeb1a..f95f0103d 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -2386,10 +2386,8 @@ void F_EndCutScene(void) F_StartGameEvaluation(); else if (cutnum == introtoplay-1) D_StartTitle(); - else if (nextmap < NEXTMAP_SPECIAL) - G_NextLevel(); else - G_EndGame(); + G_NextLevel(); } } diff --git a/src/g_game.c b/src/g_game.c index fb3bda9ec..07e6a756d 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -58,6 +58,7 @@ #include "k_respawn.h" #include "k_grandprix.h" #include "k_boss.h" +#include "k_specialstage.h" #include "k_bot.h" #include "doomstat.h" @@ -2224,6 +2225,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) INT32 starpostnum; INT32 exiting; + INT32 khudfinish; INT32 khudcardanimation; INT16 totalring; UINT8 laps; @@ -2297,6 +2299,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) botdiffincrease = players[player].botvars.diffincrease; botrival = players[player].botvars.rival; + totalring = players[player].totalring; + xtralife = players[player].xtralife; + pflags = (players[player].pflags & (PF_WANTSTOJOIN|PF_KICKSTARTACCEL|PF_SHRINKME|PF_SHRINKACTIVE)); // SRB2kart @@ -2315,12 +2320,11 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) nocontrol = 0; laps = 0; latestlap = 0; - totalring = 0; roundscore = 0; exiting = 0; + khudfinish = 0; khudcardanimation = 0; starpostnum = 0; - xtralife = 0; follower = NULL; } @@ -2357,16 +2361,22 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) laps = players[player].laps; latestlap = players[player].latestlap; - totalring = players[player].totalring; roundscore = players[player].roundscore; exiting = players[player].exiting; - khudcardanimation = (exiting > 0) ? players[player].karthud[khud_cardanimation] : 0; + if (exiting > 0) + { + khudfinish = players[player].karthud[khud_finish]; + khudcardanimation = players[player].karthud[khud_cardanimation]; + } + else + { + khudfinish = 0; + khudcardanimation = 0; + } starpostnum = players[player].starpostnum; - xtralife = players[player].xtralife; - follower = players[player].follower; pflags |= (players[player].pflags & (PF_STASIS|PF_ELIMINATED|PF_NOCONTEST|PF_FAULT|PF_LOSTLIFE)); @@ -2406,6 +2416,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->starpostnum = starpostnum; p->exiting = exiting; + p->karthud[khud_finish] = khudfinish; p->karthud[khud_cardanimation] = khudcardanimation; p->laps = laps; @@ -2908,7 +2919,7 @@ void G_ExitLevel(void) } } } - else if (grandprixinfo.gp == true) + else if (grandprixinfo.gp == true && grandprixinfo.eventmode == GPEVENT_NONE) { youlost = (grandprixinfo.wonround != true); } @@ -3144,6 +3155,7 @@ tolinfo_t TYPEOFLEVEL[NUMTOLNAMES] = { {"RACE",TOL_RACE}, {"BATTLE",TOL_BATTLE}, {"BOSS",TOL_BOSS}, + {"SPECIAL",TOL_SPECIAL}, {"TV",TOL_TV}, {NULL, 0} }; @@ -3221,14 +3233,14 @@ boolean G_GametypeUsesLives(void) if (modeattacking || metalrecording) // NOT in Record Attack return false; - if (bossinfo.boss == true) // Fighting a boss? + if ((grandprixinfo.gp == true) // In Grand Prix + && (gametype == GT_RACE) // NOT in bonus round + && grandprixinfo.eventmode == GPEVENT_NONE) // NOT in bonus { return true; } - if ((grandprixinfo.gp == true) // In Grand Prix - && (gametype == GT_RACE) // NOT in bonus round - && !G_IsSpecialStage(gamemap)) // NOT in special stage + if (bossinfo.boss == true) // Fighting a boss? { return true; } @@ -3643,11 +3655,11 @@ static void G_UpdateVisited(void) return; // Update visitation flags - mapheaderinfo[gamemap-1]->mapvisited |= MV_BEATEN; + mapheaderinfo[prevmap]->mapvisited |= MV_BEATEN; if (encoremode == true) { - mapheaderinfo[gamemap-1]->mapvisited |= MV_ENCORE; + mapheaderinfo[prevmap]->mapvisited |= MV_ENCORE; } if (modeattacking) @@ -3694,6 +3706,7 @@ static void G_HandleSaveLevel(void) static void G_GetNextMap(void) { + boolean spec = G_IsSpecialStage(prevmap+1); INT32 i; // go to next level @@ -3710,14 +3723,94 @@ static void G_GetNextMap(void) } else { - if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) // On final map + INT32 lastgametype = gametype; + + // If we're in a GP event, don't immediately follow it up with another. + // I also suspect this will not work with online GP so I'm gonna prevent it right now. + // The server might have to communicate eventmode (alongside other GP data) in XD_MAP later. + if (netgame || grandprixinfo.eventmode != GPEVENT_NONE) + { + grandprixinfo.eventmode = GPEVENT_NONE; + + 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) + { + INT16 totaltotalring = 0; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + if (players[i].spectator) + continue; + if (players[i].bot) + continue; + totaltotalring += players[i].totalring; + } + + 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)) + { + grandprixinfo.eventmode = GPEVENT_SPECIAL; + nextmap = cupLevelNum; + } + } + } + else if (grandprixinfo.roundnum == (grandprixinfo.cup->numlevels+1)/2) // 3 for a 5-map cup + { + // todo any other condition? + { + const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_BONUS]; + if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum] + && mapheaderinfo[cupLevelNum]->typeoflevel & (TOL_BOSS|TOL_BATTLE)) + { + grandprixinfo.eventmode = GPEVENT_BONUS; + nextmap = cupLevelNum; + } + } + } + + 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; + } + } + } + else if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) // On final map { nextmap = NEXTMAP_CEREMONY; // ceremonymap } else { // Proceed to next map - const INT32 cupLevelNum =grandprixinfo.cup->cachedlevels[grandprixinfo.roundnum]; + const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[grandprixinfo.roundnum]; if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum]) { @@ -3799,14 +3892,7 @@ static void G_GetNextMap(void) // Didn't get a nextmap before reaching the end? if (gettingresult != 2) { - if (marathonmode) - { - nextmap = NEXTMAP_CEREMONY; // ceremonymap - } - else - { - nextmap = NEXTMAP_TITLE; - } + nextmap = NEXTMAP_CEREMONY; // ceremonymap } } else @@ -3834,19 +3920,39 @@ static void G_GetNextMap(void) nextmap = cm; } - if (!marathonmode) + if (K_CanChangeRules(true)) { - if (cv_advancemap.value == 0) // Stay on same map. + switch (cv_advancemap.value) { - nextmap = prevmap; - } - else if (cv_advancemap.value == 2) // Go to random map. - { - nextmap = G_RandMap(G_TOLFlag(gametype), prevmap, 0, 0, false, NULL); - } - else if (nextmap >= NEXTMAP_SPECIAL) // Loop back around - { - nextmap = G_GetFirstMapOfGametype(gametype); + case 0: // Stay on same map. + nextmap = prevmap; + break; + case 3: // Voting screen. + { + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + if (players[i].spectator) + continue; + break; + } + if (i != MAXPLAYERS) + { + nextmap = NEXTMAP_VOTING; + break; + } + } + /* FALLTHRU */ + case 2: // Go to random map. + nextmap = G_RandMap(G_TOLFlag(gametype), prevmap, 0, 0, false, NULL); + break; + default: + if (nextmap >= NEXTMAP_SPECIAL) // Loop back around + { + nextmap = G_GetFirstMapOfGametype(gametype); + } + break; } } } @@ -3854,6 +3960,9 @@ static void G_GetNextMap(void) // We are committed to this map now. 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 (!spec) + lastmap = nextmap; } // @@ -3862,8 +3971,6 @@ static void G_GetNextMap(void) static void G_DoCompleted(void) { INT32 i, j = 0; - boolean spec = G_IsSpecialStage(gamemap); - SINT8 powertype = K_UsingPowerLevels(); if (modeattacking && pausedelay) pausedelay = 0; @@ -3906,8 +4013,9 @@ static void G_DoCompleted(void) } } + // See Y_StartIntermission timer handling + if ((gametyperules & GTR_CIRCUIT) && ((multiplayer && demo.playback) || j == r_splitscreen+1) && (!K_CanChangeRules(false) || cv_inttime.value > 0)) // play some generic music if there's no win/cool/lose music going on (for exitlevel commands) - if ((gametyperules & GTR_CIRCUIT) && ((multiplayer && demo.playback) || j == r_splitscreen+1) && (cv_inttime.value > 0)) S_ChangeMusicInternal("racent", true); if (automapactive) @@ -3919,14 +4027,8 @@ static void G_DoCompleted(void) if (!demo.playback) { - G_GetNextMap(); - - // Remember last map for when you come out of the special stage. - if (!spec) - lastmap = nextmap; - // Set up power level gametype scrambles - K_SetPowerLevelScrambles(powertype); + K_SetPowerLevelScrambles(K_UsingPowerLevels()); } // If the current gametype has no intermission screen set, then don't start it. @@ -3937,7 +4039,6 @@ static void G_DoCompleted(void) || (intertype == int_none)) { G_UpdateVisited(); - G_HandleSaveLevel(); G_AfterIntermission(); } else @@ -3945,7 +4046,6 @@ static void G_DoCompleted(void) G_SetGamestate(GS_INTERMISSION); Y_StartIntermission(); G_UpdateVisited(); - G_HandleSaveLevel(); } } @@ -3979,14 +4079,17 @@ void G_AfterIntermission(void) return; } + if (gamestate != GS_VOTING) + { + G_GetNextMap(); + G_HandleSaveLevel(); + } + if ((gametyperules & GTR_CAMPAIGN) && mapheaderinfo[prevmap]->cutscenenum && !modeattacking && skipstats <= 1 && (gamecomplete || !(marathonmode & MA_NOCUTSCENES))) // Start a custom cutscene. F_StartCustomCutscene(mapheaderinfo[prevmap]->cutscenenum-1, false, false); else { - if (nextmap < NEXTMAP_SPECIAL) - G_NextLevel(); - else - G_EndGame(); + G_NextLevel(); } } @@ -3998,25 +4101,15 @@ void G_AfterIntermission(void) // void G_NextLevel(void) { - if (gamestate != GS_VOTING) + if (nextmap >= NEXTMAP_SPECIAL) { - if ((cv_advancemap.value == 3) && grandprixinfo.gp == false && bossinfo.boss == false && !modeattacking && !skipstats && (multiplayer || netgame)) - { - UINT8 i; - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && !players[i].spectator) - { - gameaction = ga_startvote; - return; - } - } - } - - forceresetplayers = false; - deferencoremode = (cv_kartencore.value == 1); + G_EndGame(); + return; } + forceresetplayers = false; + deferencoremode = (cv_kartencore.value == 1); + gameaction = ga_worlddone; } @@ -4043,7 +4136,11 @@ static void G_DoWorldDone(void) static void G_DoStartVote(void) { if (server) + { + if (gamestate == GS_VOTING) + I_Error("G_DoStartVote: NEXTMAP_VOTING causes recursive vote!"); D_SetupVote(); + } gameaction = ga_nothing; } @@ -4121,8 +4218,12 @@ static void G_DoContinued(void) // when something new is added. void G_EndGame(void) { - if (demo.recording && (modeattacking || demo.savemode != DSM_NOTSAVING)) - G_SaveDemo(); + // Handle voting + if (nextmap == NEXTMAP_VOTING) + { + gameaction = ga_startvote; + return; + } // Only do evaluation and credits in singleplayer contexts if (!netgame && (gametyperules & GTR_CAMPAIGN)) @@ -4711,6 +4812,7 @@ cleanup: void G_DeferedInitNew(boolean pencoremode, INT32 map, INT32 pickedchar, UINT8 ssplayers, boolean FLS) { UINT16 color = SKINCOLOR_NONE; + INT32 dogametype; paused = false; @@ -4721,8 +4823,17 @@ 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(); + SV_StartSinglePlayerServer(dogametype, false); if (splitscreen != ssplayers) { diff --git a/src/g_game.h b/src/g_game.h index 22256b487..9c0bec553 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -44,7 +44,8 @@ typedef enum NEXTMAP_EVALUATION = INT16_MAX-2, NEXTMAP_CREDITS = INT16_MAX-3, NEXTMAP_CEREMONY = INT16_MAX-4, - NEXTMAP_INVALID = INT16_MAX-5, // Always last (swap with NEXTMAP_RESERVED when removing that) + NEXTMAP_VOTING = INT16_MAX-5, + NEXTMAP_INVALID = INT16_MAX-6, // Always last NEXTMAP_SPECIAL = NEXTMAP_INVALID } nextmapspecial_t; diff --git a/src/hu_stuff.c b/src/hu_stuff.c index e02e3ed67..63d093ae1 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -2416,7 +2416,7 @@ static void HU_DrawRankings(void) if ((gametyperules & (GTR_TIMELIMIT|GTR_POINTLIMIT)) && !bossinfo.boss) { - if ((gametyperules & GTR_TIMELIMIT) && cv_timelimit.value && timelimitintics > 0) + if ((gametyperules & GTR_TIMELIMIT) && timelimitintics > 0) { UINT32 timeval = (timelimitintics + starttime + 1 - leveltime); if (timeval > timelimitintics+1) diff --git a/src/k_battle.c b/src/k_battle.c index c918daec7..18ce0b473 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -127,12 +127,16 @@ void K_CheckBumpers(void) winnerscoreadd -= players[i].roundscore; } - if (K_CanChangeRules() == false) + if (K_CanChangeRules(true) == false) { if (nobumpers) { for (i = 0; i < MAXPLAYERS; i++) { + if (!playeringame[i]) + continue; + if (players[i].spectator) + continue; players[i].pflags |= PF_NOCONTEST; P_DoPlayerExit(&players[i]); } @@ -144,7 +148,8 @@ void K_CheckBumpers(void) if (!battlecapsules) { // Reset map to turn on battle capsules - D_MapChange(gamemap, gametype, encoremode, true, 0, false, false); + if (server) + D_MapChange(gamemap, gametype, encoremode, true, 0, false, false); } else { @@ -152,6 +157,10 @@ void K_CheckBumpers(void) { for (i = 0; i < MAXPLAYERS; i++) { + if (!playeringame[i]) + continue; + if (players[i].spectator) + continue; players[i].pflags |= PF_NOCONTEST; P_DoPlayerExit(&players[i]); } @@ -173,7 +182,13 @@ void K_CheckBumpers(void) K_KartUpdatePosition(&players[i]); for (i = 0; i < MAXPLAYERS; i++) // and it can't be merged with this loop because it needs to be all updated before exiting... multi-loops suck... + { + if (!playeringame[i]) + continue; + if (players[i].spectator) + continue; P_DoPlayerExit(&players[i]); + } } void K_CheckEmeralds(player_t *player) @@ -795,29 +810,13 @@ void K_SpawnPlayerBattleBumpers(player_t *p) } } -void K_BattleInit(void) +void K_BattleInit(boolean singleplayercontext) { size_t i; - if ((gametyperules & GTR_CAPSULES) && !battlecapsules && !bossinfo.boss) + if ((gametyperules & GTR_CAPSULES) && singleplayercontext && !battlecapsules && !bossinfo.boss) { - mapthing_t *mt; - if (modeattacking != ATTACKING_CAPSULES) - { - UINT8 n = 0; - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - continue; - n++; - } - - if (n > 1) - goto aftercapsules; - } - - mt = mapthings; + mapthing_t *mt = mapthings; for (i = 0; i < nummapthings; i++, mt++) { if (mt->type == mobjinfo[MT_BATTLECAPSULE].doomednum) @@ -826,7 +825,6 @@ void K_BattleInit(void) battlecapsules = true; } -aftercapsules: if (gametyperules & GTR_BUMPERS) { diff --git a/src/k_battle.h b/src/k_battle.h index 62f2d3724..3370e041b 100644 --- a/src/k_battle.h +++ b/src/k_battle.h @@ -29,6 +29,6 @@ void K_RunPaperItemSpawners(void); void K_RunBattleOvertime(void); void K_SetupMovingCapsule(mapthing_t *mt, mobj_t *mobj); void K_SpawnPlayerBattleBumpers(player_t *p); -void K_BattleInit(void); +void K_BattleInit(boolean singleplayercontext); #endif diff --git a/src/k_bot.c b/src/k_bot.c index 1b5a183b0..895fefdbe 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -1261,18 +1261,12 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) INT32 turnamt = 0; line_t *botController = NULL; - // Can't build a ticcmd if we aren't spawned... - if (!player->mo) - { - return; - } - // Remove any existing controls memset(cmd, 0, sizeof(ticcmd_t)); - if (gamestate != GS_LEVEL) + if (gamestate != GS_LEVEL || !player->mo || player->spectator) { - // Not in a level. + // Not in the level. return; } diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 9b4da706f..497909dc8 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -12,6 +12,7 @@ #include "k_grandprix.h" #include "k_boss.h" +#include "k_specialstage.h" #include "doomdef.h" #include "d_player.h" #include "g_game.h" @@ -96,14 +97,14 @@ INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers) } /*-------------------------------------------------- - SINT8 K_BotDefaultSkin(void) + UINT8 K_BotDefaultSkin(void) See header file for description. --------------------------------------------------*/ -SINT8 K_BotDefaultSkin(void) +UINT8 K_BotDefaultSkin(void) { const char *defaultbotskinname = "eggrobo"; - SINT8 defaultbotskin = R_SkinAvailable(defaultbotskinname); + INT32 defaultbotskin = R_SkinAvailable(defaultbotskinname); if (defaultbotskin == -1) { @@ -111,7 +112,7 @@ SINT8 K_BotDefaultSkin(void) defaultbotskin = 0; } - return defaultbotskin; + return (UINT8)defaultbotskin; } /*-------------------------------------------------- @@ -121,7 +122,7 @@ SINT8 K_BotDefaultSkin(void) --------------------------------------------------*/ void K_InitGrandPrixBots(void) { - const SINT8 defaultbotskin = K_BotDefaultSkin(); + const UINT8 defaultbotskin = K_BotDefaultSkin(); const UINT8 startingdifficulty = K_BotStartingDifficulty(grandprixinfo.gamespeed); UINT8 difficultylevels[MAXPLAYERS]; @@ -132,7 +133,9 @@ void K_InitGrandPrixBots(void) UINT8 numplayers = 0; UINT8 competitors[MAXSPLITSCREENPLAYERS]; - boolean skinusable[MAXSKINS]; + UINT8 usableskins; + UINT8 grabskins[MAXSKINS+1]; + UINT8 botskinlist[MAXPLAYERS]; UINT8 botskinlistpos = 0; @@ -142,18 +145,12 @@ void K_InitGrandPrixBots(void) memset(competitors, MAXPLAYERS, sizeof (competitors)); memset(botskinlist, defaultbotskin, sizeof (botskinlist)); - // init usable bot skins list - for (i = 0; i < MAXSKINS; i++) + // Init usable bot skins list + for (usableskins = 0; usableskins < numskins; usableskins++) { - if (i < numskins) - { - skinusable[i] = true; - } - else - { - skinusable[i] = false; - } + grabskins[usableskins] = usableskins; } + grabskins[usableskins] = MAXSKINS; #if MAXPLAYERS != 16 I_Error("GP bot difficulty levels need rebalanced for the new player count!\n"); @@ -192,7 +189,7 @@ void K_InitGrandPrixBots(void) if (numplayers < MAXSPLITSCREENPLAYERS && !players[i].spectator) { competitors[numplayers] = i; - skinusable[players[i].skin] = false; + grabskins[players[i].skin] = MAXSKINS; numplayers++; } else @@ -219,52 +216,46 @@ void K_InitGrandPrixBots(void) { player_t *p = &players[competitors[j]]; char *rivalname = skins[p->skin].rivals[i]; - SINT8 rivalnum = R_SkinAvailable(rivalname); + INT32 rivalnum = R_SkinAvailable(rivalname); - if (rivalnum != -1 && skinusable[rivalnum]) + // Intentionally referenced before (currently dummied out) unlock check. Such a tease! + if (rivalnum != -1 && grabskins[(UINT8)rivalnum] != MAXSKINS) { - botskinlist[botskinlistpos] = rivalnum; - skinusable[rivalnum] = false; - botskinlistpos++; + botskinlist[botskinlistpos++] = (UINT8)rivalnum; + grabskins[(UINT8)rivalnum] = MAXSKINS; } } } } + // Rearrange usable bot skins list to prevent gaps for randomised selection + for (i = 0; i < usableskins; i++) + { + if (!(grabskins[i] == MAXSKINS /*|| K_SkinLocked(grabskins[i])*/)) + continue; + while (usableskins > i && (grabskins[usableskins] == MAXSKINS /*|| K_SkinLocked(grabskins[i])*/)) + { + usableskins--; + } + grabskins[i] = grabskins[usableskins]; + grabskins[usableskins] = MAXSKINS; + } + // Pad the remaining list with random skins if we need to if (botskinlistpos < wantedbots) { - for (i = botskinlistpos; i < wantedbots; i++) + while (botskinlistpos < wantedbots) { - UINT8 val = M_RandomKey(numskins); - UINT8 loops = 0; + UINT8 skinnum = defaultbotskin; - while (!skinusable[val]) + if (usableskins > 0) { - if (loops >= numskins) - { - // no more skins - break; - } - - val++; - - if (val >= numskins) - { - val = 0; - } - - loops++; + UINT8 index = M_RandomKey(usableskins); + skinnum = grabskins[index]; + grabskins[index] = grabskins[--usableskins]; } - if (loops >= numskins) - { - // leave the rest of the table as the default skin - break; - } - - botskinlist[i] = val; - skinusable[val] = false; + botskinlist[botskinlistpos++] = skinnum; } } @@ -341,6 +332,16 @@ void K_UpdateGrandPrixBots(void) UINT16 newrivalscore = 0; UINT8 i; + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || !players[i].bot) + { + continue; + } + + players[i].spectator = (grandprixinfo.eventmode != GPEVENT_NONE); + } + // Find the rival. for (i = 0; i < MAXPLAYERS; i++) { @@ -519,38 +520,49 @@ void K_IncreaseBotDifficulty(player_t *bot) --------------------------------------------------*/ void K_RetireBots(void) { - const SINT8 defaultbotskin = K_BotDefaultSkin(); + const UINT8 defaultbotskin = K_BotDefaultSkin(); SINT8 newDifficulty; - boolean skinusable[MAXSKINS]; + UINT8 usableskins; + UINT8 grabskins[MAXSKINS+1]; UINT8 i; - if (grandprixinfo.gp == true && grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) + if (grandprixinfo.gp == true + && ((grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) + || grandprixinfo.eventmode != GPEVENT_NONE)) { - // Was last map, no replacement. + // No replacement. return; } - // init usable bot skins list - for (i = 0; i < MAXSKINS; i++) + // Init usable bot skins list + for (usableskins = 0; usableskins < numskins; usableskins++) { - if (i < numskins) - { - skinusable[i] = true; - } - else - { - skinusable[i] = false; - } + grabskins[usableskins] = usableskins; } + grabskins[usableskins] = MAXSKINS; + // Exclude player skins for (i = 0; i < MAXPLAYERS; i++) { - if (playeringame[i] && !players[i].spectator) - { - skinusable[players[i].skin] = false; - } + if (!playeringame[i]) + continue; + if (players[i].spectator) + continue; + + grabskins[players[i].skin] = MAXSKINS; + } + + // Rearrange usable bot skins list to prevent gaps for randomised selection + for (i = 0; i < usableskins; i++) + { + if (!(grabskins[i] == MAXSKINS /*|| K_SkinLocked(grabskins[i])*/)) + continue; + while (usableskins > i && (grabskins[usableskins] == MAXSKINS /*|| K_SkinLocked(grabskins[i])*/)) + usableskins--; + grabskins[i] = grabskins[usableskins]; + grabskins[usableskins] = MAXSKINS; } if (!grandprixinfo.gp) // Sure, let's let this happen all the time :) @@ -576,49 +588,24 @@ void K_RetireBots(void) { player_t *bot = NULL; - if (!playeringame[i] || !players[i].bot) + if (!playeringame[i] || !players[i].bot || players[i].spectator) { continue; } bot = &players[i]; - if (bot->spectator) - { - continue; - } - if (bot->pflags & PF_NOCONTEST) { - UINT8 skinnum = P_RandomKey(PR_UNDEFINED, numskins); - UINT8 loops = 0; + UINT8 skinnum = defaultbotskin; - while (!skinusable[skinnum]) + if (usableskins > 0) { - if (loops >= numskins) - { - // no more skins - break; - } - - skinnum++; - - if (skinnum >= numskins) - { - skinnum = 0; - } - - loops++; + UINT8 index = P_RandomKey(PR_RULESCRAMBLE, usableskins); + skinnum = grabskins[index]; + grabskins[index] = grabskins[--usableskins]; } - if (loops >= numskins) - { - // Use default skin - skinnum = defaultbotskin; - } - - skinusable[skinnum] = false; - bot->botvars.difficulty = newDifficulty; bot->botvars.diffincrease = 0; @@ -671,7 +658,7 @@ void K_FakeBotResults(player_t *bot) } // hey, you "won" - bot->exiting = 2; + bot->exiting = 1; bot->realtime += (bot->distancetofinish / distfactor); bot->distancetofinish = 0; K_IncreaseBotDifficulty(bot); @@ -710,18 +697,12 @@ void K_PlayerLoseLife(player_t *player) } /*-------------------------------------------------- - boolean K_CanChangeRules(void) + boolean K_CanChangeRules(boolean allowdemos) See header file for description. --------------------------------------------------*/ -boolean K_CanChangeRules(void) +boolean K_CanChangeRules(boolean allowdemos) { - if (demo.playback) - { - // We've already got our important settings! - return false; - } - if (grandprixinfo.gp == true && grandprixinfo.roundnum > 0) { // Don't cheat the rules of the GP! @@ -734,11 +715,29 @@ boolean K_CanChangeRules(void) return false; } - if (modeattacking == true) + if (specialStage.active == true) + { + // Don't cheat special stages! + return false; + } + + if (marathonmode) + { + // Don't cheat the endurance challenge! + return false; + } + + if (modeattacking != ATTACKING_NONE) { // Don't cheat the rules of Time Trials! return false; } + if (!allowdemos && demo.playback) + { + // We've already got our important settings! + return false; + } + return true; } diff --git a/src/k_grandprix.h b/src/k_grandprix.h index bd20d3894..3d966d4d3 100644 --- a/src/k_grandprix.h +++ b/src/k_grandprix.h @@ -16,6 +16,10 @@ #include "doomdef.h" #include "doomstat.h" +#define GPEVENT_NONE 0 +#define GPEVENT_BONUS 1 +#define GPEVENT_SPECIAL 2 + extern struct grandprixinfo { boolean gp; ///< If true, then we are in a Grand Prix. @@ -26,6 +30,7 @@ extern struct grandprixinfo boolean masterbots; ///< If true, all bots should be max difficulty (Master Mode) boolean initalize; ///< If true, we need to initialize a new session. boolean wonround; ///< If false, then we retry the map instead of going to the next. + UINT8 eventmode; ///< See GPEVENT_ constants } grandprixinfo; @@ -63,13 +68,13 @@ INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers); /*-------------------------------------------------- - SINT8 K_BotDefaultSkin(void); + UINT8 K_BotDefaultSkin(void); Returns the skin number of the skin the game uses as a fallback option. --------------------------------------------------*/ -SINT8 K_BotDefaultSkin(void); +UINT8 K_BotDefaultSkin(void); /*-------------------------------------------------- @@ -147,18 +152,18 @@ void K_PlayerLoseLife(player_t *player); /*-------------------------------------------------- - boolean K_CanChangeRules(void); + boolean K_CanChangeRules(boolean allowdemos); Returns whenver or not the server is allowed to change the game rules. Input Arguments:- - None + allowdemos - permits this behavior during demo playback Return:- true if can change important gameplay rules, otherwise false. --------------------------------------------------*/ -boolean K_CanChangeRules(void); +boolean K_CanChangeRules(boolean allowdemos); #endif diff --git a/src/k_hud.c b/src/k_hud.c index 72816abb1..d4127e131 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -12,6 +12,7 @@ #include "k_hud.h" #include "k_kart.h" #include "k_battle.h" +#include "k_grandprix.h" #include "k_boss.h" #include "k_color.h" #include "k_director.h" @@ -75,7 +76,9 @@ static patch_t *kp_winnernum[NUMPOSFRAMES]; static patch_t *kp_facenum[MAXPLAYERS+1]; static patch_t *kp_facehighlight[8]; +static patch_t *kp_nocontestminimap; static patch_t *kp_spbminimap; +static patch_t *kp_capsuleminimap[2]; static patch_t *kp_ringsticker[2]; static patch_t *kp_ringstickersplit[4]; @@ -311,7 +314,11 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_facehighlight[i], "%s", buffer); } + // Special minimap icons + HU_UpdatePatch(&kp_nocontestminimap, "MINIDEAD"); HU_UpdatePatch(&kp_spbminimap, "SPBMMAP"); + HU_UpdatePatch(&kp_capsuleminimap[0], "MINICAP1"); + HU_UpdatePatch(&kp_capsuleminimap[1], "MINICAP2"); // Rings & Lives HU_UpdatePatch(&kp_ringsticker[0], "RNGBACKA"); @@ -1385,33 +1392,40 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UI // TIME_Y = 6; // 6 tic_t worktime; - boolean dontdraw = false; + INT32 jitter = 0; INT32 splitflags = 0; if (!mode) { splitflags = V_HUDTRANS|V_SLIDEIN|V_SNAPTOTOP|V_SNAPTORIGHT|V_SPLITSCREEN; -#ifndef TESTOVERTIMEINFREEPLAY - if (battlecapsules) // capsules override any time limit settings - ; - else -#endif - if (bossinfo.boss == true) - ; - else if (timelimitintics > 0 && (gametyperules & GTR_TIMELIMIT)) // TODO + if (timelimitintics > 0) { if (drawtime >= timelimitintics) { - if (((drawtime-timelimitintics)/TICRATE) & 1) - { - dontdraw = true; - } + jitter = 2; + if (drawtime & 2) + jitter = -jitter; drawtime = 0; } else { drawtime = timelimitintics - drawtime; + if (secretextratime) + ; + else if (extratimeintics) + { + jitter = 2; + if (leveltime & 1) + jitter = -jitter; + } + else if (drawtime <= 5*TICRATE) + { + jitter = ((drawtime <= 3*TICRATE) && (((drawtime-1) % TICRATE) >= TICRATE-2)) + ? 3 : 1; + if (drawtime & 2) + jitter = -jitter; + } } } } @@ -1422,57 +1436,39 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UI worktime = drawtime/(60*TICRATE); + if (worktime >= 100) + { + jitter = (drawtime & 1 ? 1 : -1); + worktime = 99; + drawtime = (100*(60*TICRATE))-1; + } + if (mode && !drawtime) V_DrawKartString(TX, TY+3, splitflags, va("--'--\"--")); - else if (dontdraw) // overtime flash - ; - else if (worktime < 100) // 99:99:99 only + else { - // zero minute - if (worktime < 10) - { - V_DrawKartString(TX, TY+3, splitflags, va("0")); - // minutes time 0 __ __ - V_DrawKartString(TX+12, TY+3, splitflags, va("%d", worktime)); - } - // minutes time 0 __ __ - else - V_DrawKartString(TX, TY+3, splitflags, va("%d", worktime)); + // minutes time 00 __ __ + V_DrawKartString(TX, TY+3+jitter, splitflags, va("%d", worktime/10)); + V_DrawKartString(TX+12, TY+3-jitter, splitflags, va("%d", worktime%10)); // apostrophe location _'__ __ V_DrawKartString(TX+24, TY+3, splitflags, va("'")); worktime = (drawtime/TICRATE % 60); - // zero second _ 0_ __ - if (worktime < 10) - { - V_DrawKartString(TX+36, TY+3, splitflags, va("0")); - // seconds time _ _0 __ - V_DrawKartString(TX+48, TY+3, splitflags, va("%d", worktime)); - } - // zero second _ 00 __ - else - V_DrawKartString(TX+36, TY+3, splitflags, va("%d", worktime)); + // seconds time _ 00 __ + V_DrawKartString(TX+36, TY+3+jitter, splitflags, va("%d", worktime/10)); + V_DrawKartString(TX+48, TY+3-jitter, splitflags, va("%d", worktime%10)); // quotation mark location _ __"__ V_DrawKartString(TX+60, TY+3, splitflags, va("\"")); worktime = G_TicsToCentiseconds(drawtime); - // zero tick _ __ 0_ - if (worktime < 10) - { - V_DrawKartString(TX+72, TY+3, splitflags, va("0")); - // tics _ __ _0 - V_DrawKartString(TX+84, TY+3, splitflags, va("%d", worktime)); - } - // zero tick _ __ 00 - else - V_DrawKartString(TX+72, TY+3, splitflags, va("%d", worktime)); + // tics _ __ 00 + V_DrawKartString(TX+72, TY+3+jitter, splitflags, va("%d", worktime/10)); + V_DrawKartString(TX+84, TY+3-jitter, splitflags, va("%d", worktime%10)); } - else if ((drawtime/TICRATE) & 1) - V_DrawKartString(TX, TY+3, splitflags, va("99'59\"99")); if (emblemmap && (modeattacking || (mode == 1)) && !demo.playback) // emblem time! { @@ -3326,16 +3322,15 @@ static void K_drawKartMinimapIcon(fixed_t objx, fixed_t objy, INT32 hudx, INT32 static void K_drawKartMinimap(void) { - patch_t *AutomapPic; + patch_t *AutomapPic, *workingPic; INT32 i = 0; INT32 x, y; INT32 minimaptrans = 4; INT32 splitflags = 0; UINT8 skin = 0; UINT8 *colormap = NULL; - SINT8 localplayers[4]; + SINT8 localplayers[MAXSPLITSCREENPLAYERS]; SINT8 numlocalplayers = 0; - INT32 hyu = hyudorotime; mobj_t *mobj, *next; // for SPB drawing (or any other item(s) we may wanna draw, I dunno!) fixed_t interpx, interpy; @@ -3412,12 +3407,9 @@ static void K_drawKartMinimap(void) } // initialize - for (i = 0; i < 4; i++) + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) localplayers[i] = -1; - if (gametype == GT_RACE) - hyu *= 2; // double in race - // Player's tiny icons on the Automap. (drawn opposite direction so player 1 is drawn last in splitscreen) if (ghosts) { @@ -3448,8 +3440,7 @@ static void K_drawKartMinimap(void) if (!stplyr->mo || stplyr->spectator || stplyr->exiting) return; - localplayers[numlocalplayers] = stplyr-players; - numlocalplayers++; + localplayers[numlocalplayers++] = stplyr-players; } else { @@ -3457,50 +3448,60 @@ static void K_drawKartMinimap(void) { if (!playeringame[i]) continue; - if (!players[i].mo || players[i].spectator || players[i].exiting) + if (!players[i].mo || players[i].spectator || !players[i].mo->skin || players[i].exiting) continue; - if (i != displayplayers[0] || r_splitscreen) - { - if (gametype == GT_BATTLE && players[i].bumpers <= 0) - continue; - - if (players[i].hyudorotimer > 0) - { - if (!((players[i].hyudorotimer < TICRATE/2 - || players[i].hyudorotimer > hyu-(TICRATE/2)) - && !(leveltime & 1))) - continue; - } - } - if (i == displayplayers[0] || i == displayplayers[1] || i == displayplayers[2] || i == displayplayers[3]) { // Draw display players on top of everything else - localplayers[numlocalplayers] = i; - numlocalplayers++; + localplayers[numlocalplayers++] = i; continue; } - if (players[i].mo->skin) - skin = ((skin_t*)players[i].mo->skin)-skins; - else - skin = 0; + // Now we know it's not a display player, handle non-local player exceptions. + if ((gametyperules & GTR_BUMPERS) && players[i].bumpers <= 0) + continue; - if (players[i].mo->color) + if (players[i].hyudorotimer > 0) { - if (players[i].mo->colorized) - colormap = R_GetTranslationColormap(TC_RAINBOW, players[i].mo->color, GTC_CACHE); - else - colormap = R_GetTranslationColormap(skin, players[i].mo->color, GTC_CACHE); + if (!((players[i].hyudorotimer < TICRATE/2 + || players[i].hyudorotimer > hyudorotime-(TICRATE/2)) + && !(leveltime & 1))) + continue; + } + + mobj = players[i].mo; + + if (mobj->health <= 0 && (players[i].pflags & PF_NOCONTEST)) + { + workingPic = kp_nocontestminimap; + R_GetTranslationColormap(0, mobj->color, GTC_CACHE); + + if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer)) + mobj = mobj->tracer; } else - colormap = NULL; + { + skin = ((skin_t*)mobj->skin)-skins; - interpx = R_InterpolateFixed(players[i].mo->old_x, players[i].mo->x); - interpy = R_InterpolateFixed(players[i].mo->old_y, players[i].mo->y); + workingPic = faceprefix[skin][FACE_MINIMAP]; + + if (mobj->color) + { + if (mobj->colorized) + colormap = R_GetTranslationColormap(TC_RAINBOW, mobj->color, GTC_CACHE); + else + colormap = R_GetTranslationColormap(skin, mobj->color, GTC_CACHE); + } + else + colormap = NULL; + } + + interpx = R_InterpolateFixed(mobj->old_x, mobj->x); + interpy = R_InterpolateFixed(mobj->old_y, mobj->y); 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]))) @@ -3510,27 +3511,48 @@ static void K_drawKartMinimap(void) } } - // draw SPB(s?) + // draw minimap-pertinent objects for (mobj = kitemcap; mobj; mobj = next) { next = mobj->itnext; - if (mobj->type == MT_SPB) + + workingPic = NULL; + colormap = NULL; + + if (mobj->health <= 0) + continue; + + switch (mobj->type) { - colormap = NULL; - - if (mobj->target && !P_MobjWasRemoved(mobj->target)) - { - if (mobj->player && mobj->player->skincolor) - colormap = R_GetTranslationColormap(TC_RAINBOW, mobj->player->skincolor, GTC_CACHE); - else if (mobj->color) + case MT_SPB: + workingPic = kp_spbminimap; +#if 0 + if (mobj->target && !P_MobjWasRemoved(mobj->target) && mobj->target->player && mobj->target->player->skincolor) + { + colormap = R_GetTranslationColormap(TC_RAINBOW, mobj->target->player->skincolor, GTC_CACHE); + } + else +#endif + if (mobj->color) + { colormap = R_GetTranslationColormap(TC_RAINBOW, mobj->color, GTC_CACHE); - } + } - interpx = R_InterpolateFixed(mobj->old_x, mobj->x); - interpy = R_InterpolateFixed(mobj->old_y, mobj->y); - - K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, kp_spbminimap, colormap, AutomapPic); + break; + case MT_BATTLECAPSULE: + workingPic = kp_capsuleminimap[(mobj->extravalue1 != 0 ? 1 : 0)]; + break; + default: + break; } + + if (!workingPic) + continue; + + interpx = R_InterpolateFixed(mobj->old_x, mobj->x); + interpy = R_InterpolateFixed(mobj->old_y, mobj->y); + + K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, workingPic, colormap, AutomapPic); } // draw our local players here, opaque. @@ -3569,28 +3591,43 @@ static void K_drawKartMinimap(void) for (i = 0; i < numlocalplayers; i++) { - if (i == -1) + if (localplayers[i] == -1) continue; // this doesn't interest us - if (players[localplayers[i]].mo->skin) - skin = ((skin_t*)players[localplayers[i]].mo->skin)-skins; - else - skin = 0; + if ((players[i].hyudorotimer > 0) && (leveltime & 1)) + continue; - if (players[localplayers[i]].mo->color) + mobj = players[localplayers[i]].mo; + + if (mobj->health <= 0 && (players[localplayers[i]].pflags & PF_NOCONTEST)) { - if (players[localplayers[i]].mo->colorized) - colormap = R_GetTranslationColormap(TC_RAINBOW, players[localplayers[i]].mo->color, GTC_CACHE); - else - colormap = R_GetTranslationColormap(skin, players[localplayers[i]].mo->color, GTC_CACHE); + workingPic = kp_nocontestminimap; + R_GetTranslationColormap(0, mobj->color, GTC_CACHE); + + if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer)) + mobj = mobj->tracer; } else - colormap = NULL; + { + skin = ((skin_t*)mobj->skin)-skins; - interpx = R_InterpolateFixed(players[localplayers[i]].mo->old_x, players[localplayers[i]].mo->x); - interpy = R_InterpolateFixed(players[localplayers[i]].mo->old_y, players[localplayers[i]].mo->y); + workingPic = faceprefix[skin][FACE_MINIMAP]; - K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, faceprefix[skin][FACE_MINIMAP], colormap, AutomapPic); + if (mobj->color) + { + if (mobj->colorized) + colormap = R_GetTranslationColormap(TC_RAINBOW, mobj->color, GTC_CACHE); + else + colormap = R_GetTranslationColormap(skin, mobj->color, GTC_CACHE); + } + else + colormap = NULL; + } + + interpx = R_InterpolateFixed(mobj->old_x, mobj->x); + interpy = R_InterpolateFixed(mobj->old_y, mobj->y); + + K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, workingPic, colormap, AutomapPic); // Target reticule if ((gametype == GT_RACE && players[localplayers[i]].position == spbplace) @@ -3601,6 +3638,60 @@ static void K_drawKartMinimap(void) } } +static void K_drawKartFinish(boolean finish) +{ + INT32 timer, minsplitstationary, pnum = 0, splitflags = V_SPLITSCREEN; + patch_t **kptodraw; + + if (finish) + { + timer = stplyr->karthud[khud_finish]; + kptodraw = kp_racefinish; + minsplitstationary = 2; + } + else + { + timer = stplyr->karthud[khud_fault]; + kptodraw = kp_racefault; + minsplitstationary = 1; + } + + if (!timer || timer > 2*TICRATE) + return; + + if ((timer % (2*5)) / 5) // blink + pnum = 1; + + if (r_splitscreen > 0) + pnum += (r_splitscreen > 1) ? 2 : 4; + + if (r_splitscreen >= minsplitstationary) // 3/4p, stationary FIN + { + V_DrawScaledPatch(STCD_X - (SHORT(kptodraw[pnum]->width)/2), STCD_Y - (SHORT(kptodraw[pnum]->height)/2), splitflags, kptodraw[pnum]); + return; + } + + //else -- 1/2p, scrolling FINISH + { + INT32 x, xval, ox, interpx; + + x = ((vid.width<width)< x ? xval : x))/TICRATE; + ox = ((TICRATE - (timer - 1))*(xval > x ? xval : x))/TICRATE; + + interpx = R_InterpolateFixed(ox, x); + + if (r_splitscreen && stplyr == &players[displayplayers[1]]) + interpx = -interpx; + + V_DrawFixedPatch(interpx + (STCD_X<>1), + (STCD_Y<height)<<(FRACBITS-1)), + FRACUNIT, + splitflags, kptodraw[pnum], NULL); + } +} + static void K_drawKartStartBulbs(void) { const UINT8 start_animation[14] = { @@ -3765,39 +3856,11 @@ static void K_drawKartStartCountdown(void) if (stplyr->karthud[khud_fault] != 0) { - INT32 x, xval; - - if (r_splitscreen > 1) // 3/4p, stationary FIN - { - pnum += 2; - } - else if (r_splitscreen == 1) // wide splitscreen - { - pnum += 4; - } - - if ((leveltime % (2*5)) / 5) // blink - pnum += 1; - - if (r_splitscreen == 0) - { - x = ((vid.width<width)<karthud[khud_fault])*(xval > x ? xval : x))/TICRATE; - - V_DrawFixedPatch(x + (STCD_X<>1), - (STCD_Y<height)<<(FRACBITS-1)), - FRACUNIT, - V_SPLITSCREEN, kp_racefault[pnum], NULL); - } - else - { - V_DrawScaledPatch(STCD_X - (SHORT(kp_racefault[pnum]->width)/2), STCD_Y - (SHORT(kp_racefault[pnum]->height)/2), V_SPLITSCREEN, kp_racefault[pnum]); - } + K_drawKartFinish(false); } else if (leveltime >= introtime && leveltime < starttime-(3*TICRATE)) { - if (bossinfo.boss == false) + if (numbulbs > 1) K_drawKartStartBulbs(); } else @@ -3839,47 +3902,6 @@ static void K_drawKartStartCountdown(void) } } -static void K_drawKartFinish(void) -{ - INT32 pnum = 0, splitflags = V_SPLITSCREEN; - - if (!stplyr->karthud[khud_cardanimation] || stplyr->karthud[khud_cardanimation] >= 2*TICRATE) - return; - - if ((stplyr->karthud[khud_cardanimation] % (2*5)) / 5) // blink - pnum = 1; - - if (r_splitscreen > 1) // 3/4p, stationary FIN - { - pnum += 2; - V_DrawScaledPatch(STCD_X - (SHORT(kp_racefinish[pnum]->width)/2), STCD_Y - (SHORT(kp_racefinish[pnum]->height)/2), splitflags, kp_racefinish[pnum]); - return; - } - - //else -- 1/2p, scrolling FINISH - { - INT32 x, xval, ox, interpx; - - if (r_splitscreen) // wide splitscreen - pnum += 4; - - x = ((vid.width<width)<karthud[khud_cardanimation])*(xval > x ? xval : x))/TICRATE; - ox = ((TICRATE - (stplyr->karthud[khud_cardanimation] - 1))*(xval > x ? xval : x))/TICRATE; - - interpx = R_InterpolateFixed(ox, x); - - if (r_splitscreen && stplyr == &players[displayplayers[1]]) - interpx = -interpx; - - V_DrawFixedPatch(interpx + (STCD_X<>1), - (STCD_Y<height)<<(FRACBITS-1)), - FRACUNIT, - splitflags, kp_racefinish[pnum], NULL); - } -} - static void K_drawBattleFullscreen(void) { INT32 x = BASEVIDWIDTH/2; @@ -3931,19 +3953,19 @@ static void K_drawBattleFullscreen(void) { if (stplyr == &players[displayplayers[0]]) V_DrawFadeScreen(0xFF00, 16); - if (stplyr->exiting < 6*TICRATE && !stplyr->spectator) + if (exitcountdown <= 6*TICRATE && !stplyr->spectator) { patch_t *p = kp_battlecool; if (K_IsPlayerLosing(stplyr)) p = kp_battlelose; - else if (stplyr->position == 1) + else if (stplyr->position == 1 && (!battlecapsules || numtargets >= maptargets)) p = kp_battlewin; V_DrawFixedPatch(x<bumpers <= 0 && stplyr->karmadelay && !stplyr->spectator && drawcomebacktimer) { @@ -3985,7 +4007,7 @@ static void K_drawBattleFullscreen(void) } } - if (netgame && !stplyr->spectator) // FREE PLAY? + // FREE PLAY? { UINT8 i; @@ -3994,11 +4016,11 @@ static void K_drawBattleFullscreen(void) { if (i == displayplayers[0]) continue; - if (playeringame[i] && !stplyr->spectator) - return; + if (playeringame[i] && !players[i].spectator) + break; } - if (LUA_HudEnabled(hud_freeplay)) + if (i != MAXPLAYERS) K_drawKartFreePlay(); } } @@ -4392,8 +4414,13 @@ static void K_drawTrickCool(void) void K_drawKartFreePlay(void) { - // no splitscreen support because it's not FREE PLAY if you have more than one player in-game - // (you fool, you can take splitscreen online. :V) + // Doesn't support splitscreens higher than 2 for real estate reasons. + + if (!LUA_HudEnabled(hud_freeplay)) + return; + + if (modeattacking || grandprixinfo.gp || bossinfo.boss || stplyr->spectator) + return; if (lt_exitticker < TICRATE/2) return; @@ -4402,7 +4429,7 @@ void K_drawKartFreePlay(void) return; V_DrawKartString((BASEVIDWIDTH - (LAPS_X+1)) - 72, // mirror the laps thingy - LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|V_SNAPTOBOTTOM|V_SNAPTORIGHT, "FREE PLAY"); + LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN, "FREE PLAY"); } static void @@ -4750,7 +4777,7 @@ void K_drawKartHUD(void) if (gametype == GT_RACE && !freecam) { if (stplyr->exiting) - K_drawKartFinish(); + K_drawKartFinish(true); else if (stplyr->karthud[khud_lapanimation] && !r_splitscreen) K_drawLapStartAnim(); } @@ -4766,11 +4793,8 @@ void K_drawKartHUD(void) V_DrawScaledPatch(BASEVIDWIDTH/2 - (SHORT(kp_yougotem->width)/2), 32, V_HUDTRANS, kp_yougotem); // Draw FREE PLAY. - if (islonesome && !modeattacking && !bossinfo.boss && !stplyr->spectator) - { - if (LUA_HudEnabled(hud_freeplay)) - K_drawKartFreePlay(); - } + if (islonesome) + K_drawKartFreePlay(); if (r_splitscreen == 0 && (stplyr->pflags & PF_WRONGWAY) && ((leveltime / 8) & 1)) { diff --git a/src/k_kart.c b/src/k_kart.c index 4b1480eb9..08164b2d8 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -40,6 +40,8 @@ #include "k_collide.h" #include "k_follower.h" #include "k_objects.h" +#include "k_grandprix.h" +#include "k_specialstage.h" // SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H: // gamespeed is cc (0 for easy, 1 for normal, 2 for hard) @@ -95,70 +97,86 @@ void K_TimerReset(void) { starttime = introtime = 3; numbulbs = 1; - inDuel = false; + inDuel = rainbowstartavailable = false; + timelimitintics = extratimeintics = secretextratime = 0; } void K_TimerInit(void) { UINT8 i; - UINT8 numPlayers = 0;//, numspec = 0; + UINT8 numPlayers = 0; + boolean domodeattack = ((modeattacking != ATTACKING_NONE) + || (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE)); - if (!bossinfo.boss) + if (specialStage.active == true) { - for (i = 0; i < MAXPLAYERS; i++) + K_InitSpecialStage(); + } + else if (bossinfo.boss == false) + { + if (!domodeattack) { - if (!playeringame[i]) + for (i = 0; i < MAXPLAYERS; i++) { - continue; + if (!playeringame[i] || players[i].spectator) + { + continue; + } + + numPlayers++; } - if (players[i].spectator == true) + if (numPlayers < 2) { - //numspec++; - continue; + domodeattack = true; } + else + { + numbulbs = 5; + rainbowstartavailable = true; - numPlayers++; - } + // 1v1 activates DUEL rules! + inDuel = (numPlayers == 2); - // 1v1 activates DUEL rules! - inDuel = (numPlayers == 2); - - if (numPlayers >= 2) - { - rainbowstartavailable = true; - } - else - { - rainbowstartavailable = false; - } - - // No intro in Record Attack / 1v1 - // Leave unset for the value in K_TimerReset - if (numPlayers > 2) - { - introtime = (108) + 5; // 108 for rotation, + 5 for white fade - } - - numbulbs = 5; - - if (numPlayers > 2) - { - numbulbs += (numPlayers-2); + if (!inDuel) + { + introtime = (108) + 5; // 108 for rotation, + 5 for white fade + numbulbs += (numPlayers-2); // Extra POSITION!! time + } + } } starttime = (introtime + (3*TICRATE)) + ((2*TICRATE) + (numbulbs * bulbtime)); // Start countdown time, + buffer time } - // NOW you can try to spawn in the Battle capsules, if there's not enough players for a match - K_BattleInit(); + K_BattleInit(domodeattack); + + if ((gametyperules & GTR_TIMELIMIT) && !bossinfo.boss && !modeattacking) + { + if (!K_CanChangeRules(true)) + { + if (battlecapsules) + { + timelimitintics = (20*TICRATE); + } + else + { + timelimitintics = timelimits[gametype] * (60*TICRATE); + } + } + else +#ifndef TESTOVERTIMEINFREEPLAY + if (!battlecapsules) +#endif + { + timelimitintics = cv_timelimit.value * (60*TICRATE); + } + } if (inDuel == true) { K_SpawnDuelOnlyItems(); } - - //CONS_Printf("numbulbs set to %d (%d players, %d spectators) on tic %d\n", numbulbs, numPlayers, numspec, leveltime); } UINT32 K_GetPlayerDontDrawFlag(player_t *player) @@ -316,6 +334,7 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartdebugcolorize); CV_RegisterVar(&cv_kartdebugdirector); CV_RegisterVar(&cv_spbtest); + CV_RegisterVar(&cv_gptest); } //} @@ -325,10 +344,10 @@ boolean K_IsPlayerLosing(player_t *player) INT32 winningpos = 1; UINT8 i, pcount = 0; - if (battlecapsules && player->bumpers <= 0) - return true; // DNF in break the capsules + if (battlecapsules && numtargets == 0) + return true; // Didn't even TRY? - if (bossinfo.boss) + if (battlecapsules || bossinfo.boss) return (player->bumpers <= 0); // anything short of DNF is COOL if (player->position == 1) @@ -467,6 +486,40 @@ static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = { 5, 1 } // Jawz x2 }; +static UINT8 K_KartItemOddsSpecial[NUMKARTRESULTS-1][4] = +{ + //M N O P + { 1, 1, 0, 0 }, // Sneaker + { 0, 0, 0, 0 }, // Rocket Sneaker + { 0, 0, 0, 0 }, // Invincibility + { 0, 0, 0, 0 }, // Banana + { 0, 0, 0, 0 }, // Eggman Monitor + { 1, 1, 0, 0 }, // Orbinaut + { 1, 1, 0, 0 }, // Jawz + { 0, 0, 0, 0 }, // Mine + { 0, 0, 0, 0 }, // Land Mine + { 0, 0, 0, 0 }, // Ballhog + { 0, 0, 0, 1 }, // Self-Propelled Bomb + { 0, 0, 0, 0 }, // Grow + { 0, 0, 0, 0 }, // Shrink + { 0, 0, 0, 0 }, // Lightning Shield + { 0, 0, 0, 0 }, // Bubble Shield + { 0, 0, 0, 0 }, // Flame Shield + { 0, 0, 0, 0 }, // Hyudoro + { 0, 0, 0, 0 }, // Pogo Spring + { 0, 0, 0, 0 }, // Super Ring + { 0, 0, 0, 0 }, // Kitchen Sink + { 0, 0, 0, 0 }, // Drop Target + { 0, 0, 0, 0 }, // Garden Top + { 0, 1, 1, 0 }, // Sneaker x2 + { 0, 0, 1, 1 }, // Sneaker x3 + { 0, 0, 0, 0 }, // Banana x3 + { 0, 0, 0, 0 }, // Banana x10 + { 0, 1, 1, 0 }, // Orbinaut x3 + { 0, 0, 1, 1 }, // Orbinaut x4 + { 0, 0, 1, 1 } // Jawz x2 +}; + #define DISTVAR (2048) // Magic number distance for use with item roulette tiers #define SPBSTARTDIST (6*DISTVAR) // Distance when SPB can start appearing #define SPBFORCEDIST (12*DISTVAR) // Distance when SPB is forced onto the next person who rolls an item @@ -6449,7 +6502,8 @@ static void K_DoShrink(player_t *user) { next = mobj->itnext; - if (mobj->type == MT_SPB) + if (mobj->type == MT_SPB + || mobj->type == MT_BATTLECAPSULE) { continue; } @@ -7762,7 +7816,7 @@ void K_KartPlayerHUDUpdate(player_t *player) if (!(player->pflags & PF_FAULT)) player->karthud[khud_fault] = 0; - else if (player->karthud[khud_fault] > 0 && player->karthud[khud_fault] < 2*TICRATE) + else if (player->karthud[khud_fault] > 0 && player->karthud[khud_fault] <= 2*TICRATE) player->karthud[khud_fault]++; if (player->karthud[khud_itemblink] && player->karthud[khud_itemblink]-- <= 0) @@ -7771,30 +7825,33 @@ void K_KartPlayerHUDUpdate(player_t *player) player->karthud[khud_itemblink] = 0; } - if (gametype == GT_RACE) + if (!(gametyperules & GTR_SPHERES)) { - // 0 is the fast spin animation, set at 30 tics of ring boost or higher! - if (player->ringboost >= 30) - player->karthud[khud_ringdelay] = 0; - else - player->karthud[khud_ringdelay] = ((RINGANIM_DELAYMAX+1) * (30 - player->ringboost)) / 30; + if (player->mo && player->mo->hitlag <= 0) + { + // 0 is the fast spin animation, set at 30 tics of ring boost or higher! + if (player->ringboost >= 30) + player->karthud[khud_ringdelay] = 0; + else + player->karthud[khud_ringdelay] = ((RINGANIM_DELAYMAX+1) * (30 - player->ringboost)) / 30; - if (player->karthud[khud_ringframe] == 0 && player->karthud[khud_ringdelay] > RINGANIM_DELAYMAX) - { - player->karthud[khud_ringframe] = 0; - player->karthud[khud_ringtics] = 0; - } - else if ((player->karthud[khud_ringtics]--) <= 0) - { - if (player->karthud[khud_ringdelay] == 0) // fast spin animation + if (player->karthud[khud_ringframe] == 0 && player->karthud[khud_ringdelay] > RINGANIM_DELAYMAX) { - player->karthud[khud_ringframe] = ((player->karthud[khud_ringframe]+2) % RINGANIM_NUMFRAMES); + player->karthud[khud_ringframe] = 0; player->karthud[khud_ringtics] = 0; } - else + else if ((player->karthud[khud_ringtics]--) <= 0) { - player->karthud[khud_ringframe] = ((player->karthud[khud_ringframe]+1) % RINGANIM_NUMFRAMES); - player->karthud[khud_ringtics] = min(RINGANIM_DELAYMAX, player->karthud[khud_ringdelay])-1; + if (player->karthud[khud_ringdelay] == 0) // fast spin animation + { + player->karthud[khud_ringframe] = ((player->karthud[khud_ringframe]+2) % RINGANIM_NUMFRAMES); + player->karthud[khud_ringtics] = 0; + } + else + { + player->karthud[khud_ringframe] = ((player->karthud[khud_ringframe]+1) % RINGANIM_NUMFRAMES); + player->karthud[khud_ringtics] = min(RINGANIM_DELAYMAX, player->karthud[khud_ringdelay])-1; + } } } @@ -7824,16 +7881,20 @@ void K_KartPlayerHUDUpdate(player_t *player) player->karthud[khud_ringspblock] = (leveltime % 14); // reset to normal anim next time } + if (player->exiting) + { + if (player->karthud[khud_finish] <= 2*TICRATE) + player->karthud[khud_finish]++; + } + else + player->karthud[khud_finish] = 0; + if ((gametyperules & GTR_BUMPERS) && (player->exiting || player->karmadelay)) { if (player->exiting) { - if (player->exiting < 6*TICRATE) + if (exitcountdown < 6*TICRATE) player->karthud[khud_cardanimation] += ((164-player->karthud[khud_cardanimation])/8)+1; - else if (player->exiting == 6*TICRATE) - player->karthud[khud_cardanimation] = 0; - else if (player->karthud[khud_cardanimation] < 2*TICRATE) - player->karthud[khud_cardanimation]++; } else { @@ -7848,11 +7909,6 @@ void K_KartPlayerHUDUpdate(player_t *player) if (player->karthud[khud_cardanimation] < 0) player->karthud[khud_cardanimation] = 0; } - else if (gametype == GT_RACE && player->exiting) - { - if (player->karthud[khud_cardanimation] < 2*TICRATE) - player->karthud[khud_cardanimation]++; - } else player->karthud[khud_cardanimation] = 0; } @@ -8409,8 +8465,6 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) K_UpdateTripwire(player); - K_KartPlayerHUDUpdate(player); - if (battleovertime.enabled && !(player->pflags & PF_ELIMINATED) && player->bumpers <= 0 && player->karmadelay <= 0) { if (player->overtimekarma) @@ -11513,7 +11567,7 @@ void K_CheckSpectateStatus(void) return; continue; } - else if (!(players[i].pflags & PF_WANTSTOJOIN)) + else if (players[i].bot || !(players[i].pflags & PF_WANTSTOJOIN)) continue; respawnlist[numjoiners++] = i; diff --git a/src/k_menufunc.c b/src/k_menufunc.c index eeedc242a..e40ce2ceb 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -3443,9 +3443,7 @@ void M_CupSelectHandler(INT32 choice) // Don't restart the server if we're already in a game lol if (gamestate == GS_MENU) { - SV_StartSinglePlayerServer(); - multiplayer = true; // yeah, SV_StartSinglePlayerServer clobbers this... - netgame = levellist.netgame; // ^ ditto. + SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); } levelNum = grandprixinfo.cup->cachedlevels[0]; @@ -3600,9 +3598,7 @@ void M_LevelSelectHandler(INT32 choice) F_WipeEndScreen(); F_RunWipe(wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); - SV_StartSinglePlayerServer(); - multiplayer = true; // yeah, SV_StartSinglePlayerServer clobbers this... - netgame = levellist.netgame; // ^ ditto. + SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); CV_StealthSet(&cv_kartbot, cv_dummymatchbots.string); CV_StealthSet(&cv_kartencore, (cv_dummygpencore.value == 1) ? "On" : "Auto"); @@ -3700,7 +3696,7 @@ void M_StartTimeAttack(INT32 choice) F_WipeEndScreen(); F_RunWipe(wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); - SV_StartSinglePlayerServer(); + SV_StartSinglePlayerServer(levellist.newgametype, false); gpath = va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s", srb2home, timeattackfolder); @@ -4398,7 +4394,7 @@ void M_InitOptions(INT32 choice) // enable gameplay & server options under the right circumstances. if (gamestate == GS_MENU - || ((server || IsPlayerAdmin(consoleplayer)) && K_CanChangeRules())) + || ((server || IsPlayerAdmin(consoleplayer)) && K_CanChangeRules(false))) { OPTIONS_MainDef.menuitems[mopt_gameplay].status = IT_STRING | IT_SUBMENU; OPTIONS_MainDef.menuitems[mopt_server].status = IT_STRING | IT_SUBMENU; @@ -5734,7 +5730,7 @@ void M_OpenPauseMenu(void) Dummymenuplayer_OnChange(); // Make sure the consvar is within bounds of the amount of splitscreen players we have. - if (K_CanChangeRules()) + if (K_CanChangeRules(false)) { PAUSE_Main[mpause_psetup].status = IT_STRING | IT_CALL; diff --git a/src/k_specialstage.c b/src/k_specialstage.c new file mode 100644 index 000000000..3a2d751ac --- /dev/null +++ b/src/k_specialstage.c @@ -0,0 +1,139 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2022 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2022 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_specialstage.c +/// \brief Special Stage game logic + +#include "k_specialstage.h" +#include "doomdef.h" +#include "d_player.h" +#include "g_game.h" +#include "p_local.h" +#include "k_kart.h" +#include "s_sound.h" +#include "st_stuff.h" +#include "z_zone.h" +#include "k_waypoint.h" + +struct specialStage specialStage; + +/*-------------------------------------------------- + void K_ResetSpecialStage(void) + + See header file for description. +--------------------------------------------------*/ +void K_ResetSpecialStage(void) +{ + memset(&specialStage, 0, sizeof(struct specialStage)); +} + +/*-------------------------------------------------- + void K_InitSpecialStage(void) + + See header file for description. +--------------------------------------------------*/ +void K_InitSpecialStage(void) +{ + INT32 i; + + specialStage.beamDist = UINT32_MAX; // TODO: make proper value + + 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)); + } +} + +/*-------------------------------------------------- + static void K_MoveExitBeam(void) + + Updates the exit beam. +--------------------------------------------------*/ +static void K_MoveExitBeam(void) +{ + UINT32 moveDist = 0; + INT32 i; + + if (leveltime <= 2) + { + return; + } + + moveDist = (8 * mapobjectscale) / FRACUNIT; + + if (specialStage.beamDist <= moveDist) + { + specialStage.beamDist = 0; + + // TODO: Fail Special Stage + } + else + { + specialStage.beamDist -= moveDist; + } + + // Find players who are now outside of the level. + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *player = NULL; + + if (playeringame[i] == false) + { + continue; + } + + player = &players[i]; + + if (player->spectator == true + || player->exiting > 0 + || (player->pflags & PF_NOCONTEST)) + { + continue; + } + + if (player->distancetofinish > specialStage.beamDist) + { + P_DoTimeOver(player); + } + } +} + +/*-------------------------------------------------- + void K_TickSpecialStage(void) + + See header file for description. +--------------------------------------------------*/ +void K_TickSpecialStage(void) +{ + if (specialStage.active == false) + { + return; + } + + K_MoveExitBeam(); +} diff --git a/src/k_specialstage.h b/src/k_specialstage.h new file mode 100644 index 000000000..8e11d761d --- /dev/null +++ b/src/k_specialstage.h @@ -0,0 +1,55 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2022 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2022 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_specialstage.h +/// \brief Special Stage game logic + +#ifndef __K_SPECIALSTAGE__ +#define __K_SPECIALSTAGE__ + +#include "doomdef.h" +#include "doomstat.h" + +extern struct specialStage +{ + 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 + + UINT32 beamDist; ///< Where the exit beam is. + mobj_t *capsule; ///< The Chaos Emerald capsule. +} specialStage; + +/*-------------------------------------------------- + void K_ResetSpecialStage(void); + + Resets Special Stage information to a clean slate. +--------------------------------------------------*/ + +void K_ResetSpecialStage(void); + + +/*-------------------------------------------------- + void K_InitSpecialStage(void); + + Initializes Special Stage data on map load. +--------------------------------------------------*/ + +void K_InitSpecialStage(void); + + +/*-------------------------------------------------- + void K_TickSpecialStage(void); + + Updates Special Stage data each frame. +--------------------------------------------------*/ + +void K_TickSpecialStage(void); + + +#endif diff --git a/src/lua_script.c b/src/lua_script.c index eaacbf145..4fcd7dd00 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -205,7 +205,7 @@ int LUA_PushGlobals(lua_State *L, const char *word) lua_pushinteger(L, redscore); return 1; } else if (fastcmp(word,"timelimit")) { - lua_pushinteger(L, cv_timelimit.value); + lua_pushinteger(L, timelimitintics); return 1; } else if (fastcmp(word,"pointlimit")) { lua_pushinteger(L, cv_pointlimit.value); diff --git a/src/m_random.h b/src/m_random.h index 6f534b219..c08e32993 100644 --- a/src/m_random.h +++ b/src/m_random.h @@ -47,7 +47,7 @@ typedef enum PR_PLAYERSTARTS, // Player starts PR_VOICES, // Player voice sounds - PR_RULESCRAMBLE, // Netgame rule scrambing events + PR_RULESCRAMBLE, // Rule scrambing events PR_ITEM_ROULETTE, // Item results PR_ITEM_RINGS, // Flung ring/bumper/player (on death) diff --git a/src/p_enemy.c b/src/p_enemy.c index 827e42c8e..c4568f018 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -4032,9 +4032,10 @@ void A_AttractChase(mobj_t *actor) if ( actor->tracer->player && actor->tracer->health - && actor->tracer->player->itemtype == KITEM_LIGHTNINGSHIELD - && RINGTOTAL(actor->tracer->player) < 20 - && !(actor->tracer->player->pflags & PF_RINGLOCK) + && ((gametyperules & GTR_SPHERES) + || (actor->tracer->player->itemtype == KITEM_LIGHTNINGSHIELD + && RINGTOTAL(actor->tracer->player) < 20 + && !(actor->tracer->player->pflags & PF_RINGLOCK))) //&& P_CheckSight(actor, actor->tracer) ) { diff --git a/src/p_inter.c b/src/p_inter.c index b7e26d63a..3669f7bb7 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -590,14 +590,9 @@ void P_TouchStarPost(mobj_t *post, player_t *player, boolean snaptopost) player->starpostnum = post->health; } -// Easily make it so that overtime works offline -#define TESTOVERTIMEINFREEPLAY - /** Checks if the level timer is over the timelimit and the round should end, * unless you are in overtime. In which case leveltime may stretch out beyond * timelimitintics and overtime's status will be checked here each tick. - * Verify that the value of ::cv_timelimit is greater than zero before - * calling this function. * * \sa cv_timelimit, P_CheckPointLimit, P_UpdateSpecials */ @@ -605,27 +600,61 @@ void P_CheckTimeLimit(void) { INT32 i; - if (!cv_timelimit.value) + if (exitcountdown) return; -#ifndef TESTOVERTIMEINFREEPLAY - if (battlecapsules) // capsules override any time limit settings - return; -#endif - - if (!(gametyperules & GTR_TIMELIMIT)) + if (!timelimitintics) return; - if (bossinfo.boss == true) + if (leveltime < starttime) + { + if (secretextratime) + secretextratime--; return; + } if (leveltime < (timelimitintics + starttime)) + { + if (secretextratime) + { + secretextratime--; + timelimitintics++; + } + else if (extratimeintics) + { + timelimitintics++; + if (leveltime & 1) + ; + else + { + if (extratimeintics > 20) + { + extratimeintics -= 20; + timelimitintics += 20; + } + else + { + timelimitintics += extratimeintics; + extratimeintics = 0; + } + S_StartSound(NULL, sfx_ptally); + } + } + else + { + if (timelimitintics + starttime - leveltime <= 3*TICRATE) + { + if (((timelimitintics + starttime - leveltime) % TICRATE) == 0) + S_StartSound(NULL, sfx_s3ka7); + } + } return; + } if (gameaction == ga_completed) return; - if ((cv_overtime.value) && (gametyperules & GTR_OVERTIME)) + if ((grandprixinfo.gp == false) && (cv_overtime.value) && (gametyperules & GTR_OVERTIME)) { #ifndef TESTOVERTIMEINFREEPLAY boolean foundone = false; // Overtime is used for closing off down to a specific item. @@ -700,8 +729,6 @@ void P_CheckTimeLimit(void) } /** Checks if a player's score is over the pointlimit and the round should end. - * Verify that the value of ::cv_pointlimit is greater than zero before - * calling this function. * * \sa cv_pointlimit, P_CheckTimeLimit, P_UpdateSpecials */ @@ -709,16 +736,19 @@ void P_CheckPointLimit(void) { INT32 i; - if (!cv_pointlimit.value) + if (exitcountdown) return; - if (!(multiplayer || netgame)) + if (!K_CanChangeRules(true)) + return; + + if (!cv_pointlimit.value) return; if (!(gametyperules & GTR_POINTLIMIT)) return; - if (bossinfo.boss == true) + if (battlecapsules) return; // pointlimit is nonzero, check if it's been reached by this player @@ -852,7 +882,7 @@ boolean P_CheckRacers(void) } // Everyone should be done playing at this point now. - racecountdown = exitcountdown = 0; + racecountdown = 0; return true; } @@ -860,7 +890,7 @@ boolean P_CheckRacers(void) { // There might be bots that are still going, // but all of the humans are done, so we can exit now. - racecountdown = exitcountdown = 0; + racecountdown = 0; return true; } @@ -881,7 +911,7 @@ boolean P_CheckRacers(void) { tic_t countdown = 30*TICRATE; // 30 seconds left to finish, get going! - if (K_CanChangeRules() == true) + if (K_CanChangeRules(true) == true) { // Custom timer countdown = cv_countdowntime.value * TICRATE; @@ -1262,6 +1292,9 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget kart->old_x = target->old_x; kart->old_y = target->old_y; kart->old_z = target->old_z; + + if (target->player->pflags & PF_NOCONTEST) + P_SetTarget(&target->tracer, kart); } if (source && !P_MobjWasRemoved(source)) @@ -1426,40 +1459,94 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget case MT_BATTLECAPSULE: { + UINT8 i; mobj_t *cur; + angle_t dir = 0; - numtargets++; target->fuse = 16; target->flags |= MF_NOCLIP|MF_NOCLIPTHING; + if (inflictor) + { + dir = R_PointToAngle2(inflictor->x, inflictor->y, target->x, target->y); + P_Thrust(target, dir, P_AproxDistance(inflictor->momx, inflictor->momy)/12); + } + else if (source) + dir = R_PointToAngle2(source->x, source->y, target->x, target->y); + + target->momz += 8 * target->scale * P_MobjFlip(target); + target->flags &= ~MF_NOGRAVITY; + cur = target->hnext; while (cur && !P_MobjWasRemoved(cur)) { + cur->momx = target->momx; + cur->momy = target->momy; + cur->momz = target->momz; + // Shoot every piece outward if (!(cur->x == target->x && cur->y == target->y)) { - P_InstaThrust(cur, + P_Thrust(cur, R_PointToAngle2(target->x, target->y, cur->x, cur->y), R_PointToDist2(target->x, target->y, cur->x, cur->y) / 12 ); } - cur->momz = 8 * target->scale * P_MobjFlip(target); - cur->flags &= ~MF_NOGRAVITY; cur->tics = TICRATE; cur->frame &= ~FF_ANIMATE; // Stop animating the propellers + cur->hitlag = target->hitlag; + cur->eflags |= MFE_DAMAGEHITLAG; + cur = cur->hnext; } - // All targets busted! - if (numtargets >= maptargets) + S_StartSound(target, sfx_mbs60); + + if ((gametyperules & GTR_POINTLIMIT) && (source && source->player)) { - UINT8 i; + /*mobj_t * ring; + for (i = 0; i < 2; i++) + { + dir += (ANGLE_MAX/3); + ring = P_SpawnMobj(target->x, target->y, target->z, MT_RING); + ring->angle = dir; + P_InstaThrust(ring, dir, 16*ring->scale); + ring->momz = 8 * target->scale * P_MobjFlip(target); + P_SetTarget(&ring->tracer, source); + source->player->pickuprings++; + }*/ + + P_AddPlayerScore(source->player, 1); + K_SpawnBattlePoints(source->player, NULL, 1); + } + + // All targets busted! + if (++numtargets >= maptargets) + { + boolean givelife = false; for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; P_DoPlayerExit(&players[i]); + if (!grandprixinfo.gp) + continue; + P_GivePlayerLives(&players[i], 1); + givelife = true; + } + + if (givelife) + S_StartSound(NULL, sfx_cdfm73); + } + else if (timelimitintics) + { + S_StartSound(NULL, sfx_s221); + extratimeintics += 10*TICRATE; + secretextratime = TICRATE/2; } } break; diff --git a/src/p_mobj.c b/src/p_mobj.c index 6df9b921c..49f57dacc 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -5157,10 +5157,14 @@ boolean P_IsKartItem(INT32 type) case MT_JAWZ_SHIELD: case MT_SSMINE_SHIELD: case MT_SINK_SHIELD: - case MT_SPB: case MT_HYUDORO: return true; + // Primarily for minimap data, handle with care + case MT_SPB: + case MT_BATTLECAPSULE: + return true; + default: return P_IsKartFieldItem(type); } @@ -9407,6 +9411,9 @@ void P_MobjThinker(mobj_t *mobj) K_HandleDirectionalInfluence(mobj->player); } + if (P_IsKartItem(mobj->type)) // mobj is a kart item we want on the list: + P_AddKartItem(mobj); // add to kitem list + return; } diff --git a/src/p_saveg.c b/src/p_saveg.c index e3aa00cc6..94bca2f52 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -4597,6 +4597,10 @@ static void P_NetArchiveMisc(boolean resending) WRITEUINT32(save_p, starttime); WRITEUINT8(save_p, numbulbs); + WRITEUINT32(save_p, timelimitintics); + WRITEUINT32(save_p, extratimeintics); + WRITEUINT32(save_p, secretextratime); + // Is it paused? if (paused) WRITEUINT8(save_p, 0x2f); diff --git a/src/p_setup.c b/src/p_setup.c index 518683816..abb53d990 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -96,8 +96,8 @@ #include "k_boss.h" #include "k_terrain.h" // TRF_TRIPWIRE #include "k_brightmap.h" -#include "k_terrain.h" // TRF_TRIPWIRE #include "k_director.h" // K_InitDirector +#include "k_specialstage.h" // Replay names have time #if !defined (UNDER_CE) @@ -3565,7 +3565,7 @@ static void P_InitLevelSettings(void) // SRB2Kart: map load variables if (grandprixinfo.gp == true) { - if (gametype == GT_BATTLE) + if ((gametyperules & GTR_BUMPERS)) { gamespeed = KARTSPEED_EASY; } @@ -3584,7 +3584,10 @@ static void P_InitLevelSettings(void) else if (modeattacking) { // Just play it safe and set everything - gamespeed = KARTSPEED_HARD; + if ((gametyperules & GTR_BUMPERS)) + gamespeed = KARTSPEED_EASY; + else + gamespeed = KARTSPEED_HARD; franticitems = false; } else @@ -3848,20 +3851,29 @@ static void P_InitGametype(void) if (modeattacking && !demo.playback) P_LoadRecordGhosts(); - numlaps = 0; if (gametyperules & GTR_CIRCUIT) { - if ((netgame || multiplayer) && cv_numlaps.value + if (K_CanChangeRules(true) && cv_numlaps.value && (!(mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) || (mapheaderinfo[gamemap - 1]->numlaps > cv_numlaps.value))) { numlaps = cv_numlaps.value; } + else if ((grandprixinfo.gp == true) + && (grandprixinfo.eventmode == GPEVENT_NONE) + && cv_gptest.value) + { + numlaps = 1; + } else { numlaps = mapheaderinfo[gamemap - 1]->numlaps; } } + else + { + numlaps = 0; + } wantedcalcdelay = wantedfrequency*2; @@ -4474,6 +4486,42 @@ UINT8 P_InitMapData(INT32 numexistingmapheaders) continue; } + // Always check for cup cache reassociations. + // (The core assumption is that cups < headers.) + { + cupheader_t *cup = kartcupheaders; + INT32 j; + + while (cup) + { + for (j = 0; j < CUPCACHE_MAX; j++) + { + // No level in this slot? + if (!cup->levellist[j]) + continue; + + // Already discovered? + if (cup->cachedlevels[j] != NEXTMAP_INVALID) + continue; + + // Not your name? + if (strcasecmp(cup->levellist[j], name) != 0) + continue; + + // Only panic about back-reference for non-bonus material. + if (j < MAXLEVELLIST) + { + if (mapheaderinfo[i]->cup) + I_Error("P_InitMapData: Map %s cannot appear in cups multiple times! (First in %s, now in %s)", name, mapheaderinfo[i]->cup->name, cup->name); + mapheaderinfo[i]->cup = cup; + } + + cup->cachedlevels[j] = i; + } + cup = cup->next; + } + } + // No change? if (mapheaderinfo[i]->lumpnum == maplump) continue; @@ -4527,37 +4575,6 @@ UINT8 P_InitMapData(INT32 numexistingmapheaders) } vres_Free(virtmap); - - // Now associate it with a cup cache. - // (The core assumption is that cups < headers.) - if (i >= numexistingmapheaders) - { - cupheader_t *cup = kartcupheaders; - INT32 j; - while (cup) - { - for (j = 0; j < CUPCACHE_MAX; j++) - { - // Already discovered? - if (cup->cachedlevels[j] != NEXTMAP_INVALID) - continue; - - if (!cup->levellist[j] || strcasecmp(cup->levellist[j], name) != 0) - continue; - - // Only panic about back-reference for non-bonus material. - if (j < MAXLEVELLIST || j == CUPCACHE_SPECIAL) - { - if (mapheaderinfo[i]->cup) - I_Error("P_InitMapData: Map %s cannot appear in cups multiple times! (First in %s, now in %s)", name, mapheaderinfo[i]->cup->name, cup->name); - mapheaderinfo[i]->cup = cup; - } - - cup->cachedlevels[j] = i; - } - cup = cup->next; - } - } } } diff --git a/src/p_tick.c b/src/p_tick.c index 073dddd9e..f3ece57dd 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -37,6 +37,7 @@ #include "k_boss.h" #include "k_waypoint.h" #include "k_director.h" +#include "k_specialstage.h" tic_t leveltime; @@ -594,20 +595,32 @@ void P_Ticker(boolean run) ps_playerthink_time = I_GetPreciseTime(); +#define PLAYERCONDITION(i) (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) // First loop: Ensure all players' distance to the finish line are all accurate for (i = 0; i < MAXPLAYERS; i++) - if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) - K_UpdateDistanceFromFinishLine(&players[i]); + { + if (!PLAYERCONDITION(i)) + continue; + K_UpdateDistanceFromFinishLine(&players[i]); + } // Second loop: Ensure all player positions reflect everyone's distances for (i = 0; i < MAXPLAYERS; i++) - if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) - K_KartUpdatePosition(&players[i]); + { + if (!PLAYERCONDITION(i)) + continue; + K_KartUpdatePosition(&players[i]); + } // OK! Now that we got all of that sorted, players can think! for (i = 0; i < MAXPLAYERS; i++) - if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) - P_PlayerThink(&players[i]); + { + if (!PLAYERCONDITION(i)) + continue; + P_PlayerThink(&players[i]); + K_KartPlayerHUDUpdate(&players[i]); + } +#undef PLAYERCONDITION ps_playerthink_time = I_GetPreciseTime() - ps_playerthink_time; } @@ -690,12 +703,20 @@ void P_Ticker(boolean run) racecountdown--; if (exitcountdown > 1) + { exitcountdown--; + if (server && exitcountdown == 1) + { + SendNetXCmd(XD_EXITLEVEL, NULL, 0); + } + } K_RunItemCooldowns(); K_BossInfoTicker(); + K_TickSpecialStage(); + if ((gametyperules & GTR_BUMPERS)) { if (wantedcalcdelay && --wantedcalcdelay <= 0) diff --git a/src/p_user.c b/src/p_user.c index 090346bea..3ddd9bfda 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -505,7 +505,6 @@ INT32 P_GivePlayerRings(player_t *player, INT32 num_rings) num_rings -= (test+20); player->rings += num_rings; - //player->totalring += num_rings; // Used for GP lives later -- maybe you might want to move this earlier to discourage ring debt... return num_rings; } @@ -1261,72 +1260,81 @@ void P_DoPlayerExit(player_t *player) K_PlayerLoseLife(player); } - if ((gametyperules & GTR_CIRCUIT)) // If in Race Mode, allow + player->exiting = 1; + + if (!player->spectator) { - player->exiting = raceexittime+2; - K_KartUpdatePosition(player); - - if (cv_kartvoices.value) + if ((gametyperules & GTR_CIRCUIT)) // If in Race Mode, allow { - if (P_IsDisplayPlayer(player)) + K_KartUpdatePosition(player); + + if (cv_kartvoices.value) { - sfxenum_t sfx_id; - if (losing) - sfx_id = ((skin_t *)player->mo->skin)->soundsid[S_sfx[sfx_klose].skinsound]; - else - sfx_id = ((skin_t *)player->mo->skin)->soundsid[S_sfx[sfx_kwin].skinsound]; - S_StartSound(NULL, sfx_id); - } - else - { - if (losing) - S_StartSound(player->mo, sfx_klose); - else - S_StartSound(player->mo, sfx_kwin); - } - } - - if (cv_inttime.value > 0) - P_EndingMusic(player); - - if (P_CheckRacers()) - player->exiting = raceexittime+1; - } - else if ((gametyperules & GTR_BUMPERS)) // Battle Mode exiting - { - player->exiting = battleexittime+1; - P_EndingMusic(player); - } - else - player->exiting = raceexittime+2; // Accidental death safeguard??? - - if (grandprixinfo.gp == true) - { - if (player->bot) - { - // Bots are going to get harder... :) - K_IncreaseBotDifficulty(player); - } - else if (!losing) - { - const UINT8 lifethreshold = 20; - UINT8 extra = 0; - - // YOU WIN - grandprixinfo.wonround = true; - - // Increase your total rings - if (RINGTOTAL(player) > 0) - { - player->totalring += RINGTOTAL(player); - - extra = player->totalring / lifethreshold; - - if (extra > player->xtralife) + if (P_IsDisplayPlayer(player)) { - P_GivePlayerLives(player, extra - player->xtralife); - S_StartSound(NULL, sfx_cdfm73); - player->xtralife = extra; + sfxenum_t sfx_id; + if (losing) + sfx_id = ((skin_t *)player->mo->skin)->soundsid[S_sfx[sfx_klose].skinsound]; + else + sfx_id = ((skin_t *)player->mo->skin)->soundsid[S_sfx[sfx_kwin].skinsound]; + S_StartSound(NULL, sfx_id); + } + else + { + if (losing) + S_StartSound(player->mo, sfx_klose); + else + S_StartSound(player->mo, sfx_kwin); + } + } + + // See Y_StartIntermission timer handling + if (!K_CanChangeRules(false) || cv_inttime.value > 0) + P_EndingMusic(player); + + if (P_CheckRacers() && !exitcountdown) + exitcountdown = raceexittime+1; + } + else if ((gametyperules & GTR_BUMPERS)) // Battle Mode exiting + { + if (!exitcountdown) + exitcountdown = battleexittime+1; + P_EndingMusic(player); + } + else // Accidental death safeguard??? + { + if (!exitcountdown) + exitcountdown = raceexittime+2; + } + + if (grandprixinfo.gp == true) + { + if (player->bot) + { + // Bots are going to get harder... :) + K_IncreaseBotDifficulty(player); + } + else if (!losing) + { + const UINT8 lifethreshold = 20; + UINT8 extra = 0; + + // YOU WIN + grandprixinfo.wonround = true; + + // Increase your total rings + if (RINGTOTAL(player) > 0) + { + player->totalring += RINGTOTAL(player); + + extra = player->totalring / lifethreshold; + + if (extra > player->xtralife) + { + P_GivePlayerLives(player, extra - player->xtralife); + S_StartSound(NULL, sfx_cdfm73); + player->xtralife = extra; + } } } } @@ -3853,8 +3861,6 @@ void P_PlayerThink(player_t *player) player->old_drawangle = player->drawangle; - player->pflags &= ~PF_HITFINISHLINE; - if (player->awayviewmobj && P_MobjWasRemoved(player->awayviewmobj)) { P_SetTarget(&player->awayviewmobj, NULL); // remove awayviewmobj asap if invalid @@ -3867,11 +3873,6 @@ void P_PlayerThink(player_t *player) if (player->awayviewtics && player->awayviewtics != -1) player->awayviewtics--; - if (player->mo->hitlag > 0) - { - return; - } - // Track airtime if (P_IsObjectOnGround(player->mo) && !P_PlayerInPain(player)) // This isn't airtime, but it's control loss all the same. @@ -3946,21 +3947,6 @@ void P_PlayerThink(player_t *player) { if (gametyperules & GTR_CIRCUIT) { - INT32 i; - - // 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) - { - if (!players[i].exiting && !(players[i].pflags & PF_NOCONTEST) && players[i].lives > 0) - break; - } - } - - if (i == MAXPLAYERS && player->exiting == raceexittime+2) // finished - player->exiting = raceexittime+1; - #if 0 // If 10 seconds are left on the timer, // begin the drown music for countdown! @@ -3985,45 +3971,6 @@ void P_PlayerThink(player_t *player) } } } - - // If it is set, start subtracting - // Don't allow it to go back to 0 - if (player->exiting > 1 && (player->exiting < raceexittime+2 || !(gametyperules & GTR_CIRCUIT))) // SRB2kart - "&& player->exiting > 1" - player->exiting--; - - if (player->exiting && exitcountdown) - player->exiting = 99; // SRB2kart - - if (player->exiting == 2 || exitcountdown == 2) - { - if (server) - { - SendNetXCmd(XD_EXITLEVEL, NULL, 0); - } - } - } - - // check water content, set stuff in mobj - P_MobjCheckWater(player->mo); - -#ifndef SECTORSPECIALSAFTERTHINK - if (player->onconveyor != 1 || !P_IsObjectOnGround(player->mo)) - player->onconveyor = 0; - // check special sectors : damage & secrets - - if (!player->spectator) - P_PlayerInSpecialSector(player); -#endif - - if (player->playerstate == PST_DEAD) - { - if (player->spectator) - player->mo->renderflags |= RF_GHOSTLY; - else - player->mo->renderflags &= ~RF_GHOSTLYMASK; - P_DeathThink(player); - LUA_HookPlayer(player, HOOK(PlayerThink)); - return; } // Make sure spectators always have a score and ring count of 0. @@ -4062,7 +4009,103 @@ void P_PlayerThink(player_t *player) } } - if ((netgame || multiplayer) && player->spectator && cmd->buttons & BT_ATTACK && !player->flashing) + if (cmd->flags & TICCMD_TYPING) + { + /* + typing_duration is slow to start and slow to stop. + + typing_timer counts down a grace period before the player is not + actually considered typing anymore. + */ + if (cmd->flags & TICCMD_KEYSTROKE) + { + /* speed up if we are typing quickly! */ + if (player->typing_duration > 0 && player->typing_timer > 12) + { + if (player->typing_duration < 16) + { + player->typing_duration = 24; + } + else + { + /* slows down a tiny bit as it approaches the next dot */ + const UINT8 step = (((player->typing_duration + 15) & ~15) - + player->typing_duration) / 2; + player->typing_duration += max(step, 4); + } + } + + player->typing_timer = 15; + } + else if (player->typing_timer > 0) + { + player->typing_timer--; + } + + /* if we are in the grace period (including currently typing) */ + if (player->typing_timer + player->typing_duration > 0) + { + /* always end the cycle on two dots */ + if (player->typing_timer == 0 && + (player->typing_duration < 16 || player->typing_duration == 40)) + { + player->typing_duration = 0; + } + else if (player->typing_duration < 63) + { + player->typing_duration++; + } + else + { + player->typing_duration = 16; + } + } + } + else + { + player->typing_timer = 0; + player->typing_duration = 0; + } + + /* ------------------------------------------ / + ALL ABOVE THIS BLOCK OCCURS EVEN WITH HITLAG + / ------------------------------------------ */ + + if (player->mo->hitlag > 0) + { + return; + } + + /* ------------------------------------------ / + ALL BELOW THIS BLOCK IS STOPPED DURING HITLAG + / ------------------------------------------ */ + + player->pflags &= ~PF_HITFINISHLINE; + + // check water content, set stuff in mobj + P_MobjCheckWater(player->mo); + +#ifndef SECTORSPECIALSAFTERTHINK + if (player->onconveyor != 1 || !P_IsObjectOnGround(player->mo)) + player->onconveyor = 0; + // check special sectors : damage & secrets + + if (!player->spectator) + P_PlayerInSpecialSector(player); +#endif + + if (player->playerstate == PST_DEAD) + { + if (player->spectator) + player->mo->renderflags |= RF_GHOSTLY; + else + player->mo->renderflags &= ~RF_GHOSTLYMASK; + P_DeathThink(player); + LUA_HookPlayer(player, HOOK(PlayerThink)); + return; + } + + if ((netgame || multiplayer) && player->spectator && !player->bot && cmd->buttons & BT_ATTACK && !player->flashing) { player->pflags ^= PF_WANTSTOJOIN; player->flashing = TICRATE/2 + 1; @@ -4144,64 +4187,6 @@ void P_PlayerThink(player_t *player) player->mo->renderflags &= ~RF_DONTDRAW; } - if (cmd->flags & TICCMD_TYPING) - { - /* - typing_duration is slow to start and slow to stop. - - typing_timer counts down a grace period before the player is not - actually considered typing anymore. - */ - if (cmd->flags & TICCMD_KEYSTROKE) - { - /* speed up if we are typing quickly! */ - if (player->typing_duration > 0 && player->typing_timer > 12) - { - if (player->typing_duration < 16) - { - player->typing_duration = 24; - } - else - { - /* slows down a tiny bit as it approaches the next dot */ - const UINT8 step = (((player->typing_duration + 15) & ~15) - - player->typing_duration) / 2; - player->typing_duration += max(step, 4); - } - } - - player->typing_timer = 15; - } - else if (player->typing_timer > 0) - { - player->typing_timer--; - } - - /* if we are in the grace period (including currently typing) */ - if (player->typing_timer + player->typing_duration > 0) - { - /* always end the cycle on two dots */ - if (player->typing_timer == 0 && - (player->typing_duration < 16 || player->typing_duration == 40)) - { - player->typing_duration = 0; - } - else if (player->typing_duration < 63) - { - player->typing_duration++; - } - else - { - player->typing_duration = 16; - } - } - } - else - { - player->typing_timer = 0; - player->typing_duration = 0; - } - if (player->stairjank > 0) { player->stairjank--; diff --git a/src/st_stuff.c b/src/st_stuff.c index f0aa0c4a6..676c3992a 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -437,6 +437,7 @@ static patch_t *tcol2; static patch_t *tcroundbar; static patch_t *tcround; +static patch_t *tcbonus; static patch_t *tccircletop; static patch_t *tccirclebottom; @@ -446,6 +447,8 @@ static patch_t *tcbanner; static patch_t *tcbanner2; static patch_t *tcroundnum[10]; +static patch_t *tcroundbonus; + static patch_t *tcactnum[10]; static patch_t *tcact; @@ -495,6 +498,7 @@ static void ST_cacheLevelTitle(void) tcroundbar = (patch_t *)W_CachePatchName("TCBB0", PU_HUDGFX); tcround = (patch_t *)W_CachePatchName("TCROUND", PU_HUDGFX); + tcbonus = (patch_t *)W_CachePatchName("TCBONUS", PU_HUDGFX); tccircletop = (patch_t *)W_CachePatchName("TCSN1", PU_HUDGFX); tccirclebottom =(patch_t *)W_CachePatchName("TCSN2", PU_HUDGFX); @@ -514,6 +518,7 @@ static void ST_cacheLevelTitle(void) sprintf(buf, "TT_RND%d", i); tcroundnum[i-1] = (patch_t *)W_CachePatchName(buf, PU_HUDGFX); } + tcroundbonus = (patch_t *)W_CachePatchName("TT_RNDB", PU_HUDGFX); // Cache act # for (i=0; i < 10; i++) @@ -579,7 +584,7 @@ void ST_runTitleCard(void) { boolean run = !(paused || P_AutoPause()); INT32 auxticker; - boolean gp = (grandprixinfo.gp && grandprixinfo.roundnum); // check whether we're in grandprix + boolean gp = (marathonmode || (grandprixinfo.gp && grandprixinfo.roundnum)); if (!G_IsTitleCardAvailable()) return; @@ -764,7 +769,7 @@ void ST_drawTitleCard(void) char *lvlttl = mapheaderinfo[gamemap-1]->lvlttl; char *zonttl = mapheaderinfo[gamemap-1]->zonttl; // SRB2kart UINT8 actnum = mapheaderinfo[gamemap-1]->actnum; - boolean gp = (grandprixinfo.gp && grandprixinfo.roundnum); + boolean gp = (marathonmode || (grandprixinfo.gp && grandprixinfo.roundnum)); INT32 acttimer; fixed_t actscale; @@ -895,7 +900,9 @@ void ST_drawTitleCard(void) V_DrawFixedPatch(roundx*FRACUNIT, ((-32) + (lt_ticker%32))*FRACUNIT, FRACUNIT, V_SNAPTOTOP|V_SNAPTOLEFT, tcroundbar, NULL); // Draw ROUND text if (gp) - V_DrawFixedPatch((roundx+10)*FRACUNIT, roundy*FRACUNIT, FRACUNIT, V_SNAPTOTOP|V_SNAPTOLEFT, tcround, NULL); + V_DrawFixedPatch((roundx+10)*FRACUNIT, roundy*FRACUNIT, FRACUNIT, V_SNAPTOTOP|V_SNAPTOLEFT, + ((grandprixinfo.gp && grandprixinfo.eventmode) ? tcbonus : tcround), + NULL); // round num background V_DrawFixedPatch(roundnumx*FRACUNIT, roundnumy*FRACUNIT, FRACUNIT, V_SNAPTOBOTTOM|V_SNAPTOLEFT, tccirclebg, NULL); @@ -912,9 +919,31 @@ void ST_drawTitleCard(void) } } - // If possible, draw round number - if (gp && grandprixinfo.roundnum > 0 && grandprixinfo.roundnum < 11) // Check boundaries JUST IN CASE. - V_DrawFixedPatch(roundnumx*FRACUNIT, roundnumy*FRACUNIT, FRACUNIT, V_SNAPTOBOTTOM|V_SNAPTOLEFT, tcroundnum[grandprixinfo.roundnum-1], NULL); + // If possible, draw round number/icon + if (gp) + { + patch_t *roundico = NULL; + if (marathonmode) + ; // TODO: Ruby + else switch (grandprixinfo.eventmode) + { + case GPEVENT_BONUS: + roundico = tcroundbonus; // TODO don't show capsule if we have other bonus types + break; + /*case GPEVENT_SPECIAL: + ; // TODO: Emerald/mount + break;*/ + case GPEVENT_NONE: + if (grandprixinfo.roundnum > 0 && grandprixinfo.roundnum < 11) // Check boundaries JUST IN CASE. + roundico = tcroundnum[grandprixinfo.roundnum-1]; + break; + default: + break; + } + + if (roundico) + V_DrawFixedPatch(roundnumx*FRACUNIT, roundnumy*FRACUNIT, FRACUNIT, V_SNAPTOBOTTOM|V_SNAPTOLEFT, roundico, NULL); + } // Draw both halves of the egg V_DrawFixedPatch(eggx1*FRACUNIT, eggy1*FRACUNIT, FRACUNIT, V_SNAPTOBOTTOM|V_SNAPTOLEFT, tccircletop, NULL); diff --git a/src/y_inter.c b/src/y_inter.c index fd0383b5b..10e1b732f 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -657,18 +657,8 @@ void Y_Ticker(void) P_DoTeamscrambling(); }*/ - // multiplayer uses timer (based on cv_inttime) - if (timer) - { - if (!--timer) - { - Y_EndIntermission(); - G_AfterIntermission(); - return; - } - } - // single player is hardcoded to go away after awhile - else if (intertic == endtic) + if ((timer && !--timer) + || (intertic == endtic)) { Y_EndIntermission(); G_AfterIntermission(); @@ -680,10 +670,10 @@ void Y_Ticker(void) if (intertype == int_race || intertype == int_battle || intertype == int_battletime) { - //if (!(multiplayer && demo.playback)) // Don't advance to rankings in replays { if (!data.rankingsmode && sorttic != -1 && (intertic >= sorttic + 8)) { + // Anything with post-intermission consequences here should also occur in Y_EndIntermission. K_RetireBots(); Y_CalculateMatchData(1, Y_CompareRank); } @@ -746,8 +736,8 @@ void Y_Ticker(void) S_StartSound(NULL, (kaching ? sfx_chchng : sfx_ptally)); Y_CalculateMatchData(2, Y_CompareRank); } - else - endtic = intertic + 3*TICRATE; // 3 second pause after end of tally + /*else -- This is how to define an endtic, but we currently use timer for both SP and MP. + endtic = intertic + 3*TICRATE;*/ } } } @@ -763,21 +753,26 @@ void Y_DetermineIntermissionType(void) // set to int_none initially intertype = int_none; - if (intermissiontypes[gametype] != int_none) - intertype = intermissiontypes[gametype]; - else if (gametype == GT_RACE) + if (gametype == GT_RACE) intertype = int_race; else if (gametype == GT_BATTLE) { - UINT8 i = 0, nump = 0; - for (i = 0; i < MAXPLAYERS; i++) + if (grandprixinfo.gp == true && bossinfo.boss == false) + intertype = int_none; + else { - if (!playeringame[i] || players[i].spectator) - continue; - nump++; + 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 = (nump < 2 ? int_battletime : int_battle); } + else //if (intermissiontypes[gametype] != int_none) + intertype = intermissiontypes[gametype]; } // @@ -806,23 +801,32 @@ void Y_StartIntermission(void) powertype = K_UsingPowerLevels(); // determine the tic the intermission ends - if (!multiplayer || demo.playback) + // Technically cv_inttime is saved to demos... but this permits having extremely long timers for post-netgame chatting without stranding you on the intermission in netreplays. + if (!K_CanChangeRules(false)) { - timer = ((nump >= 2) ? 10 : 5)*TICRATE; + timer = 10*TICRATE; } else { timer = cv_inttime.value*TICRATE; - - if (!timer) - timer = 1; // prevent a weird bug } // determine the tic everybody's scores/PWR starts getting sorted sorttic = -1; - if (multiplayer || nump >= 2) + if (!timer) { - sorttic = max((timer/2) - 2*TICRATE, 2*TICRATE); // 8 second pause after match results + // Prevent a weird bug + timer = 1; + } + else if (nump < 2 && !netgame) + { + // No PWR/global score, skip it + timer /= 2; + } + else + { + // Minimum two seconds for match results, then two second slideover approx halfway through + sorttic = max((timer/2) - 2*TICRATE, 2*TICRATE); } if (intermissiontypes[gametype] != int_none) @@ -841,7 +845,7 @@ void Y_StartIntermission(void) case int_battle: case int_battletime: { - if (cv_inttime.value > 0) + if (timer > 1) S_ChangeMusicInternal("racent", true); // loop it // Calculate who won @@ -897,7 +901,11 @@ void Y_StartIntermission(void) // void Y_EndIntermission(void) { - K_RetireBots(); + if (!data.rankingsmode) + { + K_RetireBots(); + } + Y_UnloadData(); endtic = -1;