diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4e68db60b..04d1c6040 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -134,6 +134,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 b6e1ce3a8..0e131a3fb 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) @@ -1332,3 +1352,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/d_clisrv.c b/src/d_clisrv.c index 76470b865..807e55666 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,7 +2557,7 @@ 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); @@ -3771,9 +3771,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 53ae4d86c..26adaf72e 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 @@ -344,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); @@ -353,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); @@ -406,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; @@ -470,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) { @@ -566,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 { @@ -583,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 @@ -752,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 98dee8e9a..1588ff0f1 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -384,6 +384,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 // ======================================================================== @@ -627,6 +634,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 @@ -644,9 +652,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 diff --git a/src/deh_soc.c b/src/deh_soc.c index 5da9a4173..5c281db50 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2559,6 +2559,10 @@ void readmaincfg(MYFILE *f, boolean mainfile) INT32 value; boolean doClearLevels = false; +#ifdef DEVELOP + (void)mainfile; +#endif + do { if (myfgets(s, MAXLINELEN, f)) @@ -2630,10 +2634,12 @@ void readmaincfg(MYFILE *f, boolean mainfile) //clear_levels(); doClearLevels = true; } +#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'); @@ -2741,6 +2747,7 @@ void readmaincfg(MYFILE *f, boolean mainfile) } else if (fastcmp(word, "TITLEMAP")) { + Z_Free(titlemap); titlemap = Z_StrDup(word2); titlechanged = true; } @@ -2835,13 +2842,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); } @@ -2980,6 +2994,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/doomstat.h b/src/doomstat.h index 88e9bd66d..18bf1dd15 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. diff --git a/src/f_finale.c b/src/f_finale.c index acd923765..bd3adb85b 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 a0d187790..c5ec1edee 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; @@ -1449,9 +1453,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 +1465,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 +1482,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 +1549,11 @@ void G_DoLoadLevel(boolean resetplayer) } } +void G_DoLoadLevel(boolean resetplayer) +{ + G_DoLoadLevelEx(resetplayer, GS_LEVEL); +} + // // Start the title card. // @@ -1573,7 +1584,7 @@ void G_StartTitleCard(void) } // start the title card - WipeStageTitle = (!titlemapinaction); + WipeStageTitle = (gamestate == GS_LEVEL); } // @@ -1717,6 +1728,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 +1753,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 +2127,7 @@ void G_Ticker(boolean run) marathontime++; P_MapStart(); - // do player reborns if needed + if (gamestate == GS_LEVEL) { // Or, alternatively, retry. @@ -2117,11 +2145,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 +2220,6 @@ void G_Ticker(boolean run) F_TextPromptTicker(); AM_Ticker(); HU_Ticker(); - break; case GS_INTERMISSION: @@ -2237,6 +2281,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(); @@ -2349,6 +2398,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) INT16 totalring; UINT8 laps; UINT8 latestlap; + UINT32 lapPoints; UINT16 skincolor; INT32 skin; UINT8 availabilities[MAXAVAILABILITY]; @@ -2469,6 +2519,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) nocontrol = 0; laps = 0; latestlap = 0; + lapPoints = 0; roundscore = 0; exiting = 0; khudfinish = 0; @@ -2504,6 +2555,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) laps = players[player].laps; latestlap = players[player].latestlap; + lapPoints = players[player].lapPoints; roundscore = players[player].roundscore; @@ -2528,7 +2580,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); @@ -2576,6 +2628,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->laps = laps; p->latestlap = latestlap; + p->lapPoints = lapPoints; p->totalring = totalring; p->bot = bot; @@ -2732,7 +2785,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)); @@ -2828,7 +2881,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++) { @@ -2911,6 +2966,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) { @@ -2947,9 +3038,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 -- @@ -3096,13 +3192,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(); @@ -3111,11 +3214,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; @@ -3806,20 +3912,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]) @@ -4047,6 +4142,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]) @@ -4072,6 +4170,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])); + } } } @@ -4281,8 +4384,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 { @@ -5061,6 +5166,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; @@ -5355,6 +5470,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 @@ -5381,6 +5512,11 @@ boolean G_GetExitGameFlag(void) // Same deal with retrying. void G_SetRetryFlag(void) { + if (retrying == false) + { + grandprixinfo.rank.continuesUsed++; + } + retrying = true; } @@ -5438,4 +5574,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 fac245c9a..ed38b4402 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..a400c4cc2 100644 --- a/src/info.c +++ b/src/info.c @@ -21281,7 +21281,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 }, diff --git a/src/k_battle.c b/src/k_battle.c index 37bb33f4f..4266d6981 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; diff --git a/src/k_bot.c b/src/k_bot.c index 56b9a8c1f..cb0b57eb0 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) @@ -261,6 +261,9 @@ void K_UpdateMatchRaceBots(void) --------------------------------------------------*/ boolean K_PlayerUsesBotMovement(player_t *player) { + if (K_PodiumSequence() == true) + return true; + if (player->exiting) return true; @@ -1264,6 +1267,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) @@ -1282,7 +1342,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; @@ -1294,6 +1356,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 @@ -1556,6 +1624,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 ee773dfa3..12757a2a3 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) { @@ -811,6 +812,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 6d879054b..bc4779386 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -114,6 +114,24 @@ UINT8 K_BotDefaultSkin(void) return (UINT8)defaultbotskin; } +/*-------------------------------------------------- + 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) @@ -198,12 +216,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 a862245c7..efa251246 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); @@ -81,6 +85,23 @@ INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers); UINT8 K_BotDefaultSkin(void); +/*-------------------------------------------------- + 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); @@ -170,6 +191,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_kart.c b/src/k_kart.c index 3cad74abb..65a25ad07 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; @@ -1226,6 +1240,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) @@ -3215,27 +3234,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); + } } } @@ -3263,16 +3297,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); } @@ -3403,7 +3453,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; @@ -6975,6 +7025,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; @@ -6987,13 +7044,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 @@ -8471,6 +8521,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(); @@ -8837,6 +8893,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)) @@ -9263,64 +9328,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++; + } + } } } } @@ -9378,6 +9458,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 // @@ -9917,18 +10020,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 66ad64b50..7a1decb92 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -139,6 +139,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 +177,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_menufunc.c b/src/k_menufunc.c index 51bc3a2a7..e01e0a262 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -201,6 +201,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 // @@ -208,9 +225,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; } 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_waypoint.c b/src/k_waypoint.c index cf70c1dcd..b0ed8b39b 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) 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..5e3963402 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -482,12 +482,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); @@ -848,21 +846,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_local.h b/src/p_local.h index 12362a548..eb4633ff4 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -197,6 +197,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); diff --git a/src/p_mobj.c b/src/p_mobj.c index eb2e18498..efc2de06c 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -47,6 +47,8 @@ #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); @@ -3779,15 +3781,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; @@ -8088,8 +8091,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 @@ -8219,8 +8222,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 @@ -8324,8 +8327,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 @@ -9487,6 +9490,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); @@ -10464,9 +10527,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; @@ -11661,7 +11721,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++) { @@ -11686,7 +11748,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; @@ -11750,7 +11813,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); @@ -11895,6 +11958,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 @@ -11977,7 +12045,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); } @@ -12055,6 +12123,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. @@ -12063,9 +12132,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) @@ -13364,16 +13433,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..8e6a4dd15 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -137,8 +137,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 +178,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 +197,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) @@ -227,7 +227,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); @@ -527,8 +527,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 +568,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 +596,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); @@ -4684,12 +4684,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) { diff --git a/src/p_setup.c b/src/p_setup.c index d97696309..d0cc73d6f 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) { @@ -7359,7 +7365,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 +7374,8 @@ static void P_InitPlayers(void) G_SpawnPlayer(i); } } + + K_UpdateAllPlayerPositions(); } static void P_InitGametype(void) @@ -7377,6 +7385,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 +7447,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]; @@ -7697,7 +7725,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)); @@ -7705,7 +7733,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) { @@ -7745,8 +7773,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); @@ -7948,7 +7979,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? { @@ -7961,27 +7992,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 aa480024e..ef6d0c9d3 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2007,6 +2007,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) { @@ -2034,32 +2068,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++) @@ -2412,7 +2422,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) { @@ -3001,35 +3011,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 70c4a14f3..22f9187be 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; } @@ -795,10 +781,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); @@ -867,19 +855,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 f95d5afc1..3d290a678 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -58,6 +58,8 @@ #include "k_terrain.h" // K_SpawnSplashForMobj #include "k_color.h" #include "k_follower.h" +#include "k_battle.h" +#include "k_rank.h" #ifdef HW3SOUND #include "hardware/hw3sound.h" @@ -1284,7 +1286,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) { @@ -1353,6 +1355,7 @@ void P_DoPlayerExit(player_t *player) if (RINGTOTAL(player) > 0) { player->totalring += RINGTOTAL(player); + grandprixinfo.rank.rings += RINGTOTAL(player); extra = player->totalring / lifethreshold; @@ -1363,6 +1366,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; + } } } } @@ -3670,10 +3683,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++) @@ -3997,6 +4010,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 // @@ -4020,18 +4052,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_main.c b/src/r_main.c index b00717810..0dfe36d38 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) 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/typedef.h b/src/typedef.h index 632684d1e..ae61dea9a 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -46,6 +46,7 @@ TYPEDEF (respawnvars_t); TYPEDEF (botvars_t); TYPEDEF (skybox_t); TYPEDEF (itemroulette_t); +TYPEDEF (altview_t); TYPEDEF (player_t); // d_clisrv.h @@ -197,6 +198,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;