diff --git a/src/acs/environment.cpp b/src/acs/environment.cpp index 6df282498..1be630d32 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(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 249797f66..79aa4a4ae 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/d_netcmd.c b/src/d_netcmd.c index d887aaf73..af60224f9 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_game.c b/src/g_game.c index e2cd5ad70..08ea0274e 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/k_kart.c b/src/k_kart.c index 19989c731..f7cff5b06 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); @@ -13624,15 +13630,21 @@ void K_MoveKartPlayer(player_t *player, boolean onground) const angle_t angledelta = FixedAngle(36*FRACUNIT); angle_t baseangle = player->mo->angle + angledelta/2; + UINT16 buttons = player->cmd.buttons; INT16 aimingcompare = abs(cmd->throwdir) - abs(cmd->turning); // 2.2 - Pre-steering trickpanels if (!G_CompatLevel(0x000A) && !K_PlayerUsesBotMovement(player)) { - if (!(player->cmd.buttons & BT_ACCELERATE)) + if (!(buttons & BT_ACCELERATE)) { aimingcompare = 0; } + // 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. @@ -13877,9 +13889,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_pwrlv.c b/src/k_pwrlv.c index dd95294a2..ca2c09a0a 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 09a8d9624..22c1c204a 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 c5b6683fa..bc7657f11 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 c23f9e1f8..a0579ba76 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)