diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4e68db60b..e0313a411 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -64,6 +64,8 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 tables.c r_bsp.c r_data.c + r_debug.cpp + r_debug_parser.cpp r_draw.c r_fps.c r_main.c @@ -134,6 +136,8 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 k_profiles.c k_specialstage.c k_roulette.c + k_podium.c + k_rank.c ) if(SRB2_CONFIG_ENABLE_WEBM_MOVIES) diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index 6c17767a5..92aaccbf2 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -50,6 +50,7 @@ extern "C" { #include "../m_cond.h" #include "../r_skins.h" #include "../k_battle.h" +#include "../k_podium.h" } #include "call-funcs.hpp" @@ -445,6 +446,25 @@ bool CallFunc_PolyWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Wo return true; // Execution interrupted } +/*-------------------------------------------------- + bool CallFunc_CameraWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Pauses the thread until the tagged + camera is done moving. +--------------------------------------------------*/ +bool CallFunc_CameraWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argC; + + thread->state = { + ACSVM::ThreadState::WaitTag, + argV[0], + ACS_TAGTYPE_CAMERA + }; + + return true; // Execution interrupted +} + /*-------------------------------------------------- bool CallFunc_ChangeFloor(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) @@ -1335,3 +1355,59 @@ bool CallFunc_EncoreMode(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM:: thread->dataStk.push(encoremode); return false; } + +/*-------------------------------------------------- + bool CallFunc_PodiumPosition(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns the best position of all non-CPU players. +--------------------------------------------------*/ +bool CallFunc_PodiumPosition(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + UINT8 ret = MAXPLAYERS; + INT32 i; + + (void)argV; + (void)argC; + + 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->bot == true) + { + continue; + } + + ret = std::min(ret, player->position); + } + + thread->dataStk.push(ret); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_PodiumFinish(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Ends the podium sequence. Doesn't do anything + outside of podium maps. +--------------------------------------------------*/ +bool CallFunc_PodiumFinish(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + K_FinishCeremony(); + return false; +} diff --git a/src/acs/call-funcs.hpp b/src/acs/call-funcs.hpp index 4bbe3eaec..04e4b59b7 100644 --- a/src/acs/call-funcs.hpp +++ b/src/acs/call-funcs.hpp @@ -50,6 +50,7 @@ bool CallFunc_Random(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word bool CallFunc_ThingCount(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_TagWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PolyWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_CameraWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_ChangeFloor(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_ChangeCeiling(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_LineSide(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); @@ -84,4 +85,7 @@ bool CallFunc_PlayerLap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::W bool CallFunc_LowestLap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_EncoreMode(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_PodiumPosition(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_PodiumFinish(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); + #endif // __SRB2_ACS_CALL_FUNCS_HPP__ diff --git a/src/acs/environment.cpp b/src/acs/environment.cpp index 9c5c5ca61..470774f22 100644 --- a/src/acs/environment.cpp +++ b/src/acs/environment.cpp @@ -36,6 +36,7 @@ extern "C" { #include "../p_spec.h" #include "../w_wad.h" #include "../z_zone.h" +#include "../p_local.h" } #include "environment.hpp" @@ -160,6 +161,10 @@ Environment::Environment() addFuncDataACS0( 307, addCallFunc(CallFunc_PlayerLap)); addFuncDataACS0( 308, addCallFunc(CallFunc_LowestLap)); addFuncDataACS0( 309, addCallFunc(CallFunc_EncoreMode)); + + addFuncDataACS0( 500, addCallFunc(CallFunc_CameraWait)); + addFuncDataACS0( 501, addCallFunc(CallFunc_PodiumPosition)); + addFuncDataACS0( 502, addCallFunc(CallFunc_PodiumFinish)); } ACSVM::Thread *Environment::allocThread() @@ -262,6 +267,17 @@ bool Environment::checkTag(ACSVM::Word type, ACSVM::Word tag) const polyobj_t *po = Polyobj_GetForNum(tag); return (po == nullptr || po->thinker == nullptr); } + + case ACS_TAGTYPE_CAMERA: + { + const mobj_t *camera = P_FindObjectTypeFromTag(MT_ALTVIEWMAN, tag); + if (camera == nullptr || camera->spawnpoint == nullptr) + { + return true; + } + + return (camera->tracer == nullptr || P_MobjWasRemoved(camera->tracer) == true); + } } return true; diff --git a/src/acs/thread.hpp b/src/acs/thread.hpp index 4c3c307c2..7e528608a 100644 --- a/src/acs/thread.hpp +++ b/src/acs/thread.hpp @@ -57,6 +57,7 @@ enum acs_tagType_e { ACS_TAGTYPE_POLYOBJ, ACS_TAGTYPE_SECTOR, + ACS_TAGTYPE_CAMERA, }; class ThreadInfo : public ACSVM::ThreadInfo diff --git a/src/console.c b/src/console.c index a86db72f2..aac5c2c5f 100644 --- a/src/console.c +++ b/src/console.c @@ -1839,6 +1839,25 @@ static void CON_DrawConsole(void) // Console refresh drawer, call each frame // +static boolean CON_GamestateDrawHudLines(void) +{ + switch (gamestate) + { + case GS_LEVEL: + case GS_INTERMISSION: + case GS_VOTING: + case GS_CUTSCENE: + case GS_CREDITS: + case GS_EVALUATION: + case GS_WAITINGPLAYERS: + case GS_CEREMONY: + return true; + + default: + return false; + } +} + void CON_Drawer(void) { Lock_state(); @@ -1858,8 +1877,7 @@ void CON_Drawer(void) if (con_curlines > 0) CON_DrawConsole(); - else if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_CUTSCENE || gamestate == GS_CREDITS - || gamestate == GS_VOTING || gamestate == GS_EVALUATION || gamestate == GS_WAITINGPLAYERS) + else if (CON_GamestateDrawHudLines() == true) CON_DrawHudlines(); Unlock_state(); diff --git a/src/cxxutil.hpp b/src/cxxutil.hpp index 56b85c79b..835d5e74f 100644 --- a/src/cxxutil.hpp +++ b/src/cxxutil.hpp @@ -2,6 +2,7 @@ #define __SRB2_CXXUTIL_HPP__ #include +#include #include #include diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 43df8ade3..5e309717c 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1272,7 +1272,7 @@ static void CL_LoadReceivedSavegame(boolean reloading) paused = false; demo.playback = false; demo.title = false; - titlemapinaction = TITLEMAP_OFF; + titlemapinaction = false; automapactive = false; // load a base level @@ -2542,7 +2542,7 @@ void CL_ClearPlayer(INT32 playernum) int i; // Handle mobj_t pointers. - if (gamestate == GS_LEVEL) + if (G_GamestateUsesLevel() == true) { if (players[playernum].follower) { @@ -2557,10 +2557,11 @@ void CL_ClearPlayer(INT32 playernum) P_SetTarget(&players[playernum].skybox.viewpoint, NULL); P_SetTarget(&players[playernum].skybox.centerpoint, NULL); - P_SetTarget(&players[playernum].awayviewmobj, NULL); + P_SetTarget(&players[playernum].awayview.mobj, NULL); P_SetTarget(&players[playernum].followmobj, NULL); P_SetTarget(&players[playernum].hoverhyudoro, NULL); P_SetTarget(&players[playernum].stumbleIndicator, NULL); + P_SetTarget(&players[playernum].sliptideZipIndicator, NULL); } // Handle parties. @@ -3765,9 +3766,6 @@ static void Got_AddBot(UINT8 **p, INT32 playernum) sprintf(player_names[newplayernum], "%s", skins[skinnum].realname); SetPlayerSkinByNum(newplayernum, skinnum); - players[newplayernum].spectator = !(gametyperules & GTR_BOTS) - || (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE); - if (netgame) { HU_AddChatText(va("\x82*Bot %d has been added to the game", newplayernum+1), false); diff --git a/src/d_main.c b/src/d_main.c index ca62f29c9..3b34e4ec4 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -76,6 +76,7 @@ #include "m_random.h" // P_ClearRandom #include "k_specialstage.h" #include "acs/interface.h" +#include "k_podium.h" #ifdef HWRENDER #include "hardware/hw_main.h" // 3D View Rendering @@ -178,6 +179,7 @@ boolean capslock = 0; // gee i wonder what this does. void D_ProcessEvents(void) { event_t *ev; + int i; boolean eaten; boolean menuresponse = false; @@ -251,6 +253,12 @@ void D_ProcessEvents(void) { M_MapMenuControls(NULL); } + + // Update menu CMD + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + M_UpdateMenuCMD(i); + } } // @@ -337,8 +345,8 @@ static void D_Display(void) if (rendermode != render_none) { // Fade to black first - if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)) // fades to black on its own timing, always - && wipetypepre != UINT8_MAX) + if (G_GamestateUsesLevel() == false // fades to black on its own timing, always + && wipetypepre != UINT8_MAX) { F_WipeStartScreen(); F_WipeColorFill(31); @@ -346,7 +354,7 @@ static void D_Display(void) F_RunWipe(wipedefindex, wipetypepre, gamestate != GS_MENU, "FADEMAP0", false, false); } - if (gamestate != GS_LEVEL && rendermode != render_none) + if (G_GamestateUsesLevel() == false && rendermode != render_none) { V_SetPaletteLump("PLAYPAL"); // Reset the palette R_ReInitColormaps(0, NULL, 0); @@ -399,6 +407,13 @@ static void D_Display(void) HU_Drawer(); break; + case GS_CEREMONY: + if (!gametic) + break; + HU_Erase(); + HU_Drawer(); + break; + case GS_MENU: break; @@ -463,7 +478,7 @@ static void D_Display(void) // clean up border stuff // see if the border needs to be initially drawn - if (gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction && curbghide && (!hidetitlemap))) + if (G_GamestateUsesLevel() == true) { if (!automapactive && !dedicated && cv_renderview.value) { @@ -559,14 +574,30 @@ static void D_Display(void) ps_uitime = I_GetPreciseTime(); - if (gamestate == GS_LEVEL) + switch (gamestate) { - ST_Drawer(); - F_TextPromptDrawer(); - HU_Drawer(); + case GS_LEVEL: + { + ST_Drawer(); + F_TextPromptDrawer(); + HU_Drawer(); + break; + } + case GS_TITLESCREEN: + { + F_TitleScreenDrawer(); + break; + } + case GS_CEREMONY: + { + K_CeremonyDrawer(); + break; + } + default: + { + break; + } } - else - F_TitleScreenDrawer(); } else { @@ -576,7 +607,7 @@ static void D_Display(void) // change gamma if needed // (GS_LEVEL handles this already due to level-specific palettes) - if (forcerefresh && !(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction))) + if (forcerefresh && G_GamestateUsesLevel() == false) V_SetPalette(0); // draw pause pic @@ -745,17 +776,6 @@ void D_SRB2Loop(void) because I_FinishUpdate was called afterward */ -#if 0 - /* Smells like a hack... Don't fade Sonic's ass into the title screen. */ - if (gamestate != GS_TITLESCREEN) - { - static lumpnum_t gstartuplumpnum = W_CheckNumForName("STARTUP"); - if (gstartuplumpnum == LUMPERROR) - gstartuplumpnum = W_GetNumForName("MISSING"); - V_DrawScaledPatch(0, 0, 0, W_CachePatchNum(gstartuplumpnum, PU_PATCH)); - } -#endif - for (;;) { // capbudget is the minimum precise_t duration of a single loop iteration diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 71483bc61..a1fae65f2 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -453,6 +453,7 @@ consvar_t cv_kartdebugcolorize = CVAR_INIT ("debugcolorize", "Off", CV_CHEAT, CV consvar_t cv_kartdebugdirector = CVAR_INIT ("debugdirector", "Off", CV_CHEAT, CV_OnOff, NULL); consvar_t cv_spbtest = CVAR_INIT ("spbtest", "Off", CV_CHEAT|CV_NETVAR, CV_OnOff, NULL); consvar_t cv_gptest = CVAR_INIT ("gptest", "Off", CV_CHEAT|CV_NETVAR, CV_OnOff, NULL); +consvar_t cv_debugrank = CVAR_INIT ("debugrank", "Off", CV_CHEAT, CV_OnOff, NULL); static CV_PossibleValue_t capsuletest_cons_t[] = { {CV_CAPSULETEST_OFF, "Off"}, @@ -5330,14 +5331,22 @@ static void Command_Mapmd5_f(void) static void Command_ExitLevel_f(void) { - if (!(netgame || multiplayer) && !CV_CheatsEnabled()) - CONS_Printf(M_GetText("This only works in a netgame.\n")); - else if (!(server || (IsPlayerAdmin(consoleplayer)))) + if (!(server || (IsPlayerAdmin(consoleplayer)))) + { CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n")); + } + else if (K_CanChangeRules(false) == false && CV_CheatsEnabled() == false) + { + CONS_Printf(M_GetText("This cannot be used without cheats enabled.\n")); + } else if (( gamestate != GS_LEVEL && gamestate != GS_CREDITS ) || demo.playback) + { CONS_Printf(M_GetText("You must be in a level to use this.\n")); + } else + { SendNetXCmd(XD_EXITLEVEL, NULL, 0); + } } static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum) diff --git a/src/d_netcmd.h b/src/d_netcmd.h index a126de023..e6c10fa11 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -96,6 +96,7 @@ extern consvar_t cv_kartdebugitem, cv_kartdebugamount, cv_kartdebugdistribution, extern consvar_t cv_kartdebugnodes, cv_kartdebugcolorize, cv_kartdebugdirector; extern consvar_t cv_spbtest, cv_gptest, cv_reducevfx; extern consvar_t cv_kartdebugwaypoints, cv_kartdebugbotpredict; +extern consvar_t cv_debugrank; typedef enum { CV_CAPSULETEST_OFF, diff --git a/src/d_player.h b/src/d_player.h index 12a0ca6eb..a96102ecd 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -404,6 +404,13 @@ typedef struct { boolean flip; } sonicloopvars_t; +// player_t struct for all alternative viewpoint variables +struct altview_t +{ + mobj_t *mobj; + INT32 tics; +}; + // ======================================================================== // PLAYER STRUCTURE // ======================================================================== @@ -647,6 +654,7 @@ struct player_t tic_t realtime; // integer replacement for leveltime UINT8 laps; // Number of laps (optional) UINT8 latestlap; + UINT32 lapPoints; // Points given from laps INT32 starpostnum; // The number of the last starpost you hit UINT8 ctfteam; // 0 == Spectator, 1 == Red, 2 == Blue @@ -664,9 +672,7 @@ struct player_t INT32 onconveyor; // You are on a conveyor belt if nonzero - mobj_t *awayviewmobj; - INT32 awayviewtics; - angle_t awayviewaiming; // Used for cut-away view + altview_t awayview; boolean spectator; tic_t spectatewait; // reimplementable as UINT8 queue - How long have you been waiting as a spectator @@ -695,7 +701,12 @@ struct player_t UINT8 tripwireReboundDelay; // When failing Tripwire, brieftly lock out speed-based tripwire pass (anti-cheese) + UINT16 sliptideZip; // How long is our chained sliptide? Grant a proportional boost when it's over. + UINT8 sliptideZipDelay; // How long since the last sliptide? Only boost once you've been straightened out for a bit. + UINT16 sliptideZipBoost; // The actual boost granted from sliptideZip. + mobj_t *stumbleIndicator; + mobj_t *sliptideZipIndicator; #ifdef HWRENDER fixed_t fovadd; // adjust FOV for hw rendering diff --git a/src/deh_soc.c b/src/deh_soc.c index 5519e7d67..d6c5e8fbf 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2767,6 +2767,10 @@ void readmaincfg(MYFILE *f, boolean mainfile) INT32 value; boolean doClearLevels = false; +#ifdef DEVELOP + (void)mainfile; +#endif + do { if (myfgets(s, MAXLINELEN, f)) @@ -2842,10 +2846,12 @@ void readmaincfg(MYFILE *f, boolean mainfile) M_ClearStats(); M_ClearSecrets(); } +#ifndef DEVELOP else if (!mainfile && !gamedataadded) { deh_warning("You must define a custom gamedata to use \"%s\"", word); } +#endif else if (fastcmp(word, "CLEARLEVELS")) { doClearLevels = (UINT8)(value == 0 || word2[0] == 'F' || word2[0] == 'N'); @@ -2953,6 +2959,7 @@ void readmaincfg(MYFILE *f, boolean mainfile) } else if (fastcmp(word, "TITLEMAP")) { + Z_Free(titlemap); titlemap = Z_StrDup(word2); titlechanged = true; } @@ -3047,13 +3054,20 @@ void readmaincfg(MYFILE *f, boolean mainfile) } else if (fastcmp(word, "BOOTMAP")) { + Z_Free(bootmap); bootmap = Z_StrDup(word2); //titlechanged = true; } else if (fastcmp(word, "TUTORIALMAP")) { + Z_Free(tutorialmap); tutorialmap = Z_StrDup(word2); } + else if (fastcmp(word, "PODIUMMAP")) + { + Z_Free(podiummap); + podiummap = Z_StrDup(word2); + } else deh_warning("Maincfg: unknown word '%s'", word); } @@ -3192,6 +3206,14 @@ void readwipes(MYFILE *f) else if (fastcmp(pword, "FINAL")) wipeoffset = wipe_gameend_final; } + else if (fastncmp(word, "CEREMONY_", 9)) + { + pword = word + 9; + if (fastcmp(pword, "TOBLACK")) + wipeoffset = wipe_ceremony_toblack; + else if (fastcmp(pword, "FINAL")) + wipeoffset = wipe_ceremony_final; + } else if (fastncmp(word, "ENCORE_", 7)) { pword = word + 7; diff --git a/src/deh_tables.c b/src/deh_tables.c index de88639e8..9cae9323e 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -3304,6 +3304,8 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_MAGICIANBOXTOP", "S_MAGICIANBOXBOTTOM", + "S_SLIPTIDEZIP", + // Signpost sparkles "S_SIGNSPARK1", "S_SIGNSPARK2", @@ -5323,6 +5325,8 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_MONITOR_PART", "MT_MONITOR_SHARD", "MT_MAGICIANBOX", + + "MT_SLIPTIDEZIP", "MT_SIGNSPARKLE", @@ -6303,6 +6307,7 @@ struct int_const_s const INT_CONST[] = { {"RF_FULLDARK",RF_FULLDARK}, {"RF_SEMIBRIGHT",RF_SEMIBRIGHT}, {"RF_NOCOLORMAPS",RF_NOCOLORMAPS}, + {"RF_ALWAYSONTOP",RF_ALWAYSONTOP}, {"RF_SPRITETYPEMASK",RF_SPRITETYPEMASK}, {"RF_PAPERSPRITE",RF_PAPERSPRITE}, {"RF_FLOORSPRITE",RF_FLOORSPRITE}, diff --git a/src/doomstat.h b/src/doomstat.h index 88e9bd66d..33c200dbf 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -212,6 +212,8 @@ extern char * bootmap; //bootmap for loading a map on startup extern char * tutorialmap; // map to load for tutorial extern boolean tutorialmode; // are we in a tutorial right now? +extern char * podiummap; // map to load for podium + extern boolean looptitle; // CTF colors. @@ -697,7 +699,6 @@ extern UINT8 gamespeed; extern boolean franticitems; extern boolean encoremode, prevencoremode; -extern UINT32 g_hiscore; extern tic_t wantedcalcdelay; extern tic_t itemCooldowns[NUMKARTITEMS - 1]; extern tic_t mapreset; diff --git a/src/f_finale.c b/src/f_finale.c index 80e8e7d4e..b591a77a4 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -44,13 +44,14 @@ // SRB2Kart #include "k_menu.h" +#include "k_grandprix.h" // Stage of animation: // 0 = text, 1 = art screen INT32 finalecount; INT32 titlescrollxspeed = 16; INT32 titlescrollyspeed = 0; -UINT8 titlemapinaction = TITLEMAP_OFF; +boolean titlemapinaction = false; static INT32 timetonext; // Delay between screen changes @@ -62,7 +63,7 @@ static tic_t stoptimer; static boolean keypressed = false; static INT32 menuanimtimer; // Title screen: background animation timing -mobj_t *titlemapcameraref = NULL; +altview_t titlemapcam = {0}; // menu presentation state char curbgname[9]; @@ -1835,14 +1836,14 @@ void F_StartTitleScreen(void) mapthing_t *startpos; gamestate_t prevwipegamestate = wipegamestate; - titlemapinaction = TITLEMAP_LOADING; - titlemapcameraref = NULL; + titlemapinaction = true; + P_SetTarget(&titlemapcam.mobj, NULL); gamemap = titleMapNum+1; maptol = mapheaderinfo[titleMapNum]->typeoflevel; globalweather = mapheaderinfo[titleMapNum]->weather; - G_DoLoadLevel(true); + G_DoLoadLevelEx(true, GS_TITLESCREEN); if (!titlemap) return; @@ -1878,13 +1879,12 @@ void F_StartTitleScreen(void) } else { - titlemapinaction = TITLEMAP_OFF; + G_SetGamestate(GS_TITLESCREEN); + titlemapinaction = false; gamemap = 1; // g_game.c CON_ClearHUD(); } - G_SetGamestate(GS_TITLESCREEN); - // IWAD dependent stuff. animtimer = skullAnimCounter = 0; @@ -2139,7 +2139,7 @@ void F_TitleScreenTicker(boolean run) mobj_t *cameraref = NULL; // If there's a Line 422 Switch Cut-Away view, don't force us. - if (!titlemapcameraref || titlemapcameraref->type != MT_ALTVIEWMAN) + if (titlemapcam.mobj == NULL || titlemapcam.mobj->type != MT_ALTVIEWMAN) { for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) { @@ -2154,20 +2154,27 @@ void F_TitleScreenTicker(boolean run) if (mo2->type != MT_ALTVIEWMAN) continue; - cameraref = titlemapcameraref = mo2; + cameraref = mo2; break; } + + if (cameraref != NULL) + { + P_SetTarget(&titlemapcam.mobj, cameraref); + } } else - cameraref = titlemapcameraref; + { + cameraref = titlemapcam.mobj; + } - if (cameraref) + if (cameraref != NULL) { camera[0].x = cameraref->x; camera[0].y = cameraref->y; camera[0].z = cameraref->z; camera[0].angle = cameraref->angle; - camera[0].aiming = cameraref->cusval; + camera[0].aiming = cameraref->pitch; camera[0].subsector = cameraref->subsector; } else diff --git a/src/f_finale.h b/src/f_finale.h index c5972f9d1..4149f111c 100644 --- a/src/f_finale.h +++ b/src/f_finale.h @@ -104,17 +104,10 @@ extern INT16 ttloop; extern UINT16 tttics; extern boolean ttavailable[6]; - -typedef enum -{ - TITLEMAP_OFF = 0, - TITLEMAP_LOADING, - TITLEMAP_RUNNING -} titlemap_enum; - // Current menu parameters -extern mobj_t *titlemapcameraref; +extern altview_t titlemapcam; + extern char curbgname[9]; extern SINT8 curfadevalue; extern INT32 curbgcolor; @@ -184,6 +177,7 @@ enum wipe_credits_toblack, wipe_evaluation_toblack, wipe_gameend_toblack, + wipe_ceremony_toblack, wipe_intro_toblack, wipe_ending_toblack, wipe_cutscene_toblack, @@ -202,6 +196,7 @@ enum wipe_credits_final, wipe_evaluation_final, wipe_gameend_final, + wipe_ceremony_final, wipe_intro_final, wipe_ending_final, wipe_cutscene_final, diff --git a/src/f_wipe.c b/src/f_wipe.c index 227573c46..105358407 100644 --- a/src/f_wipe.c +++ b/src/f_wipe.c @@ -64,6 +64,7 @@ UINT8 wipedefs[NUMWIPEDEFS] = { 99, // wipe_credits_toblack 0, // wipe_evaluation_toblack 0, // wipe_gameend_toblack + 0, // wipe_ceremony_toblack UINT8_MAX, // wipe_intro_toblack (hardcoded) 99, // wipe_ending_toblack (hardcoded) 99, // wipe_cutscene_toblack (hardcoded) @@ -80,6 +81,7 @@ UINT8 wipedefs[NUMWIPEDEFS] = { 99, // wipe_credits_final 0, // wipe_evaluation_final 0, // wipe_gameend_final + 0, // wipe_ceremony_final 99, // wipe_intro_final (hardcoded) 99, // wipe_ending_final (hardcoded) 99 // wipe_cutscene_final (hardcoded) @@ -88,15 +90,16 @@ UINT8 wipedefs[NUMWIPEDEFS] = { static boolean g_wipedef_toblack[NUMWIPEDEFS] = { true, // wipe_credits_intermediate (0) - true, // wipe_level_toblack - true, // wipe_intermission_toblack - true, // wipe_voting_toblack, - true, // wipe_continuing_toblack - true, // wipe_titlescreen_toblack - true, // wipe_menu_toblack + true, // wipe_level_toblack + true, // wipe_intermission_toblack + true, // wipe_voting_toblack, + true, // wipe_continuing_toblack + true, // wipe_titlescreen_toblack + true, // wipe_menu_toblack true, // wipe_credits_toblack - true, // wipe_evaluation_toblack - true, // wipe_gameend_toblack + true, // wipe_evaluation_toblack + true, // wipe_gameend_toblack + true, // wipe_ceremony_toblack true, // wipe_intro_toblack (hardcoded) true, // wipe_ending_toblack (hardcoded) true, // wipe_cutscene_toblack (hardcoded) @@ -105,14 +108,15 @@ static boolean g_wipedef_toblack[NUMWIPEDEFS] = { false, // wipe_encore_towhite true, // wipe_level_final - true, // wipe_intermission_final - true, // wipe_voting_final - true, // wipe_continuing_final - true, // wipe_titlescreen_final - true, // wipe_menu_final + true, // wipe_intermission_final + true, // wipe_voting_final + true, // wipe_continuing_final + true, // wipe_titlescreen_final + true, // wipe_menu_final true, // wipe_credits_final - true, // wipe_evaluation_final - true, // wipe_gameend_final + true, // wipe_evaluation_final + true, // wipe_gameend_final + true, // wipe_ceremony_final true, // wipe_intro_final (hardcoded) true, // wipe_ending_final (hardcoded) true // wipe_cutscene_final (hardcoded) @@ -121,15 +125,16 @@ static boolean g_wipedef_toblack[NUMWIPEDEFS] = { static boolean g_wipedef_toinvert[NUMWIPEDEFS] = { false, // wipe_credits_intermediate (0) - false, // wipe_level_toblack - false, // wipe_intermission_toblack - false, // wipe_voting_toblack, - false, // wipe_continuing_toblack - false, // wipe_titlescreen_toblack - false, // wipe_menu_toblack + false, // wipe_level_toblack + false, // wipe_intermission_toblack + false, // wipe_voting_toblack, + false, // wipe_continuing_toblack + false, // wipe_titlescreen_toblack + false, // wipe_menu_toblack false, // wipe_credits_toblack - false, // wipe_evaluation_toblack - false, // wipe_gameend_toblack + false, // wipe_evaluation_toblack + false, // wipe_gameend_toblack + false, // wipe_ceremony_toblack false, // wipe_intro_toblack (hardcoded) false, // wipe_ending_toblack (hardcoded) false, // wipe_cutscene_toblack (hardcoded) @@ -138,14 +143,15 @@ static boolean g_wipedef_toinvert[NUMWIPEDEFS] = { false, // wipe_encore_towhite false, // wipe_level_final - false, // wipe_intermission_final - false, // wipe_voting_final - false, // wipe_continuing_final - false, // wipe_titlescreen_final - false, // wipe_menu_final + false, // wipe_intermission_final + false, // wipe_voting_final + false, // wipe_continuing_final + false, // wipe_titlescreen_final + false, // wipe_menu_final false, // wipe_credits_final - false, // wipe_evaluation_final - false, // wipe_gameend_final + false, // wipe_evaluation_final + false, // wipe_gameend_final + false, // wipe_ceremony_final false, // wipe_intro_final (hardcoded) false, // wipe_ending_final (hardcoded) false // wipe_cutscene_final (hardcoded) @@ -154,15 +160,16 @@ static boolean g_wipedef_toinvert[NUMWIPEDEFS] = { static boolean g_wipedef_towhite[NUMWIPEDEFS] = { false, // wipe_credits_intermediate (0) - false, // wipe_level_toblack - false, // wipe_intermission_toblack - false, // wipe_voting_toblack, - false, // wipe_continuing_toblack - false, // wipe_titlescreen_toblack - false, // wipe_menu_toblack + false, // wipe_level_toblack + false, // wipe_intermission_toblack + false, // wipe_voting_toblack, + false, // wipe_continuing_toblack + false, // wipe_titlescreen_toblack + false, // wipe_menu_toblack false, // wipe_credits_toblack - false, // wipe_evaluation_toblack - false, // wipe_gameend_toblack + false, // wipe_evaluation_toblack + false, // wipe_gameend_toblack + false, // wipe_ceremony_toblack false, // wipe_intro_toblack (hardcoded) false, // wipe_ending_toblack (hardcoded) false, // wipe_cutscene_toblack (hardcoded) @@ -171,14 +178,15 @@ static boolean g_wipedef_towhite[NUMWIPEDEFS] = { true, // wipe_encore_towhite false, // wipe_level_final - false, // wipe_intermission_final - false, // wipe_voting_final - false, // wipe_continuing_final - false, // wipe_titlescreen_final - false, // wipe_menu_final + false, // wipe_intermission_final + false, // wipe_voting_final + false, // wipe_continuing_final + false, // wipe_titlescreen_final + false, // wipe_menu_final false, // wipe_credits_final - false, // wipe_evaluation_final - false, // wipe_gameend_final + false, // wipe_evaluation_final + false, // wipe_gameend_final + false, // wipe_ceremony_final false, // wipe_intro_final (hardcoded) false, // wipe_ending_final (hardcoded) false // wipe_cutscene_final (hardcoded) @@ -187,15 +195,16 @@ static boolean g_wipedef_towhite[NUMWIPEDEFS] = { static boolean g_wipedef_crossfade[NUMWIPEDEFS] = { false, // wipe_credits_intermediate (0) - false, // wipe_level_toblack - false, // wipe_intermission_toblack - false, // wipe_voting_toblack, - false, // wipe_continuing_toblack - false, // wipe_titlescreen_toblack - false, // wipe_menu_toblack + false, // wipe_level_toblack + false, // wipe_intermission_toblack + false, // wipe_voting_toblack, + false, // wipe_continuing_toblack + false, // wipe_titlescreen_toblack + false, // wipe_menu_toblack false, // wipe_credits_toblack - false, // wipe_evaluation_toblack - false, // wipe_gameend_toblack + false, // wipe_evaluation_toblack + false, // wipe_gameend_toblack + false, // wipe_ceremony_toblack false, // wipe_intro_toblack (hardcoded) false, // wipe_ending_toblack (hardcoded) false, // wipe_cutscene_toblack (hardcoded) @@ -204,14 +213,15 @@ static boolean g_wipedef_crossfade[NUMWIPEDEFS] = { false, // wipe_encore_towhite true, // wipe_level_final - true, // wipe_intermission_final - true, // wipe_voting_final - true, // wipe_continuing_final - true, // wipe_titlescreen_final - true, // wipe_menu_final + true, // wipe_intermission_final + true, // wipe_voting_final + true, // wipe_continuing_final + true, // wipe_titlescreen_final + true, // wipe_menu_final true, // wipe_credits_final - true, // wipe_evaluation_final - true, // wipe_gameend_final + true, // wipe_evaluation_final + true, // wipe_gameend_final + true, // wipe_ceremony_final true, // wipe_intro_final (hardcoded) true, // wipe_ending_final (hardcoded) true // wipe_cutscene_final (hardcoded) diff --git a/src/g_game.c b/src/g_game.c index 59948388c..133b23e45 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -62,6 +62,8 @@ #include "k_bot.h" #include "doomstat.h" #include "k_director.h" +#include "k_podium.h" +#include "k_rank.h" #ifdef HAVE_DISCORDRPC #include "discord.h" @@ -164,6 +166,8 @@ char * bootmap = NULL; //bootmap for loading a map on startup char * tutorialmap = NULL; // map to load for tutorial boolean tutorialmode = false; // are we in a tutorial right now? +char * podiummap = NULL; // map to load for podium + boolean looptitle = true; UINT16 skincolor_redteam = SKINCOLOR_RED; @@ -298,7 +302,6 @@ SINT8 votes[MAXPLAYERS]; // Each player's vote SINT8 pickedvote; // What vote the host rolls // Server-sided, synched variables -UINT32 g_hiscore; // Highest score (points) achieved by anyone in game tic_t wantedcalcdelay; // Time before it recalculates WANTED tic_t itemCooldowns[NUMKARTITEMS - 1]; // Cooldowns to prevent item spawning tic_t mapreset; // Map reset delay when enough players have joined an empty game @@ -1449,9 +1452,9 @@ static void weaponPrefChange4(void) } // -// G_DoLoadLevel +// G_DoLoadLevelEx // -void G_DoLoadLevel(boolean resetplayer) +void G_DoLoadLevelEx(boolean resetplayer, gamestate_t newstate) { boolean doAutomate = false; INT32 i; @@ -1461,7 +1464,7 @@ void G_DoLoadLevel(boolean resetplayer) levelstarttic = gametic; // for time calculation - if (wipegamestate == GS_LEVEL) + if (wipegamestate == newstate) wipegamestate = -1; // force a wipe if (cv_currprofile.value == -1 && !demo.playback) @@ -1478,31 +1481,33 @@ void G_DoLoadLevel(boolean resetplayer) Y_EndVote(); // cleanup - if (titlemapinaction == TITLEMAP_LOADING) + // Is this actually necessary? Doesn't F_StartTitleScreen already do a significantly more comprehensive check? + if (newstate == GS_TITLESCREEN) { - //if (W_CheckNumForName(G_BuildMapName(gamemap)) == LUMPERROR) if (gamemap < 1 || gamemap > nummapheaders) { + G_SetGamestate(GS_TITLESCREEN); + titlemapinaction = false; + Z_Free(titlemap); titlemap = NULL; // let's not infinite recursion ok + Command_ExitGame_f(); return; } - - titlemapinaction = TITLEMAP_RUNNING; } - else - titlemapinaction = TITLEMAP_OFF; // Doing this matches HOSTMOD behavior. // Is that desired? IDK - doAutomate = (gamestate != GS_LEVEL); + doAutomate = (gamestate != GS_LEVEL && newstate == GS_LEVEL); - G_SetGamestate(GS_LEVEL); + G_SetGamestate(newstate); if (wipegamestate == GS_MENU) M_ClearMenus(true); I_UpdateMouseGrab(); + K_ResetCeremony(); + for (i = 0; i < MAXPLAYERS; i++) { if (resetplayer || (playeringame[i] && players[i].playerstate == PST_DEAD)) @@ -1543,6 +1548,11 @@ void G_DoLoadLevel(boolean resetplayer) } } +void G_DoLoadLevel(boolean resetplayer) +{ + G_DoLoadLevelEx(resetplayer, GS_LEVEL); +} + // // Start the title card. // @@ -1573,7 +1583,7 @@ void G_StartTitleCard(void) } // start the title card - WipeStageTitle = (!titlemapinaction); + WipeStageTitle = (gamestate == GS_LEVEL); } // @@ -1717,6 +1727,21 @@ boolean G_Responder(event_t *ev) return true; } } + else if (gamestate == GS_CEREMONY) + { + if (HU_Responder(ev)) + { + hu_keystrokes = true; + return true; // chat ate the event + } + + if (K_CeremonyResponder(ev)) + { + nextmap = NEXTMAP_TITLE; + G_EndGame(); + return true; + } + } else if (gamestate == GS_CONTINUING) { return true; @@ -1727,11 +1752,13 @@ boolean G_Responder(event_t *ev) return true; } else if (gamestate == GS_INTERMISSION || gamestate == GS_VOTING || gamestate == GS_EVALUATION) + { if (HU_Responder(ev)) { hu_keystrokes = true; return true; // chat ate the event } + } if (gamestate == GS_LEVEL && ev->type == ev_keydown && multiplayer && demo.playback && !demo.freecam) { @@ -2099,7 +2126,7 @@ void G_Ticker(boolean run) marathontime++; P_MapStart(); - // do player reborns if needed + if (gamestate == GS_LEVEL) { // Or, alternatively, retry. @@ -2117,11 +2144,28 @@ void G_Ticker(boolean run) D_MapChange(gamemap, gametype, (cv_kartencore.value == 1), false, 1, false, false); } + } + + // do player reborns if needed + if (G_GamestateUsesLevel() == true) + { + boolean changed = false; for (i = 0; i < MAXPLAYERS; i++) + { if (playeringame[i] && players[i].playerstate == PST_REBORN) + { G_DoReborn(i); + changed = true; + } + } + + if (changed == true) + { + K_UpdateAllPlayerPositions(); + } } + P_MapEnd(); // do things to change the game state @@ -2175,7 +2219,6 @@ void G_Ticker(boolean run) F_TextPromptTicker(); AM_Ticker(); HU_Ticker(); - break; case GS_INTERMISSION: @@ -2237,6 +2280,11 @@ void G_Ticker(boolean run) F_TitleScreenTicker(run); break; + case GS_CEREMONY: + P_Ticker(run); + K_CeremonyTicker(run); + break; + case GS_WAITINGPLAYERS: if (netgame) F_WaitingPlayersTicker(); @@ -2336,6 +2384,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) INT16 totalring; UINT8 laps; UINT8 latestlap; + UINT32 lapPoints; UINT16 skincolor; INT32 skin; UINT8 availabilities[MAXAVAILABILITY]; @@ -2459,6 +2508,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) nocontrol = 0; laps = 0; latestlap = 0; + lapPoints = 0; roundscore = 0; exiting = 0; khudfinish = 0; @@ -2496,6 +2546,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) laps = players[player].laps; latestlap = players[player].latestlap; + lapPoints = players[player].lapPoints; roundscore = players[player].roundscore; @@ -2523,7 +2574,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) { follower = players[player].follower; P_SetTarget(&players[player].follower, NULL); - P_SetTarget(&players[player].awayviewmobj, NULL); + P_SetTarget(&players[player].awayview.mobj, NULL); P_SetTarget(&players[player].stumbleIndicator, NULL); P_SetTarget(&players[player].followmobj, NULL); @@ -2571,6 +2622,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->laps = laps; p->latestlap = latestlap; + p->lapPoints = lapPoints; p->totalring = totalring; p->bot = bot; @@ -2730,7 +2782,7 @@ void G_MovePlayerToSpawnOrStarpost(INT32 playernum) #else // Player's first spawn should be at the "map start". // I.e. level load or join mid game. - if (leveltime > starttime && players[playernum].jointime > 0) + if (leveltime > starttime && players[playernum].jointime > 1 && K_PodiumSequence() == false) P_MovePlayerToStarpost(playernum); else P_MovePlayerToSpawn(playernum, G_FindMapStart(playernum)); @@ -2826,7 +2878,9 @@ mapthing_t *G_FindRaceStart(INT32 playernum) // SRB2Kart: figure out player spawn pos from points if (!playeringame[playernum] || players[playernum].spectator) + { return playerstarts[0]; // go to first spot if you're a spectator + } for (i = 0; i < MAXPLAYERS; i++) { @@ -2909,6 +2963,42 @@ mapthing_t *G_FindRaceStart(INT32 playernum) return NULL; } +mapthing_t *G_FindPodiumStart(INT32 playernum) +{ + const boolean doprints = P_IsLocalPlayer(&players[playernum]); + + if (numcoopstarts) + { + UINT8 pos = K_GetPodiumPosition(&players[playernum]) - 1; + UINT8 i; + + if (G_CheckSpot(playernum, playerstarts[pos % numcoopstarts])) + { + return playerstarts[pos % numcoopstarts]; + } + + // Your spot isn't available? Find whatever you can get first. + for (i = 0; i < numcoopstarts; i++) + { + if (G_CheckSpot(playernum, playerstarts[(pos + i) % numcoopstarts])) + { + return playerstarts[(pos + i) % numcoopstarts]; + } + } + + if (doprints) + { + CONS_Alert(CONS_WARNING, M_GetText("Could not spawn at any Podium starts!\n")); + } + + return NULL; + } + + if (doprints) + CONS_Alert(CONS_WARNING, M_GetText("No Podium starts in this map!\n")); + return NULL; +} + // Find a Co-op start, or fallback into other types of starts. static inline mapthing_t *G_FindRaceStartOrFallback(INT32 playernum) { @@ -2945,9 +3035,14 @@ mapthing_t *G_FindMapStart(INT32 playernum) if (!playeringame[playernum]) return NULL; + // -- Podium -- + // Single special behavior + if (K_PodiumSequence() == true) + spawnpoint = G_FindPodiumStart(playernum); + // -- Time Attack -- // Order: Race->DM->CTF - if (K_TimeAttackRules() == true) + else if (K_TimeAttackRules() == true) spawnpoint = G_FindRaceStartOrFallback(playernum); // -- CTF -- @@ -3094,13 +3189,20 @@ void G_ExitLevel(void) if (i == MAXPLAYERS) { // GAME OVER, try again from the start! - - if (netgame) + if (grandprixinfo.gp == true + && grandprixinfo.eventmode == GPEVENT_SPECIAL) { - ; // restart cup here if we do online GP + // We were in a Special Stage. + // We can still progress to the podium when we game over here. + doretry = false; + } + else if (netgame) + { + ; // Restart cup here whenever we do Online GP } else { + // Back to the menu with you. D_QuitNetGame(); CL_Reset(); D_ClearState(); @@ -3109,11 +3211,14 @@ void G_ExitLevel(void) } else { - // Go redo this course. + // We have lives, just redo this one course. G_SetRetryFlag(); } - return; + if (doretry == true) + { + return; + } } gameaction = ga_completed; @@ -3804,20 +3909,9 @@ static void G_GetNextMap(void) // Special stage else if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) { - INT16 totaltotalring = 0; + gp_rank_e grade = K_CalculateGPGrade(&grandprixinfo.rank); - 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) + if (grade >= GRADE_A) // On A rank pace? Then you get a chance for S rank! { const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_SPECIAL]; if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum]) @@ -4068,6 +4162,9 @@ static void G_DoCompleted(void) G_SetGamestate(GS_NULL); wipegamestate = GS_NULL; + grandprixinfo.rank.capsules += numtargets; + grandprixinfo.rank.position = MAXPLAYERS; + for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i]) @@ -4093,6 +4190,11 @@ static void G_DoCompleted(void) } G_PlayerFinishLevel(i); // take away cards and stuff + + if (players[i].bot == false) + { + grandprixinfo.rank.position = min(grandprixinfo.rank.position, K_GetPodiumPosition(&players[i])); + } } } @@ -4302,8 +4404,10 @@ void G_EndGame(void) { if (nextmap == NEXTMAP_CEREMONY) // end game with ceremony { - /*F_StartEnding(); -- temporary - return;*/ + if (K_StartCeremony() == true) + { + return; + } } if (nextmap == NEXTMAP_CREDITS) // end game with credits { @@ -5116,6 +5220,16 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr return; } + if (map == G_MapNumber(podiummap)+1) + { + // Didn't want to do this, but it needs to be here + // for it to work on startup. + if (K_StartCeremony() == true) + { + return; + } + } + gamemap = map; maptol = mapheaderinfo[gamemap-1]->typeoflevel; @@ -5410,6 +5524,22 @@ void G_SetGamestate(gamestate_t newstate) #endif } +boolean G_GamestateUsesLevel(void) +{ + switch (gamestate) + { + case GS_TITLESCREEN: + return titlemapinaction; + + case GS_LEVEL: + case GS_CEREMONY: + return true; + + default: + return false; + } +} + /* These functions handle the exitgame flag. Before, when the user chose to end a game, it happened immediately, which could cause crashes if the game was in the middle of something. Now, a flag @@ -5436,6 +5566,11 @@ boolean G_GetExitGameFlag(void) // Same deal with retrying. void G_SetRetryFlag(void) { + if (retrying == false) + { + grandprixinfo.rank.continuesUsed++; + } + retrying = true; } @@ -5493,4 +5628,3 @@ INT32 G_TicsToMilliseconds(tic_t tics) { return (INT32)((tics%TICRATE) * (1000.00f/TICRATE)); } - diff --git a/src/g_game.h b/src/g_game.h index 788e2ed9b..d85b98187 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -156,6 +156,7 @@ INT32 G_FindMapByNameOrCode(const char *query, char **foundmapnamep); mapthing_t *G_FindTeamStart(INT32 playernum); mapthing_t *G_FindBattleStart(INT32 playernum); mapthing_t *G_FindRaceStart(INT32 playernum); +mapthing_t *G_FindPodiumStart(INT32 playernum); mapthing_t *G_FindMapStart(INT32 playernum); void G_MovePlayerToSpawnOrStarpost(INT32 playernum); void G_SpawnPlayer(INT32 playernum); @@ -164,6 +165,7 @@ void G_SpawnPlayer(INT32 playernum); // A normal game starts at map 1, but a warp test can start elsewhere void G_DeferedInitNew(boolean pencoremode, INT32 map, INT32 pickedchar, UINT8 ssplayers, boolean FLS); +void G_DoLoadLevelEx(boolean resetplayer, gamestate_t newstate); void G_DoLoadLevel(boolean resetplayer); void G_StartTitleCard(void); @@ -250,8 +252,6 @@ void G_LoadGameSettings(void); void G_SetGameModified(boolean silent, boolean major); void G_SetUsedCheats(void); -void G_SetGamestate(gamestate_t newstate); - // Gamedata record shit void G_AllocMainRecordData(INT16 i); void G_ClearRecords(void); diff --git a/src/g_state.h b/src/g_state.h index ed526f056..b78105639 100644 --- a/src/g_state.h +++ b/src/g_state.h @@ -36,6 +36,7 @@ typedef enum GS_CREDITS, // credit sequence GS_EVALUATION, // Evaluation at the end of a game. GS_GAMEEND, // game end sequence - "did you get all those chaos emeralds?" + GS_CEREMONY, // RR: Podium sequence // Hardcoded fades or other fading methods GS_INTRO, // introduction @@ -58,10 +59,13 @@ typedef enum } gameaction_t; extern gamestate_t gamestate; -extern UINT8 titlemapinaction; +extern boolean titlemapinaction; extern UINT8 ultimatemode; // was sk_insane extern gameaction_t gameaction; +void G_SetGamestate(gamestate_t newstate); +boolean G_GamestateUsesLevel(void); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/info.c b/src/info.c index d0509e731..cb00a3bff 100644 --- a/src/info.c +++ b/src/info.c @@ -552,6 +552,8 @@ char sprnames[NUMSPRITES + 1][5] = "IMDB", // Item Monitor Small Shard (Debris) "MTWK", // Item Monitor Glass Twinkle + "SLPT", // Sliptide zip indicator + "WIPD", // Wipeout dust trail "DRIF", // Drift Sparks "BDRF", // Brake drift sparks @@ -3930,6 +3932,8 @@ state_t states[NUMSTATES] = {SPR_MGBT, FF_FLOORSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_MAGICIANBOX_TOP {SPR_MGBB, FF_FLOORSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_MAGICIANBOX_BOTTOM + {SPR_SLPT, FF_PAPERSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_SLIPTIDEZIP + {SPR_SGNS, FF_ADD|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_SIGNSPARK2}, // S_SIGNSPARK1 {SPR_SGNS, FF_ADD|FF_FULLBRIGHT|1, 1, {NULL}, 0, 0, S_SIGNSPARK3}, // S_SIGNSPARK2 {SPR_SGNS, FF_ADD|FF_FULLBRIGHT|2, 1, {NULL}, 0, 0, S_SIGNSPARK4}, // S_SIGNSPARK3 @@ -21281,7 +21285,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 10, // mass 0, // damage sfx_None, // activesound - MF_NOTHINK|MF_NOBLOCKMAP|MF_NOGRAVITY, // flags + MF_NOBLOCKMAP|MF_NOGRAVITY, // flags S_NULL // raisestate }, @@ -22583,6 +22587,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT, // flags S_NULL // raisestate }, + + { // MT_SLIPTIDEZIP + -1, // doomednum + S_SLIPTIDEZIP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 20*FRACUNIT, // height + 0, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, { // MT_SIGNSPARKLE -1, // doomednum diff --git a/src/info.h b/src/info.h index 02e194cb6..3a1921b3c 100644 --- a/src/info.h +++ b/src/info.h @@ -1103,6 +1103,8 @@ typedef enum sprite SPR_IMDB, // Item Monitor Small Shard (Debris) SPR_MTWK, // Item Monitor Glass Twinkle + SPR_SLPT, // Sliptide zip indicator + SPR_WIPD, // Wipeout dust trail SPR_DRIF, // Drift Sparks SPR_BDRF, // Brake drift sparks @@ -4339,6 +4341,8 @@ typedef enum state S_MAGICIANBOX_TOP, S_MAGICIANBOX_BOTTOM, + S_SLIPTIDEZIP, + // Signpost sparkles S_SIGNSPARK1, S_SIGNSPARK2, @@ -6394,6 +6398,7 @@ typedef enum mobj_type MT_MONITOR_PART, MT_MONITOR_SHARD, MT_MAGICIANBOX, + MT_SLIPTIDEZIP, MT_SIGNSPARKLE, diff --git a/src/k_battle.c b/src/k_battle.c index b34c7a151..38b86abaf 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -20,6 +20,7 @@ #include "k_boss.h" // bossinfo.valid #include "p_spec.h" #include "k_objects.h" +#include "k_rank.h" // Battle overtime info struct battleovertime battleovertime; @@ -91,8 +92,10 @@ void K_CheckBumpers(void) { UINT8 i; UINT8 numingame = 0; - UINT32 toproundscore = 0; UINT8 nobumpers = 0; + UINT8 eliminated = 0; + + const boolean singleplayer = (battlecapsules || bossinfo.valid); if (!(gametyperules & GTR_BUMPERS)) return; @@ -110,37 +113,35 @@ void K_CheckBumpers(void) numingame++; - if (players[i].roundscore > toproundscore) - { - toproundscore = players[i].roundscore; - } - if (players[i].bumpers <= 0) // if you don't have any bumpers, you're probably not a winner { nobumpers++; } + + if (players[i].pflags & PF_ELIMINATED) + { + eliminated++; + } } - if (battlecapsules || bossinfo.valid) + if (singleplayer + ? nobumpers > 0 && nobumpers >= numingame + : eliminated >= numingame - 1) { - if (nobumpers > 0 && nobumpers >= numingame) + for (i = 0; i < MAXPLAYERS; i++) { - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i]) - continue; - if (players[i].spectator) - continue; + if (!playeringame[i]) + continue; + if (players[i].spectator) + continue; + + if (singleplayer) players[i].pflags |= PF_NOCONTEST; - P_DoPlayerExit(&players[i]); - } + + P_DoPlayerExit(&players[i]); } return; } - else - { - g_hiscore = toproundscore; - } if (numingame <= 1) { @@ -183,10 +184,8 @@ void K_CheckEmeralds(player_t *player) continue; } - players[i].bumpers = 0; + P_DoPlayerExit(&players[i]); } - - K_CheckBumpers(); } UINT16 K_GetChaosEmeraldColor(UINT32 emeraldType) @@ -791,7 +790,14 @@ void K_BattleInit(boolean singleplayercontext) { if (!playeringame[i] || players[i].spectator) continue; + players[i].bumpers = maxbumpers; + + if (players[i].mo) + { + players[i].mo->health = maxbumpers; + } + K_SpawnPlayerBattleBumpers(players+i); } } diff --git a/src/k_bot.c b/src/k_bot.c index fa3635a94..3a79305bf 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -28,7 +28,7 @@ #include "r_things.h" // numskins #include "k_race.h" // finishBeamLine #include "m_perfstats.h" - +#include "k_podium.h" /*-------------------------------------------------- boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *p) @@ -251,6 +251,9 @@ void K_UpdateMatchRaceBots(void) --------------------------------------------------*/ boolean K_PlayerUsesBotMovement(player_t *player) { + if (K_PodiumSequence() == true) + return true; + if (player->exiting) return true; @@ -1254,6 +1257,63 @@ static INT32 K_HandleBotReverse(player_t *player, ticcmd_t *cmd, botprediction_t return turnamt; } +/*-------------------------------------------------- + static void K_BotPodiumTurning(player_t *player, ticcmd_t *cmd) + + Calculates bot turning for the podium cutscene. +--------------------------------------------------*/ +static void K_BotPodiumTurning(player_t *player, ticcmd_t *cmd) +{ + const angle_t destAngle = R_PointToAngle2( + player->mo->x, player->mo->y, + player->currentwaypoint->mobj->x, player->currentwaypoint->mobj->y + ); + const INT32 delta = AngleDeltaSigned(destAngle, player->mo->angle); + const INT16 handling = K_GetKartTurnValue(player, KART_FULLTURN); + fixed_t mul = FixedDiv(delta, (angle_t)(handling << TICCMD_REDUCE)); + + if (mul > FRACUNIT) + { + mul = FRACUNIT; + } + + if (mul < -FRACUNIT) + { + mul = -FRACUNIT; + } + + cmd->turning = FixedMul(mul, KART_FULLTURN); +} + +/*-------------------------------------------------- + static void K_BuildBotPodiumTiccmd(player_t *player, ticcmd_t *cmd) + + Calculates all bot movement for the podium cutscene. +--------------------------------------------------*/ +static void K_BuildBotPodiumTiccmd(player_t *player, ticcmd_t *cmd) +{ + if (player->currentwaypoint == NULL) + { + // We've reached the end of our path. + // Simply stop moving. + return; + } + + if (K_GetWaypointIsSpawnpoint(player->currentwaypoint) == false) + { + // Hacky flag reuse: slow down before reaching your podium stand. + cmd->forwardmove = MAXPLMOVE * 3 / 4; + } + else + { + cmd->forwardmove = MAXPLMOVE; + } + + cmd->buttons |= BT_ACCELERATE; + + K_BotPodiumTurning(player, cmd); +} + /*-------------------------------------------------- void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) @@ -1272,7 +1332,9 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) // Remove any existing controls memset(cmd, 0, sizeof(ticcmd_t)); - if (gamestate != GS_LEVEL || !player->mo || player->spectator) + if (player->mo == NULL + || player->spectator == true + || G_GamestateUsesLevel() == false) { // Not in the level. return; @@ -1284,6 +1346,12 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) return; } + if (K_PodiumSequence() == true) + { + K_BuildBotPodiumTiccmd(player, cmd); + return; + } + if (!(gametyperules & GTR_BOTS) // No bot behaviors || K_GetNumWaypoints() == 0 // No waypoints || leveltime <= introtime // During intro camera @@ -1546,6 +1614,9 @@ void K_UpdateBotGameplayVars(player_t *player) { const line_t *botController; + player->botvars.controller = UINT16_MAX; + player->botvars.rubberband = FRACUNIT; + if (gamestate != GS_LEVEL || !player->mo) { // Not in the level. diff --git a/src/k_collide.c b/src/k_collide.c index cea3497c4..43e2c04ed 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -13,6 +13,7 @@ #include "g_game.h" // Sink snipe print #include "k_objects.h" #include "k_roulette.h" +#include "k_podium.h" angle_t K_GetCollideAngle(mobj_t *t1, mobj_t *t2) { @@ -816,6 +817,12 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) boolean stungT1 = false; boolean stungT2 = false; + if (K_PodiumSequence() == true) + { + // Always regular bumps, no ring toss. + return false; + } + // Clash instead of damage if both parties have any of these conditions t1Condition = (K_IsBigger(t1, t2) == true) || (t1->player->invincibilitytimer > 0) diff --git a/src/k_grandprix.c b/src/k_grandprix.c index ef7a4cd0f..61044aa68 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -95,6 +95,24 @@ INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers) return points; } +/*-------------------------------------------------- + UINT8 K_GetGPPlayerCount(UINT8 humans) + + See header file for description. +--------------------------------------------------*/ +UINT8 K_GetGPPlayerCount(UINT8 humans) +{ + UINT8 playerCount = 8; + + if (humans > 2) + { + // Add 3 bots per player beyond 2P + playerCount += (humans - 2) * 3; + } + + return playerCount; +} + /*-------------------------------------------------- void K_InitGrandPrixBots(void) @@ -179,12 +197,7 @@ void K_InitGrandPrixBots(void) } } - if (numplayers > 2) - { - // Add 3 bots per player beyond 2P - playercount += (numplayers-2) * 3; - } - + playercount = K_GetGPPlayerCount(numplayers); wantedbots = playercount - numplayers; // Create rival list diff --git a/src/k_grandprix.h b/src/k_grandprix.h index b9d426317..9bfc982e5 100644 --- a/src/k_grandprix.h +++ b/src/k_grandprix.h @@ -15,14 +15,18 @@ #include "doomdef.h" #include "doomstat.h" +#include "k_rank.h" // gpRank_t #ifdef __cplusplus extern "C" { #endif -#define GPEVENT_NONE 0 -#define GPEVENT_BONUS 1 -#define GPEVENT_SPECIAL 2 +typedef enum +{ + GPEVENT_NONE = 0, + GPEVENT_BONUS, + GPEVENT_SPECIAL, +} gpEvent_e; extern struct grandprixinfo { @@ -34,10 +38,10 @@ extern struct grandprixinfo boolean masterbots; ///< If true, all bots should be max difficulty (Master Mode) boolean initalize; ///< If true, we need to initialize a new session. boolean wonround; ///< If false, then we retry the map instead of going to the next. - UINT8 eventmode; ///< See GPEVENT_ constants + gpEvent_e eventmode; ///< Special event mode, bots are set to spectate and a special gametype is played + gpRank_t rank; ///< Struct containing grading information. (See also: k_rank.h) } grandprixinfo; - /*-------------------------------------------------- UINT8 K_BotStartingDifficulty(SINT8 value); @@ -71,6 +75,23 @@ UINT8 K_BotStartingDifficulty(SINT8 value); INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers); +/*-------------------------------------------------- + UINT8 K_GetGPPlayerCount(UINT8 humans) + + Counts the number of total players, + including humans and bots, to put into + a GP session. + + Input Arguments:- + humans - Number of human players. + + Return:- + Number of both human players and CPU. +--------------------------------------------------*/ + +UINT8 K_GetGPPlayerCount(UINT8 humans); + + /*-------------------------------------------------- void K_InitGrandPrixBots(void); @@ -160,6 +181,7 @@ void K_PlayerLoseLife(player_t *player); boolean K_CanChangeRules(boolean allowdemos); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_hud.c b/src/k_hud.c index 7a131a56b..8b5dd5c0e 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -37,6 +37,8 @@ #include "r_fps.h" #include "m_random.h" #include "k_roulette.h" +#include "k_bot.h" +#include "k_rank.h" //{ Patch Definitions static patch_t *kp_nodraw; @@ -3005,7 +3007,7 @@ static void K_drawKartPlayerCheck(void) return; } - if (stplyr->spectator || stplyr->awayviewtics) + if (stplyr->spectator || stplyr->awayview.tics) { return; } @@ -3254,7 +3256,7 @@ static void K_drawKartNameTags(void) return; } - if (stplyr->awayviewtics) + if (stplyr->awayview.tics) { return; } @@ -4815,6 +4817,58 @@ static void K_DrawWaypointDebugger(void) } } +static void K_DrawGPRankDebugger(void) +{ + gp_rank_e grade = GRADE_E; + char gradeChar = '?'; + + if (cv_debugrank.value == 0) + { + return; + } + + if (stplyr != &players[displayplayers[0]]) // only for p1 + { + return; + } + + if (grandprixinfo.gp == false) + { + return; + } + + grade = K_CalculateGPGrade(&grandprixinfo.rank); + + V_DrawThinString(0, 0, V_SNAPTOTOP|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE, + va("POS: %d / %d", grandprixinfo.rank.position, RANK_NEUTRAL_POSITION)); + V_DrawThinString(0, 10, V_SNAPTOTOP|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE, + va("PTS: %d / %d", grandprixinfo.rank.winPoints, grandprixinfo.rank.totalPoints)); + V_DrawThinString(0, 20, V_SNAPTOTOP|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE, + va("LAPS: %d / %d", grandprixinfo.rank.laps, grandprixinfo.rank.totalLaps)); + V_DrawThinString(0, 30, V_SNAPTOTOP|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE, + va("CONTINUES: %d", grandprixinfo.rank.continuesUsed)); + V_DrawThinString(0, 40, V_SNAPTOTOP|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE, + va("CAPSULES: %d / %d", grandprixinfo.rank.capsules, grandprixinfo.rank.totalCapsules)); + V_DrawThinString(0, 50, V_SNAPTOTOP|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE, + va("RINGS: %d / %d", grandprixinfo.rank.rings, grandprixinfo.rank.totalRings)); + V_DrawThinString(0, 60, V_SNAPTOTOP|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE, + va("EMERALD: %s", (grandprixinfo.rank.specialWon == true) ? "YES" : "NO")); + + switch (grade) + { + case GRADE_E: { gradeChar = 'E'; break; } + case GRADE_D: { gradeChar = 'D'; break; } + case GRADE_C: { gradeChar = 'C'; break; } + case GRADE_B: { gradeChar = 'B'; break; } + case GRADE_A: { gradeChar = 'A'; break; } + case GRADE_S: { gradeChar = 'S'; break; } + default: { break; } + } + + V_DrawThinString(0, 80, V_SNAPTOTOP|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE|V_YELLOWMAP, + va(" ** FINAL GRADE: %c", gradeChar)); +} + void K_drawKartHUD(void) { boolean islonesome = false; @@ -5086,4 +5140,5 @@ void K_drawKartHUD(void) K_DrawWaypointDebugger(); K_DrawDirectorDebugger(); + K_DrawGPRankDebugger(); } diff --git a/src/k_hud_track.cpp b/src/k_hud_track.cpp index dbb5ce8d0..d16de57fd 100644 --- a/src/k_hud_track.cpp +++ b/src/k_hud_track.cpp @@ -2,17 +2,23 @@ #include #include +#include "core/static_vec.hpp" + #include "k_battle.h" #include "k_boss.h" #include "k_hud.h" +#include "k_objects.h" #include "m_fixed.h" #include "p_local.h" #include "p_mobj.h" +#include "r_draw.h" #include "r_fps.h" #include "r_main.h" #include "st_stuff.h" #include "v_video.h" +using namespace srb2; + namespace { @@ -21,10 +27,82 @@ struct TargetTracking mobj_t* mobj; vector3_t point; fixed_t camDist; + + skincolornum_t color() const + { + switch (mobj->type) + { + case MT_OVERTIME_CENTER: + return SKINCOLOR_BLUE; + + case MT_MONITOR: + case MT_EMERALD: + return static_cast(mobj->color); + + case MT_PLAYER: + return player_emeralds_color(); + + default: + return SKINCOLOR_NONE; + } + } + + StaticVec player_emeralds_vec() const + { + StaticVec emeralds; + + const player_t* player = mobj->player; + + if (player == nullptr) + { + return emeralds; + } + + for (int i = 0; i < 7; ++i) + { + const uint32_t emeraldFlag = (1U << i); + + if (player->emeralds & emeraldFlag) + { + emeralds.push_back(emeraldFlag); + } + } + + return emeralds; + } + + skincolornum_t player_emeralds_color() const + { + const StaticVec emeralds = player_emeralds_vec(); + + if (emeralds.empty()) + { + return SKINCOLOR_NONE; + } + + constexpr tic_t kPeriod = TICRATE / 2; + const int idx = (leveltime / kPeriod) % emeralds.size(); + + return static_cast(K_GetChaosEmeraldColor(emeralds[idx])); + } + + const uint8_t* colormap() const + { + const skincolornum_t clr = color(); + + if (clr != SKINCOLOR_NONE) + { + return R_GetTranslationColormap(TC_RAINBOW, clr, GTC_CACHE); + } + + return nullptr; + } }; void K_DrawTargetTracking(const TargetTracking& target) { + const uint8_t* colormap = target.colormap(); + trackingResult_t result = {}; int32_t timer = 0; @@ -175,10 +253,10 @@ void K_DrawTargetTracking(const TargetTracking& target) if (targetPatch) { - V_DrawFixedPatch(targetPos.x, targetPos.y, FRACUNIT, V_SPLITSCREEN, targetPatch, nullptr); + V_DrawFixedPatch(targetPos.x, targetPos.y, FRACUNIT, V_SPLITSCREEN, targetPatch, colormap); } - V_DrawFixedPatch(arrowPos.x, arrowPos.y, FRACUNIT, V_SPLITSCREEN | arrowFlags, arrowPatch, nullptr); + V_DrawFixedPatch(arrowPos.x, arrowPos.y, FRACUNIT, V_SPLITSCREEN | arrowFlags, arrowPatch, colormap); } else { @@ -207,7 +285,7 @@ void K_DrawTargetTracking(const TargetTracking& target) FRACUNIT, V_SPLITSCREEN, patch, - nullptr + colormap ); }; @@ -229,7 +307,7 @@ void K_DrawTargetTracking(const TargetTracking& target) } } -bool is_player_tracking_target(const player_t *player) +bool is_player_tracking_target(player_t *player = stplyr) { if ((gametyperules & (GTR_BUMPERS|GTR_CLOSERPLAYERS)) != (GTR_BUMPERS|GTR_CLOSERPLAYERS)) { @@ -253,17 +331,14 @@ bool is_player_tracking_target(const player_t *player) return player != stplyr; } - if (g_hiscore < 1) // SOMEONE should be scoring + // Except for DUEL mode, Overtime hides all TARGETs except + // the kiosk. + if (battleovertime.enabled) { return false; } - if (player->roundscore < g_hiscore) - { - return false; - } - - return true; + return K_IsPlayerWanted(player); } bool is_object_tracking_target(const mobj_t* mobj) @@ -277,6 +352,15 @@ bool is_object_tracking_target(const mobj_t* mobj) case MT_PLAYER: return is_player_tracking_target(mobj->player); + case MT_OVERTIME_CENTER: + return inDuel == false && battleovertime.enabled; + + case MT_EMERALD: + return is_player_tracking_target(); + + case MT_MONITOR: + return is_player_tracking_target() && Obj_MonitorGetEmerald(mobj) != 0; + default: return false; } diff --git a/src/k_kart.c b/src/k_kart.c index 05755dcbe..3a218a528 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -43,6 +43,7 @@ #include "k_boss.h" #include "k_specialstage.h" #include "k_roulette.h" +#include "k_podium.h" // SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H: // gamespeed is cc (0 for easy, 1 for normal, 2 for hard) @@ -109,6 +110,12 @@ void K_TimerInit(void) boolean domodeattack = ((modeattacking != ATTACKING_NONE) || (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE)); + if (K_PodiumSequence() == true) + { + // Leave it alone for podium + return; + } + // Rooooooolllling staaaaaaart if ((gametyperules & (GTR_ROLLINGSTART|GTR_CIRCUIT)) == (GTR_ROLLINGSTART|GTR_CIRCUIT)) { @@ -340,6 +347,7 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartdebugnodes); CV_RegisterVar(&cv_kartdebugcolorize); CV_RegisterVar(&cv_kartdebugdirector); + CV_RegisterVar(&cv_debugrank); CV_RegisterVar(&cv_spbtest); CV_RegisterVar(&cv_gptest); CV_RegisterVar(&cv_capsuletest); @@ -354,6 +362,12 @@ boolean K_IsPlayerLosing(player_t *player) INT32 winningpos = 1; UINT8 i, pcount = 0; + if (K_PodiumSequence() == true) + { + // Need to be in top 3 to win. + return (player->position > 3); + } + if (player->pflags & PF_NOCONTEST) return true; @@ -1229,6 +1243,11 @@ static boolean K_TryDraft(player_t *player, mobj_t *dest, fixed_t minDist, fixed angle_t yourangle, theirangle, diff; #endif + if (K_PodiumSequence() == true) + { + return false; + } + #ifndef EASYDRAFTTEST // Don't draft on yourself :V if (dest->player && dest->player == player) @@ -1812,6 +1831,11 @@ static void K_SpawnGenericSpeedLines(player_t *player, boolean top) fast->colorized = true; fast->renderflags |= RF_ADD; } + else if (player->sliptideZipBoost) + { + fast->color = SKINCOLOR_WHITE; + fast->colorized = true; + } } void K_SpawnNormalSpeedLines(player_t *player) @@ -3014,6 +3038,10 @@ fixed_t K_GetSpindashChargeSpeed(player_t *player) return val; } +// v2 almost broke sliptiding when it fixed turning bugs! +// This value is fine-tuned to feel like v1 again without reverting any of those changes. +#define SLIPTIDEHANDLING 7*FRACUNIT/8 + // sets boostpower, speedboost, accelboost, and handleboost to whatever we need it to be static void K_GetKartBoostPower(player_t *player) { @@ -3021,10 +3049,6 @@ static void K_GetKartBoostPower(player_t *player) const fixed_t maxmetabolismincrease = FRACUNIT/2; const fixed_t metabolism = FRACUNIT - ((9-player->kartweight) * maxmetabolismincrease / 8); - // v2 almost broke sliptiding when it fixed turning bugs! - // This value is fine-tuned to feel like v1 again without reverting any of those changes. - const fixed_t sliptidehandling = FRACUNIT/2; - fixed_t boostpower = FRACUNIT; fixed_t speedboost = 0, accelboost = 0, handleboost = 0; UINT8 numboosts = 0; @@ -3043,13 +3067,16 @@ static void K_GetKartBoostPower(player_t *player) boostpower = (4*boostpower)/5; // Note: Handling will ONLY stack when sliptiding! + // > (NB 2023-03-06: This was previously unintentionally applied while drifting as well.) + // > (This only affected drifts where you were under the effect of multiple handling boosts.) + // > (Revisit if Growvinciblity or sneaker-panels + power items feel too heavy while drifting!) // When you're not, it just uses the best instead of adding together, like the old behavior. #define ADDBOOST(s,a,h) { \ numboosts++; \ speedboost += FixedDiv(s, FRACUNIT + (metabolism * (numboosts-1))); \ accelboost += FixedDiv(a, FRACUNIT + (metabolism * (numboosts-1))); \ - if (player->aizdriftstrat) \ - handleboost += FixedDiv(h, FRACUNIT + (metabolism * (numboosts-1))); \ + if (K_Sliptiding(player)) \ + handleboost += FixedDiv(h, FRACUNIT + (metabolism * (numboosts-1))/4); \ else \ handleboost = max(h, handleboost); \ } @@ -3059,18 +3086,18 @@ static void K_GetKartBoostPower(player_t *player) UINT8 i; for (i = 0; i < player->numsneakers; i++) { - ADDBOOST(FRACUNIT/2, 8*FRACUNIT, sliptidehandling); // + 50% top speed, + 800% acceleration, +50% handling + ADDBOOST(FRACUNIT/2, 8*FRACUNIT, SLIPTIDEHANDLING); // + 50% top speed, + 800% acceleration, +50% handling } } if (player->invincibilitytimer) // Invincibility { - ADDBOOST(3*FRACUNIT/8, 3*FRACUNIT, sliptidehandling/2); // + 37.5% top speed, + 300% acceleration, +25% handling + ADDBOOST(3*FRACUNIT/8, 3*FRACUNIT, SLIPTIDEHANDLING/2); // + 37.5% top speed, + 300% acceleration, +25% handling } if (player->growshrinktimer > 0) // Grow { - ADDBOOST(0, 0, sliptidehandling/2); // + 0% top speed, + 0% acceleration, +25% handling + ADDBOOST(0, 0, SLIPTIDEHANDLING/2); // + 0% top speed, + 0% acceleration, +25% handling } if (player->flamedash) // Flame Shield dash @@ -3079,10 +3106,16 @@ static void K_GetKartBoostPower(player_t *player) ADDBOOST( dash, // + infinite top speed 3*FRACUNIT, // + 300% acceleration - FixedMul(FixedDiv(dash, FRACUNIT/2), sliptidehandling/2) // + infinite handling + FixedMul(FixedDiv(dash, FRACUNIT/2), SLIPTIDEHANDLING/2) // + infinite handling ); } + if (player->sliptideZipBoost) + { + // NB: This is intentionally under the 25% threshold required to initiate a sliptide + ADDBOOST(13*FRACUNIT/20, 4*FRACUNIT, 2*SLIPTIDEHANDLING/5); // + 65% top speed, + 400% acceleration, +20% handling + } + if (player->spindashboost) // Spindash boost { const fixed_t MAXCHARGESPEED = K_GetSpindashChargeSpeed(player); @@ -3098,7 +3131,7 @@ static void K_GetKartBoostPower(player_t *player) if (player->startboost) // Startup Boost { - ADDBOOST(FRACUNIT, 4*FRACUNIT, sliptidehandling); // + 100% top speed, + 400% acceleration, +50% handling + ADDBOOST(FRACUNIT, 4*FRACUNIT, SLIPTIDEHANDLING); // + 100% top speed, + 400% acceleration, +50% handling } if (player->driftboost) // Drift Boost @@ -3128,7 +3161,7 @@ static void K_GetKartBoostPower(player_t *player) if (player->gateBoost) // SPB Juicebox boost { - ADDBOOST(3*FRACUNIT/4, 4*FRACUNIT, sliptidehandling/2); // + 75% top speed, + 400% acceleration, +25% handling + ADDBOOST(3*FRACUNIT/4, 4*FRACUNIT, SLIPTIDEHANDLING/2); // + 75% top speed, + 400% acceleration, +25% handling } if (player->ringboost) // Ring Boost @@ -3218,27 +3251,42 @@ fixed_t K_GetKartSpeedFromStat(UINT8 kartspeed) fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower, boolean dorubberband) { const boolean mobjValid = (player->mo != NULL && P_MobjWasRemoved(player->mo) == false); - fixed_t finalspeed = K_GetKartSpeedFromStat(player->kartspeed); + fixed_t finalspeed = 0; - if (gametyperules & GTR_BUMPERS && player->bumpers <= 0) - finalspeed = 3 * finalspeed / 2; - - if (player->spheres > 0) + if (K_PodiumSequence() == true) { - fixed_t sphereAdd = (FRACUNIT/40); // 100% at max - finalspeed = FixedMul(finalspeed, FRACUNIT + (sphereAdd * player->spheres)); + // Make 1st reach their podium faster! + finalspeed = K_GetKartSpeedFromStat(max(1, 11 - (player->position * 3))); + + // Ignore other speed boosts. + doboostpower = dorubberband = false; } - - if (K_PlayerUsesBotMovement(player)) + else { - // Increase bot speed by 1-10% depending on difficulty - fixed_t add = (player->botvars.difficulty * (FRACUNIT/10)) / DIFFICULTBOT; - finalspeed = FixedMul(finalspeed, FRACUNIT + add); + finalspeed = K_GetKartSpeedFromStat(player->kartspeed); - if (player->bot && player->botvars.rival) + if (gametyperules & GTR_BUMPERS && player->bumpers <= 0) { - // +10% top speed for the rival - finalspeed = FixedMul(finalspeed, 11*FRACUNIT/10); + finalspeed = 3 * finalspeed / 2; + } + + if (player->spheres > 0) + { + fixed_t sphereAdd = (FRACUNIT/40); // 100% at max + finalspeed = FixedMul(finalspeed, FRACUNIT + (sphereAdd * player->spheres)); + } + + if (K_PlayerUsesBotMovement(player)) + { + // Increase bot speed by 1-10% depending on difficulty + fixed_t add = (player->botvars.difficulty * (FRACUNIT/10)) / DIFFICULTBOT; + finalspeed = FixedMul(finalspeed, FRACUNIT + add); + + if (player->bot && player->botvars.rival) + { + // +10% top speed for the rival + finalspeed = FixedMul(finalspeed, 11*FRACUNIT/10); + } } } @@ -3266,16 +3314,32 @@ fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower, boolean dorubberb fixed_t K_GetKartAccel(player_t *player) { fixed_t k_accel = 121; + UINT8 stat = (9 - player->kartspeed); - k_accel += 17 * (9 - player->kartspeed); // 121 - 257 + if (K_PodiumSequence() == true) + { + // Normalize to Metal's accel + stat = 1; + } + + k_accel += 17 * stat; // 121 - 257 + + if (K_PodiumSequence() == true) + { + return FixedMul(k_accel, FRACUNIT / 4); + } // karma bomb gets 2x acceleration if ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0) + { k_accel *= 2; + } // Marble Garden Top gets 1200% accel if (player->curshield == KSHIELD_TOP) + { k_accel *= 12; + } return FixedMul(k_accel, (FRACUNIT + player->accelboost) / 4); } @@ -3406,7 +3470,7 @@ fixed_t K_GetNewSpeed(player_t *player) // Don't calculate the acceleration as ever being above top speed if (oldspeed > p_speed) oldspeed = p_speed; - newspeed = FixedDiv(FixedDiv(FixedMul(oldspeed, accelmax - p_accel) + FixedMul(p_speed, p_accel), accelmax), ORIG_FRICTION); + newspeed = FixedDiv(FixedDiv(FixedMul(oldspeed, accelmax - p_accel) + FixedMul(p_speed, p_accel), accelmax), K_PlayerBaseFriction(ORIG_FRICTION)); finalspeed = newspeed - oldspeed; @@ -3876,6 +3940,31 @@ void K_InitStumbleIndicator(player_t *player) P_SetTarget(&new->target, player->mo); } +void K_InitSliptideZipIndicator(player_t *player) +{ + mobj_t *new = NULL; + + if (player == NULL) + { + return; + } + + if (player->mo == NULL || P_MobjWasRemoved(player->mo) == true) + { + return; + } + + if (player->stumbleIndicator != NULL && P_MobjWasRemoved(player->sliptideZipIndicator) == false) + { + P_RemoveMobj(player->sliptideZipIndicator); + } + + new = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_SLIPTIDEZIP); + + P_SetTarget(&player->sliptideZipIndicator, new); + P_SetTarget(&new->target, player->mo); +} + void K_UpdateStumbleIndicator(player_t *player) { const angle_t fudge = ANG15; @@ -3978,6 +4067,78 @@ void K_UpdateStumbleIndicator(player_t *player) } } +static boolean K_IsLosingSliptideZip(player_t *player) +{ + if (player->mo == NULL || P_MobjWasRemoved(player->mo) == true) + return true; + if (!K_Sliptiding(player) && player->drift == 0 && P_IsObjectOnGround(player->mo) && player->sneakertimer == 0) + return true; + return false; +} + +void K_UpdateSliptideZipIndicator(player_t *player) +{ + mobj_t *mobj = NULL; + + if (player == NULL) + { + return; + } + + if (player->mo == NULL || P_MobjWasRemoved(player->mo) == true) + { + return; + } + + if (player->stumbleIndicator == NULL || P_MobjWasRemoved(player->stumbleIndicator) == true) + { + K_InitSliptideZipIndicator(player); + return; + } + + mobj = player->sliptideZipIndicator; + angle_t momentumAngle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); + + P_MoveOrigin(mobj, player->mo->x - FixedMul(40*mapobjectscale, FINECOSINE(momentumAngle >> ANGLETOFINESHIFT)), + player->mo->y - FixedMul(40*mapobjectscale, FINESINE(momentumAngle >> ANGLETOFINESHIFT)), + player->mo->z + (player->mo->height / 2)); + mobj->angle = momentumAngle + ANGLE_90; + P_SetScale(mobj, 3 * player->mo->scale / 2); + + // No stored boost + if (player->sliptideZip == 0) + { + mobj->renderflags |= RF_DONTDRAW; + mobj->frame = 7; + return; + } + + mobj->renderflags &= ~RF_DONTDRAW; + + UINT32 chargeFrame = 7 - min(7, player->sliptideZip / 10); + UINT32 decayFrame = min(7, player->sliptideZipDelay / 5); + if (max(chargeFrame, decayFrame) > mobj->frame) + mobj->frame++; + else if (max(chargeFrame, decayFrame) < mobj->frame) + mobj->frame--; + + mobj->renderflags &= ~RF_TRANSMASK; + mobj->renderflags |= RF_PAPERSPRITE; + + if (K_IsLosingSliptideZip(player)) + { + // Decay timer's ticking + mobj->rollangle += 3*ANG30/4; + if (leveltime % 2 == 0) + mobj->renderflags |= RF_TRANS50; + } + else + { + // Storing boost + mobj->rollangle += 3*ANG15/4; + } +} + static boolean K_LastTumbleBounceCondition(player_t *player) { return (player->tumbleBounces > TUMBLEBOUNCES && player->tumbleHeight < 60); @@ -4195,35 +4356,35 @@ void K_HandleBumperChanges(player_t *player, UINT8 prevBumpers) { player->pflags |= (PF_NOCONTEST|PF_ELIMINATED); } - - P_KillMobj(player->mo, NULL, NULL, DMG_NORMAL); } K_CalculateBattleWanted(); K_CheckBumpers(); } -void K_DestroyBumpers(player_t *player, UINT8 amount) +UINT8 K_DestroyBumpers(player_t *player, UINT8 amount) { UINT8 oldBumpers = player->bumpers; if (!(gametyperules & GTR_BUMPERS)) { - return; + return 0; } amount = min(amount, player->bumpers); if (amount == 0) { - return; + return 0; } player->bumpers -= amount; K_HandleBumperChanges(player, oldBumpers); + + return amount; } -void K_TakeBumpersFromPlayer(player_t *player, player_t *victim, UINT8 amount) +UINT8 K_TakeBumpersFromPlayer(player_t *player, player_t *victim, UINT8 amount) { UINT8 oldPlayerBumpers = player->bumpers; UINT8 oldVictimBumpers = victim->bumpers; @@ -4232,14 +4393,14 @@ void K_TakeBumpersFromPlayer(player_t *player, player_t *victim, UINT8 amount) if (!(gametyperules & GTR_BUMPERS)) { - return; + return 0; } amount = min(amount, victim->bumpers); if (amount == 0) { - return; + return 0; } while ((tookBumpers < amount) && (victim->bumpers > 0)) @@ -4294,7 +4455,7 @@ void K_TakeBumpersFromPlayer(player_t *player, player_t *victim, UINT8 amount) if (tookBumpers == 0) { // No change occured. - return; + return 0; } // Play steal sound @@ -4302,6 +4463,8 @@ void K_TakeBumpersFromPlayer(player_t *player, player_t *victim, UINT8 amount) K_HandleBumperChanges(player, oldPlayerBumpers); K_HandleBumperChanges(victim, oldVictimBumpers); + + return tookBumpers; } #define MINEQUAKEDIST 4096 @@ -6983,6 +7146,13 @@ static void K_UpdateEngineSounds(player_t *player) INT32 targetsnd = 0; INT32 i; + if (leveltime < 8 || player->spectator || gamestate != GS_LEVEL) + { + // Silence the engines, and reset sound number while we're at it. + player->karthud[khud_enginesnd] = 0; + return; + } + s = (player->kartspeed - 1) / 3; w = (player->kartweight - 1) / 3; @@ -6995,13 +7165,6 @@ static void K_UpdateEngineSounds(player_t *player) class = s + (3*w); - if (leveltime < 8 || player->spectator) - { - // Silence the engines, and reset sound number while we're at it. - player->karthud[khud_enginesnd] = 0; - return; - } - #if 0 if ((leveltime % 8) != ((player-players) % 8)) // Per-player offset, to make engines sound distinct! #else @@ -7493,7 +7656,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->sneakertimer || player->ringboost || player->driftboost || player->startboost || player->eggmanexplode || player->trickboost - || player->gateBoost) + || player->gateBoost || player->sliptideZipBoost) { #if 0 if (player->invincibilitytimer) @@ -7755,6 +7918,11 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->startboost--; } + if (player->sliptideZipBoost > 0 && onground == true) + { + player->sliptideZipBoost--; + } + if (player->spindashboost) { player->spindashboost--; @@ -8124,6 +8292,8 @@ void K_KartPlayerAfterThink(player_t *player) K_UpdateStumbleIndicator(player); + K_UpdateSliptideZipIndicator(player); + // Move held objects (Bananas, Orbinaut, etc) K_MoveHeldObjects(player); @@ -8479,6 +8649,12 @@ static waypoint_t *K_GetPlayerNextWaypoint(player_t *player) --------------------------------------------------*/ void K_UpdateDistanceFromFinishLine(player_t *const player) { + if (K_PodiumSequence() == true) + { + K_UpdatePodiumWaypoints(player); + return; + } + if ((player != NULL) && (player->mo != NULL)) { waypoint_t *finishline = K_GetFinishLineWaypoint(); @@ -8845,6 +9021,15 @@ INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue) p_speed = min(currentSpeed, (p_maxspeed * 2)); } + if (K_PodiumSequence() == true) + { + // Normalize turning for podium + weightadjust = FixedDiv((p_maxspeed * 3), (p_maxspeed * 3) + (2 * FRACUNIT)); + turnfixed = FixedMul(turnfixed, weightadjust); + turnfixed *= 2; + return (turnfixed / FRACUNIT); + } + weightadjust = FixedDiv((p_maxspeed * 3) - p_speed, (p_maxspeed * 3) + (player->kartweight * FRACUNIT)); if (K_PlayerUsesBotMovement(player)) @@ -9201,7 +9386,7 @@ static void K_KartDrift(player_t *player, boolean onground) player->pflags &= ~PF_DRIFTEND; } - if ((player->handleboost == 0) + if ((player->handleboost < (SLIPTIDEHANDLING/2)) || (!player->steering) || (!player->aizdriftstrat) || (player->steering > 0) != (player->aizdriftstrat > 0)) @@ -9215,6 +9400,8 @@ static void K_KartDrift(player_t *player, boolean onground) { K_SpawnAIZDust(player); + player->sliptideZip++; + if (abs(player->aizdrifttilt) < ANGLE_22h) { player->aizdrifttilt = @@ -9232,6 +9419,40 @@ static void K_KartDrift(player_t *player, boolean onground) if (!K_Sliptiding(player)) { + if (K_IsLosingSliptideZip(player) && player->sliptideZip > 0) + { + player->sliptideZipDelay++; + if (player->sliptideZipDelay > TICRATE) + { + fixed_t maxZipPower = 2*FRACUNIT; + fixed_t minZipPower = 1*FRACUNIT; + fixed_t powerSpread = maxZipPower - minZipPower; + + int minPenalty = 2*1 + (9-9); // Kinda doing a similar thing to driftspark stage timers here. + int maxPenalty = 2*9 + (9-1); // 1/9 gets max, 9/1 gets min, everyone else gets something in between. + int penaltySpread = maxPenalty - minPenalty; + int yourPenalty = 2*player->kartspeed + (9 - player->kartweight); // And like driftsparks, speed hurts double. + + yourPenalty -= minPenalty; // Normalize; minimum penalty should take away 0 power. + + fixed_t yourPowerReduction = FixedDiv(yourPenalty * FRACUNIT, penaltySpread * FRACUNIT); + fixed_t yourPower = maxZipPower - FixedMul(yourPowerReduction, powerSpread); + int yourBoost = FixedInt(FixedMul(yourPower, player->sliptideZip * FRACUNIT)); + + /* + CONS_Printf("SZ %d MZ %d mZ %d pwS %d mP %d MP %d peS %d yPe %d yPR %d yPw %d yB %d\n", + player->sliptideZip, maxZipPower, minZipPower, powerSpread, minPenalty, maxPenalty, penaltySpread, yourPenalty, yourPowerReduction, yourPower, yourBoost); + */ + + player->sliptideZipBoost += yourBoost; + + K_SpawnDriftBoostExplosion(player, 0); + player->sliptideZip = 0; + player->sliptideZipDelay = 0; + S_StartSound(player->mo, sfx_s3kb6); + } + } + player->aizdrifttilt -= player->aizdrifttilt / 4; player->aizdriftturn -= player->aizdriftturn / 4; @@ -9240,6 +9461,10 @@ static void K_KartDrift(player_t *player, boolean onground) if (abs(player->aizdriftturn) < ANGLE_11hh) player->aizdriftturn = 0; } + else + { + player->sliptideZipDelay = 0; + } if (player->drift && ((buttons & BT_BRAKE) @@ -9271,64 +9496,79 @@ void K_KartUpdatePosition(player_t *player) return; } - for (i = 0; i < MAXPLAYERS; i++) + if (K_PodiumSequence() == true) { - if (!playeringame[i] || players[i].spectator || !players[i].mo) - continue; + position = K_GetPodiumPosition(player); - realplayers++; - - if (gametyperules & GTR_CIRCUIT) + for (i = 0; i < MAXPLAYERS; i++) { - if (player->exiting) // End of match standings - { - // Only time matters - if (players[i].realtime < player->realtime) - position++; - } - else - { - // I'm a lap behind this player OR - // My distance to the finish line is higher, so I'm behind - if ((players[i].laps > player->laps) - || (players[i].distancetofinish < player->distancetofinish)) - { - position++; - } - } + if (!playeringame[i] || players[i].spectator || !players[i].mo) + continue; + + realplayers++; } - else + } + else + { + for (i = 0; i < MAXPLAYERS; i++) { - if (player->exiting) // End of match standings - { - // Only score matters - if (players[i].roundscore > player->roundscore) - position++; - } - else - { - UINT8 myEmeralds = K_NumEmeralds(player); - UINT8 yourEmeralds = K_NumEmeralds(&players[i]); + if (!playeringame[i] || players[i].spectator || !players[i].mo) + continue; - // First compare all points - if (players[i].roundscore > player->roundscore) + realplayers++; + + if (gametyperules & GTR_CIRCUIT) + { + if (player->exiting) // End of match standings { - position++; + // Only time matters + if (players[i].realtime < player->realtime) + position++; } - else if (players[i].roundscore == player->roundscore) + else { - // Emeralds are a tie breaker - if (yourEmeralds > myEmeralds) + // I'm a lap behind this player OR + // My distance to the finish line is higher, so I'm behind + if ((players[i].laps > player->laps) + || (players[i].distancetofinish < player->distancetofinish)) { position++; } - else if (yourEmeralds == myEmeralds) + } + } + else + { + if (player->exiting) // End of match standings + { + // Only score matters + if (players[i].roundscore > player->roundscore) + position++; + } + else + { + UINT8 myEmeralds = K_NumEmeralds(player); + UINT8 yourEmeralds = K_NumEmeralds(&players[i]); + + // First compare all points + if (players[i].roundscore > player->roundscore) { - // Bumpers are the second tier tie breaker - if (players[i].bumpers > player->bumpers) + position++; + } + else if (players[i].roundscore == player->roundscore) + { + // Emeralds are a tie breaker + if (yourEmeralds > myEmeralds) { position++; } + else if (yourEmeralds == myEmeralds) + { + // Bumpers are the second tier tie breaker + if (players[i].bumpers > player->bumpers) + { + position++; + } + } } } } @@ -9386,6 +9626,29 @@ void K_KartUpdatePosition(player_t *player) player->position = position; } +void K_UpdateAllPlayerPositions(void) +{ + INT32 i; + + // First loop: Ensure all players' distance to the finish line are all accurate + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) + { + K_UpdateDistanceFromFinishLine(&players[i]); + } + } + + // Second loop: Ensure all player positions reflect everyone's distances + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) + { + K_KartUpdatePosition(&players[i]); + } + } +} + // // K_StripItems // @@ -9680,6 +9943,9 @@ static void K_KartSpindashWind(mobj_t *parent) P_SetTarget(&wind->target, parent); + if (parent->player && parent->player->sliptideZipBoost) + P_SetScale(wind, wind->scale * 2); + if (parent->momx || parent->momy) wind->angle = R_PointToAngle2(0, 0, parent->momx, parent->momy); else @@ -9755,6 +10021,11 @@ static void K_KartSpindash(player_t *player) K_KartSpindashWind(player->mo); } + if ((player->sliptideZipBoost > 0) && (spawnWind == true)) + { + K_KartSpindashWind(player->mo); + } + if (player->spindashboost > (TICRATE/2)) { K_KartSpindashDust(player->mo); @@ -9925,18 +10196,38 @@ static void K_AirFailsafe(player_t *player) } } +// +// K_PlayerBaseFriction +// +fixed_t K_PlayerBaseFriction(fixed_t original) +{ + fixed_t frict = original; + + if (K_PodiumSequence() == true) + { + frict -= FRACUNIT >> 4; + } + + if (frict > FRACUNIT) { frict = FRACUNIT; } + if (frict < 0) { frict = 0; } + + return frict; +} + // // K_AdjustPlayerFriction // void K_AdjustPlayerFriction(player_t *player) { - fixed_t prevfriction = player->mo->friction; + const fixed_t prevfriction = K_PlayerBaseFriction(player->mo->friction); if (P_IsObjectOnGround(player->mo) == false) { return; } + player->mo->friction = prevfriction; + // Reduce friction after hitting a spring if (player->tiregrease) { diff --git a/src/k_kart.h b/src/k_kart.h index 04610be45..381395155 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -99,12 +99,14 @@ angle_t K_StumbleSlope(angle_t angle, angle_t pitch, angle_t roll); void K_StumblePlayer(player_t *player); boolean K_CheckStumble(player_t *player, angle_t oldPitch, angle_t oldRoll, boolean fromAir); void K_InitStumbleIndicator(player_t *player); +void K_InitSliptideZipIndicator(player_t *player); void K_UpdateStumbleIndicator(player_t *player); +void K_UpdateSliptideZipIndicator(player_t *player); INT32 K_ExplodePlayer(player_t *player, mobj_t *inflictor, mobj_t *source); void K_DebtStingPlayer(player_t *player, mobj_t *source); void K_HandleBumperChanges(player_t *player, UINT8 prevBumpers); -void K_DestroyBumpers(player_t *player, UINT8 amount); -void K_TakeBumpersFromPlayer(player_t *player, player_t *victim, UINT8 amount); +UINT8 K_DestroyBumpers(player_t *player, UINT8 amount); +UINT8 K_TakeBumpersFromPlayer(player_t *player, player_t *victim, UINT8 amount); void K_MineFlashScreen(mobj_t *source); void K_SpawnMineExplosion(mobj_t *source, UINT8 color, tic_t delay); void K_RunFinishLineBeam(void); @@ -139,6 +141,7 @@ INT32 K_GetKartDriftSparkValueForStage(player_t *player, UINT8 stage); void K_SpawnDriftBoostExplosion(player_t *player, int stage); void K_SpawnDriftElectricSparks(player_t *player, int color, boolean shockwave); void K_KartUpdatePosition(player_t *player); +void K_UpdateAllPlayerPositions(void); SINT8 K_GetTotallyRandomResult(UINT8 useodds); mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT8 amount); void K_DropItems(player_t *player); @@ -176,6 +179,7 @@ fixed_t K_3dKartMovement(player_t *player); boolean K_PlayerEBrake(player_t *player); SINT8 K_Sliptiding(player_t *player); boolean K_FastFallBounce(player_t *player); +fixed_t K_PlayerBaseFriction(fixed_t original); void K_AdjustPlayerFriction(player_t *player); void K_MoveKartPlayer(player_t *player, boolean onground); void K_CheckSpectateStatus(void); diff --git a/src/k_menu.h b/src/k_menu.h index e4823a274..75bd392e2 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -571,6 +571,7 @@ void Addons_option_Onchange(void); void M_SortServerList(void); void M_MapMenuControls(event_t *ev); +void M_UpdateMenuCMD(UINT8 i); boolean M_Responder(event_t *ev); boolean M_MenuButtonPressed(UINT8 pid, UINT32 bt); boolean M_MenuButtonHeld(UINT8 pid, UINT32 bt); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index fbafba77e..a571ce37d 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -202,6 +202,23 @@ boolean M_PrevOpt(void) return true; } +static boolean M_GamestateCanOpenMenu(void) +{ + switch (gamestate) + { + case GS_INTRO: + case GS_CUTSCENE: + case GS_GAMEEND: + case GS_CREDITS: + case GS_EVALUATION: + case GS_CEREMONY: + return false; + + default: + return true; + } +} + // // M_Responder // @@ -209,9 +226,9 @@ boolean M_Responder(event_t *ev) { menuKey = -1; - if (dedicated || (demo.playback && demo.title) - || gamestate == GS_INTRO || gamestate == GS_CUTSCENE || gamestate == GS_GAMEEND - || gamestate == GS_CREDITS || gamestate == GS_EVALUATION) + if (dedicated + || (demo.playback && demo.title) + || M_GamestateCanOpenMenu() == false) { return false; } @@ -713,7 +730,7 @@ void M_SetMenuDelay(UINT8 i) } } -static void M_UpdateMenuCMD(UINT8 i) +void M_UpdateMenuCMD(UINT8 i) { UINT8 mp = max(1, setup_numplayers); @@ -749,19 +766,11 @@ static void M_UpdateMenuCMD(UINT8 i) void M_MapMenuControls(event_t *ev) { - INT32 i; - if (ev) { // update keys current state G_MapEventsToControls(ev); } - - // Update menu CMD - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - M_UpdateMenuCMD(i); - } } boolean M_MenuButtonPressed(UINT8 pid, UINT32 bt) diff --git a/src/k_podium.c b/src/k_podium.c new file mode 100644 index 000000000..7f00834d9 --- /dev/null +++ b/src/k_podium.c @@ -0,0 +1,504 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) by Sally "TehRealSalt" Cochenour +// Copyright (C) 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_podium.c +/// \brief Grand Prix podium cutscene + +#include "k_podium.h" + +#include "doomdef.h" +#include "doomstat.h" +#include "d_main.h" +#include "d_netcmd.h" +#include "f_finale.h" +#include "g_game.h" +#include "hu_stuff.h" +#include "r_local.h" +#include "s_sound.h" +#include "i_time.h" +#include "i_video.h" +#include "v_video.h" +#include "w_wad.h" +#include "z_zone.h" +#include "i_system.h" +#include "i_threads.h" +#include "dehacked.h" +#include "g_input.h" +#include "console.h" +#include "m_random.h" +#include "m_misc.h" // moviemode functionality +#include "y_inter.h" +#include "m_cond.h" +#include "p_local.h" +#include "p_setup.h" +#include "st_stuff.h" // hud hiding +#include "fastcmp.h" + +#include "lua_hud.h" +#include "lua_hook.h" + +#include "k_menu.h" +#include "k_grandprix.h" +#include "k_rank.h" + +static struct podiumData_s +{ + boolean ranking; + gpRank_t rank; + gp_rank_e grade; + UINT8 state; + UINT8 delay; + UINT8 fade; +} podiumData; + +#define PODIUM_STATES (9) // TODO: enum when this actually gets made + +/*-------------------------------------------------- + boolean K_PodiumSequence(void) + + See header file for description. +--------------------------------------------------*/ +boolean K_PodiumSequence(void) +{ + return (gamestate == GS_CEREMONY); +} + +/*-------------------------------------------------- + UINT8 K_GetPodiumPosition(player_t *player) + + See header file for description. +--------------------------------------------------*/ +UINT8 K_GetPodiumPosition(player_t *player) +{ + UINT8 position = 1; + INT32 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *other = NULL; + if (playeringame[i] == false) + { + continue; + } + + other = &players[i]; + if (other->spectator == true) + { + continue; + } + + if (other->score > player->score) + { + // Final score is the important part. + position++; + } + else if (other->score == player->score) + { + if (other->bot == false && player->bot == true) + { + // Bots are never as important as players. + position++; + } + else if (i < player - players) + { + // Port priority is the final tie breaker. + position++; + } + } + } + + return position; +} + +/*-------------------------------------------------- + static void K_SetPodiumWaypoint(player_t *const player, waypoint_t *const waypoint) + + Changes the player's current and next waypoints, for + use during the podium sequence. + + Input Arguments:- + player - The player to update the waypoints of. + waypoint - The new current waypoint. + + Return:- + None +--------------------------------------------------*/ +static void K_SetPodiumWaypoint(player_t *const player, waypoint_t *const waypoint) +{ + // Set the new waypoint. + player->currentwaypoint = waypoint; + + if ((waypoint == NULL) + || (waypoint->nextwaypoints == NULL) + || (waypoint->numnextwaypoints == 0U)) + { + // No waypoint, or no next waypoint. + player->nextwaypoint = NULL; + return; + } + + // Simply use the first available next waypoint. + // No need for split paths in these cutscenes. + player->nextwaypoint = waypoint->nextwaypoints[0]; +} + +/*-------------------------------------------------- + void K_InitializePodiumWaypoint(player_t *const player) + + See header file for description. +--------------------------------------------------*/ +void K_InitializePodiumWaypoint(player_t *const player) +{ + if ((player != NULL) && (player->mo != NULL)) + { + player->position = K_GetPodiumPosition(player); + + if (player->position > 0 && player->position <= MAXPLAYERS) + { + // Initialize our first waypoint to the one that + // matches our position. + K_SetPodiumWaypoint(player, K_GetWaypointFromID(player->position)); + } + else + { + // None does, so remove it if we happen to have one. + K_SetPodiumWaypoint(player, NULL); + } + } +} + +/*-------------------------------------------------- + void K_UpdatePodiumWaypoints(player_t *const player) + + See header file for description. +--------------------------------------------------*/ +void K_UpdatePodiumWaypoints(player_t *const player) +{ + if ((player != NULL) && (player->mo != NULL)) + { + if (player->currentwaypoint != NULL) + { + const fixed_t xydist = P_AproxDistance( + player->mo->x - player->currentwaypoint->mobj->x, + player->mo->y - player->currentwaypoint->mobj->y + ); + const fixed_t xyzdist = P_AproxDistance( + xydist, + player->mo->z - player->currentwaypoint->mobj->z + ); + //const fixed_t speed = P_AproxDistance(player->mo->momx, player->mo->momy); + + if (xyzdist <= player->mo->radius + player->currentwaypoint->mobj->radius) + { + // Reached waypoint, go to the next waypoint. + K_SetPodiumWaypoint(player, player->nextwaypoint); + } + } + } +} + +/*-------------------------------------------------- + boolean K_StartCeremony(void) + + See header file for description. +--------------------------------------------------*/ +boolean K_StartCeremony(void) +{ + INT32 podiumMapNum = nummapheaders; + INT32 i; + + if (grandprixinfo.gp == false) + { + return false; + } + + if (podiummap + && ((podiumMapNum = G_MapNumber(podiummap)) < nummapheaders) + && mapheaderinfo[podiumMapNum] + && mapheaderinfo[podiumMapNum]->lumpnum != LUMPERROR) + { + P_SetTarget(&titlemapcam.mobj, NULL); + + gamemap = podiumMapNum+1; + + maptol = mapheaderinfo[gamemap-1]->typeoflevel; + globalweather = mapheaderinfo[gamemap-1]->weather; + + // Make sure all of the GAME OVER'd players can spawn + // and be present for the podium + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator && !players[i].bot) + { + players[i].lives = max(1, players[i].lives); + } + } + + G_SetGametype(GT_RACE); + G_DoLoadLevelEx(false, GS_CEREMONY); + + r_splitscreen = 0; // Only one screen for the ceremony + R_ExecuteSetViewSize(); + return true; + } + + return false; +} + +/*-------------------------------------------------- + void K_FinishCeremony(void) + + See header file for description. +--------------------------------------------------*/ +void K_FinishCeremony(void) +{ + if (K_PodiumSequence() == false) + { + return; + } + + podiumData.ranking = true; +} + +/*-------------------------------------------------- + void K_ResetCeremony(void) + + See header file for description. +--------------------------------------------------*/ +void K_ResetCeremony(void) +{ + memset(&podiumData, 0, sizeof(struct podiumData_s)); + + if (K_PodiumSequence() == false) + { + return; + } + + podiumData.rank = grandprixinfo.rank; + podiumData.grade = K_CalculateGPGrade(&podiumData.rank); +} + +/*-------------------------------------------------- + void K_CeremonyTicker(boolean run) + + See header file for description. +--------------------------------------------------*/ +void K_CeremonyTicker(boolean run) +{ + // don't trigger if doing anything besides idling + if (gameaction != ga_nothing || gamestate != GS_CEREMONY) + { + return; + } + + P_TickAltView(&titlemapcam); + + if (titlemapcam.mobj != NULL) + { + camera[0].x = titlemapcam.mobj->x; + camera[0].y = titlemapcam.mobj->y; + camera[0].z = titlemapcam.mobj->z; + camera[0].angle = titlemapcam.mobj->angle; + camera[0].aiming = titlemapcam.mobj->pitch; + camera[0].subsector = titlemapcam.mobj->subsector; + } + + if (podiumData.ranking == false) + { + return; + } + + if (run == true) + { + if (podiumData.fade < 16) + { + podiumData.fade++; + } + else + { + if (podiumData.state < PODIUM_STATES) + { + podiumData.delay++; + + if (podiumData.delay > TICRATE/2) + { + podiumData.state++; + podiumData.delay = 0; + } + } + } + } +} + +/*-------------------------------------------------- + boolean K_CeremonyResponder(event_t *event) + + See header file for description. +--------------------------------------------------*/ +boolean K_CeremonyResponder(event_t *event) +{ + INT32 key = event->data1; + + if (podiumData.ranking == false || podiumData.state < PODIUM_STATES) + { + return false; + } + + // remap virtual keys (mouse & joystick buttons) + switch (key) + { + case KEY_MOUSE1: + key = KEY_ENTER; + break; + case KEY_MOUSE1 + 1: + key = KEY_BACKSPACE; + break; + case KEY_JOY1: + case KEY_JOY1 + 2: + key = KEY_ENTER; + break; + case KEY_JOY1 + 3: + key = 'n'; + break; + case KEY_JOY1 + 1: + key = KEY_BACKSPACE; + break; + case KEY_HAT1: + key = KEY_UPARROW; + break; + case KEY_HAT1 + 1: + key = KEY_DOWNARROW; + break; + case KEY_HAT1 + 2: + key = KEY_LEFTARROW; + break; + case KEY_HAT1 + 3: + key = KEY_RIGHTARROW; + break; + } + + if (event->type != ev_keydown) + { + return false; + } + + if (key != KEY_ESCAPE && key != KEY_ENTER && key != KEY_BACKSPACE) + { + return false; + } + + return true; +} + +/*-------------------------------------------------- + void K_CeremonyDrawer(void) + + See header file for description. +--------------------------------------------------*/ +void K_CeremonyDrawer(void) +{ + if (podiumData.ranking == true) + { + char gradeChar = '?'; + INT32 x = 64; + INT32 y = 48; + INT32 i; + + switch (podiumData.grade) + { + case GRADE_E: { gradeChar = 'E'; break; } + case GRADE_D: { gradeChar = 'D'; break; } + case GRADE_C: { gradeChar = 'C'; break; } + case GRADE_B: { gradeChar = 'B'; break; } + case GRADE_A: { gradeChar = 'A'; break; } + case GRADE_S: { gradeChar = 'S'; break; } + default: { break; } + } + + V_DrawFadeScreen(0xFF00, podiumData.fade); + + for (i = 0; i <= podiumData.state; i++) + { + switch (i) + { + case 1: + { + V_DrawString(x, y, V_ALLOWLOWERCASE, + va("POS: %d / %d", podiumData.rank.position, RANK_NEUTRAL_POSITION) + ); + break; + } + case 2: + { + V_DrawString(x, y, V_ALLOWLOWERCASE, + va("PTS: %d / %d", podiumData.rank.winPoints, podiumData.rank.totalPoints) + ); + break; + } + case 3: + { + V_DrawString(x, y, V_ALLOWLOWERCASE, + va("LAPS: %d / %d", podiumData.rank.laps, podiumData.rank.totalLaps) + ); + break; + } + case 4: + { + V_DrawString(x, y, V_ALLOWLOWERCASE, + va("CONTINUES: %d", podiumData.rank.continuesUsed) + ); + break; + } + case 5: + { + V_DrawString(x, y, V_ALLOWLOWERCASE, + va("CAPSULES: %d / %d", podiumData.rank.capsules, podiumData.rank.totalCapsules) + ); + break; + } + case 6: + { + V_DrawString(x, y, V_ALLOWLOWERCASE, + va("RINGS: %d / %d", podiumData.rank.rings, podiumData.rank.totalRings) + ); + break; + } + case 7: + { + V_DrawString(x, y, V_ALLOWLOWERCASE, + va("EMERALD: %s", (podiumData.rank.specialWon == true) ? "YES" : "NO") + ); + break; + } + case 8: + { + V_DrawString(x, y + 10, V_YELLOWMAP|V_ALLOWLOWERCASE, + va(" ** FINAL GRADE: %c", gradeChar) + ); + break; + } + case 9: + { + V_DrawThinString(2, BASEVIDHEIGHT - 10, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE, + "Press some button type deal to continue" + ); + break; + } + } + + y += 10; + } + } + + if (timeinmap < 16) + { + // Level fade-in + V_DrawCustomFadeScreen(((levelfadecol == 0) ? "FADEMAP1" : "FADEMAP0"), 31-(timeinmap*2)); + } +} diff --git a/src/k_podium.h b/src/k_podium.h new file mode 100644 index 000000000..08357dd02 --- /dev/null +++ b/src/k_podium.h @@ -0,0 +1,178 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) by Sally "TehRealSalt" Cochenour +// Copyright (C) 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_podium.h +/// \brief Grand Prix podium cutscene + +#ifndef __K_PODIUM__ +#define __K_PODIUM__ + +#include "doomtype.h" +#include "d_event.h" +#include "p_mobj.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/*-------------------------------------------------- + boolean K_PodiumSequence(void); + + Returns whenver or not we are in the podium + cutscene mode. + + Input Arguments:- + N/A + + Return:- + true if we're in GS_CEREMONY, otherwise false. +--------------------------------------------------*/ + +boolean K_PodiumSequence(void); + + +/*-------------------------------------------------- + UINT8 K_GetPodiumPosition(player_t *player); + + Calculates what the player's position would + be at the final standings. + + Input Arguments:- + player - The player to do the calculation for. + + Return:- + The player's final position, as a number + between 1 and MAXPLAYERS. +--------------------------------------------------*/ + +UINT8 K_GetPodiumPosition(player_t *player); + + +/*-------------------------------------------------- + void K_InitializePodiumWaypoint(player_t *const player); + + Sets a bot's current waypoint to one matching + their final podium position. + + Input Arguments:- + player - The podium bot to update. + + Return:- + N/A +--------------------------------------------------*/ + +void K_InitializePodiumWaypoint(player_t *const player); + + +/*-------------------------------------------------- + void K_UpdatePodiumWaypoints(player_t *const player); + + Helps a bot move along a predetermined path by + updating their current and next waypoints as + they move. Intended for the podium sequence. + + Input Arguments:- + player - The podium bot to update. + + Return:- + N/A +--------------------------------------------------*/ + +void K_UpdatePodiumWaypoints(player_t *const player); + + +/*-------------------------------------------------- + boolean K_StartCeremony(void); + + Loads the podium map and changes the gamestate + to the podium cutscene mode. + + Input Arguments:- + N/A + + Return:- + true if successful, otherwise false. Can fail + if there is no podium map defined. +--------------------------------------------------*/ + +boolean K_StartCeremony(void); + + +/*-------------------------------------------------- + void K_FinishCeremony(void); + + Called at the end of the podium cutscene, + displays the ranking screen and starts + accepting input. +--------------------------------------------------*/ + +void K_FinishCeremony(void); + + +/*-------------------------------------------------- + void K_ResetCeremony(void); + + Called on level load, to reset all of the + podium variables. +--------------------------------------------------*/ + +void K_ResetCeremony(void); + + +/*-------------------------------------------------- + void K_CeremonyTicker(boolean run); + + Ticker function to be ran during the podium + cutscene mode gamestate. Handles updating + the camera. + + Input Arguments:- + run - Set to true when we're running a + new game frame. + + Return:- + N/A +--------------------------------------------------*/ + +void K_CeremonyTicker(boolean run); + + +/*-------------------------------------------------- + void K_CeremonyResponder(event_t *ev); + + Responder function to be ran during the podium + cutscene mode gamestate. Handles key presses + ending the podium scene. + + Input Arguments:- + ev - The player input event. + + Return:- + true to end the podium cutscene and return + to the title screen, otherwise false. +--------------------------------------------------*/ + +boolean K_CeremonyResponder(event_t *ev); + + +/*-------------------------------------------------- + void K_CeremonyDrawer(void); + + Handles the ranking screen and other HUD for + the podium cutscene. +--------------------------------------------------*/ + +void K_CeremonyDrawer(void); + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __K_PODIUM__ diff --git a/src/k_rank.c b/src/k_rank.c new file mode 100644 index 000000000..0e07c33ec --- /dev/null +++ b/src/k_rank.c @@ -0,0 +1,416 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) by Sally "TehRealSalt" Cochenour +// Copyright (C) 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_rank.c +/// \brief Grand Prix mode ranking + +#include "k_rank.h" +#include "k_grandprix.h" +#include "k_specialstage.h" +#include "doomdef.h" +#include "d_player.h" +#include "g_game.h" +#include "k_bot.h" +#include "k_kart.h" +#include "m_random.h" +#include "r_things.h" +#include "fastcmp.h" +#include "byteptr.h" + +// I was ALMOST tempted to start tearing apart all +// of the map loading code and turning it into C++ +// and making it properly split between read-only +// and true level loading and clean up all of the +// global variable garbage it uses ... but I stopped +// myself. So here's code duplication hell instead. +static UINT32 g_rankCapsules_mapthingsPos[UINT16_MAX]; +static size_t g_rankCapsules_nummapthings = 0; +static boolean g_rankCapsules_udmf = false; +static UINT32 g_rankCapsules_count = 0; + +/*-------------------------------------------------- + static void RankCapsules_TextmapCount(size_t size) + + Counts the number of map things and records + the structure positions, for the result of + RankCapsules_CountFromMap. + + Input Arguments:- + size - Length of the TEXTMAP lump. + + Return:- + N/A +--------------------------------------------------*/ +static UINT32 RankCapsules_TextmapCount(size_t size) +{ + const char *tkn = M_TokenizerRead(0); + UINT8 brackets = 0; + + g_rankCapsules_nummapthings = 0; + + // Look for namespace at the beginning. + if (!fastcmp(tkn, "namespace")) + { + return false; + } + + // Check if namespace is valid. + tkn = M_TokenizerRead(0); + + while ((tkn = M_TokenizerRead(0)) && M_TokenizerGetEndPos() < size) + { + // Avoid anything inside bracketed stuff, only look for external keywords. + if (brackets) + { + if (fastcmp(tkn, "}")) + brackets--; + } + else if (fastcmp(tkn, "{")) + brackets++; + // Check for valid fields. + else if (fastcmp(tkn, "thing")) + g_rankCapsules_mapthingsPos[g_rankCapsules_nummapthings++] = M_TokenizerGetEndPos(); + } + + if (brackets) + { + return false; + } + + return true; +} + +/*-------------------------------------------------- + static void RankCapsules_LoadTextmap(void) + + Loads UDMF map data for the result of + RankCapsules_CountFromMap. +--------------------------------------------------*/ +static void RankCapsules_LoadTextmap(void) +{ + size_t i; + + for (i = 0; i < g_rankCapsules_nummapthings; i++) + { + const char *param, *val; + + M_TokenizerSetEndPos(g_rankCapsules_mapthingsPos[i]); + param = M_TokenizerRead(0); + + if (!fastcmp(param, "{")) + { + continue; + } + + while (true) + { + param = M_TokenizerRead(0); + + if (fastcmp(param, "}")) + { + break; + } + + val = M_TokenizerRead(1); + + if (fastcmp(param, "type")) + { + UINT16 type = atol(val); + + if (type == mobjinfo[MT_BATTLECAPSULE].doomednum) + { + g_rankCapsules_count++; + } + + break; + } + } + } +} + +/*-------------------------------------------------- + static void RankCapsules_LoadThingsLump(UINT8 *data) + + Loads binary map data for the result of + RankCapsules_CountFromMap. + + Input Arguments:- + data - Pointer to a THINGS lump. + + Return:- + N/A +--------------------------------------------------*/ +static void RankCapsules_LoadThingsLump(UINT8 *data) +{ + size_t i; + + for (i = 0; i < g_rankCapsules_nummapthings; i++) + { + UINT16 type = 0; + + data += 2; // x + data += 2; // y + + data += 2; // angle + type = READUINT16(data); // type + type &= 4095; + + data += 2; // options + + if (type == mobjinfo[MT_BATTLECAPSULE].doomednum) + { + g_rankCapsules_count++; + } + } +} + +/*-------------------------------------------------- + static boolean RankCapsules_LoadMapData(const virtres_t *virt) + + Loads either UDMF or binary map data, for the + result of RankCapsules_CountFromMap. + + Input Arguments:- + virt - Pointer to the map's virtual resource. + + Return:- + true if we could successfully load the map data, + otherwise false. +--------------------------------------------------*/ +static boolean RankCapsules_LoadMapData(const virtres_t *virt) +{ + virtlump_t *virtthings = NULL; + + // Count map data. + if (g_rankCapsules_udmf) // Count how many entries for each type we got in textmap. + { + virtlump_t *textmap = vres_Find(virt, "TEXTMAP"); + M_TokenizerOpen((char *)textmap->data); + if (!RankCapsules_TextmapCount(textmap->size)) + { + M_TokenizerClose(); + return false; + } + } + else + { + virtthings = vres_Find(virt, "THINGS"); + + if (!virtthings) + { + return false; + } + + // Traditional doom map format just assumes the number of elements from the lump sizes. + g_rankCapsules_nummapthings = virtthings->size / (5 * sizeof (INT16)); + } + + // Load map data. + if (g_rankCapsules_udmf) + { + RankCapsules_LoadTextmap(); + M_TokenizerClose(); + } + else + { + RankCapsules_LoadThingsLump(virtthings->data); + } + + return true; +} + +/*-------------------------------------------------- + static UINT32 RankCapsules_CountFromMap(const virtres_t *virt) + + Counts the number of capsules in a map, without + needing to fully load it. + + Input Arguments:- + virt - Pointer to the map's virtual resource. + + Return:- + Number of MT_BATTLECAPSULE instances found. +--------------------------------------------------*/ +static UINT32 RankCapsules_CountFromMap(const virtres_t *virt) +{ + virtlump_t *textmap = vres_Find(virt, "TEXTMAP"); + + g_rankCapsules_udmf = (textmap != NULL); + g_rankCapsules_count = 0; + + if (RankCapsules_LoadMapData(virt) == true) + { + return g_rankCapsules_count; + } + + return 0; +} + +/*-------------------------------------------------- + void K_InitGrandPrixRank(gpRank_t *rankData) + + See header file for description. +--------------------------------------------------*/ +void K_InitGrandPrixRank(gpRank_t *rankData) +{ + UINT8 numHumans = 0; + UINT32 laps = 0; + INT32 i; + + memset(rankData, 0, sizeof(gpRank_t)); + + if (grandprixinfo.cup == NULL) + { + return; + } + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + { + if (numHumans < MAXSPLITSCREENPLAYERS && players[i].spectator == false) + { + numHumans++; + } + } + } + + // Calculate players + rankData->players = numHumans; + rankData->totalPlayers = K_GetGPPlayerCount(numHumans); + + // Initialize to the neutral value. + rankData->position = RANK_NEUTRAL_POSITION; + + // Calculate total of points + // (Should this account for all coop players?) + for (i = 0; i < numHumans; i++) + { + rankData->totalPoints += grandprixinfo.cup->numlevels * K_CalculateGPRankPoints(i + 1, rankData->totalPlayers); + } + + rankData->totalRings = grandprixinfo.cup->numlevels * numHumans * 20; + + for (i = 0; i < grandprixinfo.cup->numlevels; i++) + { + const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[i]; + if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum] != NULL) + { + laps += mapheaderinfo[cupLevelNum]->numlaps; + } + } + + // +1, since 1st place laps are worth 2 pts. + for (i = 0; i < numHumans+1; i++) + { + rankData->totalLaps += laps; + } + + // Search through all of the cup's bonus levels + // for an accurate count of how many capsules they have. + for (i = 0; i < grandprixinfo.cup->numbonus; i++) + { + const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_BONUS + i]; + if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum] != NULL) + { + lumpnum_t lp = mapheaderinfo[cupLevelNum]->lumpnum; + virtres_t *virt = NULL; + + if (lp == LUMPERROR) + { + continue; + } + + virt = vres_GetMap(lp); + if (virt == NULL) + { + continue; + } + + rankData->totalCapsules += RankCapsules_CountFromMap(virt); + vres_Free(virt); + } + } +} + +/*-------------------------------------------------- + gp_rank_e K_CalculateGPGrade(gpRank_t *rankData) + + See header file for description. +--------------------------------------------------*/ +gp_rank_e K_CalculateGPGrade(gpRank_t *rankData) +{ + static const fixed_t gradePercents[GRADE_A] = { + 7*FRACUNIT/20, // D: 35% or higher + 10*FRACUNIT/20, // C: 50% or higher + 14*FRACUNIT/20, // B: 70% or higher + 17*FRACUNIT/20 // A: 85% or higher + }; + + gp_rank_e retGrade = GRADE_E; + + const INT32 positionWeight = 150; + const INT32 pointsWeight = 100; + const INT32 lapsWeight = 100; + const INT32 capsulesWeight = 100; + const INT32 ringsWeight = 50; + const INT32 total = positionWeight + pointsWeight + lapsWeight + capsulesWeight + ringsWeight; + const INT32 continuesPenalty = 20; + + INT32 ours = 0; + fixed_t percent = 0; + + if (rankData->position > 0) + { + const INT32 sc = (rankData->position - 1); + const INT32 loser = (RANK_NEUTRAL_POSITION - 1); + ours += ((loser - sc) * positionWeight) / loser; + } + + if (rankData->totalPoints > 0) + { + ours += (rankData->winPoints * pointsWeight) / rankData->totalPoints; + } + + if (rankData->totalLaps > 0) + { + ours += (rankData->laps * lapsWeight) / rankData->totalLaps; + } + + if (rankData->totalCapsules > 0) + { + ours += (rankData->capsules * capsulesWeight) / rankData->totalCapsules; + } + + if (rankData->totalRings > 0) + { + ours += (rankData->rings * ringsWeight) / rankData->totalRings; + } + + ours -= rankData->continuesUsed * continuesPenalty; + + percent = FixedDiv(ours, total); + + for (retGrade = 0; retGrade < GRADE_A; retGrade++) + { + if (percent < gradePercents[retGrade]) + { + break; + } + } + + if (rankData->specialWon == true) + { + // Winning the Special Stage gives you + // a free grade increase. + retGrade++; + } + + return retGrade; +} diff --git a/src/k_rank.h b/src/k_rank.h new file mode 100644 index 000000000..cc675db17 --- /dev/null +++ b/src/k_rank.h @@ -0,0 +1,96 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) by Sally "TehRealSalt" Cochenour +// Copyright (C) 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_rank.h +/// \brief Grand Prix mode ranking + +#ifndef __K_RANK__ +#define __K_RANK__ + +#include "doomdef.h" +#include "doomstat.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct gpRank_t +{ + UINT8 players; + UINT8 totalPlayers; + + UINT8 position; + + UINT32 winPoints; + UINT32 totalPoints; + + UINT32 laps; + UINT32 totalLaps; + + UINT32 continuesUsed; + + UINT32 capsules; + UINT32 totalCapsules; + + UINT32 rings; + UINT32 totalRings; + + boolean specialWon; +}; + +typedef enum +{ + GRADE_E, + GRADE_D, + GRADE_C, + GRADE_B, + GRADE_A, + GRADE_S +} gp_rank_e; + +// 3rd place is neutral, anything below is a penalty +#define RANK_NEUTRAL_POSITION (3) + +/*-------------------------------------------------- + void K_InitGrandPrixRank(gpRank_t *rankData); + + Calculates rank requirements for a GP session. + + Input Arguments:- + rankData - Pointer to struct that contains all + of the information required to calculate GP rank. + + Return:- + N/A +--------------------------------------------------*/ + +void K_InitGrandPrixRank(gpRank_t *rankData); + + +/*-------------------------------------------------- + gp_rank_e K_CalculateGPGrade(gpRank_t *rankData); + + Calculates the player's grade using the + variables from gpRank. + + Input Arguments:- + rankData - struct containing existing rank data. + + Return:- + gp_rank_e representing the total grade. +--------------------------------------------------*/ + +gp_rank_e K_CalculateGPGrade(gpRank_t *rankData); + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/src/k_respawn.c b/src/k_respawn.c index cb08408a2..392ab5eae 100644 --- a/src/k_respawn.c +++ b/src/k_respawn.c @@ -150,8 +150,10 @@ void K_DoIngameRespawn(player_t *player) // FAULT if ((gametyperules & GTR_CIRCUIT) && leveltime < starttime) { - if (!(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE)) - player->respawn.wp = K_GetFinishLineWaypoint()->prevwaypoints[0]; + const waypoint_t *finish = K_GetFinishLineWaypoint(); + + if (!(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE) && finish != NULL) + player->respawn.wp = finish->prevwaypoints[0]; K_DoFault(player); } @@ -159,6 +161,7 @@ void K_DoIngameRespawn(player_t *player) player->ringboost = 0; player->driftboost = player->strongdriftboost = 0; player->gateBoost = 0; + player->sliptideZip = player->sliptideZipBoost = player->sliptideZipDelay = 0; K_TumbleInterrupt(player); P_ResetPlayer(player); diff --git a/src/k_waypoint.c b/src/k_waypoint.c index cf70c1dcd..0322b5548 100644 --- a/src/k_waypoint.c +++ b/src/k_waypoint.c @@ -235,6 +235,29 @@ INT32 K_GetWaypointID(waypoint_t *waypoint) return waypointid; } +/*-------------------------------------------------- + waypoint_t *K_GetWaypointFromID(INT32 waypointID) + + See header file for description. +--------------------------------------------------*/ +waypoint_t *K_GetWaypointFromID(INT32 waypointID) +{ + waypoint_t *waypoint = NULL; + size_t i = SIZE_MAX; + + for (i = 0; i < numwaypoints; i++) + { + waypoint = &waypointheap[i]; + + if (K_GetWaypointID(waypoint) == waypointID) + { + return waypoint; + } + } + + return NULL; +} + /*-------------------------------------------------- UINT32 K_GetCircuitLength(void) @@ -578,7 +601,7 @@ static void K_DebugWaypointDrawRadius(waypoint_t *const waypoint) spawnX = waypointmobj->x; spawnY = waypointmobj->y; - spawnZ = waypointmobj->z + 16*mapobjectscale; + spawnZ = waypointmobj->z; radiusOrb = P_SpawnMobj(spawnX, spawnY, spawnZ, MT_SPARK); @@ -586,8 +609,9 @@ static void K_DebugWaypointDrawRadius(waypoint_t *const waypoint) radiusOrb->tics = 1; radiusOrb->frame &= ~FF_TRANSMASK; - radiusOrb->frame |= FF_FULLBRIGHT; + radiusOrb->frame |= FF_FULLBRIGHT|FF_REVERSESUBTRACT; radiusOrb->color = SKINCOLOR_PURPLE; + radiusOrb->renderflags |= RF_ALWAYSONTOP; radiusOrb->destscale = FixedDiv(waypointmobj->radius, spriteRadius); P_SetScale(radiusOrb, radiusOrb->destscale); @@ -627,6 +651,7 @@ void K_DebugWaypointsVisualise(void) debugmobj->frame &= ~FF_TRANSMASK; debugmobj->frame |= FF_FULLBRIGHT; //FF_TRANS20 + debugmobj->renderflags |= RF_ALWAYSONTOP; // There's a waypoint setup for this mobj! So draw that it's a valid waypoint and draw lines to its connections if (waypoint != NULL) diff --git a/src/k_waypoint.h b/src/k_waypoint.h index c5f9a3a79..051798036 100644 --- a/src/k_waypoint.h +++ b/src/k_waypoint.h @@ -141,9 +141,25 @@ INT32 K_GetWaypointNextID(waypoint_t *waypoint); Return:- The waypoint ID, -1 if there is no waypoint or mobj. --------------------------------------------------*/ + INT32 K_GetWaypointID(waypoint_t *waypoint); +/*-------------------------------------------------- + waypoint_t *K_GetWaypointFromID(INT32 waypointID) + + Returns the first waypoint with the specified ID. + + Input Arguments:- + waypointID - The ID of the waypoint to get + + Return:- + The first waypoint with this ID, NULL if the ID doesn't exist at all in the map +--------------------------------------------------*/ + +waypoint_t *K_GetWaypointFromID(INT32 waypointID); + + /*-------------------------------------------------- UINT32 K_GetCircuitLength(void) diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 211f83867..98203ed51 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -308,6 +308,12 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->tripwireLeniency); else if (fastcmp(field,"tripwireReboundDelay")) lua_pushinteger(L, plr->tripwireReboundDelay); + else if (fastcmp(field,"sliptideZip")) + lua_pushinteger(L, plr->sliptideZip); + else if (fastcmp(field,"sliptideZipDelay")) + lua_pushinteger(L, plr->sliptideZipDelay); + else if (fastcmp(field,"sliptideZipBoost")) + lua_pushinteger(L, plr->sliptideZipBoost); /* else if (fastcmp(field,"itemroulette")) lua_pushinteger(L, plr->itemroulette); @@ -482,12 +488,10 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->timeshitprev); else if (fastcmp(field,"onconveyor")) lua_pushinteger(L, plr->onconveyor); - else if (fastcmp(field,"awayviewmobj")) - LUA_PushUserdata(L, plr->awayviewmobj, META_MOBJ); - else if (fastcmp(field,"awayviewtics")) - lua_pushinteger(L, plr->awayviewtics); - else if (fastcmp(field,"awayviewaiming")) - lua_pushangle(L, plr->awayviewaiming); + else if (fastcmp(field,"awayviewmobj")) // FIXME: struct + LUA_PushUserdata(L, plr->awayview.mobj, META_MOBJ); + else if (fastcmp(field,"awayviewtics")) // FIXME: struct + lua_pushinteger(L, plr->awayview.tics); else if (fastcmp(field,"spectator")) lua_pushboolean(L, plr->spectator); @@ -688,6 +692,12 @@ static int player_set(lua_State *L) plr->tripwireLeniency = luaL_checkinteger(L, 3); else if (fastcmp(field,"tripwireReboundDelay")) plr->tripwireReboundDelay = luaL_checkinteger(L, 3); + else if (fastcmp(field,"sliptideZip")) + plr->sliptideZip = luaL_checkinteger(L, 3); + else if (fastcmp(field,"sliptideZipDelay")) + plr->sliptideZipDelay = luaL_checkinteger(L, 3); + else if (fastcmp(field,"sliptideZipBoost")) + plr->sliptideZipBoost = luaL_checkinteger(L, 3); /* else if (fastcmp(field,"itemroulette")) plr->itemroulette = luaL_checkinteger(L, 3); @@ -848,21 +858,19 @@ static int player_set(lua_State *L) plr->timeshitprev = (UINT8)luaL_checkinteger(L, 3); else if (fastcmp(field,"onconveyor")) plr->onconveyor = (INT32)luaL_checkinteger(L, 3); - else if (fastcmp(field,"awayviewmobj")) + else if (fastcmp(field,"awayviewmobj")) // FIXME: struct { mobj_t *mo = NULL; if (!lua_isnil(L, 3)) mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ)); - P_SetTarget(&plr->awayviewmobj, mo); + P_SetTarget(&plr->awayview.mobj, mo); } - else if (fastcmp(field,"awayviewtics")) + else if (fastcmp(field,"awayviewtics")) // FIXME: struct { - plr->awayviewtics = (INT32)luaL_checkinteger(L, 3); - if (plr->awayviewtics && !plr->awayviewmobj) // awayviewtics must ALWAYS have an awayviewmobj set!! - P_SetTarget(&plr->awayviewmobj, plr->mo); // but since the script might set awayviewmobj immediately AFTER setting awayviewtics, use player mobj as filler for now. + plr->awayview.tics = (INT32)luaL_checkinteger(L, 3); + if (plr->awayview.tics && !plr->awayview.mobj) // awayviewtics must ALWAYS have an awayviewmobj set!! + P_SetTarget(&plr->awayview.mobj, plr->mo); // but since the script might set awayviewmobj immediately AFTER setting awayviewtics, use player mobj as filler for now. } - else if (fastcmp(field,"awayviewaiming")) - plr->awayviewaiming = luaL_checkangle(L, 3); else if (fastcmp(field,"spectator")) plr->spectator = lua_toboolean(L, 3); else if (fastcmp(field,"bot")) diff --git a/src/lua_script.c b/src/lua_script.c index 17abc7384..f2642e516 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -212,7 +212,7 @@ int LUA_PushGlobals(lua_State *L, const char *word) lua_pushstring(L, titlemap); return 1; } else if (fastcmp(word,"titlemapinaction")) { - lua_pushboolean(L, (titlemapinaction != TITLEMAP_OFF)); + lua_pushboolean(L, titlemapinaction); return 1; } else if (fastcmp(word,"bootmap")) { lua_pushstring(L, bootmap); @@ -223,6 +223,9 @@ int LUA_PushGlobals(lua_State *L, const char *word) } else if (fastcmp(word,"tutorialmode")) { lua_pushboolean(L, tutorialmode); return 1; + } else if (fastcmp(word,"podiummap")) { + lua_pushstring(L, podiummap); + return 1; // end map vars // begin CTF colors } else if (fastcmp(word,"skincolor_redteam")) { diff --git a/src/lua_script.h b/src/lua_script.h index 9bfddc875..9e59d4d6d 100644 --- a/src/lua_script.h +++ b/src/lua_script.h @@ -139,7 +139,7 @@ void COM_Lua_f(void); // #define HAVE_LUA_SEGS #define ISINLEVEL \ - (gamestate == GS_LEVEL || titlemapinaction) + (G_GamestateUsesLevel()) #define INLEVEL if (! ISINLEVEL)\ return luaL_error(L, "This can only be used in a level!"); diff --git a/src/m_perfstats.c b/src/m_perfstats.c index 330db5f1f..53014a5ef 100644 --- a/src/m_perfstats.c +++ b/src/m_perfstats.c @@ -255,10 +255,7 @@ static void M_DrawRenderStats(void) perfstatcol_t batchcalls_col = {220, 200, V_PURPLEMAP, batchcalls_row}; - boolean rendering = ( - gamestate == GS_LEVEL || - (gamestate == GS_TITLESCREEN && titlemapinaction) - ); + boolean rendering = G_GamestateUsesLevel(); draw_row = 10; M_DrawPerfTiming(&frametime_col); @@ -619,8 +616,9 @@ void M_DrawPerfStats(void) } else if (cv_perfstats.value == PS_THINKFRAME) // lua thinkframe { - if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction))) + if (G_GamestateUsesLevel() == false) return; + if (vid.width < 640 || vid.height < 400) // low resolution { // it's not gonna fit very well.. diff --git a/src/p_enemy.c b/src/p_enemy.c index 86ae6d0c3..ccf5ff47a 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -4165,8 +4165,8 @@ void A_OverlayThink(mobj_t *actor) { angle_t viewingangle; - if (players[displayplayers[0]].awayviewtics) - viewingangle = R_PointToAngle2(actor->target->x, actor->target->y, players[displayplayers[0]].awayviewmobj->x, players[displayplayers[0]].awayviewmobj->y); + if (players[displayplayers[0]].awayview.tics) + viewingangle = R_PointToAngle2(actor->target->x, actor->target->y, players[displayplayers[0]].awayview.mobj->x, players[displayplayers[0]].awayview.mobj->y); else if (!camera[0].chase && players[displayplayers[0]].mo) viewingangle = R_PointToAngle2(actor->target->x, actor->target->y, players[displayplayers[0]].mo->x, players[displayplayers[0]].mo->y); else @@ -9390,6 +9390,10 @@ void A_SetScale(mobj_t *actor) } locvar1 = FixedMul(locvar1, mapobjectscale); // SRB2Kart + if (target->spawnpoint != NULL) + { + locvar1 = FixedMul(locvar1, target->spawnpoint->scale); + } target->destscale = locvar1; // destination scale if (!(locvar2 & 65535)) diff --git a/src/p_inter.c b/src/p_inter.c index 576825002..05579b61c 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1934,14 +1934,13 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, return false; } - K_DestroyBumpers(player, 1); - switch (type) { case DMG_DEATHPIT: // Respawn kill types player->roundconditions.fell_off = true; K_DoIngameRespawn(player); + player->mo->health -= K_DestroyBumpers(player, 1); return false; case DMG_SPECTATOR: // disappearifies, but still gotta put items back in play @@ -1998,10 +1997,11 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, P_SetTarget(&boom->target, player->mo); } - K_DestroyBumpers(player, player->bumpers); player->pflags |= PF_ELIMINATED; } + K_DestroyBumpers(player, player->bumpers); + return true; } @@ -2164,6 +2164,9 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da const boolean hardhit = (type == DMG_EXPLODE || type == DMG_KARMA || type == DMG_TUMBLE); // This damage type can do evil stuff like ALWAYS combo INT16 ringburst = 5; + // Do not die from damage outside of bumpers health system + damage = 0; + // Check if the player is allowed to be damaged! // If not, then spawn the instashield effect instead. if (!force) @@ -2293,12 +2296,12 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da K_TryHurtSoundExchange(target, source); K_BattleAwardHit(source->player, player, inflictor, takeBumpers); - K_TakeBumpersFromPlayer(source->player, player, takeBumpers); + damage = K_TakeBumpersFromPlayer(source->player, player, takeBumpers); if (type == DMG_KARMA) { // Destroy any remainder bumpers from the player for karma comeback damage - K_DestroyBumpers(player, player->bumpers); + damage = K_DestroyBumpers(player, player->bumpers); } else { @@ -2321,7 +2324,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da } else { - K_DestroyBumpers(player, takeBumpers); + damage = K_DestroyBumpers(player, takeBumpers); } if (!(damagetype & DMG_STEAL)) @@ -2407,15 +2410,12 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da if (type != DMG_STUMBLE) { player->instashield = 15; - K_SetHitLagForObjects(target, inflictor, laglength, true); } if (inflictor && !P_MobjWasRemoved(inflictor) && inflictor->type == MT_BANANA) { player->flipDI = true; } - - return true; } } else @@ -2451,16 +2451,16 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da //K_SetHitLagForObjects(target, inflictor, laglength, true); - if (player) - P_ResetPlayer(target->player); - else + if (!player) + { P_SetMobjState(target, target->info->painstate); - if (!P_MobjWasRemoved(target)) - { - // if not intent on another player, - // chase after this one - P_SetTarget(&target->target, source); + if (!P_MobjWasRemoved(target)) + { + // if not intent on another player, + // chase after this one + P_SetTarget(&target->target, source); + } } return true; diff --git a/src/p_local.h b/src/p_local.h index 12362a548..973af8621 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -66,6 +66,10 @@ extern "C" { #define P_GetPlayerViewHeight(player) (41*player->mo->height/48) +#ifdef PARANOIA +#define SCRAMBLE_REMOVED // Force debug build to crash when Removed mobj is accessed +#endif + typedef enum { THINK_POLYOBJ, @@ -197,6 +201,8 @@ void P_HaltPlayerOrbit(player_t *player); void P_ExitPlayerOrbit(player_t *player); boolean P_PlayerOrbit(player_t *player); +void P_TickAltView(altview_t *view); + void P_MovePlayer(player_t *player); void P_PlayerThink(player_t *player); void P_PlayerAfterThink(player_t *player); @@ -282,6 +288,9 @@ extern mapthing_t *itemrespawnque[ITEMQUESIZE]; extern tic_t itemrespawntime[ITEMQUESIZE]; extern size_t iquehead, iquetail; extern consvar_t cv_gravity, cv_movebob; +#ifdef SCRAMBLE_REMOVED +extern consvar_t cv_scrambleremoved; +#endif void P_RespawnBattleBoxes(void); mobjtype_t P_GetMobjtype(UINT16 mthingtype); diff --git a/src/p_mobj.c b/src/p_mobj.c index 9594935bc..4d5c68fec 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -47,10 +47,14 @@ #include "k_objects.h" #include "k_grandprix.h" #include "k_director.h" +#include "m_easing.h" +#include "k_podium.h" static CV_PossibleValue_t CV_BobSpeed[] = {{0, "MIN"}, {4*FRACUNIT, "MAX"}, {0, NULL}}; consvar_t cv_movebob = CVAR_INIT ("movebob", "1.0", CV_FLOAT|CV_SAVE, CV_BobSpeed, NULL); +consvar_t cv_scrambleremoved = CVAR_INIT ("scrambleremoved", "On", CV_NETVAR, CV_OnOff, NULL); + actioncache_t actioncachehead; static mobj_t *overlaycap = NULL; @@ -3783,15 +3787,16 @@ void P_CalcChasePostImg(player_t *player, camera_t *thiscam) { postimg = postimg_mirror; } - else if (player->awayviewtics && player->awayviewmobj && !P_MobjWasRemoved(player->awayviewmobj)) // Camera must obviously exist + else if (player->awayview.tics && player->awayview.mobj && !P_MobjWasRemoved(player->awayview.mobj)) // Camera must obviously exist { camera_t dummycam; - dummycam.subsector = player->awayviewmobj->subsector; - dummycam.x = player->awayviewmobj->x; - dummycam.y = player->awayviewmobj->y; - dummycam.z = player->awayviewmobj->z; - //dummycam.height = 40*FRACUNIT; // alt view height is 20*FRACUNIT - dummycam.height = 0; // Why? Remote viewpoint cameras have no height. + + dummycam.subsector = player->awayview.mobj->subsector; + dummycam.x = player->awayview.mobj->x; + dummycam.y = player->awayview.mobj->y; + dummycam.z = player->awayview.mobj->z; + dummycam.height = 0; + // Are we in water? if (P_CameraCheckWater(&dummycam)) postimg = postimg_water; @@ -5269,6 +5274,11 @@ static boolean P_IsTrackerType(INT32 type) case MT_PLAYER: return true; + case MT_OVERTIME_CENTER: + case MT_MONITOR: + case MT_EMERALD: + return true; + default: return false; } @@ -6390,6 +6400,7 @@ static void P_MobjSceneryThink(mobj_t *mobj) numx->destscale = scale; } +#if 0 if (K_IsPlayerWanted(mobj->target->player) && mobj->movecount != 1) { mobj_t *wanted = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_PLAYERWANTED); @@ -6400,6 +6411,7 @@ static void P_MobjSceneryThink(mobj_t *mobj) mobj->movecount = 1; } else if (!K_IsPlayerWanted(mobj->target->player)) +#endif mobj->movecount = 0; } else @@ -8092,8 +8104,8 @@ static boolean P_MobjRegularThink(mobj_t *mobj) angle_t viewingangle; statenum_t curstate = ((mobj->tics == 1) ? (mobj->state->nextstate) : ((statenum_t)(mobj->state-states))); - if (players[displayplayers[0]].awayviewtics) - viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].awayviewmobj->x, players[displayplayers[0]].awayviewmobj->y); + if (players[displayplayers[0]].awayview.tics) + viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].awayview.mobj->x, players[displayplayers[0]].awayview.mobj->y); else if (!camera[0].chase && players[displayplayers[0]].mo) viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].mo->x, players[displayplayers[0]].mo->y); else @@ -8223,8 +8235,8 @@ static boolean P_MobjRegularThink(mobj_t *mobj) { angle_t viewingangle; - if (players[displayplayers[0]].awayviewtics) - viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].awayviewmobj->x, players[displayplayers[0]].awayviewmobj->y); + if (players[displayplayers[0]].awayview.tics) + viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].awayview.mobj->x, players[displayplayers[0]].awayview.mobj->y); else if (!camera[0].chase && players[displayplayers[0]].mo) viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].mo->x, players[displayplayers[0]].mo->y); else @@ -8328,8 +8340,8 @@ static boolean P_MobjRegularThink(mobj_t *mobj) { angle_t viewingangle; - if (players[displayplayers[0]].awayviewtics) - viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].awayviewmobj->x, players[displayplayers[0]].awayviewmobj->y); + if (players[displayplayers[0]].awayview.tics) + viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].awayview.mobj->x, players[displayplayers[0]].awayview.mobj->y); else if (!camera[0].chase && players[displayplayers[0]].mo) viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].mo->x, players[displayplayers[0]].mo->y); else @@ -9491,6 +9503,66 @@ static boolean P_MobjRegularThink(mobj_t *mobj) case MT_MONITOR_PART: Obj_MonitorPartThink(mobj); break; + case MT_ALTVIEWMAN: + { + mobj->momx = mobj->momy = mobj->momz = 0; + + if (mobj->movefactor <= 0) + { + mobj->movefactor = FRACUNIT / TICRATE; // default speed + } + + if (mobj->tracer != NULL && P_MobjWasRemoved(mobj->tracer) == false) + { + fixed_t newX = Easing_Linear(mobj->movecount, mobj->extravalue1, mobj->tracer->x); + fixed_t newY = Easing_Linear(mobj->movecount, mobj->extravalue2, mobj->tracer->y); + fixed_t newZ = Easing_Linear(mobj->movecount, mobj->cusval, mobj->tracer->z); + + mobj->angle = Easing_Linear(mobj->movecount, mobj->movedir, mobj->tracer->angle); + mobj->pitch = Easing_Linear(mobj->movecount, mobj->lastlook, mobj->tracer->pitch); + + mobj->momx = newX - mobj->x; + mobj->momy = newY - mobj->y; + mobj->momz = newZ - mobj->z; + + mobj->movecount += mobj->movefactor; + + if (mobj->movecount >= FRACUNIT) + { + mobj->movecount = mobj->movecount % FRACUNIT; // time + + mobj->movedir = mobj->tracer->angle; // start angle + mobj->lastlook = mobj->tracer->pitch; // start pitch + mobj->extravalue1 = mobj->tracer->x; // start x + mobj->extravalue2 = mobj->tracer->y; // start y + mobj->cusval = mobj->tracer->z; // start z + + P_SetTarget(&mobj->tracer, P_GetNextTubeWaypoint(mobj->tracer, false)); + } + } + + // If target is valid, then we'll focus on it. + if (mobj->target != NULL && P_MobjWasRemoved(mobj->target) == false) + { + mobj->angle = R_PointToAngle2( + mobj->x, + mobj->y, + mobj->target->x, + mobj->target->y + ); + + mobj->pitch = R_PointToAngle2( + 0, + mobj->z, + R_PointToDist2( + mobj->x, mobj->y, + mobj->target->x, mobj->target->y + ), + mobj->target->z + (mobj->target->height >> 1) + ); + } + } + break; default: // check mobj against possible water content, before movement code P_MobjCheckWater(mobj); @@ -10468,9 +10540,6 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) else switch (mobj->type) { - case MT_ALTVIEWMAN: - if (titlemapinaction) mobj->flags &= ~MF_NOTHINK; - break; case MT_LOCKONINF: P_SetScale(mobj, (mobj->destscale = 3*mobj->scale)); break; @@ -11056,9 +11125,6 @@ mapthing_t *itemrespawnque[ITEMQUESIZE]; tic_t itemrespawntime[ITEMQUESIZE]; size_t iquehead, iquetail; -#ifdef PARANOIA -#define SCRAMBLE_REMOVED // Force debug build to crash when Removed mobj is accessed -#endif void P_RemoveMobj(mobj_t *mobj) { I_Assert(mobj != NULL); @@ -11194,7 +11260,10 @@ void P_RemoveMobj(mobj_t *mobj) // DBG: set everything in mobj_t to 0xFF instead of leaving it. debug memory error. #ifdef SCRAMBLE_REMOVED // Invalidate mobj_t data to cause crashes if accessed! - memset((UINT8 *)mobj + sizeof(thinker_t), 0xff, sizeof(mobj_t) - sizeof(thinker_t)); + if (cv_scrambleremoved.value) + { + memset((UINT8 *)mobj + sizeof(thinker_t), 0xff, sizeof(mobj_t) - sizeof(thinker_t)); + } #endif } @@ -11665,7 +11734,9 @@ void P_SpawnPlayer(INT32 playernum) mobj_t *mobj; if (p->playerstate == PST_REBORN) - G_PlayerReborn(playernum, false); + { + G_PlayerReborn(playernum, (p->jointime <= 1)); + } for (i = 0; i < MAXPLAYERS; i++) { @@ -11690,7 +11761,8 @@ void P_SpawnPlayer(INT32 playernum) } else if (p->bot) { - if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE) + if (K_PodiumSequence() == false + && (!(gametyperules & GTR_BOTS) || (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE))) { // Bots aren't supposed to be here. p->spectator = true; @@ -11754,7 +11826,7 @@ void P_SpawnPlayer(INT32 playernum) p->skincolor = skincolor_blueteam; } - if (leveltime > introtime) + if (leveltime > introtime && K_PodiumSequence() == false) p->flashing = K_GetKartFlashing(p); // Babysitting deterrent mobj = P_SpawnMobj(0, 0, 0, MT_PLAYER); @@ -11798,6 +11870,8 @@ void P_SpawnPlayer(INT32 playernum) K_InitStumbleIndicator(p); + K_InitSliptideZipIndicator(p); + if (gametyperules & GTR_ITEMARROWS) { mobj_t *overheadarrow = P_SpawnMobj(mobj->x, mobj->y, mobj->z + mobj->height + 16*FRACUNIT, MT_PLAYERARROW); @@ -11833,6 +11907,11 @@ void P_SpawnPlayer(INT32 playernum) p->bumpers = K_StartingBumperCount(); K_SpawnPlayerBattleBumpers(p); } + + if (p->bumpers > 0) + { + mobj->health = p->bumpers; + } } // I'm not refactoring the loop at the top of this file. @@ -11894,6 +11973,11 @@ void P_AfterPlayerSpawn(INT32 playernum) if (CheckForReverseGravity) P_CheckGravity(mobj, false); + + if (K_PodiumSequence() == true) + { + K_InitializePodiumWaypoint(p); + } } // spawn it at a playerspawn mapthing @@ -11976,7 +12060,7 @@ void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing) mobj->angle = angle; // FAULT - if (leveltime > introtime && !p->spectator) + if (gamestate == GS_LEVEL && leveltime > introtime && !p->spectator) { K_DoIngameRespawn(p); } @@ -12054,6 +12138,7 @@ void P_MovePlayerToStarpost(INT32 playernum) fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const fixed_t y, const fixed_t dz, const fixed_t offset, const boolean flip, const fixed_t scale) { + const fixed_t finalScale = FixedMul(scale, mapobjectscale); const subsector_t *ss = R_PointInSubsector(x, y); // Axis objects snap to the floor. @@ -12062,9 +12147,9 @@ fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const f // Establish height. if (flip) - return P_GetSectorCeilingZAt(ss->sector, x, y) - dz - FixedMul(scale, offset + mobjinfo[mobjtype].height); + return P_GetSectorCeilingZAt(ss->sector, x, y) - dz - FixedMul(finalScale, offset + mobjinfo[mobjtype].height); else - return P_GetSectorFloorZAt(ss->sector, x, y) + dz + FixedMul(scale, offset); + return P_GetSectorFloorZAt(ss->sector, x, y) + dz + FixedMul(finalScale, offset); } fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mthing, const fixed_t x, const fixed_t y) @@ -13363,16 +13448,14 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y, fixed_t z, mobjtype_t i) { - fixed_t relativise = FixedDiv(mthing->scale, mapobjectscale); - mobj_t *mobj = NULL; boolean doangle = true; mobj = P_SpawnMobj(x, y, z, i); mobj->spawnpoint = mthing; - P_SetScale(mobj, FixedMul(mobj->scale, relativise)); - mobj->destscale = FixedMul(mobj->destscale, relativise); + P_SetScale(mobj, FixedMul(mobj->scale, mthing->scale)); + mobj->destscale = FixedMul(mobj->destscale, mthing->scale); if (!P_SetupSpawnedMapThing(mthing, mobj, &doangle)) return mobj; diff --git a/src/p_saveg.c b/src/p_saveg.c index fc9d7daf4..e0db5fdb0 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -67,6 +67,7 @@ typedef enum SKYBOXCENTER = 0x10, HOVERHYUDORO = 0x20, STUMBLE = 0x40, + SLIPTIDEZIP = 0x80 } player_saveflags; static inline void P_ArchivePlayer(savebuffer_t *save) @@ -137,8 +138,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEANGLE(save->p, players[i].drawangle); WRITEANGLE(save->p, players[i].viewrollangle); WRITEANGLE(save->p, players[i].tilt); - WRITEANGLE(save->p, players[i].awayviewaiming); - WRITEINT32(save->p, players[i].awayviewtics); + WRITEINT32(save->p, players[i].awayview.tics); WRITEUINT8(save->p, players[i].playerstate); WRITEUINT32(save->p, players[i].pflags); @@ -179,6 +179,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT32(save->p, players[i].realtime); WRITEUINT8(save->p, players[i].laps); WRITEUINT8(save->p, players[i].latestlap); + WRITEUINT32(save->p, players[i].lapPoints); WRITEINT32(save->p, players[i].starpostnum); WRITEUINT8(save->p, players[i].ctfteam); @@ -197,7 +198,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].splitscreenindex); - if (players[i].awayviewmobj) + if (players[i].awayview.mobj) flags |= AWAYVIEW; if (players[i].followmobj) @@ -218,6 +219,9 @@ static void P_NetArchivePlayers(savebuffer_t *save) if (players[i].stumbleIndicator) flags |= STUMBLE; + if (players[i].sliptideZipIndicator) + flags |= SLIPTIDEZIP; + WRITEUINT16(save->p, flags); if (flags & SKYBOXVIEW) @@ -227,7 +231,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT32(save->p, players[i].skybox.centerpoint->mobjnum); if (flags & AWAYVIEW) - WRITEUINT32(save->p, players[i].awayviewmobj->mobjnum); + WRITEUINT32(save->p, players[i].awayview.mobj->mobjnum); if (flags & FOLLOWITEM) WRITEUINT32(save->p, players[i].followmobj->mobjnum); @@ -238,6 +242,9 @@ static void P_NetArchivePlayers(savebuffer_t *save) if (flags & STUMBLE) WRITEUINT32(save->p, players[i].stumbleIndicator->mobjnum); + if (flags & SLIPTIDEZIP) + WRITEUINT32(save->p, players[i].sliptideZipIndicator->mobjnum); + WRITEUINT32(save->p, (UINT32)players[i].followitem); WRITEUINT32(save->p, players[i].charflags); @@ -397,6 +404,10 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].tripwireReboundDelay); + WRITEUINT16(save->p, players[i].sliptideZip); + WRITEUINT8(save->p, players[i].sliptideZipDelay); + WRITEUINT16(save->p, players[i].sliptideZipBoost); + // respawnvars_t WRITEUINT8(save->p, players[i].respawn.state); WRITEUINT32(save->p, K_GetWaypointHeapIndex(players[i].respawn.wp)); @@ -527,8 +538,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].drawangle = players[i].old_drawangle = READANGLE(save->p); players[i].viewrollangle = READANGLE(save->p); players[i].tilt = READANGLE(save->p); - players[i].awayviewaiming = READANGLE(save->p); - players[i].awayviewtics = READINT32(save->p); + players[i].awayview.tics = READINT32(save->p); players[i].playerstate = READUINT8(save->p); players[i].pflags = READUINT32(save->p); @@ -569,6 +579,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].realtime = READUINT32(save->p); // integer replacement for leveltime players[i].laps = READUINT8(save->p); // Number of laps (optional) players[i].latestlap = READUINT8(save->p); + players[i].lapPoints = READUINT32(save->p); players[i].starpostnum = READINT32(save->p); players[i].ctfteam = READUINT8(save->p); // 1 == Red, 2 == Blue @@ -596,7 +607,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].skybox.centerpoint = (mobj_t *)(size_t)READUINT32(save->p); if (flags & AWAYVIEW) - players[i].awayviewmobj = (mobj_t *)(size_t)READUINT32(save->p); + players[i].awayview.mobj = (mobj_t *)(size_t)READUINT32(save->p); if (flags & FOLLOWITEM) players[i].followmobj = (mobj_t *)(size_t)READUINT32(save->p); @@ -607,6 +618,9 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) if (flags & STUMBLE) players[i].stumbleIndicator = (mobj_t *)(size_t)READUINT32(save->p); + if (flags & SLIPTIDEZIP) + players[i].sliptideZipIndicator = (mobj_t *)(size_t)READUINT32(save->p); + players[i].followitem = (mobjtype_t)READUINT32(save->p); //SetPlayerSkinByNum(i, players[i].skin); @@ -767,6 +781,10 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].tripwireReboundDelay = READUINT8(save->p); + players[i].sliptideZip = READUINT16(save->p); + players[i].sliptideZipDelay = READUINT8(save->p); + players[i].sliptideZipBoost = READUINT16(save->p); + // respawnvars_t players[i].respawn.state = READUINT8(save->p); players[i].respawn.wp = (waypoint_t *)(size_t)READUINT32(save->p); @@ -4684,12 +4702,12 @@ static void P_RelinkPointers(void) if (!P_SetTarget(&players[i].skybox.centerpoint, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "skybox.centerpoint not found on player %d\n", i); } - if (players[i].awayviewmobj) + if (players[i].awayview.mobj) { - temp = (UINT32)(size_t)players[i].awayviewmobj; - players[i].awayviewmobj = NULL; - if (!P_SetTarget(&players[i].awayviewmobj, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "awayviewmobj not found on player %d\n", i); + temp = (UINT32)(size_t)players[i].awayview.mobj; + players[i].awayview.mobj = NULL; + if (!P_SetTarget(&players[i].awayview.mobj, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "awayview.mobj not found on player %d\n", i); } if (players[i].followmobj) { @@ -4746,6 +4764,13 @@ static void P_RelinkPointers(void) if (!P_SetTarget(&players[i].stumbleIndicator, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "stumbleIndicator not found on player %d\n", i); } + if (players[i].sliptideZipIndicator) + { + temp = (UINT32)(size_t)players[i].sliptideZipIndicator; + players[i].sliptideZipIndicator = NULL; + if (!P_SetTarget(&players[i].sliptideZipIndicator, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "sliptideZipIndicator not found on player %d\n", i); + } } } @@ -4983,8 +5008,6 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) WRITESINT8(save->p, speedscramble); WRITESINT8(save->p, encorescramble); - WRITEUINT32(save->p, g_hiscore); - // battleovertime_t WRITEUINT16(save->p, battleovertime.enabled); WRITEFIXED(save->p, battleovertime.radius); @@ -5154,8 +5177,6 @@ static inline boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) speedscramble = READSINT8(save->p); encorescramble = READSINT8(save->p); - g_hiscore = READUINT32(save->p); - // battleovertime_t battleovertime.enabled = READUINT16(save->p); battleovertime.radius = READFIXED(save->p); diff --git a/src/p_setup.c b/src/p_setup.c index a9c4894ad..64b217225 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -101,6 +101,8 @@ #include "k_specialstage.h" #include "acs/interface.h" #include "doomstat.h" // MAXMUSNAMES +#include "k_podium.h" +#include "k_rank.h" // Replay names have time #if !defined (UNDER_CE) @@ -1278,7 +1280,7 @@ static void P_LoadThings(UINT8 *data) mt->options = READUINT16(data); mt->extrainfo = (UINT8)(mt->type >> 12); Tag_FSet(&mt->tags, 0); - mt->scale = mapobjectscale; + mt->scale = FRACUNIT; memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args)); memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs)); mt->special = 0; @@ -1783,7 +1785,7 @@ static void ParseTextmapThingParameter(UINT32 i, const char *param, const char * else if (fastcmp(param, "type")) mapthings[i].type = atol(val); else if (fastcmp(param, "scale") || fastcmp(param, "scalex") || fastcmp(param, "scaley")) - mapthings[i].scale = FixedMul(mapobjectscale, FLOAT_TO_FIXED(atof(val))); + mapthings[i].scale = FLOAT_TO_FIXED(atof(val)); // Flags else if (fastcmp(param, "flip") && fastcmp("true", val)) mapthings[i].options |= MTF_OBJECTFLIP; @@ -2731,7 +2733,7 @@ static void P_LoadTextmap(void) mt->z = 0; mt->extrainfo = 0; Tag_FSet(&mt->tags, 0); - mt->scale = mapobjectscale; + mt->scale = FRACUNIT; memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args)); memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs)); mt->special = 0; @@ -7083,7 +7085,11 @@ static void P_InitLevelSettings(void) gamespeed = KARTSPEED_EASY; franticitems = false; - if (grandprixinfo.gp == true) + if (K_PodiumSequence() == true) + { + ; // NOP + } + else if (grandprixinfo.gp == true) { if (gametyperules & GTR_CIRCUIT) { @@ -7109,8 +7115,6 @@ static void P_InitLevelSettings(void) franticitems = (boolean)cv_kartfrantic.value; } - g_hiscore = 0; - memset(&battleovertime, 0, sizeof(struct battleovertime)); speedscramble = encorescramble = -1; @@ -7359,7 +7363,7 @@ static void P_InitPlayers(void) players[i].mo = NULL; - if (!(gametyperules & GTR_CIRCUIT)) + if (!(gametyperules & GTR_CIRCUIT) && K_PodiumSequence() == false) { G_DoReborn(i); } @@ -7368,6 +7372,8 @@ static void P_InitPlayers(void) G_SpawnPlayer(i); } } + + K_UpdateAllPlayerPositions(); } static void P_InitGametype(void) @@ -7377,6 +7383,26 @@ static void P_InitGametype(void) spectateGriefed = 0; K_CashInPowerLevels(); // Pushes power level changes even if intermission was skipped + if (grandprixinfo.gp == true) + { + if (grandprixinfo.initalize == true) + { + K_InitGrandPrixRank(&grandprixinfo.rank); + K_InitGrandPrixBots(); + grandprixinfo.initalize = false; + } + else if (grandprixinfo.wonround == true) + { + K_UpdateGrandPrixBots(); + grandprixinfo.wonround = false; + } + } + else if (!modeattacking) + { + // We're in a Match Race, use simplistic randomized bots. + K_UpdateMatchRaceBots(); + } + P_InitPlayers(); if (modeattacking && !demo.playback) @@ -7419,7 +7445,7 @@ static void P_InitGametype(void) // Start recording replay in multiplayer with a temp filename //@TODO I'd like to fix dedis crashing when recording replays for the future too... - if (!demo.playback && multiplayer && !dedicated) + if (gamestate == GS_LEVEL && !demo.playback && multiplayer && !dedicated) { char buf[MAX_WADPATH]; char ver[128]; @@ -7698,7 +7724,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) // Fade out music here. Deduct 2 tics so the fade volume actually reaches 0. // But don't halt the music! S_Start will take care of that. This dodges a MIDI crash bug. - if (!(reloadinggamestate || titlemapinaction)) + if (!(reloadinggamestate || gamestate != GS_LEVEL)) S_FadeMusic(0, FixedMul( FixedDiv((F_GetWipeLength(wipedefs[wipe_level_toblack])-2)*NEWTICRATERATIO, NEWTICRATE), MUSICRATE)); @@ -7706,7 +7732,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) if (rendermode != render_none) V_ReloadPalette(); // Set the level palette - if (!(reloadinggamestate || titlemapinaction)) + if (!(reloadinggamestate || gamestate != GS_LEVEL)) { if (ranspecialwipe == 2) { @@ -7746,8 +7772,11 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) F_RunWipe(wipetype, wipedefs[wipetype], false, ((levelfadecol == 0) ? "FADEMAP1" : "FADEMAP0"), false, false); } - /*if (!titlemapinaction) - wipegamestate = GS_LEVEL;*/ + + /* + if (!titlemapinaction) + wipegamestate = GS_LEVEL; + */ // Close text prompt before freeing the old level F_EndTextPrompt(false, true); @@ -7949,7 +7978,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) P_MapEnd(); // tm.thing is no longer needed from this point onwards // Took me 3 hours to figure out why my progression kept on getting overwritten with the titlemap... - if (!titlemapinaction) + if (gamestate == GS_LEVEL) { if (!lastmaploaded) // Start a new game? { @@ -7962,27 +7991,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) lastmaploaded = gamemap; // HAS to be set after saving!! } - if (reloadinggamestate) - ; - else if (grandprixinfo.gp == true) - { - if (grandprixinfo.initalize == true) - { - K_InitGrandPrixBots(); - grandprixinfo.initalize = false; - } - else if (grandprixinfo.wonround == true) - { - K_UpdateGrandPrixBots(); - grandprixinfo.wonround = false; - } - } - else if (!modeattacking) - { - // We're in a Match Race, use simplistic randomized bots. - K_UpdateMatchRaceBots(); - } - if (!fromnetsave) // uglier hack { // to make a newly loaded level start on the second frame. INT32 buf = gametic % BACKUPTICS; diff --git a/src/p_spec.c b/src/p_spec.c index c7452db2d..2ebda0d68 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2008,6 +2008,40 @@ static void K_HandleLapIncrement(player_t *player) } } + if (player->laps > player->latestlap) + { + if (player->laps > 1) + { + // save best lap for record attack + if (modeattacking && player == &players[consoleplayer]) + { + if (curlap < bestlap || bestlap == 0) + { + bestlap = curlap; + } + + curlap = 0; + } + + // Update power levels for this lap. + K_UpdatePowerLevels(player, player->laps, false); + + if (nump > 1 && K_IsPlayerLosing(player) == false) + { + if (nump > 2 && player->position == 1) // 1st place in 1v1 uses thumbs up + { + player->lapPoints += 2; + } + else + { + player->lapPoints++; + } + } + } + + player->latestlap = player->laps; + } + // finished race exit setup if (player->laps > numlaps) { @@ -2035,32 +2069,8 @@ static void K_HandleLapIncrement(player_t *player) SetRandomFakePlayerSkin(player, true); } } - - - if (player->laps > player->latestlap) - { - if (player->laps > 1) - { - // save best lap for record attack - if (modeattacking && player == &players[consoleplayer]) - { - if (curlap < bestlap || bestlap == 0) - { - bestlap = curlap; - } - - curlap = 0; - } - - // Update power levels for this lap. - K_UpdatePowerLevels(player, player->laps, false); - } - - player->latestlap = player->laps; - } thwompsactive = true; // Lap 2 effects - lowestLap = P_FindLowestLap(); for (i = 0; i < numlines; i++) @@ -2413,7 +2423,7 @@ static void P_SwitchSkybox(INT32 args, player_t *player, skybox_t *skybox) } } -static mobj_t* P_FindObjectTypeFromTag(mobjtype_t type, mtag_t tag) +mobj_t* P_FindObjectTypeFromTag(mobjtype_t type, mtag_t tag) { if (udmf) { @@ -3002,35 +3012,139 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha case 422: // Cut away to another view { - mobj_t *altview; - INT32 aim; + altview_t *modifyView = NULL; + mobj_t *newViewMobj = NULL; - if ((!mo || !mo->player) && !titlemapinaction) // only players have views, and title screens - return false; - - altview = P_FindObjectTypeFromTag(MT_ALTVIEWMAN, args[0]); - if (!altview || !altview->spawnpoint) - return false; - - // If titlemap, set the camera ref for title's thinker - // This is not revoked until overwritten; awayviewtics is ignored - if (titlemapinaction) - titlemapcameraref = altview; + if (gamestate != GS_LEVEL) + { + modifyView = &titlemapcam; + } + else if (mo != NULL && mo->player != NULL) + { + modifyView = &mo->player->awayview; + } else { - P_SetTarget(&mo->player->awayviewmobj, altview); - mo->player->awayviewtics = args[1]; + return false; } - aim = (backwardsCompat) ? args[2] : altview->spawnpoint->pitch; - aim = (aim + 360) % 360; - aim *= (ANGLE_90>>8); - aim /= 90; - aim <<= 8; - if (titlemapinaction) - titlemapcameraref->cusval = (angle_t)aim; + newViewMobj = P_FindObjectTypeFromTag(MT_ALTVIEWMAN, args[0]); + if (newViewMobj == NULL || newViewMobj->spawnpoint == NULL) + { + return false; + } + + P_SetTarget(&modifyView->mobj, newViewMobj); + + if (gamestate != GS_LEVEL) + { + // If titlemap, awayview.tics is ignored + modifyView->tics = -1; + } else - mo->player->awayviewaiming = (angle_t)aim; + { + modifyView->tics = args[1]; + } + + if (args[2] != 0) + { + switch (args[2]) + { + case TMCAM_FIRST: + case TMCAM_SECOND: + case TMCAM_THIRD: + { + mobj_t *firstPlace = NULL; + INT32 i; + + 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; + } + + if (player->position == abs(args[2])) // a bit of a hack + { + firstPlace = player->mo; + break; + } + } + + P_SetTarget( + &newViewMobj->target, + firstPlace + ); + break; + } + case TMCAM_CONSOLE: + { + mobj_t *consoleMo = NULL; + if (playeringame[consoleplayer] == true) + { + consoleMo = players[consoleplayer].mo; + } + + P_SetTarget( + &newViewMobj->target, + consoleMo + ); + break; + } + default: + { + P_SetTarget( + &newViewMobj->target, + P_FindMobjFromTID(args[2], NULL, NULL) + ); + break; + } + } + } + else + { + P_SetTarget(&newViewMobj->target, NULL); + } + + if (args[3] > 0 && args[3] <= NUMTUBEWAYPOINTSEQUENCES) + { + P_SetTarget( + &newViewMobj->tracer, + P_GetFirstTubeWaypoint(args[3] - 1) + ); + newViewMobj->movecount = 0; // time + newViewMobj->movedir = newViewMobj->angle; // start angle + newViewMobj->lastlook = newViewMobj->pitch; // start pitch + newViewMobj->extravalue1 = newViewMobj->x; // start x + newViewMobj->extravalue2 = newViewMobj->y; // start y + newViewMobj->cusval = newViewMobj->z; // start z + + if (args[4] > 0) + { + newViewMobj->movefactor = FRACUNIT / args[4]; + } + else + { + newViewMobj->movefactor = FRACUNIT / TICRATE; // default speed + } + } + else + { + P_SetTarget(&newViewMobj->tracer, NULL); + } } break; diff --git a/src/p_spec.h b/src/p_spec.h index 908046936..d33cdeb3f 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -519,6 +519,14 @@ typedef enum TMLOOP_BETA = 1, } textmaploopendpointtype_t; +typedef enum +{ + TMCAM_FIRST = -1, + TMCAM_SECOND = -2, + TMCAM_THIRD = -3, + TMCAM_CONSOLE = -4, +} textmapcamerafollow_t; + // GETSECSPECIAL (specialval, section) // // Pulls out the special # from a particular section. @@ -574,6 +582,8 @@ void P_CrossSpecialLine(line_t *line, INT32 side, mobj_t *thing); void P_PushSpecialLine(line_t *line, mobj_t *thing); void P_ActivateThingSpecial(mobj_t *mo, mobj_t *source); +mobj_t* P_FindObjectTypeFromTag(mobjtype_t type, mtag_t tag); + // // Special activation info // diff --git a/src/p_tick.c b/src/p_tick.c index 6fd4dfcd4..f45eecf4a 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -78,7 +78,7 @@ void Command_Numthinkers_f(void) thinklistnum_t end = NUM_THINKERLISTS - 1; thinklistnum_t i; - if (gamestate != GS_LEVEL) + if (G_GamestateUsesLevel() == false) { CONS_Printf(M_GetText("You must be in a level to use this.\n")); return; @@ -149,7 +149,7 @@ void Command_CountMobjs_f(void) mobjtype_t i; INT32 count; - if (gamestate != GS_LEVEL) + if (G_GamestateUsesLevel() == false) { CONS_Printf(M_GetText("You must be in a level to use this.\n")); return; @@ -532,10 +532,12 @@ void P_Ticker(boolean run) // Increment jointime and quittime even if paused for (i = 0; i < MAXPLAYERS; i++) + { if (playeringame[i]) { players[i].jointime++; } + } if (objectplacing) { @@ -602,32 +604,16 @@ void P_Ticker(boolean run) ps_playerthink_time = I_GetPreciseTime(); -#define PLAYERCONDITION(i) (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) - // First loop: Ensure all players' distance to the finish line are all accurate - for (i = 0; i < MAXPLAYERS; i++) - { - if (!PLAYERCONDITION(i)) - continue; - K_UpdateDistanceFromFinishLine(&players[i]); - } - - // Second loop: Ensure all player positions reflect everyone's distances - for (i = 0; i < MAXPLAYERS; i++) - { - if (!PLAYERCONDITION(i)) - continue; - K_KartUpdatePosition(&players[i]); - } + K_UpdateAllPlayerPositions(); // OK! Now that we got all of that sorted, players can think! for (i = 0; i < MAXPLAYERS; i++) { - if (!PLAYERCONDITION(i)) + if (!(playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo))) continue; P_PlayerThink(&players[i]); K_KartPlayerHUDUpdate(&players[i]); } -#undef PLAYERCONDITION ps_playerthink_time = I_GetPreciseTime() - ps_playerthink_time; @@ -800,10 +786,12 @@ void P_Ticker(boolean run) } } - K_UpdateDirector(); - - // Always move the camera. - P_RunChaseCameras(); + if (gamestate == GS_LEVEL) + { + // Move the camera during levels. + K_UpdateDirector(); + P_RunChaseCameras(); + } LUA_HOOK(PostThinkFrame); @@ -872,19 +860,11 @@ void P_PreTicker(INT32 frames) R_UpdateMobjInterpolators(); - // First loop: Ensure all players' distance to the finish line are all accurate - for (i = 0; i < MAXPLAYERS; i++) - if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) - K_UpdateDistanceFromFinishLine(&players[i]); - - // Second loop: Ensure all player positions reflect everyone's distances - for (i = 0; i < MAXPLAYERS; i++) - if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) - K_KartUpdatePosition(&players[i]); - - // OK! Now that we got all of that sorted, players can think! LUA_HOOK(PreThinkFrame); + K_UpdateAllPlayerPositions(); + + // OK! Now that we got all of that sorted, players can think! for (i = 0; i < MAXPLAYERS; i++) if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) { diff --git a/src/p_user.c b/src/p_user.c index 2d374e060..f957eabf9 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -58,6 +58,9 @@ #include "k_terrain.h" // K_SpawnSplashForMobj #include "k_color.h" #include "k_follower.h" +#include "k_battle.h" +#include "k_rank.h" +#include "k_director.h" #ifdef HW3SOUND #include "hardware/hw3sound.h" @@ -1294,7 +1297,7 @@ void P_DoPlayerExit(player_t *player) if ((gametyperules & GTR_CIRCUIT)) // If in Race Mode, allow { - K_KartUpdatePosition(player); + K_UpdateAllPlayerPositions(); if (cv_kartvoices.value) { @@ -1363,6 +1366,7 @@ void P_DoPlayerExit(player_t *player) if (RINGTOTAL(player) > 0) { player->totalring += RINGTOTAL(player); + grandprixinfo.rank.rings += RINGTOTAL(player); extra = player->totalring / lifethreshold; @@ -1373,6 +1377,16 @@ void P_DoPlayerExit(player_t *player) player->xtralife = extra; } } + + if (grandprixinfo.eventmode == GPEVENT_NONE) + { + grandprixinfo.rank.winPoints += K_CalculateGPRankPoints(player->position, grandprixinfo.rank.totalPlayers); + grandprixinfo.rank.laps += player->lapPoints; + } + else if (grandprixinfo.eventmode == GPEVENT_SPECIAL) + { + grandprixinfo.rank.specialWon = true; + } } } } @@ -2783,11 +2797,23 @@ static void P_DeathThink(player_t *player) } } + if ((player->pflags & PF_ELIMINATED) && (gametyperules & GTR_BUMPERS)) + { + playerGone = true; + } + if (playerGone == false && player->deadtimer > TICRATE) { player->playerstate = PST_REBORN; } + // TODO: support splitscreen + // Spectate another player after 2 seconds + if (player == &players[consoleplayer] && playerGone == true && (gametyperules & GTR_BUMPERS) && player->deadtimer == 2*TICRATE) + { + K_ToggleDirector(true); + } + // Keep time rolling if (!(player->exiting || mapreset) && !(player->pflags & PF_NOCONTEST) && !stoppedclock) { @@ -3680,10 +3706,10 @@ static void P_CalcPostImg(player_t *player) else pviewheight = player->mo->z + player->viewheight; - if (player->awayviewtics && player->awayviewmobj && !P_MobjWasRemoved(player->awayviewmobj)) + if (player->awayview.tics && player->awayview.mobj && !P_MobjWasRemoved(player->awayview.mobj)) { - sector = player->awayviewmobj->subsector->sector; - pviewheight = player->awayviewmobj->z + 20*FRACUNIT; + sector = player->awayview.mobj->subsector->sector; + pviewheight = player->awayview.mobj->z; } for (i = 0; i <= (unsigned)r_splitscreen; i++) @@ -4007,6 +4033,25 @@ DoABarrelRoll (player_t *player) player->tilt = slope; } +void P_TickAltView(altview_t *view) +{ + if (view->mobj != NULL && P_MobjWasRemoved(view->mobj) == true) + { + P_SetTarget(&view->mobj, NULL); // remove view->mobj asap if invalid + view->tics = 0; // reset to zero + } + + if (view->tics > 0) + { + view->tics--; + + if (view->tics == 0) + { + P_SetTarget(&view->mobj, NULL); + } + } +} + // // P_PlayerThink // @@ -4030,18 +4075,11 @@ void P_PlayerThink(player_t *player) player->old_drawangle = player->drawangle; - if (player->awayviewmobj && P_MobjWasRemoved(player->awayviewmobj)) - { - P_SetTarget(&player->awayviewmobj, NULL); // remove awayviewmobj asap if invalid - player->awayviewtics = 0; // reset to zero - } + P_TickAltView(&player->awayview); if (player->flashcount) player->flashcount--; - if (player->awayviewtics && player->awayviewtics != -1) - player->awayviewtics--; - // Track airtime if (P_IsObjectOnGround(player->mo) && !P_PlayerInPain(player)) // This isn't airtime, but it's control loss all the same. diff --git a/src/r_debug.cpp b/src/r_debug.cpp new file mode 100644 index 000000000..8fbe9390d --- /dev/null +++ b/src/r_debug.cpp @@ -0,0 +1,105 @@ +// RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman. +// +// 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 r_debug.cpp +/// \brief Software renderer debugging + +#include // std::clamp + +#include "cxxutil.hpp" +#include "r_debug_detail.hpp" + +#include "command.h" +#include "m_fixed.h" +#include "r_draw.h" +#include "r_main.h" + +using namespace srb2::r_debug; + +namespace +{ + +CV_PossibleValue_t contrast_cons_t[] = {{-FRACUNIT, "MIN"}, {FRACUNIT, "MAX"}, {}}; + +}; // namespace + +consvar_t cv_debugrender_contrast = + CVAR_INIT("debugrender_contrast", "0.0", CV_CHEAT | CV_FLOAT, contrast_cons_t, nullptr); + +consvar_t cv_debugrender_spriteclip = CVAR_INIT("debugrender_spriteclip", "Off", CV_CHEAT, CV_OnOff, nullptr); + +UINT32 debugrender_highlight; + +void R_CheckDebugHighlight(debugrender_highlight_t k) +{ + // If highlighting is enabled for anything, surfaces + // must be highlighted in one of two colors, depending on + // whether they fall under focus of the debug. + + if (debugrender_highlight) + { + r8_flatcolor = (debugrender_highlight & (1 << k)) ? detail::kHighlightOptions[k].color : 0x1F; + } +} + +INT32 R_AdjustLightLevel(INT32 light) +{ + if (!debugrender_highlight && cv_debugrender_contrast.value == 0) + { + return light; + } + + constexpr fixed_t kRange = (LIGHTLEVELS - 1) * FRACUNIT; + const fixed_t adjust = FixedMul(cv_debugrender_contrast.value, kRange); + + if (debugrender_highlight) + { + light = (kRange / 2) - (adjust / 2); + + SRB2_ASSERT(light >= 0); + SRB2_ASSERT(light <= kRange); + } + else + { + light = std::clamp((light * FRACUNIT) - adjust, 0, kRange); + } + + return light / FRACUNIT; +} + +void Command_Debugrender_highlight(void) +{ + const bool changes = COM_Argc() > 1; + + if (!CV_CheatsEnabled()) + { + CONS_Printf("Cheats must be enabled.\n"); + return; + } + + if (changes) + { + const char* arg = COM_Argv(1); + + debugrender_highlight = 0; // always reset + + if (COM_Argc() > 2 || + // approximate match "none" + strncasecmp(arg, "none", strlen(arg))) + { + char* p = COM_Args(); + + while (p) + { + p = detail::parse_highlight_arg(p); + } + } + } + + detail::highlight_help(changes); +} diff --git a/src/r_debug_detail.hpp b/src/r_debug_detail.hpp new file mode 100644 index 000000000..7d495db47 --- /dev/null +++ b/src/r_debug_detail.hpp @@ -0,0 +1,42 @@ +// RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman. +// +// 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 r_debug_detail.cpp +/// \brief Software renderer debugging, internal header + +#ifndef __R_DEBUG_DETAIL__ +#define __R_DEBUG_DETAIL__ + +#include "r_main.h" + +namespace srb2::r_debug::detail +{ + +struct HighlightDesc +{ + uint8_t color; + const char* label; + const char* description; +}; + +constexpr HighlightDesc kHighlightOptions[NUM_SW_HI] = { + {0x96, "planes", "Sector floor/ceiling"}, + {0x49, "fofplanes", "FOF top/bottom"}, + {0xB6, "fofsides", "FOF sides"}, + {0x7A, "midtextures", "Two-sided midtexture"}, + {0xC9, "walls", "Sector upper/lower texture, one-sided midtexture"}, + {0x23, "sprites", "Sprites"}, + {0x0F, "sky", "Sky texture"}}; + +char* skip_alnum(char* p, int mode); +char* parse_highlight_arg(char* p); +void highlight_help(bool only_on); + +}; // srb2::r_debug::detail + +#endif // __R_DEBUG_DETAIL__ diff --git a/src/r_debug_parser.cpp b/src/r_debug_parser.cpp new file mode 100644 index 000000000..8d1d30080 --- /dev/null +++ b/src/r_debug_parser.cpp @@ -0,0 +1,95 @@ +// RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman. +// +// 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 r_debug_parser.cpp +/// \brief Helper functions for the debugrender_highlight command + +#include "r_debug_detail.hpp" + +#include "doomdef.h" +#include "r_main.h" + +using namespace srb2::r_debug; +using namespace srb2::r_debug::detail; + +char* detail::skip_alnum(char* p, int mode) +{ + while (*p != '\0' && !isalnum(*p) == !mode) + { + p++; + } + + return p; +} + +char* detail::parse_highlight_arg(char* p) +{ + INT32 k; + const HighlightDesc* key; + + const auto old = static_cast(debugrender_highlight); + + char* t; + int c; + + p = skip_alnum(p, 0); // skip "whitespace" + + if (*p == '\0') + { + return NULL; + } + + t = skip_alnum(p, 1); // find end of word + + c = *t; // save to restore afterward + *t = '\0'; // isolate word string + + for (k = 0; (key = &kHighlightOptions[k]), k < NUM_SW_HI; ++k) + { + // allow an approximate match + if (!strncasecmp(p, key->label, (t - p))) + { + debugrender_highlight |= (1 << k); + // keep going to match multiple with same + // prefix + } + } + + if (debugrender_highlight == old) + { + // no change? Foolish user + CONS_Alert(CONS_WARNING, "\"%s\" makes no sense\n", p); + } + + *t = c; // restore + + return t; // skip to end of word +} + +void detail::highlight_help(bool only_on) +{ + int32_t k; + const HighlightDesc* key; + + for (k = 0; (key = &kHighlightOptions[k]), k < NUM_SW_HI; ++k) + { + const bool on = (debugrender_highlight & (1 << k)) != 0; + + if (!only_on || on) + { + CONS_Printf("%s\x80 \x87%s\x80 - %s\n", on ? "\x83 ON" : "\x85OFF", key->label, key->description); + } + } + + if (!only_on) + { + CONS_Printf("\nYou can change the highlights by using a command like:\n\n" + "\x87 debugrender_highlight planes sprites\n" + "\x87 debugrender_highlight none\n"); + } +} diff --git a/src/r_defs.h b/src/r_defs.h index d4606c8ad..4e947616b 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -932,6 +932,8 @@ typedef enum RF_NOCOLORMAPS = 0x00000400, // Sprite is not drawn with colormaps + RF_ALWAYSONTOP = 0x00000800, // Sprite is drawn on top of level geometry + RF_SPRITETYPEMASK = 0x00003000, // --Different sprite types RF_PAPERSPRITE = 0x00001000, // Paper sprite RF_FLOORSPRITE = 0x00002000, // Floor sprite diff --git a/src/r_draw.c b/src/r_draw.c index 6eb5d6cfb..44959c9d7 100644 --- a/src/r_draw.c +++ b/src/r_draw.c @@ -68,6 +68,8 @@ INT32 columnofs[MAXVIDWIDTH*4]; UINT8 *topleft; +UINT8 r8_flatcolor; + // ========================================================================= // COLUMN DRAWING CODE STUFF // ========================================================================= @@ -81,6 +83,7 @@ UINT8 dc_hires; // under MSVC boolean is a byte, while on other systems, it a bi // soo lets make it a byte on all system for the ASM code UINT8 *dc_source; UINT8 *dc_brightmap; +UINT8 *dc_lightmap; // ----------------------- // translucency stuff here @@ -638,6 +641,7 @@ void R_DrawViewBorder(void) #include "r_draw8.c" #include "r_draw8_npo2.c" +#include "r_draw8_flat.c" // ========================================================================== // INCLUDE 16bpp DRAWING CODE HERE diff --git a/src/r_draw.h b/src/r_draw.h index beb19b739..200ad5183 100644 --- a/src/r_draw.h +++ b/src/r_draw.h @@ -30,6 +30,7 @@ extern UINT8 *ylookup3[MAXVIDHEIGHT*4]; extern UINT8 *ylookup4[MAXVIDHEIGHT*4]; extern INT32 columnofs[MAXVIDWIDTH*4]; extern UINT8 *topleft; +extern UINT8 r8_flatcolor; // ------------------------- // COLUMN DRAWING CODE STUFF @@ -43,6 +44,7 @@ extern UINT8 dc_hires; extern UINT8 *dc_source; // first pixel in a column extern UINT8 *dc_brightmap; // brightmap texture column, can be NULL +extern UINT8 *dc_lightmap; // lighting only // translucency stuff here extern UINT8 *dc_transmap; @@ -76,6 +78,8 @@ extern UINT8 *ds_source; extern UINT8 *ds_brightmap; extern UINT8 *ds_transmap; +extern UINT8 ds_flatcolor; + struct floatv3_t { float x, y, z; }; @@ -232,6 +236,11 @@ void R_DrawTiltedTranslucentFloorSprite_NPO2_8(void); void R_DrawTranslucentWaterSpan_NPO2_8(void); void R_DrawTiltedTranslucentWaterSpan_NPO2_8(void); +// Debugging - highlight surfaces in flat colors +void R_DrawColumn_Flat_8(void); +void R_DrawSpan_Flat_8(void); +void R_DrawTiltedSpan_Flat_8(void); + #ifdef USEASM void ASMCALL R_DrawColumn_8_ASM(void); void ASMCALL R_DrawShadeColumn_8_ASM(void); diff --git a/src/r_draw8_flat.c b/src/r_draw8_flat.c new file mode 100644 index 000000000..20372d2c4 --- /dev/null +++ b/src/r_draw8_flat.c @@ -0,0 +1,79 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 1999-2020 by Sonic Team Junior. +// Copyright (C) 2023 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 r_draw8_flat.c +/// \brief 8bpp span/column drawer functions for debugging (draws in flat colors only) +/// \note no includes because this is included as part of r_draw.c + +void R_DrawColumn_Flat_8 (void) +{ + INT32 count; + UINT8 color = dc_lightmap[r8_flatcolor]; + register UINT8 *dest; + + count = dc_yh - dc_yl; + + if (count < 0) // Zero length, column does not exceed a pixel. + return; + +#ifdef RANGECHECK + if ((unsigned)dc_x >= (unsigned)vid.width || dc_yl < 0 || dc_yh >= vid.height) + return; +#endif + + // Framebuffer destination address. + // Use ylookup LUT to avoid multiply with ScreenWidth. + // Use columnofs LUT for subwindows? + + //dest = ylookup[dc_yl] + columnofs[dc_x]; + dest = &topleft[dc_yl*vid.width + dc_x]; + + count++; + + do + { + *dest = color; + dest += vid.width; + } while (--count); +} + +void R_DrawSpan_Flat_8 (void) +{ + UINT8 *dest = ylookup[ds_y] + columnofs[ds_x1]; + + memset(dest, ds_colormap[r8_flatcolor], (ds_x2 - ds_x1) + 1); +} + +void R_DrawTiltedSpan_Flat_8 (void) +{ + // x1, x2 = ds_x1, ds_x2 + int width = ds_x2 - ds_x1; + double iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx); + + UINT8 *dest = ylookup[ds_y]; + + // Lighting is simple. It's just linear interpolation from start to end + { + float planelightfloat = PLANELIGHTFLOAT; + float lightstart, lightend; + + lightend = (iz + ds_szp->x*width) * planelightfloat; + lightstart = iz * planelightfloat; + + R_CalcTiltedLighting(FLOAT_TO_FIXED(lightstart), FLOAT_TO_FIXED(lightend)); + //CONS_Printf("tilted lighting %f to %f (foc %f)\n", lightstart, lightend, focallengthf); + } + + while (ds_x1 <= ds_x2) + { + dest[ds_x1] = planezlight[tiltlighting[ds_x1]][r8_flatcolor]; + ds_x1++; + } +} diff --git a/src/r_main.c b/src/r_main.c index b00717810..d019af9a2 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -928,7 +928,22 @@ void R_ApplyViewMorph(int s) angle_t R_ViewRollAngle(const player_t *player) { - angle_t roll = player->viewrollangle; + angle_t roll = 0; + + if (gamestate != GS_LEVEL) + { + // FIXME: The way this is implemented is totally + // incompatible with cameras that aren't directly + // tied to the player. (podium, titlemap, + // MT_ALTVIEWMAN in general) + + // All of these player variables should affect their + // camera_t in P_MoveChaseCamera, and then this + // just returns that variable instead. + return 0; + } + + roll = player->viewrollangle; if (cv_tilting.value) { @@ -1192,10 +1207,10 @@ static void R_SetupAimingFrame(int s) player_t *player = &players[displayplayers[s]]; camera_t *thiscam = &camera[s]; - if (player->awayviewtics) + if (player->awayview.tics) { - newview->aim = player->awayviewaiming; - newview->angle = player->awayviewmobj->angle; + newview->aim = player->awayview.mobj->pitch; + newview->angle = player->awayview.mobj->angle; } else if (thiscam && thiscam->chase) { @@ -1237,15 +1252,15 @@ void R_SetupFrame(int s) R_SetupAimingFrame(s); - if (player->awayviewtics) + if (player->awayview.tics) { // cut-away view stuff - r_viewmobj = player->awayviewmobj; // should be a MT_ALTVIEWMAN + r_viewmobj = player->awayview.mobj; // should be a MT_ALTVIEWMAN I_Assert(r_viewmobj != NULL); newview->x = r_viewmobj->x; newview->y = r_viewmobj->y; - newview->z = r_viewmobj->z + 20*FRACUNIT; + newview->z = r_viewmobj->z; R_SetupCommonFrame(player, r_viewmobj->subsector); } @@ -1306,10 +1321,10 @@ void R_SkyboxFrame(int s) vector3_t campos = {0,0,0}; // Position of player's actual view point mobj_t *center = player->skybox.centerpoint; - if (player->awayviewtics) { - campos.x = player->awayviewmobj->x; - campos.y = player->awayviewmobj->y; - campos.z = player->awayviewmobj->z + 20*FRACUNIT; + if (player->awayview.tics) { + campos.x = player->awayview.mobj->x; + campos.y = player->awayview.mobj->y; + campos.z = player->awayview.mobj->z; } else if (thiscam->chase) { campos.x = thiscam->x; campos.y = thiscam->y; @@ -1403,7 +1418,7 @@ boolean R_IsViewpointThirdPerson(player_t *player, boolean skybox) boolean chasecam = R_ViewpointHasChasecam(player); // cut-away view stuff - if (player->awayviewtics || skybox) + if (player->awayview.tics || skybox) return chasecam; // use outside cam view else if (!player->spectator && chasecam) @@ -1651,9 +1666,19 @@ void R_RegisterEngineStuff(void) CV_RegisterVar(&cv_maxportals); CV_RegisterVar(&cv_movebob); +#ifdef SCRAMBLE_REMOVED + CV_RegisterVar(&cv_scrambleremoved); +#endif // Frame interpolation/uncapped CV_RegisterVar(&cv_fpscap); CV_RegisterVar(&cv_drawpickups); + + // debugging + + CV_RegisterVar(&cv_debugrender_contrast); + CV_RegisterVar(&cv_debugrender_spriteclip); + + COM_AddCommand("debugrender_highlight", Command_Debugrender_highlight); } diff --git a/src/r_main.h b/src/r_main.h index 01a0d9b31..208d846a9 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -123,6 +123,31 @@ extern consvar_t cv_skybox; extern consvar_t cv_tailspickup; extern consvar_t cv_drawpickups; +// debugging + +typedef enum { + SW_HI_PLANES, + SW_HI_FOFPLANES, + SW_HI_FOFSIDES, + SW_HI_MIDTEXTURES, + SW_HI_WALLS, + SW_HI_THINGS, + SW_HI_SKY, + + NUM_SW_HI +} debugrender_highlight_t; + +extern UINT32 debugrender_highlight; + +void R_CheckDebugHighlight(debugrender_highlight_t type); +INT32 R_AdjustLightLevel(INT32 light); + +void Command_Debugrender_highlight(void); + +extern consvar_t + cv_debugrender_contrast, + cv_debugrender_spriteclip; + // Called by startup code. void R_Init(void); diff --git a/src/r_plane.c b/src/r_plane.c index 4472c9fc3..be4ecd02d 100644 --- a/src/r_plane.c +++ b/src/r_plane.c @@ -208,14 +208,17 @@ static void R_MapPlane(INT32 y, INT32 x1, INT32 x2) pindex = MAXLIGHTZ - 1; ds_colormap = planezlight[pindex]; - if (currentplane->extra_colormap) - ds_colormap = currentplane->extra_colormap->colormap + (ds_colormap - colormaps); - - ds_fullbright = colormaps; - if (encoremap && !currentplane->noencore) + if (!debugrender_highlight) { - ds_colormap += COLORMAP_REMAPOFFSET; - ds_fullbright += COLORMAP_REMAPOFFSET; + if (currentplane->extra_colormap) + ds_colormap = currentplane->extra_colormap->colormap + (ds_colormap - colormaps); + + ds_fullbright = colormaps; + if (encoremap && !currentplane->noencore) + { + ds_colormap += COLORMAP_REMAPOFFSET; + ds_fullbright += COLORMAP_REMAPOFFSET; + } } ds_y = y; @@ -613,6 +616,8 @@ static void R_DrawSkyPlane(visplane_t *pl) INT32 x; INT32 angle; + R_CheckDebugHighlight(SW_HI_SKY); + // Reset column drawer function (note: couldn't we just call walldrawerfunc directly?) // (that is, unless we'll need to switch drawers in future for some reason) R_SetColumnFunc(BASEDRAWFUNC, false); @@ -631,6 +636,7 @@ static void R_DrawSkyPlane(visplane_t *pl) dc_colormap += COLORMAP_REMAPOFFSET; dc_fullbright += COLORMAP_REMAPOFFSET; } + dc_lightmap = colormaps; dc_texturemid = skytexturemid; dc_texheight = textureheight[skytexture] >>FRACBITS; @@ -831,6 +837,7 @@ void R_DrawSinglePlane(visplane_t *pl) INT32 x, stop; ffloor_t *rover; INT32 type, spanfunctype = BASEDRAWFUNC; + debugrender_highlight_t debug = 0; void (*mapfunc)(INT32, INT32, INT32) = R_MapPlane; if (!(pl->minx <= pl->maxx)) @@ -911,10 +918,16 @@ void R_DrawSinglePlane(visplane_t *pl) light = (pl->lightlevel >> LIGHTSEGSHIFT); } else light = (pl->lightlevel >> LIGHTSEGSHIFT); + + debug = SW_HI_FOFPLANES; } else + { light = (pl->lightlevel >> LIGHTSEGSHIFT); + debug = SW_HI_PLANES; + } + #ifndef NOWATER if (pl->ripple) { @@ -1030,6 +1043,8 @@ void R_DrawSinglePlane(visplane_t *pl) if (light < 0) light = 0; + light = R_AdjustLightLevel(light); + if (pl->slope) { mapfunc = R_MapTiltedPlane; @@ -1083,6 +1098,8 @@ void R_DrawSinglePlane(visplane_t *pl) } + R_CheckDebugHighlight(debug); + // Use the correct span drawer depending on the powers-of-twoness R_SetSpanFunc(spanfunctype, !ds_powersoftwo, ds_brightmap != NULL); diff --git a/src/r_segs.c b/src/r_segs.c index c00330a4d..11bac628c 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -185,6 +185,8 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) transtable = 0; } + R_CheckDebugHighlight(SW_HI_MIDTEXTURES); + if (blendmode == AST_FOG) { R_SetColumnFunc(COLDRAWFUNC_FOG, bmnum != 0); @@ -298,6 +300,8 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) else if (P_ApplyLightOffset(lightnum)) lightnum += curline->lightOffset; + lightnum = R_AdjustLightLevel(lightnum); + if (lightnum < 0) walllights = scalelight[0]; else if (lightnum >= LIGHTLEVELS) @@ -413,12 +417,14 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) if ((rlight->flags & FOF_NOSHADE)) continue; - if (rlight->lightnum < 0) + lightnum = R_AdjustLightLevel(rlight->lightnum); + + if (lightnum < 0) xwalllights = scalelight[0]; - else if (rlight->lightnum >= LIGHTLEVELS) + else if (lightnum >= LIGHTLEVELS) xwalllights = scalelight[LIGHTLEVELS-1]; else - xwalllights = scalelight[rlight->lightnum]; + xwalllights = scalelight[lightnum]; pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT; @@ -436,6 +442,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) if (height <= windowtop) { dc_colormap = rlight->rcolormap; + dc_lightmap = xwalllights[pindex]; dc_fullbright = colormaps; if (encoremap && !(ldef->flags & ML_TFERLINE)) { @@ -461,6 +468,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) colfunc_2s(col, bmCol, -1); windowtop = windowbottom + 1; dc_colormap = rlight->rcolormap; + dc_lightmap = xwalllights[pindex]; dc_fullbright = colormaps; if (encoremap && !(ldef->flags & ML_TFERLINE)) { @@ -483,6 +491,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) pindex = MAXLIGHTSCALE - 1; dc_colormap = walllights[pindex]; + dc_lightmap = walllights[pindex]; dc_fullbright = colormaps; if (encoremap && !(ldef->flags & ML_TFERLINE)) { @@ -638,6 +647,8 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) texnum = R_GetTextureNum(sides[pfloor->master->sidenum[0]].midtexture); bmnum = R_GetTextureBrightmap(texnum); + R_CheckDebugHighlight(SW_HI_FOFSIDES); + R_SetColumnFunc(BASEDRAWFUNC, bmnum != 0); if (pfloor->master->flags & ML_TFERLINE) @@ -789,6 +800,8 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) else if (P_ApplyLightOffset(lightnum)) lightnum += curline->lightOffset; + lightnum = R_AdjustLightLevel(lightnum); + if (lightnum < 0) walllights = scalelight[0]; else if (lightnum >= LIGHTLEVELS) @@ -954,16 +967,15 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) fixed_t height; fixed_t bheight = 0; INT32 solid = 0; - INT32 lighteffect = 0; for (i = 0; i < dc_numlights; i++) { // Check if the current light effects the colormap/lightlevel rlight = &dc_lightlist[i]; - lighteffect = !(dc_lightlist[i].flags & FOF_NOSHADE); - if (lighteffect) + xwalllights = NULL; + if (!(dc_lightlist[i].flags & FOF_NOSHADE)) { - lightnum = rlight->lightnum; + lightnum = R_AdjustLightLevel(rlight->lightnum); if (lightnum < 0) xwalllights = scalelight[0]; @@ -1024,9 +1036,10 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) if (height <= windowtop) { - if (lighteffect) + if (xwalllights) { dc_colormap = rlight->rcolormap; + dc_lightmap = xwalllights[pindex]; dc_fullbright = colormaps; if (encoremap && !(curline->linedef->flags & ML_TFERLINE)) { @@ -1060,9 +1073,10 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) windowtop = bheight; else windowtop = windowbottom + 1; - if (lighteffect) + if (xwalllights) { dc_colormap = rlight->rcolormap; + dc_lightmap = xwalllights[pindex]; dc_fullbright = colormaps; if (encoremap && !(curline->linedef->flags & ML_TFERLINE)) { @@ -1087,6 +1101,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) pindex = MAXLIGHTSCALE - 1; dc_colormap = walllights[pindex]; + dc_lightmap = walllights[pindex]; dc_fullbright = colormaps; if (encoremap && !(curline->linedef->flags & ML_TFERLINE)) @@ -1353,6 +1368,7 @@ static void R_RenderSegLoop (void) pindex = MAXLIGHTSCALE-1; dc_colormap = walllights[pindex]; + dc_lightmap = walllights[pindex]; dc_fullbright = colormaps; if (encoremap && !(curline->linedef->flags & ML_TFERLINE)) { @@ -1379,6 +1395,8 @@ static void R_RenderSegLoop (void) else if (P_ApplyLightOffset(lightnum)) lightnum += curline->lightOffset; + lightnum = R_AdjustLightLevel(lightnum); + if (lightnum < 0) xwalllights = scalelight[0]; else if (lightnum >= LIGHTLEVELS) @@ -1618,6 +1636,8 @@ void R_StoreWallRange(INT32 start, INT32 stop) memset(&segleft, 0x00, sizeof(segleft)); memset(&segright, 0x00, sizeof(segright)); + R_CheckDebugHighlight(SW_HI_WALLS); + R_SetColumnFunc(BASEDRAWFUNC, false); if (ds_p == drawsegs+maxdrawsegs) @@ -2425,6 +2445,8 @@ void R_StoreWallRange(INT32 start, INT32 stop) if (P_ApplyLightOffset(lightnum)) lightnum += curline->lightOffset; + lightnum = R_AdjustLightLevel(lightnum); + if (lightnum < 0) walllights = scalelight[0]; else if (lightnum >= LIGHTLEVELS) diff --git a/src/r_things.c b/src/r_things.c index 92f9148ee..458eaac4e 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -939,6 +939,8 @@ static void R_DrawVisSprite(vissprite_t *vis) if (!dc_colormap) dc_colormap = colormaps; + dc_lightmap = colormaps; + dc_fullbright = colormaps; if (encoremap && !vis->mobj->color && !(vis->mobj->flags & MF_DONTENCOREMAP)) @@ -1143,6 +1145,8 @@ static void R_DrawPrecipitationVisSprite(vissprite_t *vis) dc_fullbright += COLORMAP_REMAPOFFSET; } + dc_lightmap = colormaps; + dc_iscale = FixedDiv(FRACUNIT, vis->scale); dc_texturemid = FixedDiv(vis->texturemid, this_scale); dc_texheight = 0; @@ -2971,6 +2975,9 @@ static void R_CreateDrawNodes(maskcount_t* mask, drawnode_t* head, boolean temps for (rover = vsprsortedhead.prev; rover != &vsprsortedhead; rover = rover->prev) { + const boolean alwaysontop = cv_debugrender_spriteclip.value || (rover->renderflags & RF_ALWAYSONTOP); + const INT32 ontopflag = cv_debugrender_spriteclip.value ? 0 : (rover->renderflags & RF_ALWAYSONTOP); + if (rover->szt > vid.height || rover->sz < 0) continue; @@ -2978,6 +2985,25 @@ static void R_CreateDrawNodes(maskcount_t* mask, drawnode_t* head, boolean temps for (r2 = head->next; r2 != head; r2 = r2->next) { + if (alwaysontop) + { + // Only sort behind other sprites; sorts in + // front of everything else. + if (!r2->sprite) + { + continue; + } + + // Only sort behind other RF_ALWAYSONTOP sprites. + // This avoids sorting behind a sprite that is + // behind level geometry and thus sorting this + // one behind level geometry too. + if (r2->sprite->renderflags ^ ontopflag) + { + continue; + } + } + if (r2->plane) { fixed_t planeobjectz, planecameraz; @@ -3210,6 +3236,8 @@ static void R_DrawSprite(vissprite_t *spr) mfloorclip = spr->clipbot; mceilingclip = spr->cliptop; + R_CheckDebugHighlight(SW_HI_THINGS); + if (spr->cut & SC_BBOX) R_DrawThingBoundingBox(spr); else if (spr->cut & SC_SPLAT) @@ -3238,6 +3266,16 @@ void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, portal_t* portal) fixed_t lowscale; INT32 silhouette; + if ((spr->renderflags & RF_ALWAYSONTOP) || cv_debugrender_spriteclip.value) + { + for (x = x1; x <= x2; x++) + { + spr->clipbot[x] = (INT16)viewheight; + spr->cliptop[x] = (INT16)con_clipviewtop; + } + return; + } + for (x = x1; x <= x2; x++) { spr->clipbot[x] = spr->cliptop[x] = CLIP_UNDEF; diff --git a/src/s_sound.c b/src/s_sound.c index 6deab1f91..b45215c73 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -540,9 +540,9 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume) continue; } - if (player->awayviewtics) + if (player->awayview.tics) { - listenmobj[i] = player->awayviewmobj; + listenmobj[i] = player->awayview.mobj; } else { @@ -572,7 +572,7 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume) continue; } - if (camera[i].chase && !player->awayviewtics) + if (camera[i].chase && !player->awayview.tics) { listener[i].x = camera[i].x; listener[i].y = camera[i].y; @@ -827,9 +827,9 @@ void S_UpdateSounds(void) continue; } - if (player->awayviewtics) + if (player->awayview.tics) { - listenmobj[i] = player->awayviewmobj; + listenmobj[i] = player->awayview.mobj; } else { @@ -858,7 +858,7 @@ void S_UpdateSounds(void) continue; } - if (camera[i].chase && !player->awayviewtics) + if (camera[i].chase && !player->awayview.tics) { listener[i].x = camera[i].x; listener[i].y = camera[i].y; diff --git a/src/screen.c b/src/screen.c index 2b78d03f1..90ccd4bac 100644 --- a/src/screen.c +++ b/src/screen.c @@ -64,6 +64,7 @@ void (*spanfuncs_npo2[SPANDRAWFUNC_MAX])(void); #ifdef USE_COL_SPAN_ASM void (*spanfuncs_asm[SPANDRAWFUNC_MAX])(void); #endif +void (*spanfuncs_flat[SPANDRAWFUNC_MAX])(void); // ------------------ // global video state @@ -170,6 +171,22 @@ void SCR_SetDrawFuncs(void) spanfuncs_npo2[SPANDRAWFUNC_TILTEDWATER] = R_DrawTiltedTranslucentWaterSpan_NPO2_8; spanfuncs_npo2[SPANDRAWFUNC_FOG] = NULL; // Not needed + // Debugging - highlight surfaces in flat colors + spanfuncs_flat[BASEDRAWFUNC] = R_DrawSpan_Flat_8; + spanfuncs_flat[SPANDRAWFUNC_TRANS] = R_DrawSpan_Flat_8; + spanfuncs_flat[SPANDRAWFUNC_TILTED] = R_DrawTiltedSpan_Flat_8; + spanfuncs_flat[SPANDRAWFUNC_TILTEDTRANS] = R_DrawTiltedSpan_Flat_8; + spanfuncs_flat[SPANDRAWFUNC_SPLAT] = R_DrawSpan_Flat_8; + spanfuncs_flat[SPANDRAWFUNC_TRANSSPLAT] = R_DrawSpan_Flat_8; + spanfuncs_flat[SPANDRAWFUNC_TILTEDSPLAT] = R_DrawTiltedSpan_Flat_8; + spanfuncs_flat[SPANDRAWFUNC_SPRITE] = R_DrawSpan_Flat_8; + spanfuncs_flat[SPANDRAWFUNC_TRANSSPRITE] = R_DrawSpan_Flat_8; + spanfuncs_flat[SPANDRAWFUNC_TILTEDSPRITE] = R_DrawTiltedSpan_Flat_8; + spanfuncs_flat[SPANDRAWFUNC_TILTEDTRANSSPRITE] = R_DrawTiltedSpan_Flat_8; + spanfuncs_flat[SPANDRAWFUNC_WATER] = R_DrawSpan_Flat_8; + spanfuncs_flat[SPANDRAWFUNC_TILTEDWATER] = R_DrawTiltedSpan_Flat_8; + spanfuncs_flat[SPANDRAWFUNC_FOG] = R_DrawSpan_Flat_8; // Not needed + #if (defined(RUSEASM) && defined(USE_COL_SPAN_ASM)) if (R_ASM) { @@ -220,13 +237,17 @@ void R_SetColumnFunc(size_t id, boolean brightmapped) colfunctype = id; + if (debugrender_highlight != 0) + { + colfunc = R_DrawColumn_Flat_8; + } #ifdef USE_COL_SPAN_ASM - if (colfuncs_asm[id] != NULL && brightmapped == false) + else if (colfuncs_asm[id] != NULL && brightmapped == false) { colfunc = colfuncs_asm[id]; } - else #endif + else { colfunc = colfuncs[id]; } @@ -236,7 +257,11 @@ void R_SetSpanFunc(size_t id, boolean npo2, boolean brightmapped) { I_Assert(id < SPANDRAWFUNC_MAX); - if (spanfuncs_npo2[id] != NULL && npo2 == true) + if (spanfuncs_flat[id] != NULL && debugrender_highlight != 0) + { + spanfunc = spanfuncs_flat[id]; + } + else if (spanfuncs_npo2[id] != NULL && npo2 == true) { spanfunc = spanfuncs_npo2[id]; } diff --git a/src/screen.h b/src/screen.h index f76fe05a3..e9cfc7d59 100644 --- a/src/screen.h +++ b/src/screen.h @@ -179,6 +179,7 @@ extern void (*spanfuncs_npo2[SPANDRAWFUNC_MAX])(void); #ifdef USE_COL_SPAN_ASM extern void (*spanfuncs_asm[SPANDRAWFUNC_MAX])(void); #endif +extern void (*spanfuncs_flat[SPANDRAWFUNC_MAX])(void); // ----- // CPUID diff --git a/src/typedef.h b/src/typedef.h index c3b404b03..5071ff01f 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -47,6 +47,7 @@ TYPEDEF (botvars_t); TYPEDEF (roundconditions_t); TYPEDEF (skybox_t); TYPEDEF (itemroulette_t); +TYPEDEF (altview_t); TYPEDEF (player_t); // d_clisrv.h @@ -198,6 +199,9 @@ TYPEDEF (t_floor_t); // k_waypoint.h TYPEDEF (waypoint_t); +// k_rank.h +TYPEDEF (gpRank_t); + // lua_hudlib_drawlist.h typedef struct huddrawlist_s *huddrawlist_h;