diff --git a/src/Sourcefile b/src/Sourcefile index 9a4d968ff..8453c55e6 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -143,6 +143,7 @@ k_director.c k_follower.c k_mapuser.c k_stats.c +k_specialstage.c h_timers.cpp stun.c lonesha256.c diff --git a/src/command.c b/src/command.c index 2c2ff1acf..914ef2b1a 100644 --- a/src/command.c +++ b/src/command.c @@ -90,6 +90,15 @@ CV_PossibleValue_t kartspeed_cons_t[] = { {KARTSPEED_EXPERT, "Expert"}, {0, NULL} }; +CV_PossibleValue_t gpdifficulty_cons_t[] = { + {KARTSPEED_EASY, "Easy"}, + {KARTSPEED_NORMAL, "Normal"}, + {KARTSPEED_HARD, "Hard"}, + {KARTSPEED_EXPERT, "Expert"}, + {KARTGP_MASTER, "Master"}, + {KARTGP_NIGHTMARE, "Nightmare"}, + {0, NULL} +}; consvar_t cv_resetnetvars = CVAR_INIT ("resetnetvars", "Off", CV_SAVE, CV_OnOff, NULL); @@ -2102,6 +2111,15 @@ static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth) return; } + if (var == &cv_kartspeed && !M_SecretUnlocked(SECRET_HARDSPEED)) + { + if (!stricmp(value, "Hard") || !stricmp(value, "Expert") || atoi(value) >= KARTSPEED_HARD) + { + CONS_Printf(M_GetText("You haven't unlocked this yet!\n")); + return; + } + } + if (var == &cv_forceskin) { INT32 skin = R_SkinAvailable(value); @@ -2307,9 +2325,14 @@ void CV_AddValue(consvar_t *var, INT32 increment) if (var->PossibleValue[max].value == var->value) currentindice = max; - if ((var == &cv_kartspeed || var == &cv_kartbattlespeed) && !M_SecretUnlocked(SECRET_HARDSPEED)) + if (var->PossibleValue == kartspeed_cons_t || var->PossibleValue == gpdifficulty_cons_t) { - max = (M_SecretUnlocked(SECRET_HARDSPEED) ? 5 : 3); + if (!M_SecretUnlocked(SECRET_HARDSPEED)) + { + max = KARTSPEED_NORMAL+1; + if (var->PossibleValue == kartspeed_cons_t) + max++; // Accommodate KARTSPEED_AUTO + } } #ifdef PARANOIA if (currentindice == -1) diff --git a/src/command.h b/src/command.h index fc81aeb1e..b6e99ebf2 100644 --- a/src/command.h +++ b/src/command.h @@ -187,7 +187,7 @@ extern CV_PossibleValue_t CV_Natural[]; #define KARTSPEED_EXPERT 3 #define KARTGP_MASTER 4 // Not a speed setting, gives hard speed with maxed out bots #define KARTGP_NIGHTMARE 5 // Not a speed setting, gives expert speed with maxed out bots -extern CV_PossibleValue_t kartspeed_cons_t[]; +extern CV_PossibleValue_t kartspeed_cons_t[], gpdifficulty_cons_t[]; // Invincibility types. #define KARTINVIN_LEGACY 0 #define KARTINVIN_ALTERN 1 diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 6efd6e9b5..cdfd1a064 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -4119,30 +4119,22 @@ 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) && !grandprixinfo.gp; // G: no multiplayer in GP! - if ((modeattacking == ATTACKING_ITEMBREAK) || (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 24761e2cf..d0799c1e2 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -509,7 +509,7 @@ typedef enum 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.cpp b/src/d_main.cpp index 275097dcb..ba016e56f 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -81,6 +81,7 @@ #include "doomstat.h" #include "m_random.h" // P_ClearRandom #include "acs/interface.h" +#include "k_specialstage.h" #define __STDC_FORMAT_MACROS #include @@ -1188,6 +1189,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; @@ -2027,34 +2031,20 @@ void D_SRB2Main(void) INT16 newskill = -1; const char *sskill = M_GetNextParm(); - const char *masterstr = "Master"; - const char *nightmarestr = "NIGHTMARE"; - - if (!strcasecmp(masterstr, sskill)) + for (j = 0; gpdifficulty_cons_t[j].strvalue; j++) { - newskill = KARTGP_MASTER; - } - else if (!strcasecmp(nightmarestr, sskill)) - { - newskill = KARTGP_NIGHTMARE; - } - else - { - for (j = 0; kartspeed_cons_t[j].strvalue; j++) + if (!strcasecmp(gpdifficulty_cons_t[j].strvalue, sskill)) { - if (!strcasecmp(kartspeed_cons_t[j].strvalue, sskill)) - { - newskill = (INT16)kartspeed_cons_t[j].value; - break; - } + newskill = (INT16)gpdifficulty_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_NIGHTMARE) - 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_NIGHTMARE) + newskill = (INT16)j; } if (grandprixinfo.gp == true) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 00a5c4aa0..ebdc006b9 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -67,6 +67,7 @@ #include "deh_tables.h" #include "m_perfstats.h" #include "g_party.h" +#include "k_specialstage.h" #define CV_RESTRICT CV_NETVAR @@ -632,6 +633,7 @@ consvar_t cv_kartdebugcheckpoint = CVAR_INIT ("kartdebugcheckpoint", "Off", 0, C consvar_t cv_kartdebugnodes = CVAR_INIT ("kartdebugnodes", "Off", 0, CV_OnOff, NULL); consvar_t cv_kartdebugcolorize = CVAR_INIT ("kartdebugcolorize", "Off", 0, CV_OnOff, NULL); consvar_t cv_kartdebugdirector = CVAR_INIT ("kartdebugdirector", "Off", 0, 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); @@ -657,9 +659,9 @@ consvar_t cv_overtime = CVAR_INIT ("overtime", "Yes", CV_NETVAR|CV_CHEAT, CV_Yes 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"}, {99999, "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[] = {{0, "MIN"}, {MAX_LAPS, "MAX"}, {-1, "Map default"}, {0, NULL}}; consvar_t cv_numlaps = CVAR_INIT ("numlaps", "Map default", CV_NETVAR|CV_CALL|CV_CHEAT, numlaps_cons_t, NumLaps_OnChange); @@ -2889,6 +2891,10 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean r { pencoremode = bossinfo.encore; } + else if (specialStage.active == true) + { + pencoremode = specialStage.encore; + } else if (grandprixinfo.gp == true) { pencoremode = grandprixinfo.encore; @@ -3280,6 +3286,7 @@ static void Command_Map_f(void) if (newgametype == GT_BATTLE) { grandprixinfo.gp = false; + specialStage.active = false; K_ResetBossInfo(); if (mapheaderinfo[newmapnum-1] && @@ -3289,76 +3296,76 @@ 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 *nightmarestr = "NIGHTMARE"; - 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 if (!strcasecmp(nightmarestr, skillname)) - { - newskill = KARTGP_NIGHTMARE; - } - 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_NIGHTMARE) newskill = (INT16)num; } + + if (newskill != -1) + { + if (newskill == KARTGP_MASTER) + { + grandprixinfo.gamespeed = KARTSPEED_HARD; + grandprixinfo.masterbots = true; + } + else if (newskill == KARTGP_NIGHTMARE) + { + grandprixinfo.gamespeed = KARTSPEED_EXPERT; + 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 if (newskill == KARTGP_NIGHTMARE) - { - grandprixinfo.gamespeed = KARTSPEED_EXPERT; - 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; } } @@ -4504,7 +4511,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; @@ -4640,7 +4647,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; @@ -5428,7 +5435,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) @@ -5443,7 +5455,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")); } @@ -5478,26 +5490,40 @@ UINT32 secretextratime = 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. @@ -5523,7 +5549,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 { @@ -5534,27 +5560,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. @@ -7313,6 +7318,11 @@ static void Command_ShowTime_f(void) // SRB2Kart: On change messages static void NumLaps_OnChange(void) { + if (K_CanChangeRules(false) == false) + { + return; + } + if (gamestate == GS_LEVEL) { numlaps = K_RaceLapCount(gamemap - 1); @@ -7341,12 +7351,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; @@ -7359,19 +7369,12 @@ static void KartFrantic_OnChange(void) static void KartSpeed_OnChange(void) { - if (!M_SecretUnlocked(SECRET_HARDSPEED) && (cv_kartspeed.value == KARTSPEED_HARD || cv_kartspeed.value == KARTSPEED_EXPERT)) - { - CONS_Printf(M_GetText("You haven't earned this yet.\n")); - CV_StealthSet(&cv_kartspeed, cv_kartspeed.defaultvalue); - return; - } - - 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("Race speed has been changed to \"%s\".\n"), cv_kartspeed.string); gamespeed = (UINT8)cv_kartspeed.value; @@ -7391,7 +7394,7 @@ static void KartBattleSpeed_OnChange(void) return; } - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7409,7 +7412,7 @@ static void KartBattleSpeed_OnChange(void) static void KartEncore_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7419,7 +7422,7 @@ static void KartEncore_OnChange(void) static void KartComeback_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7442,7 +7445,7 @@ static void KartEliminateLast_OnChange(void) static void KartRings_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7459,7 +7462,7 @@ static void KartRings_OnChange(void) static void KartPurpleDrift_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7492,7 +7495,7 @@ static void KartPurpleDrift_OnChange(void) static void KartStacking_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7525,7 +7528,7 @@ static void KartStacking_OnChange(void) static void KartChaining_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7558,7 +7561,7 @@ static void KartChaining_OnChange(void) static void KartSlipdash_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7591,7 +7594,7 @@ static void KartSlipdash_OnChange(void) static void KartSlopeBoost_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7624,7 +7627,7 @@ static void KartSlopeBoost_OnChange(void) static void KartDrafting_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7657,7 +7660,7 @@ static void KartDrafting_OnChange(void) static void KartAirDrop_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7690,7 +7693,7 @@ static void KartAirDrop_OnChange(void) static void KartItemLitter_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7723,7 +7726,7 @@ static void KartItemLitter_OnChange(void) static void KartAntiBump_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7747,7 +7750,7 @@ static void KartAntiBump_OnChange(void) static void KartItemBreaker_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7769,7 +7772,7 @@ static void KartItemBreaker_OnChange(void) static void KartInvinType_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7787,7 +7790,7 @@ static void KartInvinType_OnChange(void) static void KartBumpSpark_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } diff --git a/src/d_netcmd.h b/src/d_netcmd.h index f4f47c94c..3854d8886 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -224,6 +224,7 @@ extern consvar_t cv_kartdebugitem, cv_kartdebugamount, cv_kartdebugdistribution, extern consvar_t cv_kartdebugshrink; extern consvar_t cv_kartdebugcheckpoint, cv_kartdebugnodes, cv_kartdebugcolorize, cv_kartdebugdirector; extern consvar_t cv_kartdebugwaypoints, cv_kartdebuglap, cv_kartdebugbot, cv_kartdebugcluster, cv_kartdebugrings; +extern consvar_t cv_gptest; extern consvar_t cv_itemfinder; diff --git a/src/d_player.h b/src/d_player.h index f0640dda3..49fa266a7 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -258,6 +258,9 @@ 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: + // Big text + khud_finish, // Set when completing a round + // Camera khud_boostcam, // Camera push forward on boost khud_destboostcam, // Ditto diff --git a/src/deh_soc.c b/src/deh_soc.c index f51d52eb7..26bdfb77f 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -3632,7 +3632,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 { @@ -3649,11 +3657,13 @@ 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; } diff --git a/src/doomdef.h b/src/doomdef.h index ec0ef9102..c32408c1d 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -533,6 +533,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 a345a4434..6f6fe218d 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -530,6 +530,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) // Compat TOL_COMPAT1 = 0x0008, ///< For compat. Handles all the unused kart v1 types. diff --git a/src/f_finale.c b/src/f_finale.c index b6cf41e64..937796302 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -1876,10 +1876,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 174dc8aeb..d5b790499 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -59,6 +59,7 @@ #include "k_color.h" #include "k_grandprix.h" #include "k_boss.h" +#include "k_specialstage.h" #include "k_bot.h" #include "k_odds.h" #include "doomstat.h" @@ -2709,6 +2710,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) INT32 nextcheck; // Distace to Next Legacy Checkpoint INT32 exiting; + INT32 khudfinish; INT32 khudcardanimation; INT16 totalring; UINT8 laps; @@ -2825,6 +2827,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) latestlap = 0; roundscore = 0; exiting = 0; + khudfinish = 0; khudcardanimation = 0; starpostx =0; starposty = 0; @@ -2835,7 +2838,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) starposttime = 0; prevcheck = 0; nextcheck = 0; - xtralife = 0; for (i = 0; i < LAP__MAX; i++) { @@ -2883,7 +2885,16 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) 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; + } starpostx = players[player].starpostx; starposty = players[player].starposty; @@ -2961,6 +2972,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->nextcheck = nextcheck; p->exiting = exiting; + p->karthud[khud_finish] = khudfinish; p->karthud[khud_cardanimation] = khudcardanimation; p->laps = laps; @@ -3798,6 +3810,7 @@ tolinfo_t TYPEOFLEVEL[NUMTOLNAMES] = { {"RACE",TOL_RACE}, {"BATTLE",TOL_BATTLE}, {"BOSS",TOL_BOSS}, + {"SPECIAL",TOL_SPECIAL}, {"TV",TOL_TV}, // Compat stuff @@ -4324,7 +4337,7 @@ static void G_UpdateVisited(void) return; // Update visitation flags - maprecord_t *record = G_AllocateMapRecord(G_BuildMapName(gamemap)); + maprecord_t *record = G_AllocateMapRecord(G_BuildMapName(prevmap+1)); record->visited |= MV_BEATEN; if (encoremode == true) record->visited |= MV_ENCORE; @@ -4372,6 +4385,7 @@ static void G_HandleSaveLevel(void) static INT16 G_GetNextMap(boolean advancemap) { + boolean spec = G_IsSpecialStage(prevmap+1); INT32 i; INT16 newmap, curmap = gamestate == GS_LEVEL ? gamemap-1 : prevmap; @@ -4383,20 +4397,104 @@ static INT16 G_GetNextMap(boolean advancemap) } else if (grandprixinfo.gp == true) { - if (grandprixinfo.roundnum == 0 || grandprixinfo.cup == NULL) // Single session + // G: oh dear, this whole GP block has loads of side effects... + // for now, just repeat the same map for "map +" + // you're not supposed to use that in GP anyways + if (!advancemap || // ...right? + grandprixinfo.roundnum == 0 || grandprixinfo.cup == NULL) // Single session { newmap = curmap; // Same map } 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; + newmap = 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; + newmap = cupLevelNum; + } + } + } + + if (grandprixinfo.eventmode != GPEVENT_NONE) + { + // nextmap is set above + const INT32 newtol = mapheaderinfo[newmap]->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 { newmap = 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]) { @@ -4478,14 +4576,7 @@ static INT16 G_GetNextMap(boolean advancemap) // Didn't get a nextmap before reaching the end? if (gettingresult != 2) { - if (marathonmode) - { - newmap = NEXTMAP_CEREMONY; // ceremonymap - } - else - { - newmap = NEXTMAP_TITLE; - } + newmap = NEXTMAP_CEREMONY; // ceremonymap } } else @@ -4513,19 +4604,39 @@ static INT16 G_GetNextMap(boolean advancemap) newmap = cm; } - if (advancemap && !marathonmode) + if (advancemap && K_CanChangeRules(true)) { - if (cv_advancemap.value == 0) // Stay on same map. + switch (cv_advancemap.value) { - newmap = curmap; - } - else if (cv_advancemap.value == 2) // Go to random map. - { - newmap = G_RandMap(G_TOLFlag(gametype), curmap, 0, 0, NULL); - } - else if (nextmap >= NEXTMAP_SPECIAL) // Loop back around - { - newmap = G_GetFirstMapOfGametype(gametype); + case 0: // Stay on same map. + newmap = curmap; + break; + case 3: // Voting screen. + { + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + if (players[i].spectator) + continue; + break; + } + if (i != MAXPLAYERS) + { + newmap = NEXTMAP_VOTING; + break; + } + } + /* FALLTHRU */ + case 2: // Go to random map. + newmap = G_RandMap(G_TOLFlag(gametype), curmap, 0, 0, NULL); + break; + default: + if (newmap >= NEXTMAP_SPECIAL) // Loop back around + { + newmap = G_GetFirstMapOfGametype(gametype); + } + break; } } } @@ -4534,6 +4645,9 @@ static INT16 G_GetNextMap(boolean advancemap) if (newmap == NEXTMAP_INVALID || (newmap < NEXTMAP_SPECIAL && (newmap >= nummapheaders || !mapheaderinfo[newmap] || mapheaderinfo[newmap]->lumpnum == LUMPERROR))) I_Error("G_GetNextMap: Internal map ID %d not found (nummapheaders = %d)\n", newmap, nummapheaders); + if (!spec) + lastmap = newmap; + return newmap; } @@ -4543,8 +4657,6 @@ static INT16 G_GetNextMap(boolean advancemap) static void G_DoCompleted(void) { INT32 i, j = 0; - boolean spec = G_IsSpecialStage(gamemap); - SINT8 powertype = K_UsingPowerLevels(); if (modeattacking && pausedelay) pausedelay = 0; @@ -4587,8 +4699,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); S_ShowMusicCredit(-30*FRACUNIT, 5*TICRATE, V_SNAPTOTOP); @@ -4603,14 +4716,8 @@ static void G_DoCompleted(void) if (!demo.playback) { - nextmap = G_GetNextMap(true); - - // 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. @@ -4621,7 +4728,6 @@ static void G_DoCompleted(void) || (intertype == int_none)) { G_UpdateVisited(); - G_HandleSaveLevel(); G_AfterIntermission(); } else @@ -4629,7 +4735,6 @@ static void G_DoCompleted(void) G_SetGamestate(GS_INTERMISSION); Y_StartIntermission(); G_UpdateVisited(); - G_HandleSaveLevel(); } } @@ -4663,14 +4768,17 @@ void G_AfterIntermission(void) return; } + if (gamestate != GS_VOTING) + { + nextmap = G_GetNextMap(true); + G_HandleSaveLevel(); + } + if (grandprixinfo.gp == true && mapheaderinfo[prevmap]->cutscenenum && !modeattacking && skipstats <= 1 && (gamecomplete || !(marathonmode & MA_NOCUTSCENES))) // Start a custom cutscene. F_StartCustomCutscene(mapheaderinfo[prevmap]->cutscenenum-1, false, false); else { - if (nextmap < NEXTMAP_SPECIAL) - G_NextLevel(); - else - G_EndGame(); + G_NextLevel(); } } @@ -4682,25 +4790,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; } @@ -4727,7 +4825,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; } @@ -4805,8 +4907,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 && grandprixinfo.gp == true) @@ -5492,6 +5598,7 @@ cleanup: void G_DeferedInitNew(boolean pencoremode, INT32 map, INT32 pickedchar, UINT8 ssplayers, boolean FLS) { UINT16 color = SKINCOLOR_NONE; + INT32 dogametype; paused = false; @@ -5502,8 +5609,17 @@ void G_DeferedInitNew(boolean pencoremode, INT32 map, INT32 pickedchar, UINT8 ss G_ResetRandMapBuffer(); + if ((modeattacking == ATTACKING_ITEMBREAK) || (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 92cd3fc78..da46b853c 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -48,7 +48,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 b569eac3c..96a5ca6a7 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -2510,7 +2510,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 62bf544b5..71c76b0d2 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -261,7 +261,7 @@ void K_CheckBumpers(void) winnerscoreadd -= players[i].roundscore; } - if (K_CanChangeRules() == false) + if (K_CanChangeRules(true) == false) { if (nobumpers) { @@ -274,7 +274,8 @@ void K_CheckBumpers(void) if (!itembreaker && cv_kartitembreaker.value) { // 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 { @@ -486,22 +487,13 @@ void K_SpawnPlayerBattleBumpers(player_t *p) } } -void K_BattleInit(UINT8 numPlayers) +void K_BattleInit(boolean singleplayercontext) { - if ((gametyperules & GTR_ITEMBREAKER) && !itembreaker && !bossinfo.boss) + if ((gametyperules & GTR_ITEMBREAKER) && singleplayercontext && !itembreaker && !bossinfo.boss) { - if (modeattacking != ATTACKING_ITEMBREAK) - { - if (!cv_kartitembreaker.value) - goto afteritembreaker; - - if (numPlayers > 1) - goto afteritembreaker; - } - - itembreaker = true; + if (!(K_CanChangeRules(true) && !cv_kartitembreaker.value)) + itembreaker = true; } -afteritembreaker: if (gametyperules & GTR_BUMPERS) { diff --git a/src/k_battle.h b/src/k_battle.h index 0ef5b52ef..1e6aa9d75 100644 --- a/src/k_battle.h +++ b/src/k_battle.h @@ -21,7 +21,7 @@ void K_CheckBumpers(void); UINT8 K_NumEmeralds(player_t *player); void K_RunPaperItemSpawners(void); void K_SpawnPlayerBattleBumpers(player_t *p); -void K_BattleInit(UINT8 numPlayers); +void K_BattleInit(boolean singleplayercontext); void K_RespawnBattleBoxes(void); #ifdef __cplusplus diff --git a/src/k_bot.cpp b/src/k_bot.cpp index bc86ff5e5..cc1755de8 100644 --- a/src/k_bot.cpp +++ b/src/k_bot.cpp @@ -259,7 +259,7 @@ void K_UpdateMatchRaceBots(void) { difficulty = 0; } - else if (K_CanChangeRules() == false) + else if (K_CanChangeRules(true) == false) { difficulty = 0; } diff --git a/src/k_grandprix.c b/src/k_grandprix.c index a11f4969d..763de2c55 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" @@ -737,18 +738,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! @@ -761,11 +756,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 2644142b5..4e59fd721 100644 --- a/src/k_grandprix.h +++ b/src/k_grandprix.h @@ -171,19 +171,19 @@ 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); #ifdef __cplusplus } // extern "C" diff --git a/src/k_hud.c b/src/k_hud.c index e738f8f5f..ac1e15ac9 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1628,37 +1628,38 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UI // TIME_Y = 6; // 6 tic_t worktime; - boolean dontdraw = false; - boolean overtime = false; - UINT8 *textcolor = 0; + fixed_t jitter = 0; + boolean overtime = false, fastjitter = false, countdown = false; + UINT8 *textcolor = NULL; INT32 splitflags = 0; if (!mode) { splitflags = V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_SPLITSCREEN; -#ifndef TESTOVERTIMEINFREEPLAY - if (itembreaker) // capsules override any time limit settings - ; - else -#endif - if (bossinfo.boss == true) - ; - else if (timelimitintics > 0 && (gametyperules & GTR_TIMELIMIT)) // TODO - { - if (drawtime >= (timelimitintics - 5*TICRATE) && ((drawtime*4)/TICRATE) % 2 == 0) - { - dontdraw = true; - } - + if (timelimitintics > 0) + { if (drawtime >= timelimitintics) { overtime = true; - // drawtime = 0; + jitter = 1; } else { drawtime = timelimitintics - drawtime; + countdown = true; + if (secretextratime) + ; + else if (extratimeintics) + { + jitter = 2; + fastjitter = true; + } + else if (drawtime <= 5*TICRATE) + { + jitter = ((drawtime <= 3*TICRATE) && (((drawtime-1) % TICRATE) >= TICRATE-2)) + ? 3 : 1; + } } } } @@ -1675,10 +1676,26 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UI worktime = drawtime/(60*TICRATE); + if (drawtime != UINT32_MAX && worktime >= 100) + { + jitter = 1; + fastjitter = true; + worktime = 99; + drawtime = (100*(60*TICRATE))-1; + } + + if (fastjitter) + { + jitter *= R_GetTimeFrac(RTF_LEVEL) * (leveltime & 1 ? 1 : -1); + } + else + { + if (drawtime & 2) jitter = -jitter; + jitter *= !!(drawtime & 1) != countdown ? FRACUNIT - R_GetTimeFrac(RTF_LEVEL) : R_GetTimeFrac(RTF_LEVEL); + } + if (mode && drawtime == UINT32_MAX) V_DrawKartString(TX, TY+3, splitflags, va("--'--\"--")); - else if (dontdraw) // overtime flash - ; else if (overtime) { if (((drawtime*2)/TICRATE) % 2 == 0) @@ -1690,66 +1707,47 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UI textcolor = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_ORANGE, GTC_CACHE); } - V_DrawStringScaledEx( - (TIME_X + 33) << FRACBITS, - (TIME_Y + 3) << FRACBITS, - FRACUNIT, - FRACUNIT, - FRACUNIT, - FRACUNIT, - splitflags, - textcolor, - KART_FONT, - va("OVERTIME!") - ); - } - else if (worktime < 100) // 99:99:99 only - { - // zero minute - if (worktime < 10) + const char *overtimestr = "OVERTIME"; + for (UINT8 i = 0; i < strlen(overtimestr); i++) { - V_DrawKartString(TX, TY+3, splitflags, va("0")); - // minutes time 0 __ __ - V_DrawKartString(TX+12, TY+3, splitflags, va("%d", worktime)); + V_DrawStringScaledEx( + (TIME_X + 33 + 12*i + (i == 6 ? -4 : i == 7 ? -1 : 0)) << FRACBITS, + ((TIME_Y + 3) << FRACBITS) + (i & 1 ? jitter : -jitter), + FRACUNIT, + FRACUNIT, + FRACUNIT, + FRACUNIT, + splitflags, + textcolor, + KART_FONT, + va("%c", overtimestr[i]) + ); } - // minutes time 0 __ __ - else - V_DrawKartString(TX, TY+3, splitflags, va("%d", worktime)); + } + else + { + // minutes time 00 __ __ + V_DrawKartStringAtFixed(TX<health <= 0 && players[i].pflags & PF_NOCONTEST) + if (mobj->health <= 0 && (players[i].pflags & PF_NOCONTEST)) { workingPic = kp_nocontestminimap; @@ -4308,10 +4306,13 @@ static void K_drawKartMinimap(void) iconoffsets.y = heighthalf - workingPic->topoffset - adjusty; colormap = R_GetTranslationColormap(TC_DEFAULT, mobj->color, GTC_CACHE); + + if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer)) + mobj = mobj->tracer; } else { - skin = ((skin_t*)players[i].mo->skin)-skins; + skin = ((skin_t*)mobj->skin)-skins; workingPic = faceprefix[skin][FACE_MINIMAP]; @@ -4343,7 +4344,7 @@ static void K_drawKartMinimap(void) } #endif - colorizeplayer = players[i].mo->colorized; + colorizeplayer = mobj->colorized; if ((players[i].invincibilitytimer) && ((K_InvincibilityGradient(players[i].invincibilitytimer) > (FRACUNIT/2)) || (K_GetKartInvinType() == KARTINVIN_LEGACY))) { @@ -4352,10 +4353,10 @@ static void K_drawKartMinimap(void) } else { - usecolor = players[i].mo->color; + usecolor = mobj->color; } - if (players[i].mo->color) + if (usecolor) { if (colorizeplayer) colormap = R_GetTranslationColormap(TC_RAINBOW, usecolor, GTC_CACHE); @@ -4524,7 +4525,7 @@ static void K_drawKartMinimap(void) mobj = players[localplayers[i]].mo; - if (players[localplayers[i]].mo->health <= 0 && players[localplayers[i]].pflags & PF_NOCONTEST) + if (mobj->health <= 0 && (players[localplayers[i]].pflags & PF_NOCONTEST)) { workingPic = kp_nocontestminimap; @@ -4544,10 +4545,13 @@ static void K_drawKartMinimap(void) colormap = R_GetTranslationColormap(TC_DEFAULT, mobj->color, GTC_CACHE); nocontest = true; + + if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer)) + mobj = mobj->tracer; } else { - skin = ((skin_t*)players[localplayers[i]].mo->skin)-skins; + skin = ((skin_t*)mobj->skin)-skins; workingPic = faceprefix[skin][FACE_MINIMAP]; @@ -4578,7 +4582,7 @@ static void K_drawKartMinimap(void) } #endif - colorizeplayer = players[localplayers[i]].mo->colorized; + colorizeplayer = mobj->colorized; if ((players[localplayers[i]].invincibilitytimer) && ((K_InvincibilityGradient(players[localplayers[i]].invincibilitytimer) > @@ -4589,10 +4593,10 @@ static void K_drawKartMinimap(void) } else { - usecolor = players[localplayers[i]].mo->color; + usecolor = mobj->color; } - if (players[localplayers[i]].mo->color) + if (usecolor) { if (colorizeplayer) colormap = R_GetTranslationColormap(TC_RAINBOW, usecolor, GTC_CACHE); @@ -4701,6 +4705,56 @@ static void K_drawKartMinimap(void) } } +static void K_drawKartFinish(void) +{ + INT32 timer, minsplitstationary, pnum = 0, splitflags = V_SPLITSCREEN; + patch_t **kptodraw; + + { + timer = stplyr->karthud[khud_finish]; + kptodraw = kp_racefinish; + minsplitstationary = 2; + } + + 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, pwidth; + + x = ((vid.width<width)<height)<<(FRACBITS-1)), + FRACUNIT, + splitflags, kptodraw[pnum], NULL); + } +} + static void K_drawKartStartCountdown(void) { INT32 pnum = 0; @@ -4745,51 +4799,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 - (kp_racefinish[pnum]->width/2), STCD_Y - (kp_racefinish[pnum]->height/2), splitflags, kp_racefinish[pnum]); - return; - } - - //else -- 1/2p, scrolling FINISH - { - INT32 x, xval, ox, interpx, pwidth; - - if (r_splitscreen) // wide splitscreen - pnum += 4; - - x = ((vid.width<width)<karthud[khud_cardanimation]) * pwidth) / TICRATE; - ox = ((TICRATE - (stplyr->karthud[khud_cardanimation] - 1)) * pwidth) / TICRATE; - - - interpx = R_InterpolateFixed(ox, x); - - if (r_splitscreen && stplyrnum == 1) - interpx = -interpx; - - V_DrawFixedPatch(interpx + (STCD_X<height<<(FRACBITS-1)), - FRACUNIT, - splitflags, kp_racefinish[pnum], NULL); - } -} - static void K_drawBattleFullscreen(void) { INT32 cardanim = stplyr->karthud[khud_cardanimation] << FRACBITS; @@ -4850,7 +4859,7 @@ static void K_drawBattleFullscreen(void) { if (stplyrnum == 0) V_DrawFadeScreen(0xFF00, 16); - if (stplyr->exiting < 6*TICRATE && !stplyr->spectator) + if (stplyr->exiting <= 6*TICRATE && !stplyr->spectator) { patch_t *p = kp_battlecool; @@ -4861,8 +4870,8 @@ static void K_drawBattleFullscreen(void) V_DrawFixedPatch(x<bumper <= 0 && stplyr->karmadelay && comeback && !stplyr->spectator && drawcomebacktimer) { diff --git a/src/k_kart.c b/src/k_kart.c index d6ac28a47..d130b6671 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -65,6 +65,7 @@ #include "k_grandprix.h" #include "k_cluster.hpp" #include "k_odds.h" +#include "k_specialstage.h" #include "h_timers.h" #include "blan/b_soc.h" @@ -100,26 +101,34 @@ vector3_t clusterpoint, clusterdtf; 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)); starttime = introtime = 0; - 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; } - - numPlayers++; } introtime = (108) + 5; // 108 for rotation, + 5 for white fade @@ -131,15 +140,14 @@ void K_TimerInit(void) } } - // NOW you can try to setup Item Breaker, if there's not enough players for a match - K_BattleInit(numPlayers); + timelimitintics = extratimeintics = secretextratime = 0; // G: restore K_TimerReset? + K_BattleInit(domodeattack); - timelimitintics = extratimeintics = secretextratime = 0; - if ((gametyperules & GTR_TIMELIMIT) && !bossinfo.boss) + if ((gametyperules & GTR_TIMELIMIT) && !bossinfo.boss && !modeattacking) { - if (!K_CanChangeRules()) + if (!K_CanChangeRules(true)) { - if (grandprixinfo.gp) + if (itembreaker) { timelimitintics = (20*TICRATE); } @@ -414,6 +422,7 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_saltyhop); CV_RegisterVar(&cv_naturalcamera); + CV_RegisterVar(&cv_gptest); } //} @@ -6310,9 +6319,8 @@ void K_KartPlayerHUDUpdate(player_t *player) if (player->karthud[khud_tauntvoices]) player->karthud[khud_tauntvoices]--; - if (gametype == GT_RACE) + if (gametyperules & GTR_RINGS) { - if ((K_RingsActive() == true)) { if (player->pflags & PF_RINGLOCK) @@ -6322,16 +6330,20 @@ void K_KartPlayerHUDUpdate(player_t *player) } } + 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) 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 { @@ -6346,11 +6358,6 @@ void K_KartPlayerHUDUpdate(player_t *player) if (player->karthud[khud_cardanimation] < 0) player->karthud[khud_cardanimation] = 0; } - else if ((gametyperules & GTR_CIRCUIT) && player->exiting) - { - if (player->karthud[khud_cardanimation] < 2*TICRATE) - player->karthud[khud_cardanimation]++; - } else player->karthud[khud_cardanimation] = 0; @@ -6599,7 +6606,15 @@ UINT8 K_RaceLapCount(INT16 mapNum) return 0; } - if (cv_numlaps.value == -1 || modeattacking != ATTACKING_NONE) + if ((grandprixinfo.gp == true) + && (grandprixinfo.eventmode == GPEVENT_NONE) + && cv_gptest.value) + { + // For testing + return 1; + } + + if (cv_numlaps.value == -1 || K_CanChangeRules(true) == false) { // Use map default return mapheaderinfo[mapNum]->numlaps; @@ -7330,8 +7345,6 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) K_UpdateTripwire(player); - K_KartPlayerHUDUpdate(player); - if (P_IsObjectOnGround(player->mo)) player->waterskip = 0; @@ -11396,7 +11409,7 @@ boolean K_NotFreePlay(void) UINT8 i; UINT8 nump = 0; - if (K_CanChangeRules() == false) + if (K_CanChangeRules(true) == false) { // Rounds with direction are never FREE PLAY. return true; diff --git a/src/k_odds.c b/src/k_odds.c index 0c72f18af..82440fe7e 100644 --- a/src/k_odds.c +++ b/src/k_odds.c @@ -152,6 +152,38 @@ 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 }, // Ballhog + { 0, 0, 0, 1 }, // Self-Propelled Bomb + { 0, 0, 0, 0 }, // Grow + { 0, 0, 0, 0 }, // Shrink + { 0, 0, 0, 0 }, // Thunder Shield + { 0, 0, 0, 0 }, // Hyudoro + { 0, 0, 0, 0 }, // Pogo Spring + { 0, 0, 0, 0 }, // Kitchen Sink + { 0, 0, 0, 0 }, // Super Ring + { 0, 0, 0, 0 }, // Land Mine + { 0, 0, 0, 0 }, // Bubble Shield + { 0, 0, 0, 0 }, // Flame Shield + { 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 +}; + // Cooldown time table; contains both base (index 0) and current (index 1) // times. Base times are in seconds, current times are in tics. tic_t ItemBGone[NUMKARTRESULTS][2] = 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..65b343b3e --- /dev/null +++ b/src/k_specialstage.h @@ -0,0 +1,62 @@ +// 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" + +#ifdef __cplusplus +extern "C" { +#endif + +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); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/src/lua_script.c b/src/lua_script.c index 393699177..2292fa952 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -221,7 +221,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_menu.c b/src/m_menu.c index 838e16c9d..1413bd751 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -464,10 +464,9 @@ static CV_PossibleValue_t dummybumpspark_cons_t[] = {{BUMPSPARK_NONE, "Off"}, {0, NULL}}; consvar_t cv_dummyattackingbumpspark = CVAR_INIT ("dummyattackingbumpspark", "Off", CV_HIDEN|CV_CALL|CV_NOINIT, dummybumpspark_cons_t, Nextmap_OnChange); -static CV_PossibleValue_t dummygpdifficulty_cons_t[] = {{KARTSPEED_EASY, "Easy"}, {KARTSPEED_NORMAL, "Normal"}, {KARTSPEED_HARD, "Hard"}, {KARTSPEED_EXPERT, "Expert"}, {KARTGP_MASTER, "Master"}, {KARTGP_NIGHTMARE, "Nightmare"}, {0, NULL}}; static CV_PossibleValue_t dummygpcup_cons_t[50] = {{1, "TEMP"}}; // A REALLY BIG NUMBER, SINCE THIS IS TEMP UNTIL NEW MENUS -consvar_t cv_dummygpdifficulty = CVAR_INIT ("dummygpdifficulty", "Normal", CV_HIDEN, dummygpdifficulty_cons_t, NULL); +consvar_t cv_dummygpdifficulty = CVAR_INIT ("dummygpdifficulty", "Normal", CV_HIDEN, gpdifficulty_cons_t, NULL); consvar_t cv_dummygpencore = CVAR_INIT ("dummygpencore", "Off", CV_HIDEN, CV_OnOff, NULL); consvar_t cv_dummygpcup = CVAR_INIT ("dummygpcup", "TEMP", CV_HIDEN, dummygpcup_cons_t, NULL); @@ -6551,8 +6550,7 @@ INT32 MR_StartServer(INT32 choice) if (menustack[0] == MN_MP_SPLITSCREEN) // offline server { paused = false; - SV_StartSinglePlayerServer(); - multiplayer = true; // yeah, SV_StartSinglePlayerServer clobbers this... + SV_StartSinglePlayerServer(cv_newgametype.value, false); D_MapChange(cv_nextmap.value, cv_newgametype.value, (cv_kartencore.value == 1), 1, 1, false, false); } else diff --git a/src/p_enemy.c b/src/p_enemy.c index 796aa963c..af239e4f8 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -3335,9 +3335,12 @@ void A_AttractChase(void *thing) // Set attraction flag actor->cusval = 1; - if ((actor->tracer->player->itemtype == KITEM_THUNDERSHIELD + if ( + actor->tracer->player && actor->tracer->health + && ((gametyperules & GTR_BUMPERS) + || (actor->tracer->player->itemtype == KITEM_THUNDERSHIELD && RINGTOTAL(actor->tracer->player) < actor->tracer->player->ringmax - && !(actor->tracer->player->pflags & PF_RINGLOCK)) + && !(actor->tracer->player->pflags & PF_RINGLOCK))) //&& P_CheckSight(actor, actor->tracer) ) { diff --git a/src/p_inter.c b/src/p_inter.c index 08ec0f7a0..f129a1429 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -991,14 +991,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 */ @@ -1006,25 +1001,73 @@ void P_CheckTimeLimit(void) { INT32 i, k; - if (!cv_timelimit.value) + if (exitcountdown) return; - if (!(multiplayer || netgame)) + if (!timelimitintics) return; - if (!(gametyperules & GTR_TIMELIMIT)) + if (leveltime < starttime) + { + if (secretextratime) + secretextratime--; return; + } - if (itembreaker) - return; + INT32 timeleft = timelimitintics + starttime - leveltime; + if (timeleft > 0) + { + 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 > 60*TICRATE && !itembreaker) + { + if (timeleft == 60*TICRATE) + S_StartSound(NULL, sfx_cdfm67); // guys it's a bell it's just like splatoon guys + if (timeleft == 59*TICRATE) + S_StartSound(NULL, sfx_bhurry); // hurry up :)))) + } - if (leveltime < (timelimitintics + starttime)) + // last five second countdown + if (timeleft % TICRATE == 0) + { + if (timeleft <= 5*TICRATE) + S_StartSound(NULL, sfx_kc35); // 5, 4 + if (timeleft <= 3*TICRATE) + S_StartSound(NULL, sfx_kc3d); // 3, 2, 1 + } + } return; + } if (gameaction == ga_completed) return; - if (cv_overtime.value) + if (!itembreaker && !grandprixinfo.gp && cv_overtime.value) { INT32 playerarray[MAXPLAYERS]; INT32 tempplayer = 0; @@ -1043,7 +1086,7 @@ void P_CheckTimeLimit(void) #ifdef TESTOVERTIMEINFREEPLAY if (timelimitintics > 0 && (gametyperules & GTR_TIMELIMIT) && startedInFreePlay) { - return; + goto overtimesound; } #endif @@ -1085,23 +1128,33 @@ void P_CheckTimeLimit(void) //End the round if the top players aren't tied. if (players[playerarray[0]].roundscore == players[playerarray[1]].roundscore) - return; + goto overtimesound; } else { //In team match and CTF, determining a tie is much simpler. =P if (redscore == bluescore) - return; + goto overtimesound; } } } P_DoAllPlayersExit(0, false); + return; + +overtimesound: + if (timeleft == 0) + { + S_StartSound(NULL, sfx_s3k50); // overtime warning + S_StartSound(NULL, sfx_s3k9d); + } + if (timeleft == -2*TICRATE) + S_StartSoundAtVolume(NULL, sfx_s3k50, 128); + if (timeleft == -4*TICRATE) + S_StartSoundAtVolume(NULL, sfx_s3k50, 96); } /** 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 */ @@ -1112,7 +1165,7 @@ void P_CheckPointLimit(void) if (exitcountdown) return; - if (!K_CanChangeRules()) + if (!K_CanChangeRules(true)) return; if (!cv_pointlimit.value) @@ -1158,7 +1211,7 @@ boolean P_CheckRacers(void) { const boolean griefed = (spectateGriefed > 0); - boolean eliminateLast = (!K_CanChangeRules() && !demo.playback) || (cv_karteliminatelast.value != 0); // temp hack until this is ported properly + boolean eliminateLast = (!K_CanChangeRules(true) || (cv_karteliminatelast.value != 0)); boolean allHumansDone = true; //boolean allBotsDone = true; @@ -1277,7 +1330,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; @@ -1412,15 +1465,39 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget if (itembreaker) { + // G: uncommented this for funsies + mobj_t * ring; + angle_t dir = 0; + if (inflictor) + dir = R_PointToAngle2(inflictor->x, inflictor->y, target->x, target->y); + else + dir = R_PointToAngle2(source->x, source->y, target->x, target->y); + for (UINT8 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++; + } + target->flags2 |= MF2_BOSSFLEE; target->flags2 |= MF2_DONTRESPAWN; K_SpawnBattlePoints(source->player, NULL, 1); + // All targets busted! if (++numtargets >= nummapboxes) { - P_DoAllPlayersExit(0, true); + P_DoAllPlayersExit(0, (grandprixinfo.gp == true)); + } + else if (timelimitintics) + { + S_StartSound(NULL, sfx_s221); + extratimeintics += 10*TICRATE; + secretextratime = TICRATE/2; } - } if (cv_itemrespawn.value && modeattacking == ATTACKING_NONE && !itembreaker) @@ -1591,6 +1668,9 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget kart->old_y = target->old_y; kart->old_z = target->old_z; + if (target->player->pflags & PF_NOCONTEST) + P_SetTarget(&target->tracer, kart); + if (damagetype != DMG_TIMEOVER) { kart->angle = flingangle + ANGLE_180; diff --git a/src/p_setup.c b/src/p_setup.c index b9a98b642..637337f24 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -108,6 +108,7 @@ #include "doomstat.h" // MAXMUSNAMES #include "k_mapuser.h" #include "p_deepcopy.h" +#include "k_specialstage.h" #include "blan/b_soc.h" diff --git a/src/p_tick.c b/src/p_tick.c index 6dd793d9e..d08fcce92 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -42,6 +42,7 @@ #include "acs/interface.h" #include "k_bot.h" // K_BotTicker #include "k_odds.h" // ItemBGone +#include "k_specialstage.h" #ifdef PARANOIA #include "deh_tables.h" // MOBJTYPE_LIST @@ -752,6 +753,7 @@ void P_Ticker(boolean run) if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) { P_PlayerThink(&players[i]); + K_KartPlayerHUDUpdate(&players[i]); if (!players[i].spectator) p++; @@ -838,48 +840,6 @@ void P_Ticker(boolean run) } } - if (timelimitintics > 0) - { - // last minute - if (timelimitintics > 60*TICRATE && leveltime == timelimitintics - 60*TICRATE + starttime) - { - S_StartSound(NULL, sfx_cdfm67); // guys it's a bell it's just like splatoon guys - } - if (timelimitintics > 60*TICRATE && leveltime == timelimitintics - 59*TICRATE + starttime) - { - S_StartSound(NULL, sfx_bhurry); // hurry up :)))) - } - - // overtime - if (gameaction != ga_completed && leveltime == timelimitintics + 1 + starttime) - { - S_StartSound(NULL, sfx_s3k50); // overtime warning - S_StartSound(NULL, sfx_s3k9d); - } - if (gameaction != ga_completed && leveltime == timelimitintics + 2*TICRATE + starttime) - { - S_StartSoundAtVolume(NULL, sfx_s3k50, 128); - } - if (gameaction != ga_completed && leveltime == timelimitintics + 4*TICRATE + starttime) - { - S_StartSoundAtVolume(NULL, sfx_s3k50, 96); - } - - // last five second countdown - if (leveltime == timelimitintics - 5 * TICRATE + starttime || - leveltime == timelimitintics - 4 * TICRATE + starttime) - { - S_StartSound(NULL, sfx_kc35); // 5, 4 - } - if (leveltime == timelimitintics - TICRATE + starttime || - leveltime == timelimitintics - 2 * TICRATE + starttime || - leveltime == timelimitintics - 3 * TICRATE + starttime) - { - S_StartSound(NULL, sfx_kc35); - S_StartSound(NULL, sfx_kc3d); // 3, 2, 1 - } - } - // Change timing of start music based on gametyperules { tic_t startingtime = (gametyperules & GTR_NOCOUNTDOWN) ? introtime : starttime; @@ -961,6 +921,8 @@ void P_Ticker(boolean run) K_BossInfoTicker(); + K_TickSpecialStage(); + if (gametyperules & GTR_WANTED) { if (wantedcalcdelay && --wantedcalcdelay <= 0) diff --git a/src/p_user.c b/src/p_user.c index 0f20413d3..e79ba640e 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1311,7 +1311,8 @@ void P_DoPlayerExit(player_t *player, pflags_t flags) S_StartSound(player->mo, playerskin->soundsid[S_sfx[sfx_id].skinsound]); } - if (!K_CanChangeRules() || cv_inttime.value > 0) + // See Y_StartIntermission timer handling + if (!K_CanChangeRules(false) || cv_inttime.value > 0) P_EndingMusic(player); if (P_CheckRacers()) @@ -3854,8 +3855,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 @@ -4039,29 +4038,6 @@ void P_PlayerThink(player_t *player) } } - // 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. if (player->spectator) { @@ -4162,8 +4138,91 @@ 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; + } + + 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; @@ -4280,64 +4339,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; - } - K_KartPlayerThink(player, cmd); // SRB2kart DoABarrelRoll(player); diff --git a/src/v_video.h b/src/v_video.h index e0a20f0dd..c3c664f37 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -305,6 +305,8 @@ char * V_ScaledWordWrap( V__DrawDupxString (x,y,FRACUNIT,option,HU_FONT,string) #define V_DrawKartString( x,y,option,string ) \ V__DrawDupxString (x,y,FRACUNIT,option,KART_FONT,string) +#define V_DrawKartStringAtFixed( x,y,option,string ) \ + V__DrawOneScaleString (x,y,FRACUNIT,option,KART_FONT,string) void V_DrawCenteredString(INT32 x, INT32 y, INT32 option, const char *string); void V_DrawRightAlignedString(INT32 x, INT32 y, INT32 option, const char *string); diff --git a/src/y_inter.c b/src/y_inter.c index 027e0bf38..b54999bfa 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -769,18 +769,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(); @@ -869,8 +859,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;*/ } } } @@ -934,23 +924,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) @@ -971,7 +970,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 S_ShowMusicCredit(-30*FRACUNIT, 5*TICRATE, 0);