diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c35ff50f..d3c9be35b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,7 +88,7 @@ if(SRB2_CONFIG_ENABLE_TESTS) # https://github.com/catchorg/Catch2 CPMAddPackage( NAME Catch2 - VERSION 3.1.1 + VERSION 3.4.0 GITHUB_REPOSITORY catchorg/Catch2 OPTIONS "CATCH_INSTALL_DOCS OFF" diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index 747d74144..b122c9943 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -43,6 +43,7 @@ #include "../k_podium.h" #include "../k_bot.h" #include "../z_zone.h" +#include "../music.h" #include "call-funcs.hpp" @@ -1802,6 +1803,7 @@ bool CallFunc_MapWarp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Wor if (argV[1] == 0) skipstats = 1; + G_BeginLevelExit(); exitcountdown = 1; if (server) @@ -1856,6 +1858,91 @@ bool CallFunc_AddBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word return false; } +/*-------------------------------------------------- + bool CallFunc_StopLevelExit(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Halts the level exit if it's happening. +--------------------------------------------------*/ +bool CallFunc_StopLevelExit(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + exitcountdown = 0; + return false; +} + +/*-------------------------------------------------- + bool CallFunc_ExitLevel(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Exits the level. +--------------------------------------------------*/ +bool CallFunc_ExitLevel(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + if (exitcountdown == 1) + { + // An exit is already in progress. + return false; + } + + if (argC >= 1) + { + skipstats = (argV[0] == 0); + } + + G_BeginLevelExit(); + exitcountdown = 1; + + if (server) + SendNetXCmd(XD_EXITLEVEL, NULL, 0); + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_MusicPlay(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Play a tune. If it's already playing, restart from the + beginning. +--------------------------------------------------*/ +bool CallFunc_MusicPlay(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + ACSVM::MapScope *map = thread->scopeMap; + + Music_Play(map->getString(argV[0])->str); + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_MusicStopAll(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Stop every tune that is currently playing. +--------------------------------------------------*/ +bool CallFunc_MusicStopAll(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + Music_StopAll(); + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_MusicRemap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Change the actual song lump that a tune will play. +--------------------------------------------------*/ +bool CallFunc_MusicRemap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + ACSVM::MapScope *map = thread->scopeMap; + + Music_Remap(map->getString(argV[0])->str, map->getString(argV[1])->str); + + return false; +} + /*-------------------------------------------------- bool CallFunc_Get/SetLineProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) diff --git a/src/acs/call-funcs.hpp b/src/acs/call-funcs.hpp index bb9aa23b5..b67302f47 100644 --- a/src/acs/call-funcs.hpp +++ b/src/acs/call-funcs.hpp @@ -87,6 +87,11 @@ bool CallFunc_SetLineRenderStyle(ACSVM::Thread *thread, const ACSVM::Word *argV, bool CallFunc_MapWarp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_AddBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_StopLevelExit(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_ExitLevel(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_MusicPlay(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_MusicStopAll(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_MusicRemap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_GetLineProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_SetLineProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); diff --git a/src/acs/environment.cpp b/src/acs/environment.cpp index 78ffa2256..bfa296485 100644 --- a/src/acs/environment.cpp +++ b/src/acs/environment.cpp @@ -171,6 +171,11 @@ Environment::Environment() addFuncDataACS0( 503, addCallFunc(CallFunc_SetLineRenderStyle)); addFuncDataACS0( 504, addCallFunc(CallFunc_MapWarp)); addFuncDataACS0( 505, addCallFunc(CallFunc_AddBot)); + addFuncDataACS0( 506, addCallFunc(CallFunc_StopLevelExit)); + addFuncDataACS0( 507, addCallFunc(CallFunc_ExitLevel)); + addFuncDataACS0( 508, addCallFunc(CallFunc_MusicPlay)); + addFuncDataACS0( 509, addCallFunc(CallFunc_MusicStopAll)); + addFuncDataACS0( 510, addCallFunc(CallFunc_MusicRemap)); } ACSVM::Thread *Environment::allocThread() diff --git a/src/acs/interface.cpp b/src/acs/interface.cpp index bba86c3b9..4c14a8e2e 100644 --- a/src/acs/interface.cpp +++ b/src/acs/interface.cpp @@ -315,6 +315,22 @@ void ACS_RunEmeraldScript(mobj_t *mo) map->scriptStartType(ACS_ST_EMERALD, scriptInfo); } +/*-------------------------------------------------- + void ACS_RunGameOverScript(void) + + See header file for description. +--------------------------------------------------*/ +void ACS_RunGameOverScript(void) +{ + Environment *env = &ACSEnv; + + ACSVM::GlobalScope *const global = env->getGlobalScope(0); + ACSVM::HubScope *const hub = global->getHubScope(0); + ACSVM::MapScope *const map = hub->getMapScope(0); + + map->scriptStartType(ACS_ST_GAMEOVER, {}); +} + /*-------------------------------------------------- void ACS_Tick(void) diff --git a/src/acs/interface.h b/src/acs/interface.h index 206414663..5e9c9ba9f 100644 --- a/src/acs/interface.h +++ b/src/acs/interface.h @@ -179,6 +179,17 @@ void ACS_RunCatcherScript(mobj_t *mo); void ACS_RunEmeraldScript(mobj_t *mo); +/*-------------------------------------------------- + void ACS_RunGameOverScript(void); + + Runs the map's special scripts for exiting + the level, due to a losing condition and + without any extra lives to retry. +--------------------------------------------------*/ + +void ACS_RunGameOverScript(void); + + /*-------------------------------------------------- void ACS_Tick(void); diff --git a/src/acs/thread.hpp b/src/acs/thread.hpp index 3c146ddf6..561945cf0 100644 --- a/src/acs/thread.hpp +++ b/src/acs/thread.hpp @@ -42,6 +42,7 @@ enum acs_scriptType_e ACS_ST_OVERTIME = 7, // OVERTIME: Runs when Overtime starts in timed game modes. ACS_ST_UFO = 8, // UFO: Runs when the UFO Catcher is destroyed in a Special Stage. ACS_ST_EMERALD = 9, // EMERALD: Runs when the Chaos Emerald is collected in a Special Stage. + ACS_ST_GAMEOVER = 10, // GAMEOVER: Runs when the level ends due to a losing condition and no player has an extra life. }; // diff --git a/src/config.h.in b/src/config.h.in index e08d94e12..1dd4da57d 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -31,7 +31,7 @@ * Last updated 2020 / 08 / 30 - Kart v1.3 - patch.kart */ -#define ASSET_HASH_MAIN_KART "00000000000000000000000000000000" +#define ASSET_HASH_BIOS_PK3 "00000000000000000000000000000000" #define ASSET_HASH_GFX_PK3 "00000000000000000000000000000000" #define ASSET_HASH_TEXTURES_GENERAL_PK3 "00000000000000000000000000000000" #define ASSET_HASH_TEXTURES_SEGA_PK3 "00000000000000000000000000000000" diff --git a/src/d_clisrv.c b/src/d_clisrv.c index eb493d55e..53bbaf0b6 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -561,6 +561,21 @@ void D_ResetTiccmds(void) D_Clearticcmd(textcmds[i]->tic); } +void D_ResetTiccmdAngle(UINT8 ss, angle_t angle) +{ + INT32 i; + + for (i = 0; i < MAXGENTLEMENDELAY; ++i) + { + localcmds[ss][i].angle = angle >> TICCMD_REDUCE; + } +} + +ticcmd_t *D_LocalTiccmd(UINT8 ss) +{ + return &localcmds[ss][0]; +} + void SendKick(UINT8 playernum, UINT8 msg) { UINT8 buf[2]; diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 1d8c339e3..e2dd85bff 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -615,7 +615,10 @@ extern UINT8 playerconsole[MAXPLAYERS]; INT32 D_NumPlayers(void); boolean D_IsPlayerHumanAndGaming(INT32 player_number); + void D_ResetTiccmds(void); +void D_ResetTiccmdAngle(UINT8 ss, angle_t angle); +ticcmd_t *D_LocalTiccmd(UINT8 ss); tic_t GetLag(INT32 node); UINT8 GetFreeXCmdSize(UINT8 playerid); diff --git a/src/d_main.c b/src/d_main.c index cade52936..9652f0d10 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -886,11 +886,14 @@ void D_SRB2Loop(void) { rendertimefrac = FRACUNIT; } + + rendertimefrac_unpaused = g_time.timefrac; } else { renderdeltatics = realtics * FRACUNIT; rendertimefrac = FRACUNIT; + rendertimefrac_unpaused = FRACUNIT; } if (interp || doDisplay) @@ -1096,7 +1099,7 @@ static void ChangeDirForUrlHandler(void) static boolean AddIWAD(void) { - char * path = va(pandf,srb2path,"main.kart"); + char * path = va(pandf,srb2path,"bios.pk3"); if (FIL_ReadFileOK(path)) { @@ -1114,7 +1117,7 @@ static void IdentifyVersion(void) const char *srb2waddir = NULL; #if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL) - // change to the directory where 'main.kart' is found + // change to the directory where 'bios.pk3' is found srb2waddir = I_LocateWad(); #endif @@ -1134,7 +1137,7 @@ static void IdentifyVersion(void) // Load the IWAD if (! AddIWAD()) { - I_Error("MAIN.KART not found! Expected in %s\n", srb2waddir); + I_Error("\"bios.pk3\" not found! Expected in %s\n", srb2waddir); } // will be overwritten in case of -cdrom or unix/win home @@ -1478,7 +1481,7 @@ void D_SRB2Main(void) #endif // Check MD5s of autoloaded files // Note: Do not add any files that ignore MD5! - W_VerifyFileMD5(mainwads, ASSET_HASH_MAIN_KART); // main.kart + W_VerifyFileMD5(mainwads, ASSET_HASH_BIOS_PK3); // bios.pk3 mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_GFX_PK3); // gfx.pk3 mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_TEXTURES_GENERAL_PK3); // textures_general.pk3 mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_TEXTURES_SEGA_PK3); // textures_segazones.pk3 diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 68abb753f..582f3d98d 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -3690,11 +3690,14 @@ static void Command_Pause(void) CONS_Printf(M_GetText("You can't pause here.\n")); return; } + // TODO: this would make a great debug feature for release +#ifndef DEVELOP else if (modeattacking) // in time attack, pausing restarts the map { //M_ModeAttackRetry(0); // directly call from m_menu; return; } +#endif SendNetXCmd(XD_PAUSE, &buf, 2); } @@ -3715,8 +3718,11 @@ static void Got_Pause(UINT8 **cp, INT32 playernum) return; } + // TODO: this would make a great debug feature for release +#ifndef DEVELOP if (modeattacking && !demo.playback) return; +#endif paused = READUINT8(*cp); dedicatedpause = READUINT8(*cp); @@ -5770,7 +5776,7 @@ static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum) if (G_GamestateUsesExitLevel() == false) return; - G_ExitLevel(); + G_FinishExitLevel(); } static void Got_SetupVotecmd(UINT8 **cp, INT32 playernum) diff --git a/src/d_netfil.c b/src/d_netfil.c index 916dd75be..cb6897a8e 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -1426,7 +1426,7 @@ void PT_FileFragment(void) filename = va("%s", file->filename); nameonly(filename); - if (!strcmp(filename, "main.kart") + if (!strcmp(filename, "bios.pk3") || !strcmp(filename, "gfx.pk3") || !strcmp(filename, "textures.pk3") || !strcmp(filename, "textures_general.pk3") diff --git a/src/d_player.h b/src/d_player.h index 35795e9fa..6d6e887d1 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -504,6 +504,7 @@ struct player_t fixed_t deltaviewheight; // bounded/scaled total momentum. fixed_t bob; + fixed_t cameraOffset; skybox_t skybox; diff --git a/src/deh_tables.c b/src/deh_tables.c index 130ffbf40..0149e1ea3 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -1211,6 +1211,8 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_EMERALDSPARK6", "S_EMERALDSPARK7", + "S_EMERALDFLARE1", + // Emerald hunt shards "S_SHRD1", "S_SHRD2", @@ -4793,6 +4795,7 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_SPRAYCAN", "MT_EMERALD", "MT_EMERALDSPARK", + "MT_EMERALDFLARE", "MT_EMERHUNT", // Emerald Hunt "MT_EMERALDSPAWN", // Emerald spawner w/ delay diff --git a/src/doomstat.h b/src/doomstat.h index b1fae2b26..bd96e3a5d 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -767,8 +767,15 @@ extern UINT8 maxXtraLife; // Max extra lives from rings extern mobj_t *hunt1, *hunt2, *hunt3; // Emerald hunt locations +struct exitcondition_t +{ + boolean losing; + boolean retry; +}; + // For racing extern tic_t racecountdown, exitcountdown, musiccountdown; +extern exitcondition_t g_exit; #define DEFAULT_GRAVITY (4*FRACUNIT/5) extern fixed_t gravity; diff --git a/src/g_build_ticcmd.cpp b/src/g_build_ticcmd.cpp index 035bdc08a..1057595b9 100644 --- a/src/g_build_ticcmd.cpp +++ b/src/g_build_ticcmd.cpp @@ -206,9 +206,17 @@ class TiccmdBuilder return true; } + void toggle_freecam_input() + { + if (M_MenuButtonPressed(forplayer(), MBT_C)) + { + P_ToggleDemoCamera(); + } + } + bool director_input() { - if (G_IsPartyLocal(displayplayers[forplayer()]) == true) + if (demo.freecam || G_IsPartyLocal(displayplayers[forplayer()]) == true) { return false; } @@ -239,12 +247,14 @@ class TiccmdBuilder } } + toggle_freecam_input(); + return true; } bool spectator_analog_input() { - if (!player()->spectator && !objectplacing) + if (!player()->spectator && !objectplacing && !demo.freecam) { return false; } @@ -261,7 +271,7 @@ class TiccmdBuilder if (G_PlayerInputDown(forplayer(), gc_lookback, 0)) { - cmd->aiming -= joystickvector.yaxis; + cmd->aiming -= (joystickvector.yaxis * KART_FULLTURN) / JOYAXISRANGE; } else { @@ -361,10 +371,28 @@ public: explicit TiccmdBuilder(ticcmd_t* cmd_, INT32 realtics_, UINT8 ssplayer_) : cmd(cmd_), realtics(realtics_), ssplayer(ssplayer_), viewnum(G_PartyPosition(g_localplayers[forplayer()])) { - *cmd = {}; // blank ticcmd - - if (demo.playback) + auto regular_input = [this] { + analog_input(); + common_button_input(); + }; + + if (demo.playback || demo.freecam || player()->spectator) + { + // freecam is controllable even while paused + + *cmd = {}; + + if (!typing_input() && !director_input()) + { + regular_input(); + + if (demo.freecam) + { + toggle_freecam_input(); + } + } + return; } @@ -373,6 +401,8 @@ public: return; } + *cmd = {}; // blank ticcmd + if (gamestate == GS_LEVEL && player()->playerstate == PST_REBORN) { return; @@ -391,8 +421,7 @@ public: if (!overlay) { - analog_input(); - common_button_input(); + regular_input(); } cmd->angle = localangle[viewnum] >> TICCMD_REDUCE; diff --git a/src/g_demo.c b/src/g_demo.c index d7f240f38..de15625c0 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -4127,12 +4127,6 @@ void G_StopDemo(void) singletics = false; demo.freecam = false; - // reset democam shit too: - democam.cam = NULL; - democam.soundmobj = NULL; - democam.localangle = 0; - democam.localaiming = 0; - democam.keyboardlook = false; Z_Free(demo.skinlist); demo.skinlist = NULL; @@ -4167,7 +4161,7 @@ boolean G_CheckDemoStatus(void) I_Quit(); if (multiplayer && !demo.title) - G_ExitLevel(); + G_FinishExitLevel(); else { G_StopDemo(); diff --git a/src/g_game.c b/src/g_game.c index 17e5736d1..a692feefc 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -76,6 +76,10 @@ #include "discord.h" #endif +#ifdef HWRENDER +#include "hardware/hw_main.h" // for cv_glshearing +#endif + gameaction_t gameaction; gamestate_t gamestate = GS_NULL; UINT8 ultimatemode = false; @@ -272,6 +276,7 @@ mobj_t *hunt2; mobj_t *hunt3; tic_t racecountdown, exitcountdown, musiccountdown; // for racing +exitcondition_t g_exit; fixed_t gravity; fixed_t mapobjectscale; @@ -803,9 +808,7 @@ const char *G_BuildMapName(INT32 map) */ INT32 G_MapNumber(const char * name) { -#ifdef NEXTMAPINSOC if (strncasecmp("NEXTMAP_", name, 8) != 0) -#endif { INT32 map; UINT32 hash = quickncasehash(name, MAXMAPLUMPNAME); @@ -824,7 +827,6 @@ INT32 G_MapNumber(const char * name) return NEXTMAP_INVALID; } -#ifdef NEXTMAPINSOC name += 8; if (strcasecmp("EVALUATION", name) == 0) @@ -833,9 +835,10 @@ INT32 G_MapNumber(const char * name) return NEXTMAP_CREDITS; if (strcasecmp("CEREMONY", name) == 0) return NEXTMAP_CEREMONY; - //if (strcasecmp("TITLE", name) == 0) + if (strcasecmp("TITLE", name) == 0) return NEXTMAP_TITLE; -#endif + + return NEXTMAP_INVALID; } /** Clips the console player's mouse aiming to the current view. @@ -863,7 +866,7 @@ INT16 G_SoftwareClipAimingPitch(INT32 *aiming) INT32 limitangle; // note: the current software mode implementation doesn't have true perspective - limitangle = ANGLE_90 - ANG10; // Some viewing fun, but not too far down... + limitangle = ANGLE_45; // Some viewing fun, but not too far down... if (*aiming > limitangle) *aiming = limitangle; @@ -873,6 +876,31 @@ INT16 G_SoftwareClipAimingPitch(INT32 *aiming) return (INT16)((*aiming)>>16); } +void G_FinalClipAimingPitch(INT32 *aiming, player_t *player, boolean skybox) +{ +#ifndef HWRENDER + (void)player; + (void)skybox; +#endif + + // clip it in the case we are looking a hardware 90 degrees full aiming + // (lmps, network and use F12...) + if (rendermode == render_soft +#ifdef HWRENDER + || (rendermode == render_opengl + && (cv_glshearing.value == 1 + || (cv_glshearing.value == 2 && R_IsViewpointThirdPerson(player, skybox)))) +#endif + ) + { + G_SoftwareClipAimingPitch(aiming); + } + else + { + G_ClipAimingPitch(aiming); + } +} + static INT32 G_GetValueFromControlTable(INT32 deviceID, INT32 deadzone, INT32 *controltable) { INT32 i, failret = NO_BINDS_REACHABLE; @@ -1257,6 +1285,7 @@ void G_StartTitleCard(void) ST_startTitleCard(); // play the sound + if (gamestate != GS_CEREMONY) { sfxenum_t kstart = sfx_kstart; if (K_CheckBossIntro() == true) @@ -2900,95 +2929,116 @@ void G_AddPlayer(INT32 playernum) demo_extradata[playernum] |= DXD_JOINDATA|DXD_PLAYSTATE|DXD_COLOR|DXD_NAME|DXD_SKIN|DXD_FOLLOWER; // Set everything } -void G_ExitLevel(void) +void G_BeginLevelExit(void) +{ + g_exit.losing = true; + g_exit.retry = false; + + if (grandprixinfo.gp == true) + { + UINT8 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator) + { + K_PlayerFinishGrandPrix(&players[i]); + } + } + } + + if (!G_GametypeUsesLives() || skipstats != 0) + { + g_exit.losing = false; // never force a retry + } + else if (specialstageinfo.valid == true || (gametyperules & GTR_BOSS)) + { + UINT8 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator && !players[i].bot) + { + if (!K_IsPlayerLosing(&players[i])) + { + g_exit.losing = false; + break; + } + } + } + } + else if (grandprixinfo.gp == true && grandprixinfo.eventmode == GPEVENT_NONE) + { + g_exit.losing = (grandprixinfo.wonround != true); + } + + if (g_exit.losing) + { + // You didn't win... + + UINT8 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator && !players[i].bot) + { + if (players[i].lives > 0) + { + g_exit.retry = true; + break; + } + } + } + } + + if (g_exit.losing && specialstageinfo.valid) + { + exitcountdown = TICRATE; + } + else + { + exitcountdown = raceexittime+1; + } + + if (g_exit.losing) + { + if (!g_exit.retry) + { + ACS_RunGameOverScript(); + } + } +} + +void G_FinishExitLevel(void) { G_ResetAllDeviceRumbles(); if (gamestate == GS_LEVEL) { - UINT8 i; - boolean doretry = false; - - if (grandprixinfo.gp == true) + if (g_exit.retry) { - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && !players[i].spectator) - { - K_PlayerFinishGrandPrix(&players[i]); - } - } - } - - if (!G_GametypeUsesLives() || skipstats != 0) - ; // never force a retry - else if (specialstageinfo.valid == true || (gametyperules & GTR_BOSS)) - { - doretry = true; - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && !players[i].spectator && !players[i].bot) - { - if (!K_IsPlayerLosing(&players[i])) - { - doretry = false; - break; - } - } - } - } - else if (grandprixinfo.gp == true && grandprixinfo.eventmode == GPEVENT_NONE) - { - doretry = (grandprixinfo.wonround != true); - } - - if (doretry) - { - // You didn't win... - - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && !players[i].spectator && !players[i].bot) - { - if (players[i].lives > 0) - { - break; - } - } - } - - if (i == MAXPLAYERS) - { - // GAME OVER, try again from the start! - if (grandprixinfo.gp == true - && grandprixinfo.eventmode == GPEVENT_SPECIAL) - { - // 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. - G_HandleSaveLevel(true); - D_QuitNetGame(); - CL_Reset(); - D_ClearState(); - M_StartControlPanel(); - } - } - else + // Restart cup here whenever we do Online GP + if (!netgame) { // We have lives, just redo this one course. G_SetRetryFlag(); + return; } + } + else if (g_exit.losing) + { + // We were in a Special Stage. + // We can still progress to the podium when we game over here. + const boolean special = grandprixinfo.gp == true && grandprixinfo.eventmode == GPEVENT_SPECIAL; - if (doretry == true) + if (!netgame && !special) { + // Back to the menu with you. + G_HandleSaveLevel(true); + D_QuitNetGame(); + CL_Reset(); + D_ClearState(); + M_StartControlPanel(); return; } } @@ -3291,6 +3341,10 @@ boolean G_GametypeHasTeams(void) // boolean G_GametypeHasSpectators(void) { + // TODO: this would make a great debug feature for release +#ifdef DEVELOP + return true; +#endif return (netgame || (multiplayer && demo.netgame)); } diff --git a/src/g_game.h b/src/g_game.h index 71dd4ada8..0b922b4da 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -126,6 +126,7 @@ ticcmd_t *G_MoveTiccmd(ticcmd_t* dest, const ticcmd_t* src, const size_t n); // clip the console player aiming to the view INT32 G_ClipAimingPitch(INT32 *aiming); INT16 G_SoftwareClipAimingPitch(INT32 *aiming); +void G_FinalClipAimingPitch(INT32 *aiming, player_t *player, boolean skybox); extern angle_t localangle[MAXSPLITSCREENPLAYERS]; extern INT32 localaiming[MAXSPLITSCREENPLAYERS]; // should be an angle_t but signed @@ -205,7 +206,8 @@ boolean G_GametypeUsesLives(void); boolean G_GametypeHasTeams(void); boolean G_GametypeHasSpectators(void); INT16 G_SometimesGetDifferentEncore(void); -void G_ExitLevel(void); +void G_BeginLevelExit(void); +void G_FinishExitLevel(void); void G_NextLevel(void); void G_GetNextMap(void); void G_Continue(void); diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 976d210c2..ba480ea9a 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -310,7 +310,7 @@ static FUINT HWR_CalcSlopeLight(FUINT lightnum, pslope_t *slope, const sector_t { INT16 finallight = lightnum; - if (slope != NULL && P_ApplyLightOffsetFine(lightnum, sector)) + if (slope != NULL && sector != NULL && P_ApplyLightOffsetFine(lightnum, sector)) { finallight += slope->hwLightOffset; @@ -4773,7 +4773,7 @@ static void HWR_ProjectSprite(mobj_t *thing) if (spriterotangle != 0 && !(splat && !(thing->renderflags & RF_NOSPLATROLLANGLE))) { - rollangle = R_GetRollAngle(vflip + rollangle = R_GetRollAngle(papersprite == vflip ? spriterotangle : InvAngle(spriterotangle)); rotsprite = Patch_GetRotatedSprite(sprframe, (thing->frame & FF_FRAMEMASK), rot, flip, false, sprinfo, rollangle); diff --git a/src/info.c b/src/info.c index d6fa42032..b64175e49 100644 --- a/src/info.c +++ b/src/info.c @@ -1881,6 +1881,8 @@ state_t states[NUMSTATES] = {SPR_ESPK, FF_FULLBRIGHT|5, 3, {NULL}, 0, 0, S_EMERALDSPARK7}, // S_EMERALDSPARK6 {SPR_ESPK, FF_FULLBRIGHT|6, 3, {NULL}, 0, 0, S_NULL}, // S_EMERALDSPARK7 + {SPR_LENS, FF_FULLBRIGHT|FF_ADD|FF_TRANS10|FF_ANIMATE|11, 8, {NULL}, 7, 1, S_GAINAX_MID2}, // S_EMERALDFLARE1 + // Emerald hunt shards {SPR_SHRD, 0, -1, {NULL}, 0, 0, S_NULL}, // S_SHRD1 {SPR_SHRD, 1, -1, {NULL}, 0, 0, S_NULL}, // S_SHRD2 @@ -8345,6 +8347,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_EMERALDFLARE + -1, // doomednum + S_INVISIBLE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 8*FRACUNIT, // radius + 8*FRACUNIT, // height + 0, // display offset + 16, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, + { // MT_EMERHUNT 320, // doomednum S_SHRD1, // spawnstate diff --git a/src/info.h b/src/info.h index ac593e259..09abce486 100644 --- a/src/info.h +++ b/src/info.h @@ -2365,6 +2365,8 @@ typedef enum state S_EMERALDSPARK6, S_EMERALDSPARK7, + S_EMERALDFLARE1, + // Emerald hunt shards S_SHRD1, S_SHRD2, @@ -5982,6 +5984,7 @@ typedef enum mobj_type MT_SPRAYCAN, MT_EMERALD, MT_EMERALDSPARK, + MT_EMERALDFLARE, MT_EMERHUNT, // Emerald Hunt MT_EMERALDSPAWN, // Emerald spawner w/ delay diff --git a/src/k_battle.c b/src/k_battle.c index 66e98561c..4daa5afe4 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -127,7 +127,10 @@ void K_CheckBumpers(void) { if (nobumpers > 0 && nobumpers >= numingame) { + // TODO: this would make a great debug feature for release +#ifndef DEVELOP P_DoAllPlayersExit(PF_NOCONTEST, false); +#endif return; } } @@ -275,8 +278,7 @@ void K_DropEmeraldsFromPlayer(player_t *player, UINT32 emeraldType) if ((player->emeralds & emeraldFlag) && (emeraldFlag & emeraldType)) { - mobj_t *emerald = K_SpawnChaosEmerald(player->mo->x, player->mo->y, player->mo->z, player->mo->angle - ANGLE_90, flip, emeraldFlag); - P_SetTarget(&emerald->target, player->mo); + K_SpawnChaosEmerald(player->mo->x, player->mo->y, player->mo->z, player->mo->angle - ANGLE_90, flip, emeraldFlag); player->emeralds &= ~emeraldFlag; break; // Drop only one emerald. Emerald wins are hard enough! diff --git a/src/k_grandprix.c b/src/k_grandprix.c index dfbb65669..0d4b4b654 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -844,6 +844,12 @@ boolean K_CanChangeRules(boolean allowdemos) --------------------------------------------------*/ void K_PlayerFinishGrandPrix(player_t *player) { + if (grandprixinfo.wonround == true) + { + // This was already completed. + return; + } + if (player->exiting == false) { // You did not finish diff --git a/src/k_hud.c b/src/k_hud.c index 0d928db0c..569304485 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -5169,7 +5169,8 @@ static void K_DrawDirectorButton(INT32 idx, const char *label, patch_t *kp[2], I static void K_drawDirectorHUD(void) { - const INT32 p = G_PartyMember(consoleplayer, R_GetViewNumber()); + const UINT8 viewnum = R_GetViewNumber(); + const INT32 p = viewnum < G_PartySize(consoleplayer) ? G_PartyMember(consoleplayer, viewnum) : -1; const char *itemtxt = "Join"; UINT8 offs = 0; @@ -5192,12 +5193,15 @@ static void K_drawDirectorHUD(void) offs = 2; } + K_DrawDirectorButton(offs + 1, "Freecam", kp_button_c[0], 0); + if (p == -1 || !playeringame[p] || players[p].spectator == false) { return; } - K_DrawDirectorButton(offs + 1, "Director", kp_button_r, + // TODO: this is too close to the screen bottom + K_DrawDirectorButton(offs + 2, "Director", kp_button_r, (directorinfo.active ? V_YELLOWMAP : 0)); if (players[p].flashing) @@ -5643,6 +5647,11 @@ void K_drawKartHUD(void) if (stplyr->karthud[khud_trickcool]) K_drawTrickCool(); + if (freecam) + { + K_DrawDirectorButton(3, "Freecam", kp_button_c[0], 0); + } + if (modeattacking || freecam) // everything after here is MP and debug only return; @@ -5659,7 +5668,7 @@ void K_drawKartHUD(void) K_drawKartPowerUps(); - if (G_IsPartyLocal(displayplayers[viewnum]) == false && !demo.playback) + if (G_IsPartyLocal(displayplayers[viewnum]) == false) { K_drawDirectorHUD(); } diff --git a/src/k_kart.c b/src/k_kart.c index 7e49c0847..08e80e2cf 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -7794,6 +7794,8 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->mo->spritexoffset = 0; player->mo->spriteyoffset = 0; + player->cameraOffset = 0; + if (player->curshield == KSHIELD_TOP) { mobj_t *top = K_GetGardenTop(player); @@ -7814,6 +7816,21 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) } } + if (!P_MobjWasRemoved(player->whip) && (player->whip->flags2 & MF2_AMBUSH)) + { + // Linear acceleration and deceleration to a peak. + // There is a constant total time to complete but the + // acceleration and deceleration times can be made + // asymmetrical. + const fixed_t hop = 24 * mapobjectscale; + const INT32 duration = 12; + const INT32 mid = (duration / 2) - 1; + const INT32 t = (duration - mid) - player->whip->fuse; + + player->cameraOffset = hop - (abs(t * hop) / (t < 0 ? mid : duration - mid)); + player->mo->sprzoff += player->cameraOffset; + } + K_UpdateOffroad(player); K_UpdateDraft(player); K_UpdateEngineSounds(player); // Thanks, VAda! @@ -10943,9 +10960,13 @@ void K_MoveKartPlayer(player_t *player, boolean onground) P_SetTarget(&whip->target, player->mo); K_MatchGenericExtraFlags(whip, player->mo); P_SpawnFakeShadow(whip, 20); - whip->fuse = 12; // Changing instawhip animation duration? Look here - player->flashing = max(player->flashing, 12); - player->mo->momz += 4*mapobjectscale; + whip->fuse = INSTAWHIP_DURATION; + player->flashing = max(player->flashing, INSTAWHIP_DURATION); + + if (P_IsObjectOnGround(player->mo)) + { + whip->flags2 |= MF2_AMBUSH; + } if (!K_PowerUpRemaining(player, POWERUP_BADGE)) { diff --git a/src/k_kart.h b/src/k_kart.h index 22e7ac9fa..8982e9d75 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -29,6 +29,7 @@ Make sure this matches the actual number of states #define GROW_PHYSICS_SCALE (3*FRACUNIT/2) #define SHRINK_PHYSICS_SCALE (3*FRACUNIT/4) +#define INSTAWHIP_DURATION (12) #define INSTAWHIP_COOLDOWN (TICRATE*2) #define INSTAWHIP_STARTOFRACE (255) #define INSTAWHIP_STARTOFBATTLE (1) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 4899b4c06..f5b3ee3d9 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -816,7 +816,13 @@ void M_Drawer(void) // draw pause pic if (paused && !demo.playback && (menuactive || cv_showhud.value)) { - M_DrawPausedText(0); + // Don't cover the Stereo player! + boolean stereo_open = menuactive && currentMenu == &MISC_SoundTestDef; + + if (stereo_open == false) + { + M_DrawPausedText(0); + } } // focus lost notification goes on top of everything, even the former everything @@ -5048,7 +5054,7 @@ void M_DrawAddons(void) m = numwadfiles-(mainwads+musicwads+1); - V_DrawCenteredString(BASEVIDWIDTH/2, y+4, (majormods ? highlightflags : V_TRANSLUCENT), va("%ld ADD-ON%s LOADED", (long)m, (m == 1) ? "" : "S")); //+2 for music, sounds, +1 for main.kart + V_DrawCenteredString(BASEVIDWIDTH/2, y+4, (majormods ? highlightflags : V_TRANSLUCENT), va("%ld ADD-ON%s LOADED", (long)m, (m == 1) ? "" : "S")); //+2 for music, sounds, +1 for bios.pk3 } #undef addonsseperation diff --git a/src/k_objects.h b/src/k_objects.h index 8066e7078..79afc08cb 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -207,6 +207,9 @@ void Obj_SneakerPanelCollide(mobj_t *pad, mobj_t *mo); /* Emerald */ void Obj_SpawnEmeraldSparks(mobj_t *source); void Obj_EmeraldThink(mobj_t *emerald); +void Obj_EmeraldFlareThink(mobj_t *flare); +void Obj_BeginEmeraldOrbit(mobj_t *emerald, mobj_t *target, fixed_t radius, INT32 revolution_time, tic_t fuse); +void Obj_GiveEmerald(mobj_t *emerald); #ifdef __cplusplus } // extern "C" diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 9af9ccd32..bd1771a89 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -2869,7 +2869,8 @@ static int lib_gExitLevel(lua_State *L) // Moved this bit to G_SetCustomExitVars if (n >= 1) // Don't run the reset to defaults option lib_gSetCustomExitVars(L); - G_ExitLevel(); + G_BeginLevelExit(); + G_FinishExitLevel(); return 0; } diff --git a/src/menus/transient/pause-replay.c b/src/menus/transient/pause-replay.c index 52cc4b7e5..68b4859dc 100644 --- a/src/menus/transient/pause-replay.c +++ b/src/menus/transient/pause-replay.c @@ -236,20 +236,7 @@ void M_PlaybackToggleFreecam(INT32 choice) splitscreen = 0; R_ExecuteSetViewSize(); - P_InitCameraCmd(); // init camera controls - if (!demo.freecam) // toggle on - { - demo.freecam = true; - democam.cam = &camera[0]; // this is rather useful - } - else // toggle off - { - demo.freecam = false; - // reset democam vars: - democam.cam = NULL; - //democam.turnheld = false; - democam.keyboardlook = false; // reset only these. localangle / aiming gets set before the cam does anything anyway - } + P_ToggleDemoCamera(); } void M_PlaybackQuit(INT32 choice) diff --git a/src/objects/emerald.c b/src/objects/emerald.c index 40524cd12..fdf7449f2 100644 --- a/src/objects/emerald.c +++ b/src/objects/emerald.c @@ -1,10 +1,25 @@ #include "../k_battle.h" #include "../k_objects.h" +#include "../k_specialstage.h" #include "../info.h" #include "../m_random.h" #include "../p_local.h" +#include "../r_main.h" +#include "../s_sound.h" #include "../tables.h" +#define emerald_type(o) ((o)->extravalue1) +#define emerald_anim_start(o) ((o)->movedir) +#define emerald_revolution_time(o) ((o)->threshold) +#define emerald_start_radius(o) ((o)->movecount) +#define emerald_target_radius(o) ((o)->extravalue2) +#define emerald_z_shift(o) ((o)->reactiontime) +#define emerald_scale_rate(o) ((o)->movefactor) + +// Think of this like EMERALD_SPEED_UP / EMERALD_SPEED_UP_RATE +#define EMERALD_SPEED_UP (1) // speed up by this much... +#define EMERALD_SPEED_UP_RATE (1) // ...every N tics + void Obj_SpawnEmeraldSparks(mobj_t *mobj) { if (leveltime % 3 != 0) @@ -25,31 +40,95 @@ void Obj_SpawnEmeraldSparks(mobj_t *mobj) sparkle->sprzoff = mobj->sprzoff; } +static INT32 get_elapsed(mobj_t *emerald) +{ + return leveltime - min((tic_t)emerald_anim_start(emerald), leveltime); +} + +static INT32 get_revolve_time(mobj_t *emerald) +{ + return max(1, emerald_revolution_time(emerald)); +} + +static fixed_t get_suck_factor(mobj_t *emerald) +{ + const INT32 suck_time = get_revolve_time(emerald) * 2; + + return (min(get_elapsed(emerald), suck_time) * FRACUNIT) / suck_time; +} + +static fixed_t get_current_radius(mobj_t *emerald) +{ + fixed_t s = emerald_start_radius(emerald); + fixed_t t = emerald_target_radius(emerald); + + return s + FixedMul(t - s, get_suck_factor(emerald)); +} + +static fixed_t get_bob(mobj_t *emerald) +{ + // With a fuse, the emerald experiences "speed up" and the + // scale also shrinks. All of these these effects caused + // the bob phase shift to look disproportioned. + angle_t phase = emerald->fuse ? 0 : get_elapsed(emerald) * ((ANGLE_MAX / get_revolve_time(emerald)) / 2); + + return FixedMul(30 * mapobjectscale, FSIN(emerald->angle + phase)); +} + +static fixed_t center_of(mobj_t *mobj) +{ + return mobj->z + (mobj->height / 2); +} + +static fixed_t get_target_z(mobj_t *emerald) +{ + fixed_t shift = FixedMul(emerald_z_shift(emerald), FRACUNIT - get_suck_factor(emerald)); + + return center_of(emerald->target) + get_bob(emerald) + shift; +} + +static void speed_up(mobj_t *emerald) +{ + // Revolution time shouldn't decrease below zero. + if (emerald_revolution_time(emerald) <= EMERALD_SPEED_UP) + { + return; + } + + if (get_elapsed(emerald) % EMERALD_SPEED_UP_RATE) + { + return; + } + + // Decrease the fuse proportionally to the revolution time. + const fixed_t ratio = (emerald->fuse * FRACUNIT) / emerald_revolution_time(emerald); + + emerald_revolution_time(emerald) -= EMERALD_SPEED_UP; + + emerald->fuse = max(1, (emerald_revolution_time(emerald) * ratio) / FRACUNIT); +} + static void Obj_EmeraldOrbitPlayer(mobj_t *emerald) { - const int kOrbitTics = 64; - const int kPhaseTics = 128; - - const fixed_t orbit_radius = 100 * mapobjectscale; - const fixed_t orbit_height = 30 * mapobjectscale; - - mobj_t *targ = emerald->target; - - angle_t a = emerald->angle; - - fixed_t x = FixedMul(orbit_radius, FCOS(a)); - fixed_t y = FixedMul(orbit_radius, FSIN(a)); - - angle_t phase = (ANGLE_MAX / kPhaseTics) * (leveltime % kPhaseTics); + fixed_t r = get_current_radius(emerald); + fixed_t x = FixedMul(r, FCOS(emerald->angle)); + fixed_t y = FixedMul(r, FSIN(emerald->angle)); P_MoveOrigin( emerald, - targ->x + x, - targ->y + y, - targ->z + targ->height + FixedMul(orbit_height, FSIN(a + phase)) + emerald->target->x + x, + emerald->target->y + y, + get_target_z(emerald) ); - emerald->angle += ANGLE_MAX / kOrbitTics; + emerald->angle += ANGLE_MAX / get_revolve_time(emerald); + + if (emerald->fuse > 0) + { + speed_up(emerald); + + P_InstaScale(emerald, emerald->fuse * emerald_scale_rate(emerald)); + } } void Obj_EmeraldThink(mobj_t *emerald) @@ -84,3 +163,156 @@ void Obj_EmeraldThink(mobj_t *emerald) K_BattleOvertimeKiller(emerald); } + +static mobj_t *spawn_glow(mobj_t *flare) +{ + mobj_t *targ = flare->target; + mobj_t *x = P_SpawnGhostMobj(targ); + + x->old_x = targ->old_x; + x->old_y = targ->old_y; + x->old_z = targ->old_z; + + x->fuse = 2; // this actually does last one tic + x->extravalue1 = 1; + x->extravalue2 = 0; + + x->renderflags = RF_ADD | RF_ALWAYSONTOP; + + // FIXME: linkdraw doesn't work consistently, so I drew it on top of everyting (and through walls) +#if 0 + P_SetTarget(&x->tracer, targ); + x->flags2 |= MF2_LINKDRAW; + x->dispoffset = 1000; +#endif + + return x; +} + +static mobj_t *spawn_glow_colorize(mobj_t *flare) +{ + mobj_t *x = spawn_glow(flare); + + x->color = flare->color; + x->colorized = true; + + return x; +} + +void Obj_EmeraldFlareThink(mobj_t *flare) +{ + const INT32 kExtraTics = 3; + const INT32 flare_tics = states[S_EMERALDFLARE1].tics + kExtraTics; + + if (P_MobjWasRemoved(flare->target)) + { + P_RemoveMobj(flare); + return; + } + + // Target is assumed to be the emerald in orbit. When + // emerald fuse runs out, it shall update player's emerald + // flags. Time the flare animation so it ends with the + // emerald fuse. + if (!flare->fuse && flare->target->fuse > flare_tics) + { + return; + } + + if (flare->state == &states[S_INVISIBLE]) + { + // In special stages, just follow the emerald. + if (specialstageinfo.valid == false) + { + // Update target to player. We don't need to track + // the emerald anymore. + P_SetTarget(&flare->target, flare->target->target); + + if (P_MobjWasRemoved(flare->target)) + { + P_RemoveMobj(flare); + return; + } + } + + P_SetMobjState(flare, S_EMERALDFLARE1); + flare->fuse = flare_tics; + } + + // Focus on center of player. + P_SetOrigin(flare, flare->target->x, flare->target->y, center_of(flare->target)); + + if (leveltime & 1) + { + // Stacked for more exposure + spawn_glow_colorize(flare); + spawn_glow(flare); + spawn_glow(flare); + } +} + +static void spawn_lens_flare(mobj_t *emerald) +{ + mobj_t *flare = P_SpawnMobjFromMobj(emerald, 0, 0, 0, MT_EMERALDFLARE); + + P_SetTarget(&flare->target, emerald); + P_InstaScale(flare, emerald->target->scale); + + flare->color = emerald->color; + flare->colorized = true; + + flare->renderflags |= RF_ALWAYSONTOP; + + // FIXME: linkdraw doesn't work consistently, so I drew it on top of everyting (and through walls) +#if 0 + P_SetTarget(&flare->tracer, emerald->target); + flare->flags2 |= MF2_LINKDRAW; + flare->dispoffset = 1000; +#endif +} + +void Obj_BeginEmeraldOrbit(mobj_t *emerald, mobj_t *target, fixed_t radius, INT32 revolution_time, tic_t fuse) +{ + P_SetTarget(&emerald->target, target); + + emerald_anim_start(emerald) = leveltime; + emerald_revolution_time(emerald) = revolution_time; + + emerald_start_radius(emerald) = R_PointToDist2(target->x, target->y, emerald->x, emerald->y); + emerald_target_radius(emerald) = radius; + + emerald->fuse = fuse; + + if (fuse) + { + emerald_scale_rate(emerald) = emerald->scale / fuse; + } + + emerald->angle = R_PointToAngle2(target->x, target->y, emerald->x, emerald->y); + emerald_z_shift(emerald) = emerald->z - get_target_z(emerald); + + emerald->flags |= MF_NOGRAVITY | MF_NOCLIP | MF_NOCLIPTHING | MF_NOCLIPHEIGHT; + emerald->shadowscale = 0; + + spawn_lens_flare(emerald); +} + +void Obj_GiveEmerald(mobj_t *emerald) +{ + if (P_MobjWasRemoved(emerald->target)) + { + return; + } + + player_t *player = emerald->target->player; + + if (!player) + { + return; + } + + player->emeralds |= emerald_type(emerald); + K_CheckEmeralds(player); + + S_StartSound(emerald->target, emerald->info->deathsound); +} diff --git a/src/objects/instawhip.c b/src/objects/instawhip.c index 64ddd8ae5..dc1e410ed 100644 --- a/src/objects/instawhip.c +++ b/src/objects/instawhip.c @@ -34,7 +34,8 @@ void Obj_InstaWhipThink (mobj_t *whip) // Visuals whip->renderflags |= RF_NOSPLATBILLBOARD|RF_FULLBRIGHT; - if (whip->renderflags & RF_DONTDRAW) + // This is opposite of player flashing tics + if (leveltime & 1) whip->renderflags &= ~RF_DONTDRAW; else whip->renderflags |= RF_DONTDRAW; diff --git a/src/objects/shrink.c b/src/objects/shrink.c index 5336630ba..e5dd2b8c6 100644 --- a/src/objects/shrink.c +++ b/src/objects/shrink.c @@ -789,11 +789,7 @@ void Obj_CreateShrinkPohbees(player_t *owner) pohbees[j].start = GetPohbeeStart(player->nextwaypoint); pohbees[j].end = endWaypoint; pohbees[j].lasers = 1; - - if (player->position == 1) - { - pohbees[j].first = true; - } + pohbees[j].first = (player->position == 1); numPohbees++; } @@ -801,6 +797,7 @@ void Obj_CreateShrinkPohbees(player_t *owner) for (i = 0; i < numPohbees; i++) { + // omg pobby hi!!! CreatePohbee(owner, pohbees[i].start, pohbees[i].end, pohbees[i].lasers); if (pohbees[i].first == true) diff --git a/src/objects/ufo.c b/src/objects/ufo.c index f8e15c459..6a9f4bbdc 100644 --- a/src/objects/ufo.c +++ b/src/objects/ufo.c @@ -883,7 +883,7 @@ boolean Obj_UFOEmeraldCollect(mobj_t *ufo, mobj_t *toucher) const int kScaleTics = 16; // Emerald will now orbit the player - P_SetTarget(&emerald->target, toucher); + Obj_BeginEmeraldOrbit(emerald, toucher, 100 * mapobjectscale, 64, 0); // Scale down because the emerald is huge // Super Emerald needs to be scaled down further diff --git a/src/p_inter.c b/src/p_inter.c index a171df1e1..3358af6fd 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -464,9 +464,14 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (toucher->hitlag > 0) return; - player->emeralds |= special->extravalue1; - K_CheckEmeralds(player); - break; + // Emerald will now orbit the player + + { + const tic_t orbit = 2*TICRATE; + Obj_BeginEmeraldOrbit(special, toucher, toucher->radius, orbit, orbit * 20); + } + + return; case MT_SPECIAL_UFO: if (Obj_UFOEmeraldCollect(special, toucher) == false) { @@ -2031,7 +2036,15 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, if (!player->exiting && (specialstageinfo.valid == true || modeattacking & ATTACKING_SPB)) { + // TODO: this would make a great debug feature for release +#ifdef DEVELOP + if (type != DMG_SPECTATOR) + { + P_DoPlayerExit(player, PF_NOCONTEST); + } +#else P_DoPlayerExit(player, PF_NOCONTEST); +#endif } if (player->exiting) diff --git a/src/p_local.h b/src/p_local.h index fe75081d5..eaf1d6349 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -126,6 +126,9 @@ struct camera_t // SRB2Kart: camera pitches on slopes angle_t pitch; + // Freecam: aiming needs to be reset after switching from chasecam + boolean reset_aiming; + // Interpolation data fixed_t old_x, old_y, old_z; angle_t old_angle, old_aiming; @@ -134,13 +137,7 @@ struct camera_t // demo freecam or something before i commit die struct demofreecam_s { - camera_t *cam; // this is useful when the game is paused, notably - mobj_t *soundmobj; // mobj to play sound from, used in s_sound - - angle_t localangle; // keeps track of the cam angle for cmds - angle_t localaiming; // ditto with aiming - boolean turnheld; // holding turn button for gradual turn speed - boolean keyboardlook; // keyboard look + UINT8 button_a_held; // A button was held since entering from menu, so don't move camera }; extern struct demofreecam_s democam; @@ -159,7 +156,7 @@ boolean P_TryCameraMove(fixed_t x, fixed_t y, camera_t *thiscam); void P_SlideCameraMove(camera_t *thiscam); void P_DemoCameraMovement(camera_t *cam); boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcalled); -void P_InitCameraCmd(void); +void P_ToggleDemoCamera(void); boolean P_PlayerInPain(player_t *player); void P_ResetPlayer(player_t *player); diff --git a/src/p_mobj.c b/src/p_mobj.c index 5f265b508..5d0ec3224 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -7550,6 +7550,14 @@ static boolean P_MobjRegularThink(mobj_t *mobj) case MT_EMERALD: Obj_EmeraldThink(mobj); + if (P_MobjWasRemoved(mobj)) + { + return false; + } + break; + case MT_EMERALDFLARE: + Obj_EmeraldFlareThink(mobj); + if (P_MobjWasRemoved(mobj)) { return false; @@ -9824,6 +9832,12 @@ static boolean P_FuseThink(mobj_t *mobj) break; } + case MT_EMERALD: + { + Obj_GiveEmerald(mobj); + P_RemoveMobj(mobj); + return false; + } case MT_PLAYER: break; // don't remove default: @@ -11840,7 +11854,10 @@ void P_SpawnPlayer(INT32 playernum) } else // Otherwise, never spectator. { + // TODO: this would make a great debug feature for release +#ifndef DEVELOP p->spectator = false; +#endif } } @@ -11968,6 +11985,15 @@ void P_SpawnPlayer(INT32 playernum) { K_ToggleDirector(players[consoleplayer].spectator && pcount > 0); } + + // TODO: handle splitscreen + // Spectators can switch to freecam. This should be + // disabled when they enter the race, or when the level + // changes. + if (playernum == consoleplayer && !demo.playback) + { + demo.freecam = false; + } } void P_AfterPlayerSpawn(INT32 playernum) @@ -12001,12 +12027,15 @@ void P_AfterPlayerSpawn(INT32 playernum) p->drawangle = mobj->angle; - for (i = 0; i <= r_splitscreen; i++) + if (p->spectator == false) { - if (camera[i].chase) + for (i = 0; i <= r_splitscreen; i++) { - if (displayplayers[i] == playernum) - P_ResetCamera(p, &camera[i]); + if (camera[i].chase) + { + if (displayplayers[i] == playernum) + P_ResetCamera(p, &camera[i]); + } } } diff --git a/src/p_saveg.c b/src/p_saveg.c index c8574c911..580e5bf38 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -5735,6 +5735,10 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) WRITEUINT32(save->p, racecountdown); WRITEUINT32(save->p, exitcountdown); + // exitcondition_t + WRITEUINT8(save->p, g_exit.losing); + WRITEUINT8(save->p, g_exit.retry); + WRITEFIXED(save->p, gravity); WRITEFIXED(save->p, mapobjectscale); @@ -5910,6 +5914,10 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) racecountdown = READUINT32(save->p); exitcountdown = READUINT32(save->p); + // exitcondition_t + g_exit.losing = READUINT8(save->p); + g_exit.retry = READUINT8(save->p); + gravity = READFIXED(save->p); mapobjectscale = READFIXED(save->p); diff --git a/src/p_setup.c b/src/p_setup.c index 4b9b1d4a8..23337787b 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -1220,46 +1220,6 @@ static void P_LoadSidedefs(UINT8 *data) sd->toptexture = sd->midtexture = sd->bottomtexture = 0; break; - case 413: // Change music - { - if (!isfrontside) - break; - - char process[8+1]; - - sd->toptexture = sd->midtexture = sd->bottomtexture = 0; - if (msd->bottomtexture[0] != '-' || msd->bottomtexture[1] != '\0') - { - M_Memcpy(process,msd->bottomtexture,8); - process[8] = '\0'; - sd->bottomtexture = get_number(process); - } - - if (!(msd->midtexture[0] == '-' && msd->midtexture[1] == '\0') || msd->midtexture[1] != '\0') - { - M_Memcpy(process,msd->midtexture,8); - process[8] = '\0'; - sd->midtexture = get_number(process); - } - - if (msd->toptexture[0] != '-' && msd->toptexture[1] != '\0') - { - sd->line->stringargs[0] = Z_Malloc(7, PU_LEVEL, NULL); - M_Memcpy(process,msd->toptexture,8); - process[8] = '\0'; - - // If they type in O_ or D_ and their music name, just shrug, - // then copy the rest instead. - if ((process[0] == 'O' || process[0] == 'D') && process[7]) - M_Memcpy(sd->line->stringargs[0], process+2, 6); - else // Assume it's a proper music name. - M_Memcpy(sd->line->stringargs[0], process, 6); - sd->line->stringargs[0][6] = '\0'; - } - - break; - } - case 414: // Play SFX { sd->toptexture = sd->midtexture = sd->bottomtexture = 0; @@ -5586,28 +5546,6 @@ static void P_ConvertBinaryLinedefTypes(void) lines[i].args[3] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS; lines[i].args[4] = lines[i].frontsector->ceilingheight >> FRACBITS; break; - case 413: //Change music - if (lines[i].flags & ML_NOCLIMB) - lines[i].args[1] |= TMM_ALLPLAYERS; - if (lines[i].flags & ML_SKEWTD) - lines[i].args[1] |= TMM_OFFSET; - if (lines[i].flags & ML_NOSKEW) - lines[i].args[1] |= TMM_FADE; - if (lines[i].flags & ML_BLOCKPLAYERS) - lines[i].args[1] |= TMM_NORELOAD; - if (lines[i].flags & ML_NOTBOUNCY) - lines[i].args[1] |= TMM_FORCERESET; - if (lines[i].flags & ML_MIDSOLID) - lines[i].args[1] |= TMM_NOLOOP; - if (lines[i].flags & ML_MIDPEG) - lines[i].args[1] |= TMM_NOCREDIT; - lines[i].args[2] = sides[lines[i].sidenum[0]].midtexture; - lines[i].args[3] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS; - lines[i].args[4] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS; - lines[i].args[5] = (lines[i].sidenum[1] != 0xffff) ? sides[lines[i].sidenum[1]].textureoffset >> FRACBITS : 0; - lines[i].args[6] = (lines[i].sidenum[1] != 0xffff) ? sides[lines[i].sidenum[1]].rowoffset >> FRACBITS : -1; - lines[i].args[7] = sides[lines[i].sidenum[0]].bottomtexture; - break; case 414: //Play sound effect lines[i].args[3] = tag; if (tag != 0) @@ -7524,6 +7462,9 @@ static void P_InitLevelSettings(void) racecountdown = exitcountdown = musiccountdown = exitfadestarted = 0; curlap = bestlap = 0; // SRB2Kart + g_exit.losing = false; + g_exit.retry = false; + // Gamespeed and frantic items gamespeed = KARTSPEED_EASY; franticitems = false; @@ -7754,6 +7695,8 @@ static void P_SetupCamera(UINT8 pnum, camera_t *cam) cam->subsector = R_PointInSubsector(cam->x, cam->y); // make sure camera has a subsector set -- Monster Iestyn (12/11/18) } } + + cam->chase = false; // tell camera to reset its position next tic } static void P_InitCamera(void) @@ -8165,7 +8108,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) if (K_PodiumSequence()) { // mapmusrng is set by local player position in K_ResetCeremony - P_ResetLevelMusic(); P_LoadLevelMusic(); } else if (gamestate == GS_LEVEL) diff --git a/src/p_spec.c b/src/p_spec.c index 0d08cdcc0..5bb9131cb 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2886,88 +2886,6 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha } break; - case 413: // Change music - // FIXME: port to new music system -#if 0 - // console player only unless TMM_ALLPLAYERS is set - if ((args[1] & TMM_ALLPLAYERS) || (mo && mo->player && P_IsLocalPlayer(mo->player)) || titlemapinaction) - { - boolean musicsame = (!stringargs[0] || !stringargs[0][0] || !strnicmp(stringargs[0], S_MusicName(), 7)); - UINT16 tracknum = (UINT16)max(args[7], 0); - INT32 position = (INT32)max(args[2], 0); - UINT32 prefadems = (UINT32)max(args[3], 0); - UINT32 postfadems = (UINT32)max(args[4], 0); - UINT8 fadetarget = (UINT8)max(args[5], 0); - INT16 fadesource = (INT16)max(args[6], -1); - - // Seek offset from current song position - if (args[1] & TMM_OFFSET) - { - // adjust for loop point if subtracting - if (position < 0 && S_GetMusicLength() && - S_GetMusicPosition() > S_GetMusicLoopPoint() && - S_GetMusicPosition() + position < S_GetMusicLoopPoint()) - position = max(S_GetMusicLength() - (S_GetMusicLoopPoint() - (S_GetMusicPosition() + position)), 0); - else - position = max(S_GetMusicPosition() + position, 0); - } - - // Fade current music to target volume (if music won't be changed) - if ((args[1] & TMM_FADE) && fadetarget && musicsame) - { - // 0 fadesource means fade from current volume. - // meaning that we can't specify volume 0 as the source volume -- this starts at 1. - if (!fadesource) - fadesource = -1; - - if (!postfadems) - S_SetInternalMusicVolume(fadetarget); - else - S_FadeMusicFromVolume(fadetarget, fadesource, postfadems); - - if (position) - S_SetMusicPosition(position); - } - // Change the music and apply position/fade operations - else - { - if (!stringargs[0]) - break; - - strncpy(mapmusname, stringargs[0], 7); - mapmusname[6] = 0; - - mapmusflags = tracknum & MUSIC_TRACKMASK; - if (!(args[1] & TMM_NORELOAD)) - mapmusflags |= MUSIC_RELOADRESET; - if (args[1] & TMM_FORCERESET) - mapmusflags |= MUSIC_FORCERESET; - - mapmusposition = position; - mapmusresume = 0; - - S_ChangeMusicEx(mapmusname, mapmusflags, !(args[1] & TMM_NOLOOP), position, - !(args[1] & TMM_FADE) ? prefadems : 0, - !(args[1] & TMM_FADE) ? postfadems : 0); - - if (!(args[1] & TMM_NOCREDIT)) - S_ShowMusicCredit(); - - if ((args[1] & TMM_FADE) && fadetarget) - { - if (!postfadems) - S_SetInternalMusicVolume(fadetarget); - else - S_FadeMusicFromVolume(fadetarget, fadesource, postfadems); - } - } - - // Except, you can use the TMM_NORELOAD flag to change this behavior. - // if (mapmusflags & MUSIC_RELOADRESET) then it will reset the music in G_PlayerReborn. - } -#endif - break; - case 414: // Play SFX P_PlaySFX(stringargs[0] ? get_number(stringargs[0]) : sfx_None, mo, callsec, args[3], args[1], args[2]); break; diff --git a/src/p_tick.c b/src/p_tick.c index b6b470ce5..8e408e92b 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -729,8 +729,8 @@ void P_Ticker(boolean run) timeinmap = (timeinmap-1) & ~3; G_PreviewRewind(leveltime); } - else if (demo.freecam && democam.cam) // special case: allow freecam to MOVE during pause! - P_DemoCameraMovement(democam.cam); + else + P_RunChaseCameras(); // special case: allow freecam to MOVE during pause! return; } diff --git a/src/p_user.c b/src/p_user.c index d2f148283..e60230a0c 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1082,6 +1082,15 @@ boolean P_IsDisplayPlayer(player_t *player) return false; } + // Freecam still techically has a player in + // displayplayers. But since the camera is detached, it + // would be weird if sounds were heard from that player's + // perspective. + if (demo.freecam) + { + return false; + } + for (i = 0; i <= r_splitscreen; i++) // DON'T skip P1 { if (player == &players[displayplayers[i]]) @@ -1309,19 +1318,12 @@ void P_DoPlayerExit(player_t *player, pflags_t flags) if (P_CheckRacers() && !exitcountdown) { - if (specialout == true) - { - exitcountdown = TICRATE; - } - else - { - exitcountdown = raceexittime+1; - } + G_BeginLevelExit(); } } else if (!exitcountdown) // All other gametypes { - exitcountdown = raceexittime+1; + G_BeginLevelExit(); } if (grandprixinfo.gp == true && player->bot == false && losing == false) @@ -2350,45 +2352,6 @@ static void P_UpdatePlayerAngle(player_t *player) } } - -// -// P_SpectatorMovement -// -// Control for spectators in multiplayer -// -static void P_SpectatorMovement(player_t *player) -{ - ticcmd_t *cmd = &player->cmd; - - P_UpdatePlayerAngle(player); - - ticruned++; - if (!(cmd->flags & TICCMD_RECEIVED)) - ticmiss++; - - if (cmd->buttons & BT_ACCELERATE) - player->mo->z += 32*mapobjectscale; - else if (cmd->buttons & BT_BRAKE) - player->mo->z -= 32*mapobjectscale; - - if (!(player->mo->flags & MF_NOCLIPHEIGHT)) - { - if (player->mo->z > player->mo->ceilingz - player->mo->height) - player->mo->z = player->mo->ceilingz - player->mo->height; - if (player->mo->z < player->mo->floorz) - player->mo->z = player->mo->floorz; - } - - player->mo->momx = player->mo->momy = player->mo->momz = 0; - if (cmd->forwardmove != 0) - { - P_Thrust(player->mo, player->mo->angle, cmd->forwardmove*mapobjectscale); - - // Quake-style flying spectators :D - player->mo->momz += FixedMul(cmd->forwardmove*mapobjectscale, AIMINGTOSLOPE(player->aiming)); - } -} - // // P_MovePlayer void P_MovePlayer(player_t *player) @@ -2415,7 +2378,6 @@ void P_MovePlayer(player_t *player) if (player->spectator) { player->mo->eflags &= ~MFE_VERTICALFLIP; // deflip... - P_SpectatorMovement(player); return; } @@ -2970,184 +2932,87 @@ fixed_t t_cam_dist[MAXSPLITSCREENPLAYERS] = {-42,-42,-42,-42}; fixed_t t_cam_height[MAXSPLITSCREENPLAYERS] = {-42,-42,-42,-42}; fixed_t t_cam_rotate[MAXSPLITSCREENPLAYERS] = {-42,-42,-42,-42}; -// Heavily simplified version of G_BuildTicCmd that only takes the local first player's control input and converts it to readable ticcmd_t -// we then throw that ticcmd garbage in the camera and make it move -// TODO: please just use the normal ticcmd function somehow - -static ticcmd_t cameracmd; - struct demofreecam_s democam; -// called by m_menu to reinit cam input every time it's toggled -void P_InitCameraCmd(void) -{ - memset(&cameracmd, 0, sizeof(ticcmd_t)); // initialize cmd -} - -static ticcmd_t *P_CameraCmd(camera_t *cam) -{ - /* - INT32 forward, axis; //i - // these ones used for multiple conditions - boolean turnleft, turnright, mouseaiming; - boolean invertmouse, lookaxis, usejoystick, kbl; - INT32 player_invert; - INT32 screen_invert; - */ - ticcmd_t *cmd = &cameracmd; - - (void)cam; - - if (!demo.playback) - return cmd; // empty cmd, no. - - /* - kbl = democam.keyboardlook; - - G_CopyTiccmd(cmd, I_BaseTiccmd(), 1); // empty, or external driver - - mouseaiming = true; - invertmouse = cv_invertmouse.value; - lookaxis = cv_lookaxis[0].value; - - usejoystick = true; - turnright = PlayerInputDown(1, gc_turnright); - turnleft = PlayerInputDown(1, gc_turnleft); - - axis = PlayerJoyAxis(1, AXISTURN); - - if (encoremode) - { - turnright ^= turnleft; // swap these using three XORs - turnleft ^= turnright; - turnright ^= turnleft; - axis = -axis; - } - - if (axis != 0) - { - turnright = turnright || (axis > 0); - turnleft = turnleft || (axis < 0); - } - forward = 0; - - cmd->turning = 0; - - // let movement keys cancel each other out - if (turnright && !(turnleft)) - { - cmd->turning -= KART_FULLTURN; - } - else if (turnleft && !(turnright)) - { - cmd->turning += KART_FULLTURN; - } - - cmd->turning -= (mousex * 8) * (encoremode ? -1 : 1); - - axis = PlayerJoyAxis(1, AXISMOVE); - if (PlayerInputDown(1, gc_a) || (usejoystick && axis > 0)) - cmd->buttons |= BT_ACCELERATE; - axis = PlayerJoyAxis(1, AXISBRAKE); - if (PlayerInputDown(1, gc_brake) || (usejoystick && axis > 0)) - cmd->buttons |= BT_BRAKE; - axis = PlayerJoyAxis(1, AXISAIM); - if (PlayerInputDown(1, gc_aimforward) || (usejoystick && axis < 0)) - forward += MAXPLMOVE; - if (PlayerInputDown(1, gc_aimbackward) || (usejoystick && axis > 0)) - forward -= MAXPLMOVE; - - // fire with any button/key - axis = PlayerJoyAxis(1, AXISFIRE); - if (PlayerInputDown(1, gc_fire) || (usejoystick && axis > 0)) - cmd->buttons |= BT_ATTACK; - - // spectator aiming shit, ahhhh... - player_invert = invertmouse ? -1 : 1; - screen_invert = 1; // nope - - // mouse look stuff (mouse look is not the same as mouse aim) - kbl = false; - - // looking up/down - cmd->aiming += (mlooky<<19)*player_invert*screen_invert; - - axis = PlayerJoyAxis(1, AXISLOOK); - - // spring back if not using keyboard neither mouselookin' - if (!kbl && !lookaxis && !mouseaiming) - cmd->aiming = 0; - - if (PlayerInputDown(1, gc_lookup) || (axis < 0)) - { - cmd->aiming += KB_LOOKSPEED * screen_invert; - kbl = true; - } - else if (PlayerInputDown(1, gc_lookdown) || (axis > 0)) - { - cmd->aiming -= KB_LOOKSPEED * screen_invert; - kbl = true; - } - - if (PlayerInputDown(1, gc_centerview)) // No need to put a spectator limit on this one though :V - cmd->aiming = 0; - - cmd->forwardmove += (SINT8)forward; - - if (cmd->forwardmove > MAXPLMOVE) - cmd->forwardmove = MAXPLMOVE; - else if (cmd->forwardmove < -MAXPLMOVE) - cmd->forwardmove = -MAXPLMOVE; - - if (cmd->turning > KART_FULLTURN) - cmd->turning = KART_FULLTURN; - else if (cmd->turning < -KART_FULLTURN) - cmd->turning = -KART_FULLTURN; - - democam.keyboardlook = kbl; - */ - - return cmd; -} - void P_DemoCameraMovement(camera_t *cam) { ticcmd_t *cmd; angle_t thrustangle; - mobj_t *awayviewmobj_hack; player_t *lastp; - // update democam stuff with what we got here: - democam.cam = cam; - democam.localangle = cam->angle; - democam.localaiming = cam->aiming; + boolean moving = false; // first off we need to get button input - cmd = P_CameraCmd(cam); + cmd = D_LocalTiccmd(0); + + if (cmd->aiming != 0) + { + cam->aiming += cmd->aiming << TICCMD_REDUCE; + + cam->reset_aiming = false; + } - cam->aiming += cmd->aiming << TICCMD_REDUCE; cam->angle += cmd->turning << TICCMD_REDUCE; - democam.localangle += cmd->turning << TICCMD_REDUCE; - democam.localaiming += cmd->aiming << TICCMD_REDUCE; - - cam->aiming = G_ClipAimingPitch((INT32 *)&cam->aiming); - democam.localaiming = G_ClipAimingPitch((INT32 *)&democam.localaiming); - // camera movement: - if (cmd->buttons & BT_ACCELERATE) - cam->z += 32*mapobjectscale; - else if (cmd->buttons & BT_BRAKE) - cam->z -= 32*mapobjectscale; + if (!democam.button_a_held) + { + if (cmd->buttons & BT_ACCELERATE) + { + cam->z += 32*mapobjectscale; + moving = true; + } + else if (cmd->buttons & BT_BRAKE) + { + cam->z -= 32*mapobjectscale; + moving = true; + } + } + + if (!(cmd->buttons & (BT_ACCELERATE | BT_DRIFT)) && democam.button_a_held) + { + democam.button_a_held--; + } // if you hold item, you will lock on to displayplayer. (The last player you were ""f12-ing"") - if (cmd->buttons & BT_ATTACK) + if (demo.freecam && cmd->buttons & BT_ATTACK) { lastp = &players[displayplayers[0]]; // Fun fact, I was trying displayplayers[0]->mo as if it was Lua like an absolute idiot. cam->angle = R_PointToAngle2(cam->x, cam->y, lastp->mo->x, lastp->mo->y); cam->aiming = R_PointToAngle2(0, cam->z, R_PointToDist2(cam->x, cam->y, lastp->mo->x, lastp->mo->y), lastp->mo->z + lastp->mo->scale*128*P_MobjFlip(lastp->mo)); // This is still unholy. Aim a bit above their heads. + + cam->reset_aiming = false; } + if (cmd->forwardmove != 0) + { + moving = true; + } + + // After switching to democam, the vertical angle of + // chasecam is inherited. This is intentional because it + // creates a smooth transition. However, moving + // forward/back will have a slope. So, as long as democam + // controls haven't been used to alter the vertical angle, + // slowly reset it to flat. + if ((cam->reset_aiming && moving) || ((cmd->buttons & BT_DRIFT) && !democam.button_a_held)) + { + INT32 aiming = cam->aiming; + INT32 smooth = FixedMul(ANGLE_11hh / 4, FCOS(cam->aiming)); + + if (abs(smooth) < abs(aiming)) + { + cam->aiming -= smooth * intsign(aiming); + } + else + { + cam->aiming = 0; + cam->reset_aiming = false; // completely smoothed out + } + } + + G_FinalClipAimingPitch((INT32 *)&cam->aiming, NULL, false); + cam->momx = cam->momy = cam->momz = 0; if (cmd->forwardmove != 0) @@ -3156,25 +3021,35 @@ void P_DemoCameraMovement(camera_t *cam) cam->x += FixedMul(cmd->forwardmove*mapobjectscale, FINECOSINE(thrustangle)); cam->y += FixedMul(cmd->forwardmove*mapobjectscale, FINESINE(thrustangle)); - cam->z += FixedMul(cmd->forwardmove*mapobjectscale, AIMINGTOSLOPE(cam->aiming)); + + if (!cam->reset_aiming) + { + cam->z += FixedMul(cmd->forwardmove*mapobjectscale, AIMINGTOSLOPE(cam->aiming)); + } // momentums are useless here, directly add to the coordinates // this.......... doesn't actually check for floors and walls and whatnot but the function to do that is a pure mess so fuck that. // besides freecam going inside walls sounds pretty cool on paper. } - // awayviewmobj hack; this is to prevent us from hearing sounds from the player's perspective - - awayviewmobj_hack = P_SpawnMobj(cam->x, cam->y, cam->z, MT_THOK); - awayviewmobj_hack->tics = 2; - awayviewmobj_hack->renderflags |= RF_DONTDRAW; - - democam.soundmobj = awayviewmobj_hack; - // update subsector to avoid crashes; cam->subsector = R_PointInSubsector(cam->x, cam->y); } +void P_ToggleDemoCamera(void) +{ + if (!demo.freecam) // toggle on + { + demo.freecam = true; + democam.button_a_held = 2; + camera[0].reset_aiming = true; + } + else // toggle off + { + demo.freecam = false; + } +} + void P_ResetCamera(player_t *player, camera_t *thiscam) { tic_t tries = 0; @@ -3211,6 +3086,8 @@ void P_ResetCamera(player_t *player, camera_t *thiscam) thiscam->radius = 20*FRACUNIT; thiscam->height = 16*FRACUNIT; + thiscam->reset_aiming = true; + while (!P_MoveChaseCamera(player,thiscam,true) && ++tries < 2*TICRATE); } @@ -3239,8 +3116,6 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall thiscam->old_angle = thiscam->angle; thiscam->old_aiming = thiscam->aiming; - democam.soundmobj = NULL; // reset this each frame, we don't want the game crashing for stupid reasons now do we - // We probably shouldn't move the camera if there is no player or player mobj somehow if (!player || !player->mo) return true; @@ -3249,12 +3124,15 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall if (thiscam->subsector == NULL || thiscam->subsector->sector == NULL) return true; - if (demo.freecam) + if (demo.freecam || player->spectator) { P_DemoCameraMovement(thiscam); return true; } + if (paused || P_AutoPause()) + return true; + playerScale = FixedDiv(player->mo->scale, mapobjectscale); scaleDiff = playerScale - FRACUNIT; @@ -3542,6 +3420,8 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall z = mo->z + pviewheight + distz; } + z += player->cameraOffset; + // point viewed by the camera // this point is just 64 unit forward the player dist = 64*cameraScale; @@ -3586,13 +3466,13 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall if (mo->eflags & MFE_VERTICALFLIP) { - angle = R_PointToAngle2(0, thiscam->z + thiscam->height, dist, mo->z + mo->height - player->mo->height); + angle = R_PointToAngle2(0, thiscam->z + thiscam->height, dist, mo->z + mo->height - player->mo->height + player->cameraOffset); if (thiscam->pitch < ANGLE_180 && thiscam->pitch > angle) angle += (thiscam->pitch - angle)/2; } else { - angle = R_PointToAngle2(0, thiscam->z, dist, mo->z + player->mo->height); + angle = R_PointToAngle2(0, thiscam->z, dist, mo->z + player->mo->height + player->cameraOffset); if (thiscam->pitch >= ANGLE_180 && thiscam->pitch < angle) angle -= (angle - thiscam->pitch)/2; } @@ -3890,7 +3770,7 @@ void P_DoTimeOver(player_t *player) if (!exitcountdown) { - exitcountdown = raceexittime; + G_BeginLevelExit(); } } @@ -4802,6 +4682,22 @@ void P_ForceLocalAngle(player_t *player, angle_t angle) break; } } + + // jartha: OK, I don't really know how ticcmds work. The + // specific problem I'm trying to fix is that, on level + // load, the player angle gets reset. But the ticcmds + // copied in afterward don't match this angle, and they + // influence the player steering. + for (i = 0; i <= splitscreen; i++) + { + if (player == &players[g_localplayers[i]]) + { + D_ResetTiccmdAngle(i, angle); + localsteering[i] = angle; + + break; + } + } } boolean P_PlayerFullbright(player_t *player) diff --git a/src/r_fps.c b/src/r_fps.c index 8f870abf2..f21d38943 100644 --- a/src/r_fps.c +++ b/src/r_fps.c @@ -22,9 +22,6 @@ #include "r_state.h" #include "z_zone.h" #include "console.h" // con_startup_loadprogress -#ifdef HWRENDER -#include "hardware/hw_main.h" // for cv_glshearing -#endif static CV_PossibleValue_t fpscap_cons_t[] = { #ifdef DEVELOP @@ -117,23 +114,7 @@ static vector3_t *R_LerpVector3(const vector3_t *from, const vector3_t *to, fixe // 18/08/18: (No it's actually 16*viewheight, thanks Jimita for finding this out) static void R_SetupFreelook(player_t *player, boolean skybox) { -#ifndef HWRENDER - (void)player; - (void)skybox; -#endif - - // clip it in the case we are looking a hardware 90 degrees full aiming - // (lmps, network and use F12...) - if (rendermode == render_soft -#ifdef HWRENDER - || (rendermode == render_opengl - && (cv_glshearing.value == 1 - || (cv_glshearing.value == 2 && R_IsViewpointThirdPerson(player, skybox)))) -#endif - ) - { - G_SoftwareClipAimingPitch((INT32 *)&aimingangle); - } + G_FinalClipAimingPitch((INT32 *)&aimingangle, player, skybox); centeryfrac = (viewheight/2)<spectator) + if (!player->spectator && !demo.freecam) { roll += player->tilt; } @@ -1227,7 +1228,7 @@ R_SetupCommonFrame else newview->sector = R_PointInSubsector(newview->x, newview->y)->sector; - R_InterpolateView(rendertimefrac); + R_InterpolateView(rendertimefrac_unpaused); } static void R_SetupAimingFrame(int s) @@ -1265,8 +1266,12 @@ void R_SetupFrame(int s) R_SetViewContext(VIEWCONTEXT_PLAYER1 + s); - if (player->spectator) // no spectator chasecam - chasecam = false; // force chasecam off + if (player->spectator) + { + // Free flying spectator uses demo freecam. This + // requires chasecam to be enabled. + chasecam = true; + } if (chasecam && (thiscam && !thiscam->chase)) { @@ -1292,7 +1297,7 @@ void R_SetupFrame(int s) R_SetupCommonFrame(player, r_viewmobj->subsector); } - else if (!player->spectator && chasecam) + else if (chasecam) // use outside cam view { r_viewmobj = NULL; @@ -1430,10 +1435,8 @@ boolean R_ViewpointHasChasecam(player_t *player) } } - if (player->playerstate == PST_DEAD || gamestate == GS_TITLESCREEN) + if (player->playerstate == PST_DEAD || gamestate == GS_TITLESCREEN || player->spectator) chasecam = true; // force chasecam on - else if (player->spectator) // no spectator chasecam - chasecam = false; // force chasecam off return chasecam; } @@ -1446,7 +1449,7 @@ boolean R_IsViewpointThirdPerson(player_t *player, boolean skybox) if (player->awayview.tics || skybox) return chasecam; // use outside cam view - else if (!player->spectator && chasecam) + else if (chasecam) return true; // use the player's eyes view diff --git a/src/r_main.h b/src/r_main.h index dcd496cfb..0f5560308 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -39,6 +39,8 @@ extern size_t validcount, linecount, loopcount, framecount; // The fraction of a tic being drawn (for interpolation between two tics) extern fixed_t rendertimefrac; +// Same as rendertimefrac but not suspended when the game is paused +extern fixed_t rendertimefrac_unpaused; // Evaluated delta tics for this frame (how many tics since the last frame) extern fixed_t renderdeltatics; // The current render is a new logical tic diff --git a/src/r_spritefx.cpp b/src/r_spritefx.cpp index 4db957880..756e0a96e 100644 --- a/src/r_spritefx.cpp +++ b/src/r_spritefx.cpp @@ -22,7 +22,7 @@ INT32 R_ThingLightLevel(mobj_t* thing) if (player) { - if (player->instaShieldCooldown && (player->rings <= 0) && (leveltime & 1)) + if (player->instaShieldCooldown && !player->whip && (player->rings <= 0) && (leveltime & 1)) { // Darken on every other frame of instawhip cooldown lightlevel -= 128; diff --git a/src/s_sound.c b/src/s_sound.c index a9e5a4656..eb6c9a2e1 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -540,11 +540,6 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume) continue; } - if (i == 0 && democam.soundmobj) - { - continue; - } - if (player->awayview.tics) { listenmobj[i] = player->awayview.mobj; @@ -554,7 +549,7 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume) listenmobj[i] = player->mo; } - if (origin && origin == listenmobj[i]) + if (origin && origin == listenmobj[i] && !demo.freecam) { itsUs = true; } @@ -827,11 +822,6 @@ void S_UpdateSounds(void) continue; } - if (i == 0 && democam.soundmobj) - { - continue; - } - if (player->awayview.tics) { listenmobj[i] = player->awayview.mobj; @@ -898,12 +888,15 @@ void S_UpdateSounds(void) { boolean itsUs = false; - for (i = r_splitscreen; i >= 0; i--) + if (!demo.freecam) { - if (c->origin != listenmobj[i]) - continue; + for (i = r_splitscreen; i >= 0; i--) + { + if (c->origin != listenmobj[i]) + continue; - itsUs = true; + itsUs = true; + } } if (itsUs == false) diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index aed47915d..d634aa368 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -137,7 +137,7 @@ typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T); #define UNIXBACKTRACE #endif -// Locations for searching for main.kart +// Locations for searching for bios.pk3 #if defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON) #define DEFAULTWADLOCATION1 "/usr/local/share/games/RingRacers" #define DEFAULTWADLOCATION2 "/usr/local/games/RingRacers" @@ -150,7 +150,7 @@ typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T); /** \brief WAD file to look for */ -#define WADKEYWORD "main.kart" +#define WADKEYWORD "bios.pk3" /** \brief holds wad path */ static char returnWadPath[256]; @@ -2104,7 +2104,7 @@ static void pathonly(char *s) } } -/** \brief search for main.kart in the given path +/** \brief search for bios.pk3 in the given path \param searchDir starting path @@ -2128,9 +2128,9 @@ static const char *searchWad(const char *searchDir) return NULL; } -/** \brief go through all possible paths and look for main.kart +/** \brief go through all possible paths and look for bios.pk3 - \return path to main.kart if any + \return path to bios.pk3 if any */ static const char *locateWad(void) @@ -2260,7 +2260,7 @@ const char *I_LocateWad(void) if (waddir) { - // change to the directory where we found main.kart + // change to the directory where we found bios.pk3 #if defined (_WIN32) SetCurrentDirectoryA(waddir); #else diff --git a/src/sdl12/i_system.c b/src/sdl12/i_system.c index 409339ded..0f633f290 100644 --- a/src/sdl12/i_system.c +++ b/src/sdl12/i_system.c @@ -145,7 +145,7 @@ void __set_fpscr(long); // in libgcc / kernel's startup.s? #define O_BINARY 0 #endif -// Locations for searching the main.kart +// Locations for searching the bios.pk3 #ifdef _arch_dreamcast #define DEFAULTWADLOCATION1 "/cd" #define DEFAULTWADLOCATION2 "/pc" @@ -217,7 +217,7 @@ void __set_fpscr(long); // in libgcc / kernel's startup.s? /** \brief WAD file to look for */ -#define WADKEYWORD "main.kart" +#define WADKEYWORD "bios.pk3" /** \brief holds wad path */ static char returnWadPath[256]; @@ -3436,7 +3436,7 @@ static void pathonly(char *s) } } -/** \brief search for main.kart in the given path +/** \brief search for bios.pk3 in the given path \param searchDir starting path @@ -3460,9 +3460,9 @@ static const char *searchWad(const char *searchDir) return NULL; } -/** \brief go through all possible paths and look for main.kart +/** \brief go through all possible paths and look for bios.pk3 - \return path to main.kart if any + \return path to bios.pk3 if any */ static const char *locateWad(void) { @@ -3579,7 +3579,7 @@ const char *I_LocateWad(void) if (waddir) { - // change to the directory where we found main.kart + // change to the directory where we found bios.pk3 #if (defined (_WIN32) && !defined (_WIN32_WCE)) && !defined (_XBOX) SetCurrentDirectoryA(waddir); #elif !defined (_WIN32_WCE) && !defined (_PS3) diff --git a/src/st_stuff.c b/src/st_stuff.c index dc4133089..c7166c414 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -1171,7 +1171,7 @@ static void ST_overlayDrawer(void) { if (cv_showviewpointtext.value) { - if (!demo.title && !P_IsLocalPlayer(stplyr)) + if (!demo.title && !P_IsLocalPlayer(stplyr) && !demo.freecam) { if (!r_splitscreen) { diff --git a/src/typedef.h b/src/typedef.h index a27ca364f..93427ad47 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -134,6 +134,7 @@ TYPEDEF (unloaded_mapheader_t); TYPEDEF (tolinfo_t); TYPEDEF (cupheader_t); TYPEDEF (unloaded_cupheader_t); +TYPEDEF (exitcondition_t); // font.h TYPEDEF (font_t); diff --git a/src/y_inter.c b/src/y_inter.c index 391d0861b..6c980a7ee 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -71,6 +71,7 @@ static patch_t *widebgpatch = NULL; static patch_t *bgtile = NULL; // SPECTILE/SRB2BACK static patch_t *interpic = NULL; // custom picture defined in map header +#define INFINITE_TIMER (INT16_MAX) // just some arbitrarily large value that won't easily overflow static INT32 timer; static INT32 powertype = PWRLV_DISABLED; @@ -86,6 +87,16 @@ intertype_t intertype = int_none; static huddrawlist_h luahuddrawlist_intermission; +static boolean Y_CanSkipIntermission(void) +{ + if (!netgame) + { + return true; + } + + return false; +} + static void Y_UnloadData(void); // @@ -1533,6 +1544,17 @@ finalcounter: } } + if (Y_CanSkipIntermission()) + { + K_drawButton( + 2*FRACUNIT, + (BASEVIDHEIGHT - 16)*FRACUNIT, + 0, + kp_button_a[1], + M_MenuConfirmHeld(0) + ); + } + else { const INT32 tickDown = (timer + 1)/TICRATE; @@ -1578,6 +1600,46 @@ void Y_Ticker(void) LUA_HOOK(IntermissionThinker); + if (Y_CanSkipIntermission()) + { + if (M_MenuConfirmPressed(0)) + { + // If there is a roundqueue, make time for it. + // Else, end instantly on button press. + // Actually, give it a slight delay, so the "kaching" sound isn't cut off. + const tic_t end = roundqueue.size != 0 ? 3*TICRATE : TICRATE; + + if (intertic == -1) // card flip hasn't started + { + if (sorttic != -1) + { + intertic = sorttic; + } + else + { + intertic = 0; + timer = end; + } + } + else if (timer >= INFINITE_TIMER && intertic >= sorttic + 16) // card done flipping + { + const INT32 kaching = sorttic + 16 + (2*TICRATE); + + if (intertic < kaching) + { + intertic = kaching; // kaching immediately + } + + timer = end; + } + } + + if (intertic == -1) + { + return; + } + } + intertic++; // Team scramble code for team match and CTF. @@ -1590,7 +1652,7 @@ void Y_Ticker(void) P_DoTeamscrambling(); }*/ - if ((timer && !--timer) + if ((timer < INFINITE_TIMER && --timer <= 0) || (intertic == endtic)) { Y_EndIntermission(); @@ -1665,6 +1727,12 @@ void Y_Ticker(void) r++; data.jitter[data.num[q]] = 1; + // Player can skip the tally, kaching! + if (Y_CanSkipIntermission() && timer < INFINITE_TIMER) + { + data.increase[data.num[q]] = 0; + } + if (powertype != PWRLV_DISABLED) { // Power Levels @@ -1815,6 +1883,21 @@ void Y_StartIntermission(void) sorttic = max((timer/2) - 2*TICRATE, 2*TICRATE); } + // TODO: code's a mess, I'm just making it extra clear + // that this piece of code is supposed to take priority + // over the above. :) + if (Y_CanSkipIntermission()) + { + timer = INFINITE_TIMER; // doesn't count down + + if (sorttic != -1) + { + // Will start immediately, but must be triggered. + // Needs to be TICRATE to bypass a condition in Y_Ticker. + sorttic = TICRATE; + } + } + // We couldn't display the intermission even if we wanted to. // But we still need to give the players their score bonuses, dummy. //if (dedicated) return;