From a4b8ba58fd588058d8ff65c1276ac1ed8d676241 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Sat, 1 Oct 2022 21:32:55 -0400 Subject: [PATCH 01/29] Basic start on Special Stages --- src/Sourcefile | 1 + src/d_main.cpp | 4 ++ src/d_netcmd.c | 127 ++++++++++++++++++++++----------------- src/doomstat.h | 1 + src/g_game.c | 1 + src/k_kart.c | 7 ++- src/k_odds.c | 32 ++++++++++ src/k_specialstage.c | 139 +++++++++++++++++++++++++++++++++++++++++++ src/k_specialstage.h | 62 +++++++++++++++++++ src/p_setup.c | 1 + src/p_tick.c | 3 + 11 files changed, 321 insertions(+), 57 deletions(-) create mode 100644 src/k_specialstage.c create mode 100644 src/k_specialstage.h 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/d_main.cpp b/src/d_main.cpp index ad3e43469..92dbf418e 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; diff --git a/src/d_netcmd.c b/src/d_netcmd.c index a27f040f3..1daa24df0 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 @@ -3276,6 +3277,7 @@ static void Command_Map_f(void) if (newgametype == GT_BATTLE) { grandprixinfo.gp = false; + specialStage.active = false; K_ResetBossInfo(); if (mapheaderinfo[newmapnum-1] && @@ -3285,76 +3287,89 @@ 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)) - { - newskill = KARTGP_MASTER; - } - else if (!strcasecmp(nightmarestr, skillname)) - { - newskill = KARTGP_NIGHTMARE; - } - else - { - for (j = 0; kartspeed_cons_t[j].strvalue; j++) - { - if (!strcasecmp(kartspeed_cons_t[j].strvalue, skillname)) - { - newskill = (INT16)kartspeed_cons_t[j].value; - break; - } - } + 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 (!kartspeed_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 (option_skill) { - if (newskill == KARTGP_MASTER) + const char *masterstr = "Master"; + const char *nightmarestr = "NIGHTMARE"; + const char *skillname = COM_Argv(option_skill + 1); + INT32 newskill = -1; + INT32 j; + + if (!strcasecmp(masterstr, skillname)) { - grandprixinfo.gamespeed = KARTSPEED_HARD; - grandprixinfo.masterbots = true; + newskill = KARTGP_MASTER; } - else if (newskill == KARTGP_NIGHTMARE) + else if (!strcasecmp(nightmarestr, skillname)) { - grandprixinfo.gamespeed = KARTSPEED_EXPERT; - grandprixinfo.masterbots = true; + newskill = KARTGP_NIGHTMARE; } else { - grandprixinfo.gamespeed = newskill; - grandprixinfo.masterbots = false; + for (j = 0; kartspeed_cons_t[j].strvalue; j++) + { + if (!strcasecmp(kartspeed_cons_t[j].strvalue, skillname)) + { + newskill = (INT16)kartspeed_cons_t[j].value; + break; + } + } + + if (!kartspeed_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; + } } } + + 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; } } 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/g_game.c b/src/g_game.c index b19f41e2e..34a29dd4c 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3798,6 +3798,7 @@ tolinfo_t TYPEOFLEVEL[NUMTOLNAMES] = { {"RACE",TOL_RACE}, {"BATTLE",TOL_BATTLE}, {"BOSS",TOL_BOSS}, + {"SPECIAL",TOL_SPECIAL}, {"TV",TOL_TV}, // Compat stuff diff --git a/src/k_kart.c b/src/k_kart.c index 5637410a2..06945d6da 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" @@ -104,7 +105,11 @@ void K_TimerInit(void) starttime = introtime = 0; - if (!bossinfo.boss) + if (specialStage.active == true) + { + K_InitSpecialStage(); + } + else if (bossinfo.boss == false) { for (i = 0; i < MAXPLAYERS; i++) { diff --git a/src/k_odds.c b/src/k_odds.c index 0c72f18af..add7ee004 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 + { 0, 0, 2, 3, 4, 0, 0, 0 }, // Sneaker + { 0, 0, 0, 0, 0, 0, 0, 0 }, // Rocket Sneaker + { 0, 0, 0, 0, 2, 5, 5, 7 }, // Invincibility + { 2, 3, 1, 0, 0, 0, 0, 0 }, // Banana + { 1, 2, 0, 0, 0, 0, 0, 0 }, // Eggman Monitor + { 5, 5, 2, 2, 0, 0, 0, 0 }, // Orbinaut + { 0, 4, 2, 1, 0, 0, 0, 0 }, // Jawz + { 0, 3, 3, 1, 0, 0, 0, 0 }, // Mine + { 0, 0, 2, 2, 0, 0, 0, 0 }, // Ballhog + { 0, 0, 0, 0, 0, 2, 4, 0 }, // Self-Propelled Bomb + { 0, 0, 0, 0, 2, 5, 0, 0 }, // Grow + { 0, 0, 0, 0, 0, 2, 4, 2 }, // Shrink + { 1, 0, 0, 0, 0, 0, 0, 0 }, // Thunder Shield + { 3, 0, 0, 0, 0, 0, 0, 0 }, // Hyudoro + { 0, 0, 0, 0, 0, 0, 0, 0 }, // Pogo Spring + { 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink + { 2, 1, 1, 0, 0, 0, 0, 0 }, // Super Ring + { 3, 0, 0, 0, 0, 0, 0, 0 }, // Land Mine + { 0, 1, 2, 1, 0, 0, 0, 0 }, // Bubble Shield + { 0, 0, 0, 0, 0, 1, 3, 5 }, // Flame Shield + { 0, 0, 0, 0, 0, 0, 0, 0 }, // Sneaker x2 + { 0, 0, 0, 0, 4, 4, 4, 0 }, // Sneaker x3 + { 0, 1, 1, 0, 0, 0, 0, 0 }, // Banana x3 + { 0, 0, 0, 1, 0, 0, 0, 0 }, // Banana x10 + { 0, 0, 1, 0, 0, 0, 0, 0 }, // Orbinaut x3 + { 0, 0, 0, 2, 0, 0, 0, 0 }, // Orbinaut x4 + { 0, 0, 1, 2, 1, 0, 0, 0 } // Jawz x2 +}; + // 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/p_setup.c b/src/p_setup.c index 60ae6591c..e55e9a6b0 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 0c5a96976..bd0bac91e 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 @@ -961,6 +962,8 @@ void P_Ticker(boolean run) K_BossInfoTicker(); + K_TickSpecialStage(); + if (gametyperules & GTR_WANTED) { if (wantedcalcdelay && --wantedcalcdelay <= 0) From d01f3e606e021f2146431755051c4ec4bf60b063 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Sat, 1 Oct 2022 21:36:17 -0400 Subject: [PATCH 02/29] Actually add proper item table --- src/k_odds.c | 54 ++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/k_odds.c b/src/k_odds.c index add7ee004..82440fe7e 100644 --- a/src/k_odds.c +++ b/src/k_odds.c @@ -155,33 +155,33 @@ static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = static UINT8 K_KartItemOddsSpecial[NUMKARTRESULTS-1][4] = { //M N O P - { 0, 0, 2, 3, 4, 0, 0, 0 }, // Sneaker - { 0, 0, 0, 0, 0, 0, 0, 0 }, // Rocket Sneaker - { 0, 0, 0, 0, 2, 5, 5, 7 }, // Invincibility - { 2, 3, 1, 0, 0, 0, 0, 0 }, // Banana - { 1, 2, 0, 0, 0, 0, 0, 0 }, // Eggman Monitor - { 5, 5, 2, 2, 0, 0, 0, 0 }, // Orbinaut - { 0, 4, 2, 1, 0, 0, 0, 0 }, // Jawz - { 0, 3, 3, 1, 0, 0, 0, 0 }, // Mine - { 0, 0, 2, 2, 0, 0, 0, 0 }, // Ballhog - { 0, 0, 0, 0, 0, 2, 4, 0 }, // Self-Propelled Bomb - { 0, 0, 0, 0, 2, 5, 0, 0 }, // Grow - { 0, 0, 0, 0, 0, 2, 4, 2 }, // Shrink - { 1, 0, 0, 0, 0, 0, 0, 0 }, // Thunder Shield - { 3, 0, 0, 0, 0, 0, 0, 0 }, // Hyudoro - { 0, 0, 0, 0, 0, 0, 0, 0 }, // Pogo Spring - { 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink - { 2, 1, 1, 0, 0, 0, 0, 0 }, // Super Ring - { 3, 0, 0, 0, 0, 0, 0, 0 }, // Land Mine - { 0, 1, 2, 1, 0, 0, 0, 0 }, // Bubble Shield - { 0, 0, 0, 0, 0, 1, 3, 5 }, // Flame Shield - { 0, 0, 0, 0, 0, 0, 0, 0 }, // Sneaker x2 - { 0, 0, 0, 0, 4, 4, 4, 0 }, // Sneaker x3 - { 0, 1, 1, 0, 0, 0, 0, 0 }, // Banana x3 - { 0, 0, 0, 1, 0, 0, 0, 0 }, // Banana x10 - { 0, 0, 1, 0, 0, 0, 0, 0 }, // Orbinaut x3 - { 0, 0, 0, 2, 0, 0, 0, 0 }, // Orbinaut x4 - { 0, 0, 1, 2, 1, 0, 0, 0 } // Jawz x2 + { 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) From 387315ad0fb20b052c441c6dd251c3552aa6a22d Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 4 Sep 2022 14:56:07 +0100 Subject: [PATCH 03/29] Rework gamespeed cvar handling a little bit. - Make the menu-only dummykartspeed and dummygpdifficulty cvars also affected by the unlock system. - Master mode is currently behind SECRET_HARDSPEED, this can be changed later when we're seriously thinking about unlock progression. - Complete forwardport of changes to cv_kartspeed from 1.4+, since I missed a spot previously. --- src/command.c | 27 +++++++++++++++++++++++++-- src/command.h | 2 +- src/d_netcmd.c | 7 ------- src/m_menu.c | 3 +-- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/command.c b/src/command.c index 8614d9fd4..7983cb5df 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); @@ -2100,6 +2109,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); @@ -2305,9 +2323,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_netcmd.c b/src/d_netcmd.c index 1daa24df0..68aaaefb2 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -7361,13 +7361,6 @@ 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) { return; diff --git a/src/m_menu.c b/src/m_menu.c index 838e16c9d..02738f4af 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); From 65f4af966a30926fc10a1fab53628ac610fc3a71 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 11 Oct 2022 16:41:48 +0100 Subject: [PATCH 04/29] Use GP difficulty str values instead of hardcoded "Master" exception --- src/d_main.cpp | 34 ++++++++++------------------------ src/d_netcmd.c | 33 ++++++++++----------------------- 2 files changed, 20 insertions(+), 47 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index 92dbf418e..62974ec4c 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -2031,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 68aaaefb2..a2702edaa 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -3305,37 +3305,24 @@ static void Command_Map_f(void) if (option_skill) { - const char *masterstr = "Master"; - const char *nightmarestr = "NIGHTMARE"; const char *skillname = COM_Argv(option_skill + 1); INT32 newskill = -1; INT32 j; - if (!strcasecmp(masterstr, skillname)) + for (j = 0; gpdifficulty_cons_t[j].strvalue; j++) { - newskill = KARTGP_MASTER; - } - else if (!strcasecmp(nightmarestr, skillname)) - { - newskill = KARTGP_NIGHTMARE; - } - else - { - for (j = 0; kartspeed_cons_t[j].strvalue; j++) + if (!strcasecmp(gpdifficulty_cons_t[j].strvalue, skillname)) { - if (!strcasecmp(kartspeed_cons_t[j].strvalue, skillname)) - { - 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 - { - 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 (!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) From 22f6c8b532417900aa8c7cd6aae3f7f5134fa6fc Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 11 Oct 2022 23:00:44 +0100 Subject: [PATCH 05/29] Rework time limit a bit - Make timelimitintics handled a bit more like gamespeed, encore, frantic, etc - update on mapload/starttime, not during gameplay - Use default setting if can't change rules - this is a surprise tool that will help us later - Have it properly update when adjusting gametype from the menu - Cleaned up SV_StartSinglePlayerServer to do this - Remove CV_SAVE to prevent time limit bruh moments --- src/d_clisrv.c | 20 +++++---------- src/d_clisrv.h | 2 +- src/d_netcmd.c | 66 +++++++++++++++++++++--------------------------- src/g_game.c | 12 ++++++++- src/hu_stuff.c | 2 +- src/lua_script.c | 2 +- src/m_menu.c | 3 +-- src/p_inter.c | 8 ++---- src/p_setup.c | 16 ++++++++++++ 9 files changed, 68 insertions(+), 63 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index cd2123507..8c6c130ae 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_netcmd.c b/src/d_netcmd.c index a2702edaa..e667c3051 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -658,9 +658,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); @@ -5467,26 +5467,39 @@ 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) { - 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 + 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")); + } + timelimitintics = cv_timelimit.value * (60*TICRATE); - // 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. - } - #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. @@ -5512,7 +5525,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 { @@ -5523,27 +5536,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. @@ -7335,7 +7327,7 @@ static void KartFrantic_OnChange(void) 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; @@ -7353,7 +7345,7 @@ static void KartSpeed_OnChange(void) 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; diff --git a/src/g_game.c b/src/g_game.c index 34a29dd4c..0f596da29 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -5493,6 +5493,7 @@ cleanup: void G_DeferedInitNew(boolean pencoremode, INT32 map, INT32 pickedchar, UINT8 ssplayers, boolean FLS) { UINT16 color = SKINCOLOR_NONE; + INT32 dogametype; paused = false; @@ -5503,8 +5504,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/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/lua_script.c b/src/lua_script.c index 823cb9a48..f9e7adab4 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 02738f4af..1413bd751 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -6550,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_inter.c b/src/p_inter.c index d37a8faab..3663e4449 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -997,8 +997,6 @@ void P_TouchStarPost(mobj_t *post, player_t *player, boolean snaptopost) /** 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,7 +1004,7 @@ void P_CheckTimeLimit(void) { INT32 i, k; - if (!cv_timelimit.value) + if (!timelimitintics) return; if (!(multiplayer || netgame)) @@ -1024,7 +1022,7 @@ void P_CheckTimeLimit(void) if (gameaction == ga_completed) return; - if (cv_overtime.value) + if ((grandprixinfo.gp == false) && (cv_overtime.value)) { INT32 playerarray[MAXPLAYERS]; INT32 tempplayer = 0; @@ -1100,8 +1098,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 */ diff --git a/src/p_setup.c b/src/p_setup.c index e55e9a6b0..d1ebd6ae9 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -8390,6 +8390,22 @@ static void P_InitGametype(void) numlaps = K_RaceLapCount(gamemap - 1); + if ((gametyperules & GTR_TIMELIMIT) && !bossinfo.boss) + { + if (K_CanChangeRules() == false) + { + timelimitintics = timelimits[gametype] * (60*TICRATE); + } + else + { + timelimitintics = cv_timelimit.value * (60*TICRATE); + } + } + else + { + timelimitintics = 0; + } + wantedcalcdelay = wantedfrequency*2; indirectitemcooldown = 0; From 75f609b6e5bc155aa8c048180550c16697a200c1 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 11 Oct 2022 23:03:56 +0100 Subject: [PATCH 06/29] Add a goofy little jitter to the HUD - Medium strength at 5 seconds to go - Big strength in overtime --- src/k_hud.c | 75 ++++++++++++++++++++++------------------------------- 1 file changed, 31 insertions(+), 44 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index aa96c8158..7f3e3d758 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1612,7 +1612,7 @@ 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; boolean overtime = false; UINT8 *textcolor = 0; @@ -1621,28 +1621,33 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UI { 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 (drawtime >= timelimitintics) { overtime = true; - // drawtime = 0; + jitter = 2; + if (drawtime & 2) + jitter = -jitter; + drawtime = 0; } else { drawtime = timelimitintics - drawtime; + if (drawtime < 5*TICRATE) + { + jitter = 1; + if (drawtime & 2) + jitter = -jitter; + } } } } @@ -1659,10 +1664,15 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UI worktime = drawtime/(60*TICRATE); + if (drawtime != UINT32_MAX && worktime >= 100) + { + jitter = (drawtime & 1 ? 1 : -1); + worktime = 99; + drawtime = (100*(60*TICRATE))-1; + } + 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) @@ -1687,53 +1697,30 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UI va("OVERTIME!") ); } - 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 ((modeattacking || (mode == 1)) && G_EmblemsEnabled()) // emblem time! { From 9337651e99333fc870920489922d08697958b98d Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 12 Oct 2022 19:48:01 +0100 Subject: [PATCH 07/29] Rework nextmap a little further - Set gamemap after intermission, rather than before. - Fixes a bug where the NO CONTESTed player wouldn't get cycled out on the 4th round, because it had already been incremented by G_GetNextMap. - Make the vote screen a special NEXTMAP_VOTING constant, for sanity, and to make cv_advancemap handled all in one place. - Along with the above, fixes a bug where changing cv_advancemap would only enable or disable the voting screen, not change the nextmap. - Call G_EndGame in G_NextLevel, since they were never used seperately. - Add Special Stage and marathonmode to K_CanChangeRules. --- src/f_finale.c | 4 +- src/g_game.c | 114 +++++++++++++++++++++++++--------------------- src/g_game.h | 3 +- src/k_grandprix.c | 13 ++++++ 4 files changed, 77 insertions(+), 57 deletions(-) diff --git a/src/f_finale.c b/src/f_finale.c index 890550abb..fe7fac4af 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -1871,10 +1871,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 0f596da29..547c00262 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4325,7 +4325,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; @@ -4373,6 +4373,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; @@ -4479,14 +4480,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 @@ -4514,19 +4508,39 @@ static INT16 G_GetNextMap(boolean advancemap) newmap = cm; } - if (advancemap && !marathonmode) + if (advancemap && K_CanChangeRules()) { - 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; } } } @@ -4535,6 +4549,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; } @@ -4544,8 +4561,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; @@ -4604,14 +4619,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. @@ -4622,7 +4631,6 @@ static void G_DoCompleted(void) || (intertype == int_none)) { G_UpdateVisited(); - G_HandleSaveLevel(); G_AfterIntermission(); } else @@ -4630,7 +4638,6 @@ static void G_DoCompleted(void) G_SetGamestate(GS_INTERMISSION); Y_StartIntermission(); G_UpdateVisited(); - G_HandleSaveLevel(); } } @@ -4664,14 +4671,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(); } } @@ -4683,25 +4693,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; } @@ -4728,7 +4728,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; } @@ -4806,8 +4810,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) 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/k_grandprix.c b/src/k_grandprix.c index a11f4969d..b1d73c6c3 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" @@ -761,6 +762,18 @@ boolean K_CanChangeRules(void) return false; } + if (specialStage.active == true) + { + // Don't cheat special stages! + return false; + } + + if (marathonmode) + { + // Don't cheat the endurance challenge! + return false; + } + if (modeattacking == true) { // Don't cheat the rules of Time Trials! From 75a828a38fd2c3d3610e2608a1d08ec08c8763c1 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 12 Oct 2022 20:17:20 +0100 Subject: [PATCH 08/29] Make intermission timer-dependent material consistent via K_CanChangeRules() Prevents setting inttime 0 in singleplayer contexts --- src/g_game.c | 2 +- src/y_inter.c | 24 ++++++++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 547c00262..e9b2a82b7 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4604,7 +4604,7 @@ static void G_DoCompleted(void) } // 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)) + if ((gametyperules & GTR_CIRCUIT) && ((multiplayer && demo.playback) || j == r_splitscreen+1) && (!K_CanChangeRules() || cv_inttime.value > 0)) { S_ChangeMusicInternal("racent", true); S_ShowMusicCredit(-30*FRACUNIT, 5*TICRATE, V_SNAPTOTOP); diff --git a/src/y_inter.c b/src/y_inter.c index 027e0bf38..24c356c64 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -934,23 +934,31 @@ void Y_StartIntermission(void) powertype = K_UsingPowerLevels(); // determine the tic the intermission ends - if (!multiplayer || demo.playback) + if (!K_CanChangeRules()) { - 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 +979,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); From ed77846963fa999f645ff938f60cd707412caca4 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 13 Oct 2022 13:12:07 +0100 Subject: [PATCH 09/29] Simplify intermission timer code - We had two ways of ending the intermission, but only one would actually be used, and it resulted in code duplication. - Combine the two ways. - Disable the reached-but-didn't-do-anything-before-now code path for setting endtic. --- src/y_inter.c | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/y_inter.c b/src/y_inter.c index 24c356c64..7a0bedcea 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;*/ } } } From 39b63505f1861482403e85503cae36e90efebb75 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 13 Oct 2022 17:37:35 +0100 Subject: [PATCH 10/29] Rework exit timing nittygritty G: can't change any of this for compat reasons - ~~exitcountdown is now used for both No Contest and regular exits~~ - ~~Set in P_DoPlayerExit~~ - ~~Handles sending XD_EXITLEVEL in P_Ticker~~ - ~~player->exiting is now 0 or 1 (we can make it a bool or a new timer later)~~ - ~~Fixes a longstanding bug where failing a GP round could restart multiple times~~ Also: - Fix a possible waiting-in-the-wings issue where mapchanges would occour client-side in K_CheckBumpers - Add `gptest` cheat - sets numlaps to 1 lap on mapload for quick but legitimate(ish) progression --- src/d_netcmd.c | 1 + src/d_netcmd.h | 1 + src/k_battle.c | 3 ++- src/k_kart.c | 8 +++++++- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index e667c3051..9740bdff0 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -633,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); 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/k_battle.c b/src/k_battle.c index e37cfee39..f0a8a4e07 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -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 { diff --git a/src/k_kart.c b/src/k_kart.c index 06945d6da..559fa9b14 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -6604,7 +6604,13 @@ UINT8 K_RaceLapCount(INT16 mapNum) return 0; } - if (cv_numlaps.value == -1 || modeattacking != ATTACKING_NONE) + if (cv_gptest.value) + { + // For testing + return 1; + } + + if (cv_numlaps.value == -1 || K_CanChangeRules() == false) { // Use map default return mapheaderinfo[mapNum]->numlaps; From 56fe869def8a95c348bdc7dac476d9d052656474 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 13 Oct 2022 18:14:48 +0100 Subject: [PATCH 11/29] Seperate FINISH text from khud_cardanimation - Fixes an issue where the card animation and the FINISH animation operating on two different timers, but using the same variable, would intefere with each other - Also makes khud_fault use the same drawer, so it can benefit from interpolation --- src/d_player.h | 3 ++ src/g_game.c | 14 +++++++- src/k_hud.c | 98 +++++++++++++++++++++++++------------------------- src/k_kart.c | 18 +++++----- 4 files changed, 75 insertions(+), 58 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index a7787ef0e..f57151577 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/g_game.c b/src/g_game.c index e9b2a82b7..2559cb707 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2709,6 +2709,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 +2826,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) latestlap = 0; roundscore = 0; exiting = 0; + khudfinish = 0; khudcardanimation = 0; starpostx =0; starposty = 0; @@ -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; diff --git a/src/k_hud.c b/src/k_hud.c index 7f3e3d758..4481581ee 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -4657,6 +4657,53 @@ 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; + + 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 && stplyrnum == 1) + interpx = -interpx; + + V_DrawFixedPatch(interpx + (STCD_X<>1), + (STCD_Y<height)<<(FRACBITS-1)), + FRACUNIT, + splitflags, kptodraw[pnum], NULL); + } +} + static void K_drawKartStartCountdown(void) { INT32 pnum = 0; @@ -4701,51 +4748,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; @@ -4806,7 +4808,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; @@ -4817,8 +4819,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 559fa9b14..f4b13f00f 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -419,6 +419,7 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_saltyhop); CV_RegisterVar(&cv_naturalcamera); + CV_RegisterVar(&cv_gptest); } //} @@ -6327,16 +6328,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 { @@ -6351,11 +6356,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; From b26d2b29de2547a154f5a4aeeab506acade1ce44 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 8 Mar 2023 00:30:03 -0800 Subject: [PATCH 12/29] Fix 2P FINISH text scrolling Was using the wrong center offset. --- src/k_hud.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index 4481581ee..ab7456dd1 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -4685,19 +4685,22 @@ static void K_drawKartFinish(void) //else -- 1/2p, scrolling FINISH { - INT32 x, xval, ox, interpx; + INT32 x, xval, ox, interpx, pwidth; x = ((vid.width<width)< x ? xval : x))/TICRATE; - ox = ((TICRATE - (timer - 1))*(xval > x ? xval : x))/TICRATE; + + pwidth = max(xval, x); + + x = ((TICRATE - timer) * pwidth) / TICRATE; + ox = ((TICRATE - (timer - 1)) * pwidth) / TICRATE; interpx = R_InterpolateFixed(ox, x); if (r_splitscreen && stplyrnum == 1) interpx = -interpx; - V_DrawFixedPatch(interpx + (STCD_X<>1), + V_DrawFixedPatch(interpx + (STCD_X<height)<<(FRACBITS-1)), FRACUNIT, splitflags, kptodraw[pnum], NULL); From 2939f63418fe14c752bb9de0e1d4b8f03d5831f3 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 14 Oct 2022 18:34:34 +0100 Subject: [PATCH 13/29] Support alternate gameplay events during GP - Implementation details: - grandprixinfo.eventmode is the reference point - All bots have spectator applied and removed at map start depending on eventmode, and I've done my best to guard against side effects of not removing them entirely - You shouldn't turn off grandprixinfo.gp when turning on things like specialStage.active or bossinfo.boss when pursuing eventmode behaviour - Probably needs to be integrated into XD_MAP for any future netplay support, is currently disabled. - You technically don't have to assign a Capsules map to be the bonus and a Special Stage to be the special. A Capsules map can be assigned to a Special Stage too, and a Boss can be assigned to either of them. - Special Stages are still just as incomplete as they were before. - Break the Capsules has special behaviour. - Timelimit starts at 20 seconds. - Earn 10 seconds (plus a little extra cheaty time) every capsule you destroy. - WIN + extra life if you bust all the capsules, COOL if you get some but run out of time, LOSE if you lose your bumper or run out of time without breaking a single capsule. - Supposed to also give you rings, but ran into a LOT of difficulty with this and didn't want to commit half-baked stuff, so it'll be a later project. Also: - Fix a long standing bug where totalring was reset between maps, preventing the sum from adding up across GP rounds and depriving you of extra lives you were owed. - Fix an issue where Break the Capsules record attack was KARTSPEED_HARD. - Send timelimitintics in savegames, since it's handled seperately now. --- src/d_netcmd.c | 4 +++ src/g_game.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++--- src/k_battle.c | 2 +- src/k_hud.c | 10 +++++- src/p_enemy.c | 7 ++-- src/p_inter.c | 64 +++++++++++++++++++++++++++++------ src/p_setup.c | 16 --------- 7 files changed, 161 insertions(+), 34 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 9740bdff0..88b49cd74 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2891,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; diff --git a/src/g_game.c b/src/g_game.c index 2559cb707..72ad99903 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" @@ -2837,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++) { @@ -4397,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]) { diff --git a/src/k_battle.c b/src/k_battle.c index f0a8a4e07..c95455925 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -493,7 +493,7 @@ void K_BattleInit(UINT8 numPlayers) { if (modeattacking != ATTACKING_ITEMBREAK) { - if (!cv_kartitembreaker.value) + if (K_CanChangeRules() && !cv_kartitembreaker.value) goto afteritembreaker; if (numPlayers > 1) diff --git a/src/k_hud.c b/src/k_hud.c index ab7456dd1..6ab76b78c 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1642,7 +1642,15 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UI else { drawtime = timelimitintics - drawtime; - if (drawtime < 5*TICRATE) + if (secretextratime) + ; + else if (extratimeintics) + { + jitter = 2; + if (leveltime & 1) + jitter = -jitter; + } + else if (drawtime < 5*TICRATE) { jitter = 1; if (drawtime & 2) diff --git a/src/p_enemy.c b/src/p_enemy.c index 36ce44d69..5f83e1748 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -3284,9 +3284,12 @@ void A_AttractChase(mobj_t *actor) // 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 3663e4449..30d535eb8 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1004,17 +1004,37 @@ void P_CheckTimeLimit(void) { INT32 i, k; + if (exitcountdown) + return; + if (!timelimitintics) return; - if (!(multiplayer || netgame)) - return; - - if (!(gametyperules & GTR_TIMELIMIT)) - return; - - if (itembreaker) - return; + 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); + } + } if (leveltime < (timelimitintics + starttime)) return; @@ -1408,15 +1428,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) diff --git a/src/p_setup.c b/src/p_setup.c index d1ebd6ae9..e55e9a6b0 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -8390,22 +8390,6 @@ static void P_InitGametype(void) numlaps = K_RaceLapCount(gamemap - 1); - if ((gametyperules & GTR_TIMELIMIT) && !bossinfo.boss) - { - if (K_CanChangeRules() == false) - { - timelimitintics = timelimits[gametype] * (60*TICRATE); - } - else - { - timelimitintics = cv_timelimit.value * (60*TICRATE); - } - } - else - { - timelimitintics = 0; - } - wantedcalcdelay = wantedfrequency*2; indirectitemcooldown = 0; From caf91c30ddf98d8b1ebdbdc7cfb56c4ce0469574 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 15 Oct 2022 14:57:35 +0100 Subject: [PATCH 14/29] Fix the case where GP coop would cause no Capsules + possibly DUELs in eventmode Also reduces some of the dead time before Break The Capsules begins by cutting out the majority of POSITION!! and hiding the one measly bulb that would otherwise appear --- src/k_battle.c | 17 ++++------------- src/k_battle.h | 2 +- src/k_kart.c | 29 ++++++++++++++++------------- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/src/k_battle.c b/src/k_battle.c index c95455925..0e4f8b368 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -487,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 (K_CanChangeRules() && !cv_kartitembreaker.value) - goto afteritembreaker; - - if (numPlayers > 1) - goto afteritembreaker; - } - - itembreaker = true; + if (!(K_CanChangeRules() && !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_kart.c b/src/k_kart.c index f4b13f00f..fb5ae7262 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -101,7 +101,9 @@ vector3_t clusterpoint, clusterdtf; void K_TimerInit(void) { UINT8 i; - UINT8 numPlayers = 0;//, numspec = 0; + UINT8 numPlayers = 0; + boolean singleplayercontext = ((modeattacking != ATTACKING_NONE) + || (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE)); starttime = introtime = 0; @@ -111,20 +113,22 @@ void K_TimerInit(void) } else if (bossinfo.boss == false) { - for (i = 0; i < MAXPLAYERS; i++) + if (!singleplayercontext) { - 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; + singleplayercontext = true; } - - numPlayers++; } introtime = (108) + 5; // 108 for rotation, + 5 for white fade @@ -136,13 +140,10 @@ 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; if ((gametyperules & GTR_TIMELIMIT) && !bossinfo.boss) { - if (!K_CanChangeRules()) + if (singleplayercontext) { if (grandprixinfo.gp) { @@ -161,6 +162,8 @@ void K_TimerInit(void) timelimitintics = cv_timelimit.value * (60*TICRATE); } } + + K_BattleInit(singleplayercontext); } UINT32 K_GetPlayerDontDrawFlag(player_t *player) From 877ee24ac24f6cf60def423498515b062daf9204 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 15 Oct 2022 15:15:22 +0100 Subject: [PATCH 15/29] Add "allowdemos" option to K_CanChangeRules() It's been used interchangably as "this is a singleplayer gameplay context" and "This is a no cvar changing context". This addition repairs some behaviour which might have been inconsistent between netgame and netreplay. --- src/d_netcmd.c | 47 ++++++++++++++++++++++++++--------------------- src/g_game.c | 5 +++-- src/k_battle.c | 4 ++-- src/k_bot.cpp | 2 +- src/k_grandprix.c | 16 ++++++++-------- src/k_grandprix.h | 6 +++--- src/k_kart.c | 4 ++-- src/p_inter.c | 6 +++--- src/p_user.c | 3 ++- src/y_inter.c | 3 ++- 10 files changed, 52 insertions(+), 44 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 88b49cd74..6ca81be48 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -4507,7 +4507,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; @@ -4643,7 +4643,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; @@ -5472,7 +5472,7 @@ UINT32 secretextratime = 0; */ static void TimeLimit_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7299,6 +7299,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); @@ -7327,7 +7332,7 @@ static void NumLaps_OnChange(void) static void KartFrantic_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7345,7 +7350,7 @@ static void KartFrantic_OnChange(void) static void KartSpeed_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7370,7 +7375,7 @@ static void KartBattleSpeed_OnChange(void) return; } - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7388,7 +7393,7 @@ static void KartBattleSpeed_OnChange(void) static void KartEncore_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7398,7 +7403,7 @@ static void KartEncore_OnChange(void) static void KartComeback_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7421,7 +7426,7 @@ static void KartEliminateLast_OnChange(void) static void KartRings_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7438,7 +7443,7 @@ static void KartRings_OnChange(void) static void KartPurpleDrift_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7471,7 +7476,7 @@ static void KartPurpleDrift_OnChange(void) static void KartStacking_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7504,7 +7509,7 @@ static void KartStacking_OnChange(void) static void KartChaining_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7537,7 +7542,7 @@ static void KartChaining_OnChange(void) static void KartSlipdash_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7570,7 +7575,7 @@ static void KartSlipdash_OnChange(void) static void KartSlopeBoost_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7603,7 +7608,7 @@ static void KartSlopeBoost_OnChange(void) static void KartDrafting_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7636,7 +7641,7 @@ static void KartDrafting_OnChange(void) static void KartAirDrop_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7669,7 +7674,7 @@ static void KartAirDrop_OnChange(void) static void KartItemLitter_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7702,7 +7707,7 @@ static void KartItemLitter_OnChange(void) static void KartAntiBump_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7726,7 +7731,7 @@ static void KartAntiBump_OnChange(void) static void KartItemBreaker_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7748,7 +7753,7 @@ static void KartItemBreaker_OnChange(void) static void KartInvinType_OnChange(void) { - if (K_CanChangeRules() == false) + if (K_CanChangeRules(false) == false) { return; } @@ -7766,7 +7771,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/g_game.c b/src/g_game.c index 72ad99903..37736297b 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4604,7 +4604,7 @@ static INT16 G_GetNextMap(boolean advancemap) newmap = cm; } - if (advancemap && K_CanChangeRules()) + if (advancemap && K_CanChangeRules(true)) { switch (cv_advancemap.value) { @@ -4699,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) && (!K_CanChangeRules() || cv_inttime.value > 0)) { S_ChangeMusicInternal("racent", true); S_ShowMusicCredit(-30*FRACUNIT, 5*TICRATE, V_SNAPTOTOP); diff --git a/src/k_battle.c b/src/k_battle.c index 0e4f8b368..22167df68 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) { @@ -491,7 +491,7 @@ void K_BattleInit(boolean singleplayercontext) { if ((gametyperules & GTR_ITEMBREAKER) && singleplayercontext && !itembreaker && !bossinfo.boss) { - if (!(K_CanChangeRules() && !cv_kartitembreaker.value)) + if (!(K_CanChangeRules(true) && !cv_kartitembreaker.value)) itembreaker = true; } 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 b1d73c6c3..a48914659 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -738,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! @@ -780,5 +774,11 @@ boolean K_CanChangeRules(void) 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_kart.c b/src/k_kart.c index fb5ae7262..c51714de2 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -6613,7 +6613,7 @@ UINT8 K_RaceLapCount(INT16 mapNum) return 1; } - if (cv_numlaps.value == -1 || K_CanChangeRules() == false) + if (cv_numlaps.value == -1 || K_CanChangeRules(true) == false) { // Use map default return mapheaderinfo[mapNum]->numlaps; @@ -11366,7 +11366,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/p_inter.c b/src/p_inter.c index 30d535eb8..dd3de7054 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1128,7 +1128,7 @@ void P_CheckPointLimit(void) if (exitcountdown) return; - if (!K_CanChangeRules()) + if (!K_CanChangeRules(true)) return; if (!cv_pointlimit.value) @@ -1174,7 +1174,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; @@ -1293,7 +1293,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; diff --git a/src/p_user.c b/src/p_user.c index d13c794a1..b855ff1e6 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()) diff --git a/src/y_inter.c b/src/y_inter.c index 7a0bedcea..b54999bfa 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -924,7 +924,8 @@ void Y_StartIntermission(void) powertype = K_UsingPowerLevels(); // determine the tic the intermission ends - if (!K_CanChangeRules()) + // 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 = 10*TICRATE; } From fe01c780febe5d9e79f814e82c30c10af41d213f Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 15 Oct 2022 15:43:11 +0100 Subject: [PATCH 16/29] Fix cup level list behaviour - Frees existing string data - Unbinds cup backreference here instead of in p_setup --- src/deh_soc.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 88f5675ed..d7729d6da 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; } From 805b82abd76f0496c9d1b82e896530982d7ce3e2 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 15 Oct 2022 15:45:59 +0100 Subject: [PATCH 17/29] Don't make gptest behaviour apply in Record Attack. --- src/k_kart.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index c51714de2..74820e3a2 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -6607,7 +6607,9 @@ UINT8 K_RaceLapCount(INT16 mapNum) return 0; } - if (cv_gptest.value) + if ((grandprixinfo.gp == true) + && (grandprixinfo.eventmode == GPEVENT_NONE) + && cv_gptest.value) { // For testing return 1; From 6b8ac3c301947d9129af7a9a3cfa613ab9a8df76 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 15 Oct 2022 15:51:57 +0100 Subject: [PATCH 18/29] Fix pointlimit messages occouring in singleplayer contexts Still an awful child that hasn't shaped up like their sister cv_timelimit, but this will do for now. --- src/d_netcmd.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 6ca81be48..fb15d4ea0 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -5422,7 +5422,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) @@ -5437,7 +5442,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")); } From 5a1d232c9a8ef3af7e9c30a6b60fbf5551f05667 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 15 Oct 2022 15:55:31 +0100 Subject: [PATCH 19/29] No time limit in Capsule timeattack --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 74820e3a2..f1a47e4a5 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -141,7 +141,7 @@ void K_TimerInit(void) } timelimitintics = extratimeintics = secretextratime = 0; - if ((gametyperules & GTR_TIMELIMIT) && !bossinfo.boss) + if ((gametyperules & GTR_TIMELIMIT) && !bossinfo.boss && !modeattacking) { if (singleplayercontext) { From dea4512fd8c51dc386d4b7f0b49abc56f4f98723 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 15 Oct 2022 15:58:24 +0100 Subject: [PATCH 20/29] If timelimit is set while being modified (extremely unlikely but technically possible), stop adding extra time. --- src/d_netcmd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index fb15d4ea0..06c1a5589 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -5494,6 +5494,7 @@ static void TimeLimit_OnChange(void) } timelimitintics = cv_timelimit.value * (60*TICRATE); + extratimeintics = secretextratime = 0; #ifdef HAVE_DISCORDRPC DRPC_UpdatePresence(); From eeafd2bcc072d1eec5a7c244194038bccd0d3018 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 15 Oct 2022 16:03:39 +0100 Subject: [PATCH 21/29] If timelimit is being modified before starttime, tick the secret timer but don't modify the total limit. --- src/p_inter.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/p_inter.c b/src/p_inter.c index dd3de7054..fa1cdc2a1 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1010,6 +1010,13 @@ void P_CheckTimeLimit(void) if (!timelimitintics) return; + if (leveltime < starttime) + { + if (secretextratime) + secretextratime--; + return; + } + if (secretextratime) { secretextratime--; From 50721175ce5bd5968d5ea7e83db7a327acf11d8b Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 15 Oct 2022 16:29:53 +0100 Subject: [PATCH 22/29] Rearrange P_PlayerThink to make some things occur even in Hitlag Notably REALTIME - prevents the timer stopping when you get hit, but also: - Typing indicator - Airtime tracking - Kickstart accel - No Contesting due to racecountdown - Spectator data wipe - Stronger preventative measure for possible finish line multi-hit events --- src/p_user.c | 170 +++++++++++++++++++++++++-------------------------- 1 file changed, 85 insertions(+), 85 deletions(-) diff --git a/src/p_user.c b/src/p_user.c index b855ff1e6..1b034b8d8 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -3855,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 @@ -4040,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) { @@ -4163,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; @@ -4281,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); From 6ab7b3e6c31976f8c003edc2b003967349969928 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 15 Oct 2022 16:49:51 +0100 Subject: [PATCH 23/29] Also tick (almost) all karthud array timers even during hitlag Only exception is timers relating to ring usage, as that's intended to signify ring boost power (which is obviously paused during hitlag) --- src/k_kart.c | 5 +---- src/p_tick.c | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index f1a47e4a5..b67c44461 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -6319,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) @@ -7346,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; diff --git a/src/p_tick.c b/src/p_tick.c index bd0bac91e..86fdde2ed 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -753,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++; From 45c23445d67fb19e55b147702ce356895120d06a Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 16 Oct 2022 14:21:52 +0100 Subject: [PATCH 24/29] timelimitintics-related shenanigans - Play a countdown sound (same as introcountdown) per each of the last 3 seconds - Jitter the time display HUD extra strong in a two-tic window around the above - Fix timelimitintics not being set in TESTOVERTIMEINFREEPLAY builds (which is now all DEVELOP builds) --- src/doomdef.h | 5 +++++ src/k_hud.c | 5 +++-- src/p_inter.c | 54 ++++++++++++++++++++++++++++----------------------- 3 files changed, 38 insertions(+), 26 deletions(-) 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/k_hud.c b/src/k_hud.c index 6ab76b78c..633898fa2 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1650,9 +1650,10 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UI if (leveltime & 1) jitter = -jitter; } - else if (drawtime < 5*TICRATE) + else if (drawtime <= 5*TICRATE) { - jitter = 1; + jitter = ((drawtime <= 3*TICRATE) && (((drawtime-1) % TICRATE) >= TICRATE-2)) + ? 3 : 1; if (drawtime & 2) jitter = -jitter; } diff --git a/src/p_inter.c b/src/p_inter.c index fa1cdc2a1..c7a7900d0 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -991,9 +991,6 @@ 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. @@ -1017,34 +1014,43 @@ void P_CheckTimeLimit(void) return; } - if (secretextratime) + if (leveltime < (timelimitintics + starttime)) { - secretextratime--; - timelimitintics++; - } - else if (extratimeintics) - { - timelimitintics++; - if (leveltime & 1) - ; - else + if (secretextratime) { - if (extratimeintics > 20) - { - extratimeintics -= 20; - timelimitintics += 20; - } + secretextratime--; + timelimitintics++; + } + else if (extratimeintics) + { + timelimitintics++; + if (leveltime & 1) + ; else { - timelimitintics += extratimeintics; - extratimeintics = 0; + if (extratimeintics > 20) + { + extratimeintics -= 20; + timelimitintics += 20; + } + else + { + timelimitintics += extratimeintics; + extratimeintics = 0; + } + S_StartSound(NULL, sfx_ptally); } - S_StartSound(NULL, sfx_ptally); } - } - - if (leveltime < (timelimitintics + starttime)) + else + { + if (timelimitintics + starttime - leveltime <= 3*TICRATE) + { + if (((timelimitintics + starttime - leveltime) % TICRATE) == 0) + S_StartSound(NULL, sfx_s3ka7); + } + } return; + } if (gameaction == ga_completed) return; From f4b2d9b4fdc65691a73839a734bd43c960ce0be4 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 16 Oct 2022 14:22:51 +0100 Subject: [PATCH 25/29] Fix modeattacking being assumed to be boolean in K_CanChangeRules --- src/k_grandprix.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_grandprix.c b/src/k_grandprix.c index a48914659..763de2c55 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -768,7 +768,7 @@ boolean K_CanChangeRules(boolean allowdemos) return false; } - if (modeattacking == true) + if (modeattacking != ATTACKING_NONE) { // Don't cheat the rules of Time Trials! return false; From 204951f6afe334d64cedae0c6fa941af8c7afba6 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 16 Oct 2022 14:23:21 +0100 Subject: [PATCH 26/29] Clean up K_TimerInit() - Fix not being able to change timelimit in MP break the capsules - Use more readable variable name (singleplayercontext --> domodeattack) - Reset timelimitintics in K_TimerReset() --- src/k_kart.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index b67c44461..e3089ebd4 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -102,7 +102,7 @@ void K_TimerInit(void) { UINT8 i; UINT8 numPlayers = 0; - boolean singleplayercontext = ((modeattacking != ATTACKING_NONE) + boolean domodeattack = ((modeattacking != ATTACKING_NONE) || (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE)); starttime = introtime = 0; @@ -113,7 +113,7 @@ void K_TimerInit(void) } else if (bossinfo.boss == false) { - if (!singleplayercontext) + if (!domodeattack) { for (i = 0; i < MAXPLAYERS; i++) { @@ -127,7 +127,7 @@ void K_TimerInit(void) if (numPlayers < 2) { - singleplayercontext = true; + domodeattack = true; } } @@ -140,12 +140,14 @@ void K_TimerInit(void) } } - timelimitintics = extratimeintics = secretextratime = 0; + timelimitintics = extratimeintics = secretextratime = 0; // G: restore K_TimerReset? + K_BattleInit(domodeattack); + if ((gametyperules & GTR_TIMELIMIT) && !bossinfo.boss && !modeattacking) { - if (singleplayercontext) + if (!K_CanChangeRules(true)) { - if (grandprixinfo.gp) + if (itembreaker) { timelimitintics = (20*TICRATE); } @@ -162,8 +164,6 @@ void K_TimerInit(void) timelimitintics = cv_timelimit.value * (60*TICRATE); } } - - K_BattleInit(singleplayercontext); } UINT32 K_GetPlayerDontDrawFlag(player_t *player) From 2a6627a1b86418be083941badd6535a23225268a Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 16 Oct 2022 14:33:52 +0100 Subject: [PATCH 27/29] Always show timelimit if timelimitintics is set, to allow spotting weird bugs before they cause problems --- src/k_hud.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index 633898fa2..d5ff97af4 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1621,9 +1621,7 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UI { splitflags = V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_SPLITSCREEN; - if (bossinfo.boss == true) - ; - else if (timelimitintics > 0 && (gametyperules & GTR_TIMELIMIT)) // TODO + if (timelimitintics > 0) { /* if (drawtime >= (timelimitintics - 5*TICRATE) && ((drawtime*4)/TICRATE) % 2 == 0) From d9b8eab1ea1e6b3e01e34ab37674a9ae00fc0363 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 16 Oct 2022 14:54:13 +0100 Subject: [PATCH 28/29] No contesters use the position of their broken kart on the minimap This is instead of their flung body, which drifts significantly from where you died and muddies the "LOL YOU DIED THAT CLOSE TO THE FINISH!?" voicecall laughter moments --- src/k_hud.c | 26 ++++++++++++++++---------- src/p_inter.c | 3 +++ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index d5ff97af4..916670223 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -4253,7 +4253,7 @@ static void K_drawKartMinimap(void) mobj = players[i].mo; - if (players[i].mo->health <= 0 && players[i].pflags & PF_NOCONTEST) + if (mobj->health <= 0 && (players[i].pflags & PF_NOCONTEST)) { workingPic = kp_nocontestminimap; @@ -4271,10 +4271,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]; @@ -4306,7 +4309,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))) { @@ -4315,10 +4318,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); @@ -4487,7 +4490,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; @@ -4507,10 +4510,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]; @@ -4541,7 +4547,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) > @@ -4552,10 +4558,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); diff --git a/src/p_inter.c b/src/p_inter.c index c7a7900d0..67cee3667 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1644,6 +1644,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; From 92f17189b0ccc04336d9eb5698eb7c968cdb7da9 Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Thu, 23 Oct 2025 23:15:26 +0200 Subject: [PATCH 29/29] Repair and interpolate overtime/jitters --- src/k_hud.c | 76 +++++++++++++++++++++++++++------------------------ src/p_inter.c | 42 ++++++++++++++++++++++------ src/p_tick.c | 42 ---------------------------- src/v_video.h | 2 ++ 4 files changed, 75 insertions(+), 87 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index 916670223..20c77b1c5 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1612,9 +1612,9 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UI // TIME_Y = 6; // 6 tic_t worktime; - INT32 jitter = 0; - 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) @@ -1623,37 +1623,26 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UI if (timelimitintics > 0) { - /* - if (drawtime >= (timelimitintics - 5*TICRATE) && ((drawtime*4)/TICRATE) % 2 == 0) - { - dontdraw = true; - } - */ if (drawtime >= timelimitintics) { overtime = true; - jitter = 2; - if (drawtime & 2) - jitter = -jitter; - drawtime = 0; + jitter = 1; } else { drawtime = timelimitintics - drawtime; + countdown = true; if (secretextratime) ; else if (extratimeintics) { jitter = 2; - if (leveltime & 1) - jitter = -jitter; + fastjitter = true; } else if (drawtime <= 5*TICRATE) { jitter = ((drawtime <= 3*TICRATE) && (((drawtime-1) % TICRATE) >= TICRATE-2)) ? 3 : 1; - if (drawtime & 2) - jitter = -jitter; } } } @@ -1673,11 +1662,22 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UI if (drawtime != UINT32_MAX && worktime >= 100) { - jitter = (drawtime & 1 ? 1 : -1); + 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 (overtime) @@ -1691,24 +1691,28 @@ 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!") - ); + const char *overtimestr = "OVERTIME"; + for (UINT8 i = 0; i < strlen(overtimestr); i++) + { + 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]) + ); + } } else { // 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)); + V_DrawKartStringAtFixed(TX< 0) { if (secretextratime) { @@ -1043,11 +1044,22 @@ void P_CheckTimeLimit(void) } else { - if (timelimitintics + starttime - leveltime <= 3*TICRATE) + if (timelimitintics > 60*TICRATE && !itembreaker) { - if (((timelimitintics + starttime - leveltime) % TICRATE) == 0) - S_StartSound(NULL, sfx_s3ka7); - } + 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 :)))) + } + + // 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; } @@ -1055,7 +1067,7 @@ void P_CheckTimeLimit(void) if (gameaction == ga_completed) return; - if ((grandprixinfo.gp == false) && (cv_overtime.value)) + if (!itembreaker && !grandprixinfo.gp && cv_overtime.value) { INT32 playerarray[MAXPLAYERS]; INT32 tempplayer = 0; @@ -1074,7 +1086,7 @@ void P_CheckTimeLimit(void) #ifdef TESTOVERTIMEINFREEPLAY if (timelimitintics > 0 && (gametyperules & GTR_TIMELIMIT) && startedInFreePlay) { - return; + goto overtimesound; } #endif @@ -1116,18 +1128,30 @@ 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. diff --git a/src/p_tick.c b/src/p_tick.c index 86fdde2ed..d07cef0e2 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -840,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; 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);