Merge branch 'sealed-acs-splitscreen' into 'master'

Sealed ACS Splitscreen (resolves #1209)

Closes #1209

See merge request KartKrew/Kart!2190
This commit is contained in:
Oni 2024-03-31 05:54:16 +00:00
commit 8089cbd3a7
14 changed files with 200 additions and 29 deletions

View file

@ -39,6 +39,7 @@
#include "../r_textures.h" #include "../r_textures.h"
#include "../m_cond.h" #include "../m_cond.h"
#include "../r_skins.h" #include "../r_skins.h"
#include "../k_kart.h"
#include "../k_battle.h" #include "../k_battle.h"
#include "../k_grandprix.h" #include "../k_grandprix.h"
#include "../k_podium.h" #include "../k_podium.h"
@ -1646,6 +1647,54 @@ bool CallFunc_PlayerBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::W
return false; 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 *>(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_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 *>(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) bool CallFunc_GetObjectDye(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)

View file

@ -76,6 +76,8 @@ 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_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_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_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_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_PlayerEmeralds(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_PlayerLap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PlayerLap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);

View file

@ -172,6 +172,8 @@ Environment::Environment()
addFuncDataACS0( 316, addCallFunc(CallFunc_PositionStart)); addFuncDataACS0( 316, addCallFunc(CallFunc_PositionStart));
addFuncDataACS0( 317, addCallFunc(CallFunc_FreePlay)); addFuncDataACS0( 317, addCallFunc(CallFunc_FreePlay));
addFuncDataACS0( 318, addCallFunc(CallFunc_CheckTutorialChallenge)); addFuncDataACS0( 318, addCallFunc(CallFunc_CheckTutorialChallenge));
addFuncDataACS0( 319, addCallFunc(CallFunc_PlayerLosing));
addFuncDataACS0( 320, addCallFunc(CallFunc_PlayerExiting));
addFuncDataACS0( 500, addCallFunc(CallFunc_CameraWait)); addFuncDataACS0( 500, addCallFunc(CallFunc_CameraWait));
addFuncDataACS0( 501, addCallFunc(CallFunc_PodiumPosition)); addFuncDataACS0( 501, addCallFunc(CallFunc_PodiumPosition));

View file

@ -244,6 +244,29 @@ void ACS_RunPlayerEnterScript(player_t *player)
map->scriptStartTypeForced(ACS_ST_ENTER, scriptInfo); 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) void ACS_RunLapScript(mobj_t *mo, line_t *line)

View file

@ -139,6 +139,22 @@ void ACS_RunPlayerDeathScript(player_t *player);
void ACS_RunPlayerEnterScript(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); void ACS_RunLapScript(mobj_t *mo, line_t *line);

View file

@ -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_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_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_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
}; };
// //

View file

@ -2520,9 +2520,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); ACS_RunPlayerEnterScript(p);
} }
@ -2531,15 +2531,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
ACS_RunPlayerRespawnScript(p); ACS_RunPlayerRespawnScript(p);
} }
} }
if (betweenmaps)
return;
if (leveltime < starttime)
return;
if (exiting)
return;
} }
// //

View file

@ -2749,13 +2749,6 @@ void K_PlayOvertakeSound(mobj_t *source)
boolean tasteful = (!source->player || !source->player->karthud[khud_voices]); 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 ( if (
cv_kartvoices.value cv_kartvoices.value
&& (tasteful || cv_kartvoices.value == 2) && (tasteful || cv_kartvoices.value == 2)
@ -11100,7 +11093,7 @@ void K_KartUpdatePosition(player_t *player)
if (position != oldposition) // Changed places? 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. // 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... UINT32 soundpos = (max(0, position - 1) * MAXPLAYERS)/realplayers; // always 1-15 despite there being 16 players at max...
@ -11143,12 +11136,6 @@ void K_KartUpdatePosition(player_t *player)
player->topinfirst = 0; player->topinfirst = 0;
} }
// Special stages: fade out music near the finish line
if (P_IsPartyPlayer(player))
{
K_FadeOutSpecialMusic(player->distancetofinish);
}
player->position = position; player->position = position;
} }
@ -12106,14 +12093,23 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
boolean HOLDING_ITEM = (player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT)); boolean HOLDING_ITEM = (player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT));
boolean NO_HYUDORO = (player->stealingtimer == 0); 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, if (player->oldposition < player->position) // But first, if you lost a place,
{ {
player->oldposition = player->position; // then the other player taunts. player->oldposition = player->position; // then the other player taunts.
K_RegularVoiceTimers(player); // and you can't for a bit 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!" K_PlayOvertakeSound(player->mo); // Say "YOU'RE TOO SLOW!"
player->oldposition = player->position; // Restore the old position, player->oldposition = player->position; // Restore the old position,

View file

@ -120,6 +120,19 @@ void K_TickSpecialStage(void)
return; 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)) if (P_MobjWasRemoved(specialstageinfo.ufo))
{ {
P_SetTarget(&specialstageinfo.ufo, NULL); P_SetTarget(&specialstageinfo.ufo, NULL);
@ -148,6 +161,53 @@ mobj_t *K_GetPossibleSpecialTarget(void)
return specialstageinfo.ufo; 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) void K_FadeOutSpecialMusic(UINT32 distance)

View file

@ -68,6 +68,21 @@ void K_TickSpecialStage(void);
mobj_t *K_GetPossibleSpecialTarget(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) void K_FadeOutSpecialMusic(UINT32 distance)

View file

@ -149,6 +149,9 @@ static void Obj_EmeraldOrbitPlayer(mobj_t *emerald)
if (!(bestplayer->pflags & PF_NOCONTEST)) if (!(bestplayer->pflags & PF_NOCONTEST))
{ {
P_MoveOrigin(emerald, bestplayer->mo->x, bestplayer->mo->y, bestplayer->mo->z); 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); Obj_BeginEmeraldOrbit(emerald, bestplayer->mo, 100 * mapobjectscale, 64, 0);
return; 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_orbit(emerald), target);
P_SetTarget(&emerald->punt_ref, 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); P_SetTarget(&emerald_award(emerald), target);
} }

View file

@ -8892,6 +8892,16 @@ void P_PostLoadLevel(void)
ACS_RunLevelStartScripts(); ACS_RunLevelStartScripts();
LUA_HookInt(gamemap, HOOK(MapLoad)); 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(); P_MapEnd();
// We're now done loading the level. // We're now done loading the level.

View file

@ -1997,7 +1997,7 @@ static void K_HandleLapIncrement(player_t *player)
if (specialstageinfo.valid == true) if (specialstageinfo.valid == true)
{ {
// Don't permit a win just by sneaking ahead of the UFO/emerald. // 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; applyflags |= PF_NOCONTEST;

View file

@ -71,6 +71,7 @@
#include "k_credits.h" #include "k_credits.h"
#include "k_hud.h" // K_AddMessage #include "k_hud.h" // K_AddMessage
#include "m_easing.h" #include "m_easing.h"
#include "acs/interface.h"
#ifdef HWRENDER #ifdef HWRENDER
#include "hardware/hw_light.h" #include "hardware/hw_light.h"
@ -1342,6 +1343,8 @@ void P_DoPlayerExit(player_t *player, pflags_t flags)
demo.savebutton = leveltime; demo.savebutton = leveltime;
} }
} }
ACS_RunPlayerFinishScript(player);
} }
// //