diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 04c0c16dc443eae3a19121b11372185b5b39e346..29f5ef5fff9f1bc4afb83006c65f54088762e6dc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,6 +2,10 @@ include: - '.gitlab/ci/templates/*.yml' - '.gitlab/ci/jobs/*.yml' +workflow: + auto_cancel: + on_new_commit: interruptible + variables: GIT_CLONE_PATH: $CI_BUILDS_DIR/$CI_CONCURRENT_ID/$CI_PROJECT_PATH GIT_DEPTH: 20 @@ -10,5 +14,6 @@ stages: - build default: + interruptible: true artifacts: expire_in: 1 day diff --git a/src/acs/environment.cpp b/src/acs/environment.cpp index 6df2824981c687af99bfd34e9b967b425e19882a..1be630d32163335d58024ba7af676c553390a6eb 100644 --- a/src/acs/environment.cpp +++ b/src/acs/environment.cpp @@ -46,6 +46,9 @@ Environment::Environment() // Not that we're adding any modules to it, though. :p global->active = true; + // Set a branch limit (same as ZDoom's instruction limit) + branchLimit = 2000000; + // Add the data & function pointers. // Starting with raw ACS0 codes. I'm using this classic-style @@ -405,3 +408,42 @@ ACSVM::Word Environment::callSpecImpl P_ProcessSpecial(activator, spec, args, stringargs); return 1; } + +void Environment::printKill(ACSVM::Thread *thread, ACSVM::Word type, ACSVM::Word data) +{ + std::string scriptName; + + ACSVM::String *scriptNamePtr = (thread->script != nullptr) ? (thread->script->name.s) : nullptr; + if (scriptNamePtr && scriptNamePtr->len) + scriptName = std::string(scriptNamePtr->str); + else + scriptName = std::to_string((int)thread->script->name.i); + + ACSVM::KillType killType = static_cast<ACSVM::KillType>(type); + + if (killType == ACSVM::KillType::BranchLimit) + { + CONS_Alert(CONS_ERROR, "Terminated runaway script %s\n", scriptName.c_str()); + return; + } + else if (killType == ACSVM::KillType::UnknownCode) + { + CONS_Alert(CONS_ERROR, "ACSVM ERROR: Unknown opcode %d in script %s\n", data, scriptName.c_str()); + } + else if (killType == ACSVM::KillType::UnknownFunc) + { + CONS_Alert(CONS_ERROR, "ACSVM ERROR: Unknown function %d in script %s\n", data, scriptName.c_str()); + } + else if (killType == ACSVM::KillType::OutOfBounds) + { + CONS_Alert(CONS_ERROR, "ACSVM ERROR: Jumped to out of bounds location %lu in script %s\n", + (thread->codePtr - thread->module->codeV.data() - 1), scriptName.c_str()); + } + else + { + CONS_Alert(CONS_ERROR, "ACSVM ERROR: Kill %u:%d at %lu in script %s\n", + type, data, (thread->codePtr - thread->module->codeV.data() - 1), scriptName.c_str()); + } + + CONS_Printf("Script terminated.\n"); +} diff --git a/src/acs/environment.hpp b/src/acs/environment.hpp index 249797f669feb3d3935888aea1be079c201f3e4e..79aa4a4ae6b72ee0a75c0eb476876fce89e5322a 100644 --- a/src/acs/environment.hpp +++ b/src/acs/environment.hpp @@ -32,6 +32,8 @@ public: virtual ACSVM::Thread *allocThread(); + virtual void printKill(ACSVM::Thread *thread, ACSVM::Word type, ACSVM::Word data); + protected: virtual void loadModule(ACSVM::Module *module); diff --git a/src/cvars.cpp b/src/cvars.cpp index 83c360eb2a7a4487ac9f0fd2d982cdc6e072b8bf..d092a322b5ef413d39aa196db684f687021fe505 100644 --- a/src/cvars.cpp +++ b/src/cvars.cpp @@ -860,7 +860,7 @@ consvar_t cv_ufo_health = OnlineCheat("ufo_health", "-1").min_max(-1, 100).descr consvar_t cv_botscanvote = ServerCheat("botscanvote", "No").yes_no(); void Gravity_OnChange(void); -consvar_t cv_gravity = ServerCheat("gravity", "0.8").floating_point().onchange(Gravity_OnChange).description("Change the default gravity"); // change DEFAULT_GRAVITY if you change this +consvar_t cv_gravity = ServerCheat("gravity", "0.8").floating_point().min_max(0, 200*FRACUNIT).onchange(Gravity_OnChange).description("Change the default gravity"); // change DEFAULT_GRAVITY if you change this consvar_t cv_kartdebugcolorize = ServerCheat("debugcolorize", "Off").on_off().description("Show all colorized options on the HUD"); consvar_t cv_kartdebugdirector = ServerCheat("debugdirector", "Off").on_off().description("Show director AI on the HUD"); diff --git a/src/d_clisrv.c b/src/d_clisrv.c index f2a8cd1cb7daab39a842d4babea0464d0c2cac63..c4eafdf0325de976d02aa6b7dca66b039d41749f 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -5762,8 +5762,26 @@ static void CL_SendClientCmd(void) spike_time = 0; } + /* if (server) // Clients have to wait for the gamestate to make it back. Servers don't! lagDelay *= 2; // Simulate the HELLFUCK NIGHTMARE of a complete round trip. + */ + + // [deep breath in] + // Plausible, elegant explanation that is WRONG AND SUPER HARMFUL. + // Clients with stable connections were adding their mindelay to network delay, + // even when their mindelay was as high or higher than network delay—which made + // client delay APPEAR slower than host mindelay, by the exact value that made + // "lmao just double it" make sense at the time. + // + // While this fix made client connections match server mindelay in our most common + // test environment, it also masked an issue that seriously affected online handling + // responsiveness, completely ruining our opportunity to further investigate it! + // + // See UpdatePingTable. + // I am taking this shitty code to my grave as an example of "never trust your brain". + // -Tyron 2024-05-15 + } packetsize = sizeof (clientcmd_pak); @@ -6335,8 +6353,15 @@ static void UpdatePingTable(void) } else // We're a client, handle mindelay on the way out. { - if ((neededtic - gametic) < (tic_t)cv_mindelay.value) - lowest_lag = cv_mindelay.value - (neededtic - gametic); + // Previously (neededtic - gametic) - WRONG VALUE! + // Pretty sure that's measuring jitter, not RTT. + // Stable connections would be punished by adding their mindelay to network delay! + tic_t mydelay = playerpingtable[consoleplayer]; + + if (mydelay < (tic_t)cv_mindelay.value) + lowest_lag = cv_mindelay.value - mydelay; + else + lowest_lag = 0; } } diff --git a/src/d_main.cpp b/src/d_main.cpp index 5835846157b32f5a413039b11d0eae0cb705751a..8186663a7a4cd2ebe3a80678d2a13b9e256f1afa 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -118,7 +118,7 @@ extern "C" consvar_t cv_lua_profile, cv_menuframeskip; #define ASSET_HASH_MAPS_PK3 "a8bd1f924531c483f500d96583b7b837" #define ASSET_HASH_UNLOCKS_PK3 "ebc06ff46c2cc80e93dadf5f7099d7b8" #define ASSET_HASH_STAFFGHOSTS_PK3 "9cb77f6c0e801c1bc61ca84870b65707" -#define ASSET_HASH_SHADERS_PK3 "dbfb1d5eb9818cd2fb81680c0bab05c0" +#define ASSET_HASH_SHADERS_PK3 "7aefd2aa55129b31210aa094cf782695" #ifdef USE_PATCH_FILE #define ASSET_HASH_PATCH_PK3 "00000000000000000000000000000000" #endif diff --git a/src/d_netcmd.c b/src/d_netcmd.c index d887aaf733d57ac627f50411e4de11668b300eaa..af60224f901399629c2d2db912ddf9bd230710a2 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -6461,14 +6461,12 @@ void Command_Retry_f(void) */ static void Command_Isgamemodified_f(void) { - if (majormods) - CONS_Printf("The game has been modified with major addons, so you cannot play Record Attack.\n"); - else if (savemoddata) - CONS_Printf("The game has been modified with an addon with its own save data, so you can play Record Attack and earn medals.\n"); + if (savemoddata) + CONS_Printf("The game has been modified with an addon using its own save data.\n"); else if (modifiedgame) - CONS_Printf("The game has been modified with only minor addons. You can play Record Attack, earn medals and unlock extras.\n"); + CONS_Printf("The game has been modified, but is still using Ring Racers save data.\n"); else - CONS_Printf("The game has not been modified. You can play Record Attack, earn medals and unlock extras.\n"); + CONS_Printf("The game has not been modified.\n"); } #ifdef _DEBUG diff --git a/src/g_demo.cpp b/src/g_demo.cpp index 6da10879b8e133be7cb6d1a51d5ef2682e8d5c72..59138015b2f7724e8dac303d90efeff4788050c9 100644 --- a/src/g_demo.cpp +++ b/src/g_demo.cpp @@ -170,8 +170,9 @@ demoghost *ghosts = NULL; // - SPB cup TA replays were recorded at this time // - Slope physics changed with a scaling fix // - 0x000C (Ring Racers v2.2) +// - 0x000D (Ring Racers v2.3) -#define DEMOVERSION 0x000C +#define DEMOVERSION 0x000D boolean G_CompatLevel(UINT16 level) { @@ -2278,6 +2279,7 @@ void G_BeginRecording(void) WRITEUINT8(demobuf.p, grandprixinfo.gamespeed); WRITEUINT8(demobuf.p, grandprixinfo.masterbots == true); WRITEUINT8(demobuf.p, grandprixinfo.eventmode); + WRITEUINT32(demobuf.p, grandprixinfo.specialDamage); } // Save netUnlocked from actual unlocks @@ -2525,8 +2527,9 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) { case DEMOVERSION: // latest always supported case 0x0009: // older staff ghosts - case 0x000A: // older staff ghosts - case 0x000B: // older staff ghosts + case 0x000A: // 2.0, 2.1 + case 0x000B: // 2.2 indev (staff ghosts) + case 0x000C: // 2.2 break; // too old, cannot support. default: @@ -2676,8 +2679,9 @@ void G_LoadDemoInfo(menudemo_t *pdemo, boolean allownonmultiplayer) { case DEMOVERSION: // latest always supported case 0x0009: // older staff ghosts - case 0x000A: // older staff ghosts - case 0x000B: // older staff ghosts + case 0x000A: // 2.0, 2.1 + case 0x000B: // 2.2 indev (staff ghosts) + case 0x000C: // 2.2 if (P_SaveBufferRemaining(&info) < 64) { goto corrupt; @@ -3105,8 +3109,9 @@ void G_DoPlayDemoEx(const char *defdemoname, lumpnum_t deflumpnum) { case DEMOVERSION: // latest always supported case 0x0009: // older staff ghosts - case 0x000A: // older staff ghosts - case 0x000B: // older staff ghosts + case 0x000A: // 2.0, 2.1 + case 0x000B: // 2.2 indev (staff ghosts) + case 0x000C: // 2.2 break; // too old, cannot support. default: @@ -3275,6 +3280,10 @@ void G_DoPlayDemoEx(const char *defdemoname, lumpnum_t deflumpnum) grandprixinfo.gamespeed = READUINT8(demobuf.p); grandprixinfo.masterbots = READUINT8(demobuf.p) != 0; grandprixinfo.eventmode = static_cast<gpEvent_e>(READUINT8(demobuf.p)); + if (demo.version >= 0x000D) + { + grandprixinfo.specialDamage = READUINT32(demobuf.p); + } } // Load unlocks into netUnlocked @@ -3565,8 +3574,9 @@ void G_AddGhost(savebuffer_t *buffer, const char *defdemoname) { case DEMOVERSION: // latest always supported case 0x0009: // older staff ghosts - case 0x000A: // older staff ghosts - case 0x000B: // older staff ghosts + case 0x000A: // 2.0, 2.1 + case 0x000B: // 2.2 indev (staff ghosts) + case 0x000C: // 2.2 break; // too old, cannot support. default: @@ -3653,7 +3663,11 @@ void G_AddGhost(savebuffer_t *buffer, const char *defdemoname) } if ((flags & DF_GRANDPRIX)) + { p += 3; + if (ghostversion >= 0x000D) + p++; + } // Skip unlockables { @@ -3823,8 +3837,9 @@ staffbrief_t *G_GetStaffGhostBrief(UINT8 *buffer) { case DEMOVERSION: // latest always supported case 0x0009: // older staff ghosts - case 0x000A: // older staff ghosts - case 0x000B: // older staff ghosts + case 0x000A: // 2.0, 2.1 + case 0x000B: // 2.2 indev (staff ghosts) + case 0x000C: // 2.2 break; // too old, cannot support. @@ -3878,7 +3893,11 @@ staffbrief_t *G_GetStaffGhostBrief(UINT8 *buffer) } if ((flags & DF_GRANDPRIX)) + { p += 3; + if (ghostversion >= 0x000D) + p++; + } // Skip unlockables { diff --git a/src/g_game.c b/src/g_game.c index e2cd5ad7095dbd4794e416a15e61cfcdffe212d3..08ea0274ec61ad64dc4176309323829496b463dc 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -358,7 +358,7 @@ void G_ClearRecords(void) // TODO: Technically, these should only remove time attack records here. // But I'm out of juice for dev (+ literally, just finished some OJ). - // The stats need to be cleared in M_ClearStats, and I guess there's + // The stats need to be cleared in M_ClearStats, and I guess there's // no perfect place to wipe mapvisited because it's not actually part of // basegame progression... so here's fine for launch. ~toast 100424 unloaded_mapheader_t *unloadedmap, *nextunloadedmap = NULL; @@ -652,7 +652,7 @@ static void G_UpdateRecordReplays(void) } } -// for consistency among messages: this modifies the game and removes savemoddata. +// for consistency among messages: this marks the game as modified. void G_SetGameModified(boolean silent, boolean major) { if ((majormods && modifiedgame) || !mainwads || (refreshdirmenu & REFRESHDIR_GAMEDATA)) // new gamedata amnesty? @@ -666,9 +666,6 @@ void G_SetGameModified(boolean silent, boolean major) //savemoddata = false; -- there is literally no reason to do this anymore. majormods = true; - if (!silent) - CONS_Alert(CONS_NOTICE, M_GetText("Game must be restarted to play Record Attack.\n")); - // If in record attack recording, cancel it. if (modeattacking) M_EndModeAttackRun(); diff --git a/src/g_gamedata.cpp b/src/g_gamedata.cpp index a2f60b92dfebb791171178477e56603ac092c409..a8890ed2179e55a5f7a3de6f1e9c367803f68339 100644 --- a/src/g_gamedata.cpp +++ b/src/g_gamedata.cpp @@ -287,15 +287,28 @@ void srb2::save_ng_gamedata() std::string gamedataname_s {gamedatafilename}; fs::path savepath {fmt::format("{}/{}", srb2home, gamedataname_s)}; - int random_number = rand(); - fs::path tmpsavepath {fmt::format("{}/{}_{}.tmp", srb2home, gamedataname_s, random_number)}; + fs::path baksavepath {fmt::format("{}/{}.bak", srb2home, gamedataname_s)}; json ngdata_json = ng; + + if (fs::exists(savepath)) + { + try + { + fs::rename(savepath, baksavepath); + } + catch (const fs::filesystem_error& ex) + { + CONS_Alert(CONS_ERROR, "Failed to record backup save. Not attempting to save. %s\n", ex.what()); + return; + } + } + try { - std::string tmpsavepathstring = tmpsavepath.string(); - srb2::io::FileStream file {tmpsavepathstring, srb2::io::FileStreamMode::kWrite}; + std::string savepathstring = savepath.string(); + srb2::io::FileStream file {savepathstring, srb2::io::FileStreamMode::kWrite}; // The header is necessary to validate during loading. srb2::io::write(static_cast<uint32_t>(GD_VERSION_MAJOR), file); // major @@ -308,21 +321,11 @@ void srb2::save_ng_gamedata() } catch (const std::exception& ex) { - CONS_Alert(CONS_ERROR, "NG Gamedata save failed: %s\n", ex.what()); + CONS_Alert(CONS_ERROR, "NG Gamedata save failed. Check directory for a ringdata.dat.bak. %s\n", ex.what()); } catch (...) { - CONS_Alert(CONS_ERROR, "NG Gamedata save failed\n"); - } - - try - { - // Now that the save is written successfully, move it over the old save - fs::rename(tmpsavepath, savepath); - } - catch (const fs::filesystem_error& ex) - { - CONS_Alert(CONS_ERROR, "NG Gamedata save succeeded but did not replace old save successfully: %s\n", ex.what()); + CONS_Alert(CONS_ERROR, "NG Gamedata save failed. Check directory for a ringdata.dat.bak.\n"); } } diff --git a/src/k_botitem.cpp b/src/k_botitem.cpp index da6aa762d945f046a70e893f9e42d096b2f3ce4b..630cfeb17bcbd030749cf4cf4c9d161dc40b3bd4 100644 --- a/src/k_botitem.cpp +++ b/src/k_botitem.cpp @@ -31,6 +31,7 @@ #include "m_random.h" #include "r_things.h" // numskins #include "k_roulette.h" +#include "m_easing.h" /*-------------------------------------------------- static inline boolean K_ItemButtonWasDown(const player_t *player) @@ -1193,7 +1194,10 @@ static void K_BotItemLightning(const player_t *player, ticcmd_t *cmd) { ZoneScoped; - if (K_BotUseItemNearPlayer(player, cmd, 192*player->mo->scale) == false) + fixed_t radius = 192 * player->mo->scale; + radius = Easing_Linear(FRACUNIT * player->botvars.difficulty / MAXBOTDIFFICULTY, 2*radius, radius); + + if (K_BotUseItemNearPlayer(player, cmd, radius) == false) { if (player->botvars.itemconfirm > 10*TICRATE) { @@ -1232,7 +1236,8 @@ static void K_BotItemBubble(const player_t *player, ticcmd_t *cmd) if (player->bubblecool <= 0) { - const fixed_t radius = 192 * player->mo->scale; + fixed_t radius = 192 * player->mo->scale; + radius = Easing_Linear(FRACUNIT * player->botvars.difficulty / MAXBOTDIFFICULTY, 2*radius, radius); for (i = 0; i < MAXPLAYERS; i++) { diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 9b28719c9b34b76bac80b041f14da5a8c0bc6c78..2fbfcd0c3afc281ef6c754345d6f05738f373787 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -2148,6 +2148,12 @@ void K_DrawKartPositionNumXY( boolean exit, boolean lastLap, boolean losing ) { + if (cv_reducevfx.value != 0) + { + // Reduce the flashing rate + counter /= 4; + } + counter /= 3; // Alternate colors every three frames UINT8 *color = NULL; @@ -5025,14 +5031,31 @@ static void K_drawKartStartBulbs(void) bulbtic -= 14; + // Reduce VFX disables the bulb animation while still presenting this indicator + if (bulbtic > length) { bulbtic -= length; - patchnum = chillloop_animation[bulbtic % 2]; + + if (cv_reducevfx.value != 0) + { + patchnum = chillloop_animation[0]; + } + else + { + patchnum = chillloop_animation[bulbtic % 2]; + } } else { - patchnum = loop_animation[bulbtic % 4]; + if (cv_reducevfx.value != 0) + { + patchnum = loop_animation[0]; + } + else + { + patchnum = loop_animation[bulbtic % 4]; + } } } } diff --git a/src/k_kart.c b/src/k_kart.c index dbe69ea42aff3f4541231d01c2010a188551c60f..15bb2f35ef6fae97b19f7f719eac9f0cef9b6e34 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -12617,7 +12617,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) // if a player picks up an item during the instawhip input safety window—the one that triggers // after you burn to 0 rings—they can continue to hold the input, then charge a usable whip // without stopping the roulette and acquiring an item, which cancels it. - // + // // No ghosts use this technique, but your least favorite tournament player might. if (player->itemRoulette.active) { @@ -12857,9 +12857,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) // Banana x3 thrown { + player->itemamount--; K_ThrowKartItem(player, false, MT_BANANA, -1, 0, 0); K_PlayAttackTaunt(player->mo); - player->itemamount--; K_UpdateHnextList(player, false); player->botvars.itemconfirm = 0; } @@ -12923,9 +12923,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) // Orbinaut x3 thrown { + player->itemamount--; K_ThrowKartItem(player, true, MT_ORBINAUT, 1, 0, 0); K_PlayAttackTaunt(player->mo); - player->itemamount--; K_UpdateHnextList(player, false); player->botvars.itemconfirm = 0; } @@ -12966,9 +12966,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->itemflags & IF_ITEMOUT)) // Jawz thrown { + player->itemamount--; K_ThrowKartItem(player, true, MT_JAWZ, 1, 0, 0); K_PlayAttackTaunt(player->mo); - player->itemamount--; K_UpdateHnextList(player, false); player->botvars.itemconfirm = 0; } @@ -12994,9 +12994,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) { + player->itemamount--; K_ThrowKartItem(player, false, MT_SSMINE, 1, 1, 0); K_PlayAttackTaunt(player->mo); - player->itemamount--; player->itemflags &= ~IF_ITEMOUT; K_UpdateHnextList(player, true); player->botvars.itemconfirm = 0; @@ -13032,9 +13032,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) { + player->itemamount--; K_ThrowKartItem(player, (player->throwdir > 0), MT_DROPTARGET, -1, 0, 0); K_PlayAttackTaunt(player->mo); - player->itemamount--; player->itemflags &= ~IF_ITEMOUT; K_UpdateHnextList(player, true); player->botvars.itemconfirm = 0; @@ -13276,9 +13276,10 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (player->bubbleblowup > bubbletime*2) { + player->itemamount--; K_ThrowKartItem(player, (player->throwdir > 0), MT_BUBBLESHIELDTRAP, -1, 0, 0); if (player->throwdir == -1) - { + { P_InstaThrust(player->mo, player->mo->angle, player->speed + (80 * mapobjectscale)); player->wavedashboost += TICRATE; player->wavedashpower = FRACUNIT; @@ -13288,7 +13289,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->bubbleblowup = 0; player->bubblecool = 0; player->itemflags &= ~IF_HOLDREADY; - player->itemamount--; player->botvars.itemconfirm = 0; } } @@ -13362,7 +13362,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->mo, player->mo->angle, FixedMul((50*player->mo->scale), K_GetKartGameSpeedScalar(gamespeed)) ); - + player->wavedashboost += TICRATE; player->wavedashpower = FRACUNIT; player->fakeBoost = TICRATE/3; @@ -13454,9 +13454,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->itemflags & IF_ITEMOUT)) // Sink thrown { + player->itemamount--; K_ThrowKartItem(player, false, MT_SINK, 1, 2, 0); K_PlayAttackTaunt(player->mo); - player->itemamount--; player->itemflags &= ~IF_ITEMOUT; K_UpdateHnextList(player, true); player->botvars.itemconfirm = 0; @@ -13465,11 +13465,11 @@ void K_MoveKartPlayer(player_t *player, boolean onground) case KITEM_GACHABOM: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { + player->itemamount--; K_SetItemOut(player); // need this to set itemscale K_ThrowKartItem(player, true, MT_GACHABOM, 0, 0, 0); K_UnsetItemOut(player); K_PlayAttackTaunt(player->mo); - player->itemamount--; player->roundconditions.gachabom_miser = ( (player->roundconditions.gachabom_miser == 0) ? 1 : 0xFF @@ -13596,7 +13596,13 @@ void K_MoveKartPlayer(player_t *player, boolean onground) // We'll never need to go above that. if (player->tricktime <= TRICKDELAY) + { + // 2.3 - Prevent accidental fastfalls during trickdelay + if (!G_CompatLevel(0x000C)) + player->pflags |= PF_NOFASTFALL; + player->tricktime++; + } // debug shit //CONS_Printf("%d\n", player->mo->momz / mapobjectscale); @@ -13628,14 +13634,20 @@ void K_MoveKartPlayer(player_t *player, boolean onground) // INT16 aimingcompare = abs(cmd->throwdir) - abs(cmd->turning); boolean cantrick = true; + UINT16 buttons = player->cmd.buttons; // 2.2 - Pre-steering trickpanels if (!G_CompatLevel(0x000A) && !K_PlayerUsesBotMovement(player)) { - if (!(player->cmd.buttons & BT_ACCELERATE)) + if (!(buttons & BT_ACCELERATE)) { cantrick = false; } + // 2.3 - also allow tricking with the Spindash button + else if (!G_CompatLevel(0x000C) && ((buttons & BT_SPINDASHMASK) == BT_SPINDASHMASK)) + { + player->pflags |= PF_NOFASTFALL; + } } // Uses cmd->turning over steering intentionally. @@ -13880,9 +13892,22 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } else { - if ((player->pflags & PF_TRICKDELAY) && !(player->cmd.buttons & BT_ACCELERATE) && (player->tricktime >= TRICKDELAY)) + if (G_CompatLevel(0x000C)) { - player->pflags &= ~PF_TRICKDELAY; + if ((player->pflags & PF_TRICKDELAY) && !(player->cmd.buttons & BT_ACCELERATE) && (player->tricktime >= TRICKDELAY)) + { + player->pflags &= ~PF_TRICKDELAY; + } + } + else + // 2.3 - Spindash to trick + { + // Ignore pre-existing Accel inputs if not pressing Spindash. Always ignore pre-existing Spindash inputs to prevent accidental tricking. + if ((player->pflags & PF_TRICKDELAY) && (!(player->cmd.buttons & BT_ACCELERATE) || (((player->cmd.buttons & BT_SPINDASHMASK) == BT_SPINDASHMASK) && (player->oldcmd.buttons & BT_SPINDASHMASK) != BT_SPINDASHMASK)) && (player->tricktime >= TRICKDELAY)) + { + player->pflags &= ~PF_TRICKDELAY; + player->pflags |= PF_NOFASTFALL; + } } } diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 624625dab50a5f93899c773aa90fcaa99d41b479..16d25583895e68a27518b2f499b4e07182fb32a7 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -88,6 +88,9 @@ static void M_AddFloatVar(consvar_t *cv, fixed_t step) const CV_PossibleValue_t *values = cv->PossibleValue; + if (values == NULL) //cvar is unbounded and will not work! return is here only as a failsafe to prevent crashes + return; + for (i = 0; values[i].strvalue; ++i) { if (cv->value == values[i].value) @@ -220,7 +223,7 @@ static void M_ChangeCvar(INT32 choice) "Turning on Auto Roulette", "\"Ring Racers\" is not designed with random items in mind. With Auto Roulette, you cannot select the item results you want or select an item early." "\n" - "You will be at a distinct \x85" "disadvantage. \x80\n" + "You will be at a distinct \x85" "disadvantage. \x80\n" "\n" "ARE YOU SURE?", M_ChangeCvarResponse, diff --git a/src/k_profiles.cpp b/src/k_profiles.cpp index 3491d70fc2256da62b53fd1e7edf8d52cea44518..0b9425fcb6b33959e1821aa13cbdd1b2e727dd86 100644 --- a/src/k_profiles.cpp +++ b/src/k_profiles.cpp @@ -319,12 +319,24 @@ void PR_SaveProfiles(void) std::vector<uint8_t> ubjson = json::to_ubjson(ng); std::string realpath = fmt::format("{}/{}", srb2home, PROFILESFILE); - int random_number = rand(); - std::string tmppath = fmt::format("{}_{}.tmp", realpath, random_number); + std::string bakpath = fmt::format("{}.bak", realpath); + + if (fs::exists(realpath)) + { + try + { + fs::rename(realpath, bakpath); + } + catch (const fs::filesystem_error& ex) + { + CONS_Alert(CONS_ERROR, "Failed to record profiles backup. Not attempting to save profiles. %s\n", ex.what()); + return; + } + } try { - io::FileStream file {tmppath, io::FileStreamMode::kWrite}; + io::FileStream file {realpath, io::FileStreamMode::kWrite}; io::write(static_cast<uint32_t>(0x52494E47), file, io::Endian::kBE); // "RING" io::write(static_cast<uint32_t>(0x5052464C), file, io::Endian::kBE); // "PRFL" @@ -334,16 +346,14 @@ void PR_SaveProfiles(void) io::write(static_cast<uint8_t>(0), file); // reserved4 io::write_exact(file, tcb::as_bytes(tcb::make_span(ubjson))); file.close(); - - fs::rename(tmppath, realpath); } catch (const std::exception& ex) { - I_Error("Couldn't save profiles. Are you out of Disk space / playing in a protected folder?\n\nException: %s", ex.what()); + I_Error("Couldn't save profiles. Are you out of Disk space / playing in a protected folder? Check directory for a ringprofiles.prf.bak if the profiles file is corrupt.\n\nException: %s", ex.what()); } catch (...) { - I_Error("Couldn't save profiles. Are you out of Disk space / playing in a protected folder?"); + I_Error("Couldn't save profiles. Are you out of Disk space / playing in a protected folder? Check directory for a ringprofiles.prf.bak if the profiles file is corrupt."); } } diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index dd95294a23dd3eeba91d54a75342b3264be86378..ca2c09a0a7d4ac1f73e09d1f780d81af977a938f 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -138,7 +138,7 @@ INT16 K_PowerLevelPlacementScore(player_t *player) INT16 K_CalculatePowerLevelAvg(void) { - fixed_t avg = 0; + INT32 avg = 0; UINT8 div = 0; SINT8 t = PWRLV_DISABLED; UINT8 i; @@ -166,7 +166,7 @@ INT16 K_CalculatePowerLevelAvg(void) || clientpowerlevels[i][t] == 0) // splitscreen player continue; - avg += (clientpowerlevels[i][t] << FRACBITS); + avg += clientpowerlevels[i][t]; div++; } @@ -178,7 +178,7 @@ INT16 K_CalculatePowerLevelAvg(void) avg /= div; - return (INT16)(avg >> FRACBITS); + return (INT16)avg; } void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit) diff --git a/src/menus/extras-addons.c b/src/menus/extras-addons.c index 09a8d962493283313f4954b90c253afddacce886..22c1c204a8582b0ba12f7ed11a3ed1797dcca1f3 100644 --- a/src/menus/extras-addons.c +++ b/src/menus/extras-addons.c @@ -157,7 +157,7 @@ void M_AddonsRefresh(void) else if (majormods && !prevmajormods) { S_StartSound(NULL, sfx_s221); - message = va("%c%s\x80\nYou've loaded a gameplay-modifying addon.\n\nRecord Attack has been disabled, but you\ncan still play alone in local Multiplayer.\n\nIf you wish to play Record Attack mode, restart the game to disable loaded addons.\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); + message = va("%c%s\x80\nYou've loaded a gameplay-modifying addon.\nCheck the console log for more info.\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); prevmajormods = majormods; } diff --git a/src/objects/instawhip.c b/src/objects/instawhip.c index c5b6683fae26c6bceda56af2ba7adcb7b328be5b..bc7657f11c3e0c58813c192b5abbf35b43465a34 100644 --- a/src/objects/instawhip.c +++ b/src/objects/instawhip.c @@ -59,11 +59,12 @@ void Obj_InstaWhipThink (mobj_t *whip) void Obj_SpawnInstaWhipRecharge(player_t *player, angle_t angleOffset) { - mobj_t *x = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_INSTAWHIP_RECHARGE); + mobj_t *x = P_SpawnMobjFromMobj(player->mo, 0, 0, player->mo->height / 2, MT_INSTAWHIP_RECHARGE); // This was previously used to delay the visual, back when this was VFX for a cooldown // instead of VFX for a charge. We want to instantly bail out of that state now. x->tics = 1; + x->eflags &= ~MFE_VERTICALFLIP; // Fix the visual being misaligned. x->renderflags |= RF_SLOPESPLAT | RF_NOSPLATBILLBOARD; P_SetTarget(&recharge_target(x), player->mo); @@ -81,7 +82,8 @@ void Obj_InstaWhipRechargeThink(mobj_t *x) } P_MoveOrigin(x, target->x, target->y, target->z + (target->height / 2)); - P_InstaScale(x, 2 * target->scale); + if (x->scale != target->scale * 2) + P_InstaScale(x, target->scale * 2); x->angle = target->angle + recharge_offset(x); // Flickers every other frame @@ -91,6 +93,10 @@ void Obj_InstaWhipRechargeThink(mobj_t *x) void Obj_SpawnInstaWhipReject(player_t *player) { mobj_t *x = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_INSTAWHIP_REJECT); + x->eflags &= ~MFE_VERTICALFLIP; + // Fixes an issue with gravflip misplacing the object for the first tic. + if (player->mo->eflags & MFE_VERTICALFLIP) + P_SetOrigin(x, player->mo->x, player->mo->y, player->mo->z); P_SetTarget(&recharge_target(x), player->mo); } diff --git a/src/p_user.c b/src/p_user.c index c23f9e1f8486ebb0a904c6bf12f256d3825785c5..a0579ba76e26356274a68c691be50aa419230a46 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2220,7 +2220,7 @@ static INT16 P_FindClosestTurningForAngle(player_t *player, INT32 targetAngle, I // Slightly frumpy binary search for the ideal turning input. // We do this instead of reversing K_GetKartTurnValue so that future handling changes are automatically accounted for. - + while (attempts++ < 20) // Practical calls of this function search maximum 10 times, this is solely for safety. { // These need to be treated as signed, or situations where boundaries straddle 0 are a mess. @@ -2341,7 +2341,7 @@ static void P_UpdatePlayerAngle(player_t *player) // Corrections via fake turn go through easing. // That means undoing them takes the same amount of time as doing them. // This can lead to oscillating death spiral states on a multi-tic correction, as we swing past the target angle. - // So before we go into death-spirals, if our predicton is _almost_ right... + // So before we go into death-spirals, if our predicton is _almost_ right... angle_t leniency_base; if (G_CompatLevel(0x000A)) { @@ -2450,7 +2450,7 @@ void P_MovePlayer(player_t *player) ////////////////////// P_UpdatePlayerAngle(player); - + ticruned++; if (!(cmd->flags & TICCMD_RECEIVED)) ticmiss++; @@ -4255,7 +4255,7 @@ void P_PlayerThink(player_t *player) } else if (cmd->buttons & BT_ACCELERATE) { - if (!player->exiting && !(player->oldcmd.buttons & BT_ACCELERATE)) + if (!player->exiting && !(player->oldcmd.buttons & BT_ACCELERATE) && ((cmd->buttons & BT_SPINDASHMASK) != BT_SPINDASHMASK) && player->trickpanel != TRICKSTATE_READY) { player->kickstartaccel = 0; } @@ -4550,7 +4550,7 @@ void P_PlayerThink(player_t *player) { player->stairjank--; } - + // Random skin / "ironman" { UINT32 skinflags = (demo.playback) diff --git a/src/sdl/i_system.cpp b/src/sdl/i_system.cpp index 49b5f06041e037cb3b44c5dd51207f517f4dd54c..b32b5096948b5b3d25883578e1c6d3e909ba19f8 100644 --- a/src/sdl/i_system.cpp +++ b/src/sdl/i_system.cpp @@ -428,7 +428,7 @@ static void I_ReportSignal(int num, int coredumped) } #ifndef NEWSIGNALHANDLER -FUNCNORETURN static ATTRNORETURN void signal_handler(INT32 num) +static ATTRNORETURN void signal_handler(INT32 num) { g_in_exiting_signal_handler = true; @@ -447,10 +447,8 @@ FUNCNORETURN static ATTRNORETURN void signal_handler(INT32 num) write_backtrace(num); #endif I_ReportSignal(num, 0); - I_ShutdownSystem(); signal(num, SIG_DFL); //default signal action raise(num); - I_Quit(); } #endif @@ -1738,11 +1736,7 @@ void I_Error(const char *error, ...) if (errorcount == 7) SDL_Quit(); if (errorcount == 8) - { - M_SaveConfig(NULL); - G_DirtyGameData(); // done first in case an error is in G_SaveGameData - G_SaveGameData(); - } + G_DirtyGameData(); if (errorcount > 20) { va_start(argptr, error); @@ -1776,9 +1770,10 @@ void I_Error(const char *error, ...) I_OutputMsg("\nI_Error(): %s\n", buffer); // --- - M_SaveConfig(NULL); // save game config, cvars.. - G_DirtyGameData(); // done first in case an error is in G_SaveGameData - G_SaveGameData(); // Tails 12-08-2002 + // FUCK OFF, stop allocating memory to write entire gamedata & configs + // when the program needs to shut down ASAP and we already save + // these all the time! Just set the dirty bit and GET OUT! + G_DirtyGameData(); // Shutdown. Here might be other errors.