From e9205e19d67e64a05e3400b5a2a42fc2a9238e96 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 30 Mar 2024 15:56:44 +0000 Subject: [PATCH 1/8] ACS_RunPlayerEnterScript: If betweenmaps, defer until P_PostLoadLevel Guarantees that the ENTER script actually consistently runs, since previously it was happening before ACS scope invalidation --- src/g_game.c | 13 ++----------- src/p_setup.cpp | 10 ++++++++++ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 03e286d80..7f421ddf8 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2519,9 +2519,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) } } - if (p->spectator == false) + if (p->spectator == false && !betweenmaps) { - if (betweenmaps || enteredGame == true) + if (enteredGame == true) { ACS_RunPlayerEnterScript(p); } @@ -2530,15 +2530,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) ACS_RunPlayerRespawnScript(p); } } - - if (betweenmaps) - return; - - if (leveltime < starttime) - return; - - if (exiting) - return; } // diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 180e6fc39..78927211b 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -8892,6 +8892,16 @@ void P_PostLoadLevel(void) ACS_RunLevelStartScripts(); LUA_HookInt(gamemap, HOOK(MapLoad)); + UINT8 i; + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + if (players[i].spectator) + continue; + ACS_RunPlayerEnterScript(&players[i]); + } + P_MapEnd(); // We're now done loading the level. From a752ed095f08cfad86707faab09dd5b3411ced84 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 30 Mar 2024 16:00:08 +0000 Subject: [PATCH 2/8] K_FadeOutSpecialMusic: Iterate over all display players in K_TickSpecialStage, instead of clobbering the volume in K_KartUpdatePosition Prevents weird audio clipping near the end of the course --- src/k_kart.c | 6 ------ src/k_specialstage.c | 13 +++++++++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 374170290..e6b2642fd 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -11143,12 +11143,6 @@ void K_KartUpdatePosition(player_t *player) player->topinfirst = 0; } - // Special stages: fade out music near the finish line - if (P_IsPartyPlayer(player)) - { - K_FadeOutSpecialMusic(player->distancetofinish); - } - player->position = position; } diff --git a/src/k_specialstage.c b/src/k_specialstage.c index 910147a05..f40a65b86 100644 --- a/src/k_specialstage.c +++ b/src/k_specialstage.c @@ -120,6 +120,19 @@ void K_TickSpecialStage(void) return; } + // Special stages: fade out music near the finish line + UINT8 i; + UINT32 lowestdistance = UINT32_MAX; + for (i = 0; i <= r_splitscreen; i++) + { + if (!playeringame[displayplayers[i]] || players[displayplayers[i]].spectator) + continue; + if (players[displayplayers[i]].distancetofinish >= lowestdistance) + continue; + lowestdistance = players[displayplayers[i]].distancetofinish; + } + K_FadeOutSpecialMusic(lowestdistance); + if (P_MobjWasRemoved(specialstageinfo.ufo)) { P_SetTarget(&specialstageinfo.ufo, NULL); From 56534a747e38668ea76b63e7550cc7b32c4004f5 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 30 Mar 2024 16:01:03 +0000 Subject: [PATCH 3/8] Do not play overtake voices/passing sounds in K_Cooperative You're not competing! --- src/k_kart.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index e6b2642fd..a22e142c4 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -2749,13 +2749,6 @@ void K_PlayOvertakeSound(mobj_t *source) boolean tasteful = (!source->player || !source->player->karthud[khud_voices]); - if (!(gametyperules & GTR_CIRCUIT)) // Only in race - return; - - // 4 seconds from before race begins, 10 seconds afterwards - if (leveltime < starttime+(10*TICRATE)) - return; - if ( cv_kartvoices.value && (tasteful || cv_kartvoices.value == 2) @@ -11100,7 +11093,7 @@ void K_KartUpdatePosition(player_t *player) if (position != oldposition) // Changed places? { - if (player->positiondelay <= 0 && position < oldposition && P_IsDisplayPlayer(player) == true) + if (!K_Cooperative() && player->positiondelay <= 0 && position < oldposition && P_IsDisplayPlayer(player) == true) { // Play sound when getting closer to 1st. UINT32 soundpos = (max(0, position - 1) * MAXPLAYERS)/realplayers; // always 1-15 despite there being 16 players at max... @@ -12100,14 +12093,23 @@ void K_MoveKartPlayer(player_t *player, boolean onground) boolean HOLDING_ITEM = (player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT)); boolean NO_HYUDORO = (player->stealingtimer == 0); - if (!player->exiting) + // Play overtake sounds, but only if + // - your place changed + // - not exiting + // - in relevant gametype + // - more than 10 seconds after initial scramble + if (player->oldposition != player->position + && !player->exiting + && (gametyperules & GTR_CIRCUIT) + && !K_Cooperative() + && leveltime >= starttime+(10*TICRATE)) { if (player->oldposition < player->position) // But first, if you lost a place, { player->oldposition = player->position; // then the other player taunts. K_RegularVoiceTimers(player); // and you can't for a bit } - else if (player->oldposition > player->position) // Otherwise, + else // Otherwise, { K_PlayOvertakeSound(player->mo); // Say "YOU'RE TOO SLOW!" player->oldposition = player->position; // Restore the old position, From a7079d6ea2bb1a057d6c73bfa821e86a832cbb85 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 30 Mar 2024 16:02:35 +0000 Subject: [PATCH 4/8] Add FINISH script calling type to ACS Called at the end of a successful P_DoPlayerExit --- src/acs/interface.cpp | 23 +++++++++++++++++++++++ src/acs/interface.h | 16 ++++++++++++++++ src/acs/thread.hpp | 1 + src/p_user.c | 3 +++ 4 files changed, 43 insertions(+) diff --git a/src/acs/interface.cpp b/src/acs/interface.cpp index c010bc6c6..e5d6e196a 100644 --- a/src/acs/interface.cpp +++ b/src/acs/interface.cpp @@ -244,6 +244,29 @@ void ACS_RunPlayerEnterScript(player_t *player) map->scriptStartTypeForced(ACS_ST_ENTER, scriptInfo); } +/*-------------------------------------------------- + void ACS_RunPlayerFinishScript(player_t *player) + + See header file for description. +--------------------------------------------------*/ +void ACS_RunPlayerFinishScript(player_t *player) +{ + Environment *env = &ACSEnv; + + ACSVM::GlobalScope *const global = env->getGlobalScope(0); + ACSVM::HubScope *const hub = global->getHubScope(0); + ACSVM::MapScope *const map = hub->getMapScope(0); + + ACSVM::MapScope::ScriptStartInfo scriptInfo; + ThreadInfo info; + + P_SetTarget(&info.mo, player->mo); + + scriptInfo.info = &info; + + map->scriptStartTypeForced(ACS_ST_FINISH, scriptInfo); +} + /*-------------------------------------------------- void ACS_RunLapScript(mobj_t *mo, line_t *line) diff --git a/src/acs/interface.h b/src/acs/interface.h index 0b9881950..cbb8ab809 100644 --- a/src/acs/interface.h +++ b/src/acs/interface.h @@ -139,6 +139,22 @@ void ACS_RunPlayerDeathScript(player_t *player); void ACS_RunPlayerEnterScript(player_t *player); +/*-------------------------------------------------- + void ACS_RunPlayerFinishScript(player_t *player); + + Runs the map's special script for a player + finishing (P_DoPlayerExit). + + Input Arguments:- + player: The player to run the script for. + + Return:- + None +--------------------------------------------------*/ + +void ACS_RunPlayerFinishScript(player_t *player); + + /*-------------------------------------------------- void ACS_RunLapScript(mobj_t *mo, line_t *line); diff --git a/src/acs/thread.hpp b/src/acs/thread.hpp index faa345c15..f8338239c 100644 --- a/src/acs/thread.hpp +++ b/src/acs/thread.hpp @@ -43,6 +43,7 @@ enum acs_scriptType_e 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. + ACS_ST_FINISH = 11, // FINISH: Runs when a player finishes }; // diff --git a/src/p_user.c b/src/p_user.c index 1c55f0236..d251100d3 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -71,6 +71,7 @@ #include "k_credits.h" #include "k_hud.h" // K_AddMessage #include "m_easing.h" +#include "acs/interface.h" #ifdef HWRENDER #include "hardware/hw_light.h" @@ -1342,6 +1343,8 @@ void P_DoPlayerExit(player_t *player, pflags_t flags) demo.savebutton = leveltime; } } + + ACS_RunPlayerFinishScript(player); } // From 385988eeedab38b46a59ebf8b194ac1cf42b3b8d Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 30 Mar 2024 16:03:37 +0000 Subject: [PATCH 5/8] Add PlayerLosing() to ACS Exposes K_IsPlayerLosing. Necessary for Sealed Star script adjustment --- src/acs/call-funcs.cpp | 25 +++++++++++++++++++++++++ src/acs/call-funcs.hpp | 1 + src/acs/environment.cpp | 1 + 3 files changed, 27 insertions(+) diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index 703029339..4b376627c 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -39,6 +39,7 @@ #include "../r_textures.h" #include "../m_cond.h" #include "../r_skins.h" +#include "../k_kart.h" #include "../k_battle.h" #include "../k_grandprix.h" #include "../k_podium.h" @@ -1646,6 +1647,30 @@ bool CallFunc_PlayerBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::W return false; } +/*-------------------------------------------------- + bool CallFunc_PlayerLosing(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns the activating player's losing status. +--------------------------------------------------*/ +bool CallFunc_PlayerLosing(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast(thread)->info; + + (void)argV; + (void)argC; + + if ((info != NULL) + && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) + && (info->mo->player != NULL)) + { + thread->dataStk.push(K_IsPlayerLosing(info->mo->player)); + return false; + } + + thread->dataStk.push(false); + return false; +} + /*-------------------------------------------------- bool CallFunc_GetObjectDye(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 c346e5995..086206be5 100644 --- a/src/acs/call-funcs.hpp +++ b/src/acs/call-funcs.hpp @@ -76,6 +76,7 @@ bool CallFunc_HaveUnlockableTrigger(ACSVM::Thread *thread, const ACSVM::Word *ar bool CallFunc_HaveUnlockable(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PlayerSkin(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PlayerBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_PlayerLosing(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_GetObjectDye(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PlayerEmeralds(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PlayerLap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); diff --git a/src/acs/environment.cpp b/src/acs/environment.cpp index 9929fbcc8..5c974dafc 100644 --- a/src/acs/environment.cpp +++ b/src/acs/environment.cpp @@ -172,6 +172,7 @@ Environment::Environment() addFuncDataACS0( 316, addCallFunc(CallFunc_PositionStart)); addFuncDataACS0( 317, addCallFunc(CallFunc_FreePlay)); addFuncDataACS0( 318, addCallFunc(CallFunc_CheckTutorialChallenge)); + addFuncDataACS0( 319, addCallFunc(CallFunc_PlayerLosing)); addFuncDataACS0( 500, addCallFunc(CallFunc_CameraWait)); addFuncDataACS0( 501, addCallFunc(CallFunc_PodiumPosition)); From c3112ea0c84754eaeb93ac0c0e31b39c2a3985ec Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 30 Mar 2024 16:15:35 +0000 Subject: [PATCH 6/8] K_PlayerIsEmptyHandedInSpecial Replaces the simple condition in K_HandleLapIncrement for successful player exit Essentially means you can get EMPTY HANDED? for crossing the finish line before the player with the emerald does --- src/k_specialstage.c | 47 ++++++++++++++++++++++++++++++++++++++++++++ src/k_specialstage.h | 15 ++++++++++++++ src/p_spec.c | 2 +- 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/k_specialstage.c b/src/k_specialstage.c index f40a65b86..9891a5ddd 100644 --- a/src/k_specialstage.c +++ b/src/k_specialstage.c @@ -161,6 +161,53 @@ mobj_t *K_GetPossibleSpecialTarget(void) return specialstageinfo.ufo; } +/*-------------------------------------------------- + boolean K_PlayerIsEmptyHandedInSpecial(void) + + See header file for description. +--------------------------------------------------*/ +boolean K_PlayerIsEmptyHandedInSpecial(player_t *player) +{ + if (specialstageinfo.valid == false) + return false; // Not Sealed Star + + if (!(specialstageinfo.ufo == NULL || P_MobjWasRemoved(specialstageinfo.ufo))) + return true; // UFO exists + + thinker_t *think; + mobj_t *thing; + player_t *orbitplayer = NULL; + for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next) + { + if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) + continue; + + thing = (mobj_t *)think; + if (thing->type != MT_EMERALD) + continue; + + // emerald_award(thing) + if (!thing->tracer || P_MobjWasRemoved(thing->tracer)) + continue; + if (thing->tracer->type != MT_PLAYER || !thing->tracer->player) + continue; + + orbitplayer = thing->tracer->player; + + if (orbitplayer->exiting && !(orbitplayer->pflags & PF_NOCONTEST)) + { + // Another player has successfully taken the emerald to the end + return false; + } + + // The emerald is being carried, but not by you + return (orbitplayer != player); + } + + // EMERALD DELETED!? + return true; +} + /*-------------------------------------------------- void K_FadeOutSpecialMusic(UINT32 distance) diff --git a/src/k_specialstage.h b/src/k_specialstage.h index cc26ca352..f0e194479 100644 --- a/src/k_specialstage.h +++ b/src/k_specialstage.h @@ -68,6 +68,21 @@ void K_TickSpecialStage(void); mobj_t *K_GetPossibleSpecialTarget(void); +/*-------------------------------------------------- + boolean K_PlayerIsEmptyHandedInSpecial(player_t *player) + + Gets whether the player has failed a Sealed + Star via finishing without an Emerald + + Input Arguments:- + player - Player to check for + + Return:- + Should player fail or not +--------------------------------------------------*/ + +boolean K_PlayerIsEmptyHandedInSpecial(player_t *player); + /*-------------------------------------------------- void K_FadeOutSpecialMusic(UINT32 distance) diff --git a/src/p_spec.c b/src/p_spec.c index 15865499e..7b49461a2 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1997,7 +1997,7 @@ static void K_HandleLapIncrement(player_t *player) if (specialstageinfo.valid == true) { // Don't permit a win just by sneaking ahead of the UFO/emerald. - if (!(specialstageinfo.ufo == NULL || P_MobjWasRemoved(specialstageinfo.ufo))) + if (K_PlayerIsEmptyHandedInSpecial(player)) { applyflags |= PF_NOCONTEST; From bd82ca8ccf9c80bc33815044e1d2386f5584098c Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 30 Mar 2024 16:38:53 +0000 Subject: [PATCH 7/8] Add PlayerExiting() ACS callfunc --- src/acs/call-funcs.cpp | 24 ++++++++++++++++++++++++ src/acs/call-funcs.hpp | 1 + src/acs/environment.cpp | 1 + 3 files changed, 26 insertions(+) diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index 4b376627c..17e6474f7 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -1671,6 +1671,30 @@ bool CallFunc_PlayerLosing(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM return false; } +/*-------------------------------------------------- + bool CallFunc_PlayerExiting(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns the activating player's exiting status. +--------------------------------------------------*/ +bool CallFunc_PlayerExiting(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast(thread)->info; + + (void)argV; + (void)argC; + + if ((info != NULL) + && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) + && (info->mo->player != NULL)) + { + thread->dataStk.push((info->mo->player->exiting != 0)); + return false; + } + + thread->dataStk.push(false); + return false; +} + /*-------------------------------------------------- bool CallFunc_GetObjectDye(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 086206be5..8ad5df737 100644 --- a/src/acs/call-funcs.hpp +++ b/src/acs/call-funcs.hpp @@ -77,6 +77,7 @@ bool CallFunc_HaveUnlockable(ACSVM::Thread *thread, const ACSVM::Word *argV, ACS bool CallFunc_PlayerSkin(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PlayerBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PlayerLosing(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_PlayerExiting(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_GetObjectDye(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PlayerEmeralds(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PlayerLap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); diff --git a/src/acs/environment.cpp b/src/acs/environment.cpp index 5c974dafc..5c6a4ffd8 100644 --- a/src/acs/environment.cpp +++ b/src/acs/environment.cpp @@ -173,6 +173,7 @@ Environment::Environment() addFuncDataACS0( 317, addCallFunc(CallFunc_FreePlay)); addFuncDataACS0( 318, addCallFunc(CallFunc_CheckTutorialChallenge)); addFuncDataACS0( 319, addCallFunc(CallFunc_PlayerLosing)); + addFuncDataACS0( 320, addCallFunc(CallFunc_PlayerExiting)); addFuncDataACS0( 500, addCallFunc(CallFunc_CameraWait)); addFuncDataACS0( 501, addCallFunc(CallFunc_PodiumPosition)); From 04a33107892807eed0508338873800026e66358e Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sat, 30 Mar 2024 17:53:29 -0700 Subject: [PATCH 8/8] Ensure full ownership transfer when Special emeralds switch target --- src/objects/emerald.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/objects/emerald.c b/src/objects/emerald.c index bb482965d..44fdff118 100644 --- a/src/objects/emerald.c +++ b/src/objects/emerald.c @@ -149,6 +149,9 @@ static void Obj_EmeraldOrbitPlayer(mobj_t *emerald) if (!(bestplayer->pflags & PF_NOCONTEST)) { P_MoveOrigin(emerald, bestplayer->mo->x, bestplayer->mo->y, bestplayer->mo->z); + P_SetTarget(&emerald->tracer, NULL); // Ensures that tracer is correctly reset, allowing ACS to detect empty-handdeness. + // "Why not just check target, which has to be set for any part of the behavior to work at all?" + // Because Hyudoro is an emerald. I will not explain further. Program for a different game. Obj_BeginEmeraldOrbit(emerald, bestplayer->mo, 100 * mapobjectscale, 64, 0); return; } @@ -316,7 +319,7 @@ void Obj_BeginEmeraldOrbit(mobj_t *emerald, mobj_t *target, fixed_t radius, INT3 P_SetTarget(&emerald_orbit(emerald), target); P_SetTarget(&emerald->punt_ref, target); - if (P_MobjWasRemoved(emerald_award(emerald))) + if (!emerald_award(emerald) || P_MobjWasRemoved(emerald_award(emerald))) { P_SetTarget(&emerald_award(emerald), target); }