Merge branch 'podium' into 'master'

Podium

See merge request KartKrew/Kart!1014
This commit is contained in:
Oni 2023-03-07 05:58:20 +00:00
commit cdc56ab2cc
51 changed files with 2541 additions and 451 deletions

View file

@ -134,6 +134,8 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
k_profiles.c
k_specialstage.c
k_roulette.c
k_podium.c
k_rank.c
)
if(SRB2_CONFIG_ENABLE_WEBM_MOVIES)

View file

@ -50,6 +50,7 @@ extern "C" {
#include "../m_cond.h"
#include "../r_skins.h"
#include "../k_battle.h"
#include "../k_podium.h"
}
#include "call-funcs.hpp"
@ -445,6 +446,25 @@ bool CallFunc_PolyWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Wo
return true; // Execution interrupted
}
/*--------------------------------------------------
bool CallFunc_CameraWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
Pauses the thread until the tagged
camera is done moving.
--------------------------------------------------*/
bool CallFunc_CameraWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
{
(void)argC;
thread->state = {
ACSVM::ThreadState::WaitTag,
argV[0],
ACS_TAGTYPE_CAMERA
};
return true; // Execution interrupted
}
/*--------------------------------------------------
bool CallFunc_ChangeFloor(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
@ -1332,3 +1352,59 @@ bool CallFunc_EncoreMode(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::
thread->dataStk.push(encoremode);
return false;
}
/*--------------------------------------------------
bool CallFunc_PodiumPosition(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
Returns the best position of all non-CPU players.
--------------------------------------------------*/
bool CallFunc_PodiumPosition(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
{
UINT8 ret = MAXPLAYERS;
INT32 i;
(void)argV;
(void)argC;
for (i = 0; i < MAXPLAYERS; i++)
{
player_t *player = NULL;
if (playeringame[i] == false)
{
continue;
}
player = &players[i];
if (player->spectator == true)
{
continue;
}
if (player->bot == true)
{
continue;
}
ret = std::min(ret, player->position);
}
thread->dataStk.push(ret);
return false;
}
/*--------------------------------------------------
bool CallFunc_PodiumFinish(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
Ends the podium sequence. Doesn't do anything
outside of podium maps.
--------------------------------------------------*/
bool CallFunc_PodiumFinish(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
{
(void)argV;
(void)argC;
K_FinishCeremony();
return false;
}

View file

@ -50,6 +50,7 @@ bool CallFunc_Random(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word
bool CallFunc_ThingCount(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_TagWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_PolyWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_CameraWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_ChangeFloor(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_ChangeCeiling(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_LineSide(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
@ -84,4 +85,7 @@ bool CallFunc_PlayerLap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::W
bool CallFunc_LowestLap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_EncoreMode(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_PodiumPosition(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_PodiumFinish(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
#endif // __SRB2_ACS_CALL_FUNCS_HPP__

View file

@ -36,6 +36,7 @@ extern "C" {
#include "../p_spec.h"
#include "../w_wad.h"
#include "../z_zone.h"
#include "../p_local.h"
}
#include "environment.hpp"
@ -160,6 +161,10 @@ Environment::Environment()
addFuncDataACS0( 307, addCallFunc(CallFunc_PlayerLap));
addFuncDataACS0( 308, addCallFunc(CallFunc_LowestLap));
addFuncDataACS0( 309, addCallFunc(CallFunc_EncoreMode));
addFuncDataACS0( 500, addCallFunc(CallFunc_CameraWait));
addFuncDataACS0( 501, addCallFunc(CallFunc_PodiumPosition));
addFuncDataACS0( 502, addCallFunc(CallFunc_PodiumFinish));
}
ACSVM::Thread *Environment::allocThread()
@ -262,6 +267,17 @@ bool Environment::checkTag(ACSVM::Word type, ACSVM::Word tag)
const polyobj_t *po = Polyobj_GetForNum(tag);
return (po == nullptr || po->thinker == nullptr);
}
case ACS_TAGTYPE_CAMERA:
{
const mobj_t *camera = P_FindObjectTypeFromTag(MT_ALTVIEWMAN, tag);
if (camera == nullptr || camera->spawnpoint == nullptr)
{
return true;
}
return (camera->tracer == nullptr || P_MobjWasRemoved(camera->tracer) == true);
}
}
return true;

View file

@ -57,6 +57,7 @@ enum acs_tagType_e
{
ACS_TAGTYPE_POLYOBJ,
ACS_TAGTYPE_SECTOR,
ACS_TAGTYPE_CAMERA,
};
class ThreadInfo : public ACSVM::ThreadInfo

View file

@ -1839,6 +1839,25 @@ static void CON_DrawConsole(void)
// Console refresh drawer, call each frame
//
static boolean CON_GamestateDrawHudLines(void)
{
switch (gamestate)
{
case GS_LEVEL:
case GS_INTERMISSION:
case GS_VOTING:
case GS_CUTSCENE:
case GS_CREDITS:
case GS_EVALUATION:
case GS_WAITINGPLAYERS:
case GS_CEREMONY:
return true;
default:
return false;
}
}
void CON_Drawer(void)
{
Lock_state();
@ -1858,8 +1877,7 @@ void CON_Drawer(void)
if (con_curlines > 0)
CON_DrawConsole();
else if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_CUTSCENE || gamestate == GS_CREDITS
|| gamestate == GS_VOTING || gamestate == GS_EVALUATION || gamestate == GS_WAITINGPLAYERS)
else if (CON_GamestateDrawHudLines() == true)
CON_DrawHudlines();
Unlock_state();

View file

@ -1272,7 +1272,7 @@ static void CL_LoadReceivedSavegame(boolean reloading)
paused = false;
demo.playback = false;
demo.title = false;
titlemapinaction = TITLEMAP_OFF;
titlemapinaction = false;
automapactive = false;
// load a base level
@ -2542,7 +2542,7 @@ void CL_ClearPlayer(INT32 playernum)
int i;
// Handle mobj_t pointers.
if (gamestate == GS_LEVEL)
if (G_GamestateUsesLevel() == true)
{
if (players[playernum].follower)
{
@ -2557,7 +2557,7 @@ void CL_ClearPlayer(INT32 playernum)
P_SetTarget(&players[playernum].skybox.viewpoint, NULL);
P_SetTarget(&players[playernum].skybox.centerpoint, NULL);
P_SetTarget(&players[playernum].awayviewmobj, NULL);
P_SetTarget(&players[playernum].awayview.mobj, NULL);
P_SetTarget(&players[playernum].followmobj, NULL);
P_SetTarget(&players[playernum].hoverhyudoro, NULL);
P_SetTarget(&players[playernum].stumbleIndicator, NULL);
@ -3771,9 +3771,6 @@ static void Got_AddBot(UINT8 **p, INT32 playernum)
sprintf(player_names[newplayernum], "%s", skins[skinnum].realname);
SetPlayerSkinByNum(newplayernum, skinnum);
players[newplayernum].spectator = !(gametyperules & GTR_BOTS)
|| (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE);
if (netgame)
{
HU_AddChatText(va("\x82*Bot %d has been added to the game", newplayernum+1), false);

View file

@ -76,6 +76,7 @@
#include "m_random.h" // P_ClearRandom
#include "k_specialstage.h"
#include "acs/interface.h"
#include "k_podium.h"
#ifdef HWRENDER
#include "hardware/hw_main.h" // 3D View Rendering
@ -344,8 +345,8 @@ static void D_Display(void)
if (rendermode != render_none)
{
// Fade to black first
if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)) // fades to black on its own timing, always
&& wipetypepre != UINT8_MAX)
if (G_GamestateUsesLevel() == false // fades to black on its own timing, always
&& wipetypepre != UINT8_MAX)
{
F_WipeStartScreen();
F_WipeColorFill(31);
@ -353,7 +354,7 @@ static void D_Display(void)
F_RunWipe(wipedefindex, wipetypepre, gamestate != GS_MENU, "FADEMAP0", false, false);
}
if (gamestate != GS_LEVEL && rendermode != render_none)
if (G_GamestateUsesLevel() == false && rendermode != render_none)
{
V_SetPaletteLump("PLAYPAL"); // Reset the palette
R_ReInitColormaps(0, NULL, 0);
@ -406,6 +407,13 @@ static void D_Display(void)
HU_Drawer();
break;
case GS_CEREMONY:
if (!gametic)
break;
HU_Erase();
HU_Drawer();
break;
case GS_MENU:
break;
@ -470,7 +478,7 @@ static void D_Display(void)
// clean up border stuff
// see if the border needs to be initially drawn
if (gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction && curbghide && (!hidetitlemap)))
if (G_GamestateUsesLevel() == true)
{
if (!automapactive && !dedicated && cv_renderview.value)
{
@ -566,14 +574,30 @@ static void D_Display(void)
ps_uitime = I_GetPreciseTime();
if (gamestate == GS_LEVEL)
switch (gamestate)
{
ST_Drawer();
F_TextPromptDrawer();
HU_Drawer();
case GS_LEVEL:
{
ST_Drawer();
F_TextPromptDrawer();
HU_Drawer();
break;
}
case GS_TITLESCREEN:
{
F_TitleScreenDrawer();
break;
}
case GS_CEREMONY:
{
K_CeremonyDrawer();
break;
}
default:
{
break;
}
}
else
F_TitleScreenDrawer();
}
else
{
@ -583,7 +607,7 @@ static void D_Display(void)
// change gamma if needed
// (GS_LEVEL handles this already due to level-specific palettes)
if (forcerefresh && !(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
if (forcerefresh && G_GamestateUsesLevel() == false)
V_SetPalette(0);
// draw pause pic
@ -752,17 +776,6 @@ void D_SRB2Loop(void)
because I_FinishUpdate was called afterward
*/
#if 0
/* Smells like a hack... Don't fade Sonic's ass into the title screen. */
if (gamestate != GS_TITLESCREEN)
{
static lumpnum_t gstartuplumpnum = W_CheckNumForName("STARTUP");
if (gstartuplumpnum == LUMPERROR)
gstartuplumpnum = W_GetNumForName("MISSING");
V_DrawScaledPatch(0, 0, 0, W_CachePatchNum(gstartuplumpnum, PU_PATCH));
}
#endif
for (;;)
{
// capbudget is the minimum precise_t duration of a single loop iteration

View file

@ -453,6 +453,7 @@ consvar_t cv_kartdebugcolorize = CVAR_INIT ("debugcolorize", "Off", CV_CHEAT, CV
consvar_t cv_kartdebugdirector = CVAR_INIT ("debugdirector", "Off", CV_CHEAT, CV_OnOff, NULL);
consvar_t cv_spbtest = CVAR_INIT ("spbtest", "Off", CV_CHEAT|CV_NETVAR, CV_OnOff, NULL);
consvar_t cv_gptest = CVAR_INIT ("gptest", "Off", CV_CHEAT|CV_NETVAR, CV_OnOff, NULL);
consvar_t cv_debugrank = CVAR_INIT ("debugrank", "Off", CV_CHEAT, CV_OnOff, NULL);
static CV_PossibleValue_t capsuletest_cons_t[] = {
{CV_CAPSULETEST_OFF, "Off"},
@ -5330,14 +5331,22 @@ static void Command_Mapmd5_f(void)
static void Command_ExitLevel_f(void)
{
if (!(netgame || multiplayer) && !CV_CheatsEnabled())
CONS_Printf(M_GetText("This only works in a netgame.\n"));
else if (!(server || (IsPlayerAdmin(consoleplayer))))
if (!(server || (IsPlayerAdmin(consoleplayer))))
{
CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
}
else if (K_CanChangeRules(false) == false && CV_CheatsEnabled() == false)
{
CONS_Printf(M_GetText("This cannot be used without cheats enabled.\n"));
}
else if (( gamestate != GS_LEVEL && gamestate != GS_CREDITS ) || demo.playback)
{
CONS_Printf(M_GetText("You must be in a level to use this.\n"));
}
else
{
SendNetXCmd(XD_EXITLEVEL, NULL, 0);
}
}
static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum)

View file

@ -96,6 +96,7 @@ extern consvar_t cv_kartdebugitem, cv_kartdebugamount, cv_kartdebugdistribution,
extern consvar_t cv_kartdebugnodes, cv_kartdebugcolorize, cv_kartdebugdirector;
extern consvar_t cv_spbtest, cv_gptest, cv_reducevfx;
extern consvar_t cv_kartdebugwaypoints, cv_kartdebugbotpredict;
extern consvar_t cv_debugrank;
typedef enum {
CV_CAPSULETEST_OFF,

View file

@ -384,6 +384,13 @@ typedef struct {
boolean flip;
} sonicloopvars_t;
// player_t struct for all alternative viewpoint variables
struct altview_t
{
mobj_t *mobj;
INT32 tics;
};
// ========================================================================
// PLAYER STRUCTURE
// ========================================================================
@ -627,6 +634,7 @@ struct player_t
tic_t realtime; // integer replacement for leveltime
UINT8 laps; // Number of laps (optional)
UINT8 latestlap;
UINT32 lapPoints; // Points given from laps
INT32 starpostnum; // The number of the last starpost you hit
UINT8 ctfteam; // 0 == Spectator, 1 == Red, 2 == Blue
@ -644,9 +652,7 @@ struct player_t
INT32 onconveyor; // You are on a conveyor belt if nonzero
mobj_t *awayviewmobj;
INT32 awayviewtics;
angle_t awayviewaiming; // Used for cut-away view
altview_t awayview;
boolean spectator;
tic_t spectatewait; // reimplementable as UINT8 queue - How long have you been waiting as a spectator

View file

@ -2559,6 +2559,10 @@ void readmaincfg(MYFILE *f, boolean mainfile)
INT32 value;
boolean doClearLevels = false;
#ifdef DEVELOP
(void)mainfile;
#endif
do
{
if (myfgets(s, MAXLINELEN, f))
@ -2630,10 +2634,12 @@ void readmaincfg(MYFILE *f, boolean mainfile)
//clear_levels();
doClearLevels = true;
}
#ifndef DEVELOP
else if (!mainfile && !gamedataadded)
{
deh_warning("You must define a custom gamedata to use \"%s\"", word);
}
#endif
else if (fastcmp(word, "CLEARLEVELS"))
{
doClearLevels = (UINT8)(value == 0 || word2[0] == 'F' || word2[0] == 'N');
@ -2741,6 +2747,7 @@ void readmaincfg(MYFILE *f, boolean mainfile)
}
else if (fastcmp(word, "TITLEMAP"))
{
Z_Free(titlemap);
titlemap = Z_StrDup(word2);
titlechanged = true;
}
@ -2835,13 +2842,20 @@ void readmaincfg(MYFILE *f, boolean mainfile)
}
else if (fastcmp(word, "BOOTMAP"))
{
Z_Free(bootmap);
bootmap = Z_StrDup(word2);
//titlechanged = true;
}
else if (fastcmp(word, "TUTORIALMAP"))
{
Z_Free(tutorialmap);
tutorialmap = Z_StrDup(word2);
}
else if (fastcmp(word, "PODIUMMAP"))
{
Z_Free(podiummap);
podiummap = Z_StrDup(word2);
}
else
deh_warning("Maincfg: unknown word '%s'", word);
}
@ -2980,6 +2994,14 @@ void readwipes(MYFILE *f)
else if (fastcmp(pword, "FINAL"))
wipeoffset = wipe_gameend_final;
}
else if (fastncmp(word, "CEREMONY_", 9))
{
pword = word + 9;
if (fastcmp(pword, "TOBLACK"))
wipeoffset = wipe_ceremony_toblack;
else if (fastcmp(pword, "FINAL"))
wipeoffset = wipe_ceremony_final;
}
else if (fastncmp(word, "ENCORE_", 7))
{
pword = word + 7;

View file

@ -212,6 +212,8 @@ extern char * bootmap; //bootmap for loading a map on startup
extern char * tutorialmap; // map to load for tutorial
extern boolean tutorialmode; // are we in a tutorial right now?
extern char * podiummap; // map to load for podium
extern boolean looptitle;
// CTF colors.

View file

@ -44,13 +44,14 @@
// SRB2Kart
#include "k_menu.h"
#include "k_grandprix.h"
// Stage of animation:
// 0 = text, 1 = art screen
INT32 finalecount;
INT32 titlescrollxspeed = 16;
INT32 titlescrollyspeed = 0;
UINT8 titlemapinaction = TITLEMAP_OFF;
boolean titlemapinaction = false;
static INT32 timetonext; // Delay between screen changes
@ -62,7 +63,7 @@ static tic_t stoptimer;
static boolean keypressed = false;
static INT32 menuanimtimer; // Title screen: background animation timing
mobj_t *titlemapcameraref = NULL;
altview_t titlemapcam = {0};
// menu presentation state
char curbgname[9];
@ -1835,14 +1836,14 @@ void F_StartTitleScreen(void)
mapthing_t *startpos;
gamestate_t prevwipegamestate = wipegamestate;
titlemapinaction = TITLEMAP_LOADING;
titlemapcameraref = NULL;
titlemapinaction = true;
P_SetTarget(&titlemapcam.mobj, NULL);
gamemap = titleMapNum+1;
maptol = mapheaderinfo[titleMapNum]->typeoflevel;
globalweather = mapheaderinfo[titleMapNum]->weather;
G_DoLoadLevel(true);
G_DoLoadLevelEx(true, GS_TITLESCREEN);
if (!titlemap)
return;
@ -1878,13 +1879,12 @@ void F_StartTitleScreen(void)
}
else
{
titlemapinaction = TITLEMAP_OFF;
G_SetGamestate(GS_TITLESCREEN);
titlemapinaction = false;
gamemap = 1; // g_game.c
CON_ClearHUD();
}
G_SetGamestate(GS_TITLESCREEN);
// IWAD dependent stuff.
animtimer = skullAnimCounter = 0;
@ -2139,7 +2139,7 @@ void F_TitleScreenTicker(boolean run)
mobj_t *cameraref = NULL;
// If there's a Line 422 Switch Cut-Away view, don't force us.
if (!titlemapcameraref || titlemapcameraref->type != MT_ALTVIEWMAN)
if (titlemapcam.mobj == NULL || titlemapcam.mobj->type != MT_ALTVIEWMAN)
{
for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
{
@ -2154,20 +2154,27 @@ void F_TitleScreenTicker(boolean run)
if (mo2->type != MT_ALTVIEWMAN)
continue;
cameraref = titlemapcameraref = mo2;
cameraref = mo2;
break;
}
if (cameraref != NULL)
{
P_SetTarget(&titlemapcam.mobj, cameraref);
}
}
else
cameraref = titlemapcameraref;
{
cameraref = titlemapcam.mobj;
}
if (cameraref)
if (cameraref != NULL)
{
camera[0].x = cameraref->x;
camera[0].y = cameraref->y;
camera[0].z = cameraref->z;
camera[0].angle = cameraref->angle;
camera[0].aiming = cameraref->cusval;
camera[0].aiming = cameraref->pitch;
camera[0].subsector = cameraref->subsector;
}
else

View file

@ -104,17 +104,10 @@ extern INT16 ttloop;
extern UINT16 tttics;
extern boolean ttavailable[6];
typedef enum
{
TITLEMAP_OFF = 0,
TITLEMAP_LOADING,
TITLEMAP_RUNNING
} titlemap_enum;
// Current menu parameters
extern mobj_t *titlemapcameraref;
extern altview_t titlemapcam;
extern char curbgname[9];
extern SINT8 curfadevalue;
extern INT32 curbgcolor;
@ -184,6 +177,7 @@ enum
wipe_credits_toblack,
wipe_evaluation_toblack,
wipe_gameend_toblack,
wipe_ceremony_toblack,
wipe_intro_toblack,
wipe_ending_toblack,
wipe_cutscene_toblack,
@ -202,6 +196,7 @@ enum
wipe_credits_final,
wipe_evaluation_final,
wipe_gameend_final,
wipe_ceremony_final,
wipe_intro_final,
wipe_ending_final,
wipe_cutscene_final,

View file

@ -64,6 +64,7 @@ UINT8 wipedefs[NUMWIPEDEFS] = {
99, // wipe_credits_toblack
0, // wipe_evaluation_toblack
0, // wipe_gameend_toblack
0, // wipe_ceremony_toblack
UINT8_MAX, // wipe_intro_toblack (hardcoded)
99, // wipe_ending_toblack (hardcoded)
99, // wipe_cutscene_toblack (hardcoded)
@ -80,6 +81,7 @@ UINT8 wipedefs[NUMWIPEDEFS] = {
99, // wipe_credits_final
0, // wipe_evaluation_final
0, // wipe_gameend_final
0, // wipe_ceremony_final
99, // wipe_intro_final (hardcoded)
99, // wipe_ending_final (hardcoded)
99 // wipe_cutscene_final (hardcoded)
@ -88,15 +90,16 @@ UINT8 wipedefs[NUMWIPEDEFS] = {
static boolean g_wipedef_toblack[NUMWIPEDEFS] = {
true, // wipe_credits_intermediate (0)
true, // wipe_level_toblack
true, // wipe_intermission_toblack
true, // wipe_voting_toblack,
true, // wipe_continuing_toblack
true, // wipe_titlescreen_toblack
true, // wipe_menu_toblack
true, // wipe_level_toblack
true, // wipe_intermission_toblack
true, // wipe_voting_toblack,
true, // wipe_continuing_toblack
true, // wipe_titlescreen_toblack
true, // wipe_menu_toblack
true, // wipe_credits_toblack
true, // wipe_evaluation_toblack
true, // wipe_gameend_toblack
true, // wipe_evaluation_toblack
true, // wipe_gameend_toblack
true, // wipe_ceremony_toblack
true, // wipe_intro_toblack (hardcoded)
true, // wipe_ending_toblack (hardcoded)
true, // wipe_cutscene_toblack (hardcoded)
@ -105,14 +108,15 @@ static boolean g_wipedef_toblack[NUMWIPEDEFS] = {
false, // wipe_encore_towhite
true, // wipe_level_final
true, // wipe_intermission_final
true, // wipe_voting_final
true, // wipe_continuing_final
true, // wipe_titlescreen_final
true, // wipe_menu_final
true, // wipe_intermission_final
true, // wipe_voting_final
true, // wipe_continuing_final
true, // wipe_titlescreen_final
true, // wipe_menu_final
true, // wipe_credits_final
true, // wipe_evaluation_final
true, // wipe_gameend_final
true, // wipe_evaluation_final
true, // wipe_gameend_final
true, // wipe_ceremony_final
true, // wipe_intro_final (hardcoded)
true, // wipe_ending_final (hardcoded)
true // wipe_cutscene_final (hardcoded)
@ -121,15 +125,16 @@ static boolean g_wipedef_toblack[NUMWIPEDEFS] = {
static boolean g_wipedef_toinvert[NUMWIPEDEFS] = {
false, // wipe_credits_intermediate (0)
false, // wipe_level_toblack
false, // wipe_intermission_toblack
false, // wipe_voting_toblack,
false, // wipe_continuing_toblack
false, // wipe_titlescreen_toblack
false, // wipe_menu_toblack
false, // wipe_level_toblack
false, // wipe_intermission_toblack
false, // wipe_voting_toblack,
false, // wipe_continuing_toblack
false, // wipe_titlescreen_toblack
false, // wipe_menu_toblack
false, // wipe_credits_toblack
false, // wipe_evaluation_toblack
false, // wipe_gameend_toblack
false, // wipe_evaluation_toblack
false, // wipe_gameend_toblack
false, // wipe_ceremony_toblack
false, // wipe_intro_toblack (hardcoded)
false, // wipe_ending_toblack (hardcoded)
false, // wipe_cutscene_toblack (hardcoded)
@ -138,14 +143,15 @@ static boolean g_wipedef_toinvert[NUMWIPEDEFS] = {
false, // wipe_encore_towhite
false, // wipe_level_final
false, // wipe_intermission_final
false, // wipe_voting_final
false, // wipe_continuing_final
false, // wipe_titlescreen_final
false, // wipe_menu_final
false, // wipe_intermission_final
false, // wipe_voting_final
false, // wipe_continuing_final
false, // wipe_titlescreen_final
false, // wipe_menu_final
false, // wipe_credits_final
false, // wipe_evaluation_final
false, // wipe_gameend_final
false, // wipe_evaluation_final
false, // wipe_gameend_final
false, // wipe_ceremony_final
false, // wipe_intro_final (hardcoded)
false, // wipe_ending_final (hardcoded)
false // wipe_cutscene_final (hardcoded)
@ -154,15 +160,16 @@ static boolean g_wipedef_toinvert[NUMWIPEDEFS] = {
static boolean g_wipedef_towhite[NUMWIPEDEFS] = {
false, // wipe_credits_intermediate (0)
false, // wipe_level_toblack
false, // wipe_intermission_toblack
false, // wipe_voting_toblack,
false, // wipe_continuing_toblack
false, // wipe_titlescreen_toblack
false, // wipe_menu_toblack
false, // wipe_level_toblack
false, // wipe_intermission_toblack
false, // wipe_voting_toblack,
false, // wipe_continuing_toblack
false, // wipe_titlescreen_toblack
false, // wipe_menu_toblack
false, // wipe_credits_toblack
false, // wipe_evaluation_toblack
false, // wipe_gameend_toblack
false, // wipe_evaluation_toblack
false, // wipe_gameend_toblack
false, // wipe_ceremony_toblack
false, // wipe_intro_toblack (hardcoded)
false, // wipe_ending_toblack (hardcoded)
false, // wipe_cutscene_toblack (hardcoded)
@ -171,14 +178,15 @@ static boolean g_wipedef_towhite[NUMWIPEDEFS] = {
true, // wipe_encore_towhite
false, // wipe_level_final
false, // wipe_intermission_final
false, // wipe_voting_final
false, // wipe_continuing_final
false, // wipe_titlescreen_final
false, // wipe_menu_final
false, // wipe_intermission_final
false, // wipe_voting_final
false, // wipe_continuing_final
false, // wipe_titlescreen_final
false, // wipe_menu_final
false, // wipe_credits_final
false, // wipe_evaluation_final
false, // wipe_gameend_final
false, // wipe_evaluation_final
false, // wipe_gameend_final
false, // wipe_ceremony_final
false, // wipe_intro_final (hardcoded)
false, // wipe_ending_final (hardcoded)
false // wipe_cutscene_final (hardcoded)
@ -187,15 +195,16 @@ static boolean g_wipedef_towhite[NUMWIPEDEFS] = {
static boolean g_wipedef_crossfade[NUMWIPEDEFS] = {
false, // wipe_credits_intermediate (0)
false, // wipe_level_toblack
false, // wipe_intermission_toblack
false, // wipe_voting_toblack,
false, // wipe_continuing_toblack
false, // wipe_titlescreen_toblack
false, // wipe_menu_toblack
false, // wipe_level_toblack
false, // wipe_intermission_toblack
false, // wipe_voting_toblack,
false, // wipe_continuing_toblack
false, // wipe_titlescreen_toblack
false, // wipe_menu_toblack
false, // wipe_credits_toblack
false, // wipe_evaluation_toblack
false, // wipe_gameend_toblack
false, // wipe_evaluation_toblack
false, // wipe_gameend_toblack
false, // wipe_ceremony_toblack
false, // wipe_intro_toblack (hardcoded)
false, // wipe_ending_toblack (hardcoded)
false, // wipe_cutscene_toblack (hardcoded)
@ -204,14 +213,15 @@ static boolean g_wipedef_crossfade[NUMWIPEDEFS] = {
false, // wipe_encore_towhite
true, // wipe_level_final
true, // wipe_intermission_final
true, // wipe_voting_final
true, // wipe_continuing_final
true, // wipe_titlescreen_final
true, // wipe_menu_final
true, // wipe_intermission_final
true, // wipe_voting_final
true, // wipe_continuing_final
true, // wipe_titlescreen_final
true, // wipe_menu_final
true, // wipe_credits_final
true, // wipe_evaluation_final
true, // wipe_gameend_final
true, // wipe_evaluation_final
true, // wipe_gameend_final
true, // wipe_ceremony_final
true, // wipe_intro_final (hardcoded)
true, // wipe_ending_final (hardcoded)
true // wipe_cutscene_final (hardcoded)

View file

@ -62,6 +62,8 @@
#include "k_bot.h"
#include "doomstat.h"
#include "k_director.h"
#include "k_podium.h"
#include "k_rank.h"
#ifdef HAVE_DISCORDRPC
#include "discord.h"
@ -164,6 +166,8 @@ char * bootmap = NULL; //bootmap for loading a map on startup
char * tutorialmap = NULL; // map to load for tutorial
boolean tutorialmode = false; // are we in a tutorial right now?
char * podiummap = NULL; // map to load for podium
boolean looptitle = true;
UINT16 skincolor_redteam = SKINCOLOR_RED;
@ -1449,9 +1453,9 @@ static void weaponPrefChange4(void)
}
//
// G_DoLoadLevel
// G_DoLoadLevelEx
//
void G_DoLoadLevel(boolean resetplayer)
void G_DoLoadLevelEx(boolean resetplayer, gamestate_t newstate)
{
boolean doAutomate = false;
INT32 i;
@ -1461,7 +1465,7 @@ void G_DoLoadLevel(boolean resetplayer)
levelstarttic = gametic; // for time calculation
if (wipegamestate == GS_LEVEL)
if (wipegamestate == newstate)
wipegamestate = -1; // force a wipe
if (cv_currprofile.value == -1 && !demo.playback)
@ -1478,31 +1482,33 @@ void G_DoLoadLevel(boolean resetplayer)
Y_EndVote();
// cleanup
if (titlemapinaction == TITLEMAP_LOADING)
// Is this actually necessary? Doesn't F_StartTitleScreen already do a significantly more comprehensive check?
if (newstate == GS_TITLESCREEN)
{
//if (W_CheckNumForName(G_BuildMapName(gamemap)) == LUMPERROR)
if (gamemap < 1 || gamemap > nummapheaders)
{
G_SetGamestate(GS_TITLESCREEN);
titlemapinaction = false;
Z_Free(titlemap);
titlemap = NULL; // let's not infinite recursion ok
Command_ExitGame_f();
return;
}
titlemapinaction = TITLEMAP_RUNNING;
}
else
titlemapinaction = TITLEMAP_OFF;
// Doing this matches HOSTMOD behavior.
// Is that desired? IDK
doAutomate = (gamestate != GS_LEVEL);
doAutomate = (gamestate != GS_LEVEL && newstate == GS_LEVEL);
G_SetGamestate(GS_LEVEL);
G_SetGamestate(newstate);
if (wipegamestate == GS_MENU)
M_ClearMenus(true);
I_UpdateMouseGrab();
K_ResetCeremony();
for (i = 0; i < MAXPLAYERS; i++)
{
if (resetplayer || (playeringame[i] && players[i].playerstate == PST_DEAD))
@ -1543,6 +1549,11 @@ void G_DoLoadLevel(boolean resetplayer)
}
}
void G_DoLoadLevel(boolean resetplayer)
{
G_DoLoadLevelEx(resetplayer, GS_LEVEL);
}
//
// Start the title card.
//
@ -1573,7 +1584,7 @@ void G_StartTitleCard(void)
}
// start the title card
WipeStageTitle = (!titlemapinaction);
WipeStageTitle = (gamestate == GS_LEVEL);
}
//
@ -1717,6 +1728,21 @@ boolean G_Responder(event_t *ev)
return true;
}
}
else if (gamestate == GS_CEREMONY)
{
if (HU_Responder(ev))
{
hu_keystrokes = true;
return true; // chat ate the event
}
if (K_CeremonyResponder(ev))
{
nextmap = NEXTMAP_TITLE;
G_EndGame();
return true;
}
}
else if (gamestate == GS_CONTINUING)
{
return true;
@ -1727,11 +1753,13 @@ boolean G_Responder(event_t *ev)
return true;
}
else if (gamestate == GS_INTERMISSION || gamestate == GS_VOTING || gamestate == GS_EVALUATION)
{
if (HU_Responder(ev))
{
hu_keystrokes = true;
return true; // chat ate the event
}
}
if (gamestate == GS_LEVEL && ev->type == ev_keydown && multiplayer && demo.playback && !demo.freecam)
{
@ -2099,7 +2127,7 @@ void G_Ticker(boolean run)
marathontime++;
P_MapStart();
// do player reborns if needed
if (gamestate == GS_LEVEL)
{
// Or, alternatively, retry.
@ -2117,11 +2145,28 @@ void G_Ticker(boolean run)
D_MapChange(gamemap, gametype, (cv_kartencore.value == 1), false, 1, false, false);
}
}
// do player reborns if needed
if (G_GamestateUsesLevel() == true)
{
boolean changed = false;
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] && players[i].playerstate == PST_REBORN)
{
G_DoReborn(i);
changed = true;
}
}
if (changed == true)
{
K_UpdateAllPlayerPositions();
}
}
P_MapEnd();
// do things to change the game state
@ -2175,7 +2220,6 @@ void G_Ticker(boolean run)
F_TextPromptTicker();
AM_Ticker();
HU_Ticker();
break;
case GS_INTERMISSION:
@ -2237,6 +2281,11 @@ void G_Ticker(boolean run)
F_TitleScreenTicker(run);
break;
case GS_CEREMONY:
P_Ticker(run);
K_CeremonyTicker(run);
break;
case GS_WAITINGPLAYERS:
if (netgame)
F_WaitingPlayersTicker();
@ -2349,6 +2398,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
INT16 totalring;
UINT8 laps;
UINT8 latestlap;
UINT32 lapPoints;
UINT16 skincolor;
INT32 skin;
UINT8 availabilities[MAXAVAILABILITY];
@ -2469,6 +2519,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
nocontrol = 0;
laps = 0;
latestlap = 0;
lapPoints = 0;
roundscore = 0;
exiting = 0;
khudfinish = 0;
@ -2504,6 +2555,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
laps = players[player].laps;
latestlap = players[player].latestlap;
lapPoints = players[player].lapPoints;
roundscore = players[player].roundscore;
@ -2528,7 +2580,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
{
follower = players[player].follower;
P_SetTarget(&players[player].follower, NULL);
P_SetTarget(&players[player].awayviewmobj, NULL);
P_SetTarget(&players[player].awayview.mobj, NULL);
P_SetTarget(&players[player].stumbleIndicator, NULL);
P_SetTarget(&players[player].followmobj, NULL);
@ -2576,6 +2628,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
p->laps = laps;
p->latestlap = latestlap;
p->lapPoints = lapPoints;
p->totalring = totalring;
p->bot = bot;
@ -2732,7 +2785,7 @@ void G_MovePlayerToSpawnOrStarpost(INT32 playernum)
#else
// Player's first spawn should be at the "map start".
// I.e. level load or join mid game.
if (leveltime > starttime && players[playernum].jointime > 0)
if (leveltime > starttime && players[playernum].jointime > 1 && K_PodiumSequence() == false)
P_MovePlayerToStarpost(playernum);
else
P_MovePlayerToSpawn(playernum, G_FindMapStart(playernum));
@ -2828,7 +2881,9 @@ mapthing_t *G_FindRaceStart(INT32 playernum)
// SRB2Kart: figure out player spawn pos from points
if (!playeringame[playernum] || players[playernum].spectator)
{
return playerstarts[0]; // go to first spot if you're a spectator
}
for (i = 0; i < MAXPLAYERS; i++)
{
@ -2911,6 +2966,42 @@ mapthing_t *G_FindRaceStart(INT32 playernum)
return NULL;
}
mapthing_t *G_FindPodiumStart(INT32 playernum)
{
const boolean doprints = P_IsLocalPlayer(&players[playernum]);
if (numcoopstarts)
{
UINT8 pos = K_GetPodiumPosition(&players[playernum]) - 1;
UINT8 i;
if (G_CheckSpot(playernum, playerstarts[pos % numcoopstarts]))
{
return playerstarts[pos % numcoopstarts];
}
// Your spot isn't available? Find whatever you can get first.
for (i = 0; i < numcoopstarts; i++)
{
if (G_CheckSpot(playernum, playerstarts[(pos + i) % numcoopstarts]))
{
return playerstarts[(pos + i) % numcoopstarts];
}
}
if (doprints)
{
CONS_Alert(CONS_WARNING, M_GetText("Could not spawn at any Podium starts!\n"));
}
return NULL;
}
if (doprints)
CONS_Alert(CONS_WARNING, M_GetText("No Podium starts in this map!\n"));
return NULL;
}
// Find a Co-op start, or fallback into other types of starts.
static inline mapthing_t *G_FindRaceStartOrFallback(INT32 playernum)
{
@ -2947,9 +3038,14 @@ mapthing_t *G_FindMapStart(INT32 playernum)
if (!playeringame[playernum])
return NULL;
// -- Podium --
// Single special behavior
if (K_PodiumSequence() == true)
spawnpoint = G_FindPodiumStart(playernum);
// -- Time Attack --
// Order: Race->DM->CTF
if (K_TimeAttackRules() == true)
else if (K_TimeAttackRules() == true)
spawnpoint = G_FindRaceStartOrFallback(playernum);
// -- CTF --
@ -3096,13 +3192,20 @@ void G_ExitLevel(void)
if (i == MAXPLAYERS)
{
// GAME OVER, try again from the start!
if (netgame)
if (grandprixinfo.gp == true
&& grandprixinfo.eventmode == GPEVENT_SPECIAL)
{
; // restart cup here if we do online GP
// 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.
D_QuitNetGame();
CL_Reset();
D_ClearState();
@ -3111,11 +3214,14 @@ void G_ExitLevel(void)
}
else
{
// Go redo this course.
// We have lives, just redo this one course.
G_SetRetryFlag();
}
return;
if (doretry == true)
{
return;
}
}
gameaction = ga_completed;
@ -3806,20 +3912,9 @@ static void G_GetNextMap(void)
// Special stage
else if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels)
{
INT16 totaltotalring = 0;
gp_rank_e grade = K_CalculateGPGrade(&grandprixinfo.rank);
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
continue;
if (players[i].spectator)
continue;
if (players[i].bot)
continue;
totaltotalring += players[i].totalring;
}
if (totaltotalring >= 50)
if (grade >= GRADE_A) // On A rank pace? Then you get a chance for S rank!
{
const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_SPECIAL];
if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum])
@ -4047,6 +4142,9 @@ static void G_DoCompleted(void)
G_SetGamestate(GS_NULL);
wipegamestate = GS_NULL;
grandprixinfo.rank.capsules += numtargets;
grandprixinfo.rank.position = MAXPLAYERS;
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i])
@ -4072,6 +4170,11 @@ static void G_DoCompleted(void)
}
G_PlayerFinishLevel(i); // take away cards and stuff
if (players[i].bot == false)
{
grandprixinfo.rank.position = min(grandprixinfo.rank.position, K_GetPodiumPosition(&players[i]));
}
}
}
@ -4281,8 +4384,10 @@ void G_EndGame(void)
{
if (nextmap == NEXTMAP_CEREMONY) // end game with ceremony
{
/*F_StartEnding(); -- temporary
return;*/
if (K_StartCeremony() == true)
{
return;
}
}
if (nextmap == NEXTMAP_CREDITS) // end game with credits
{
@ -5061,6 +5166,16 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr
return;
}
if (map == G_MapNumber(podiummap)+1)
{
// Didn't want to do this, but it needs to be here
// for it to work on startup.
if (K_StartCeremony() == true)
{
return;
}
}
gamemap = map;
maptol = mapheaderinfo[gamemap-1]->typeoflevel;
@ -5355,6 +5470,22 @@ void G_SetGamestate(gamestate_t newstate)
#endif
}
boolean G_GamestateUsesLevel(void)
{
switch (gamestate)
{
case GS_TITLESCREEN:
return titlemapinaction;
case GS_LEVEL:
case GS_CEREMONY:
return true;
default:
return false;
}
}
/* These functions handle the exitgame flag. Before, when the user
chose to end a game, it happened immediately, which could cause
crashes if the game was in the middle of something. Now, a flag
@ -5381,6 +5512,11 @@ boolean G_GetExitGameFlag(void)
// Same deal with retrying.
void G_SetRetryFlag(void)
{
if (retrying == false)
{
grandprixinfo.rank.continuesUsed++;
}
retrying = true;
}
@ -5438,4 +5574,3 @@ INT32 G_TicsToMilliseconds(tic_t tics)
{
return (INT32)((tics%TICRATE) * (1000.00f/TICRATE));
}

View file

@ -156,6 +156,7 @@ INT32 G_FindMapByNameOrCode(const char *query, char **foundmapnamep);
mapthing_t *G_FindTeamStart(INT32 playernum);
mapthing_t *G_FindBattleStart(INT32 playernum);
mapthing_t *G_FindRaceStart(INT32 playernum);
mapthing_t *G_FindPodiumStart(INT32 playernum);
mapthing_t *G_FindMapStart(INT32 playernum);
void G_MovePlayerToSpawnOrStarpost(INT32 playernum);
void G_SpawnPlayer(INT32 playernum);
@ -164,6 +165,7 @@ void G_SpawnPlayer(INT32 playernum);
// A normal game starts at map 1, but a warp test can start elsewhere
void G_DeferedInitNew(boolean pencoremode, INT32 map, INT32 pickedchar,
UINT8 ssplayers, boolean FLS);
void G_DoLoadLevelEx(boolean resetplayer, gamestate_t newstate);
void G_DoLoadLevel(boolean resetplayer);
void G_StartTitleCard(void);
@ -250,8 +252,6 @@ void G_LoadGameSettings(void);
void G_SetGameModified(boolean silent, boolean major);
void G_SetUsedCheats(void);
void G_SetGamestate(gamestate_t newstate);
// Gamedata record shit
void G_AllocMainRecordData(INT16 i);
void G_ClearRecords(void);

View file

@ -36,6 +36,7 @@ typedef enum
GS_CREDITS, // credit sequence
GS_EVALUATION, // Evaluation at the end of a game.
GS_GAMEEND, // game end sequence - "did you get all those chaos emeralds?"
GS_CEREMONY, // RR: Podium sequence
// Hardcoded fades or other fading methods
GS_INTRO, // introduction
@ -58,10 +59,13 @@ typedef enum
} gameaction_t;
extern gamestate_t gamestate;
extern UINT8 titlemapinaction;
extern boolean titlemapinaction;
extern UINT8 ultimatemode; // was sk_insane
extern gameaction_t gameaction;
void G_SetGamestate(gamestate_t newstate);
boolean G_GamestateUsesLevel(void);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -21281,7 +21281,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
10, // mass
0, // damage
sfx_None, // activesound
MF_NOTHINK|MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
S_NULL // raisestate
},

View file

@ -20,6 +20,7 @@
#include "k_boss.h" // bossinfo.valid
#include "p_spec.h"
#include "k_objects.h"
#include "k_rank.h"
// Battle overtime info
struct battleovertime battleovertime;

View file

@ -28,7 +28,7 @@
#include "r_things.h" // numskins
#include "k_race.h" // finishBeamLine
#include "m_perfstats.h"
#include "k_podium.h"
/*--------------------------------------------------
boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *p)
@ -261,6 +261,9 @@ void K_UpdateMatchRaceBots(void)
--------------------------------------------------*/
boolean K_PlayerUsesBotMovement(player_t *player)
{
if (K_PodiumSequence() == true)
return true;
if (player->exiting)
return true;
@ -1264,6 +1267,63 @@ static INT32 K_HandleBotReverse(player_t *player, ticcmd_t *cmd, botprediction_t
return turnamt;
}
/*--------------------------------------------------
static void K_BotPodiumTurning(player_t *player, ticcmd_t *cmd)
Calculates bot turning for the podium cutscene.
--------------------------------------------------*/
static void K_BotPodiumTurning(player_t *player, ticcmd_t *cmd)
{
const angle_t destAngle = R_PointToAngle2(
player->mo->x, player->mo->y,
player->currentwaypoint->mobj->x, player->currentwaypoint->mobj->y
);
const INT32 delta = AngleDeltaSigned(destAngle, player->mo->angle);
const INT16 handling = K_GetKartTurnValue(player, KART_FULLTURN);
fixed_t mul = FixedDiv(delta, (angle_t)(handling << TICCMD_REDUCE));
if (mul > FRACUNIT)
{
mul = FRACUNIT;
}
if (mul < -FRACUNIT)
{
mul = -FRACUNIT;
}
cmd->turning = FixedMul(mul, KART_FULLTURN);
}
/*--------------------------------------------------
static void K_BuildBotPodiumTiccmd(player_t *player, ticcmd_t *cmd)
Calculates all bot movement for the podium cutscene.
--------------------------------------------------*/
static void K_BuildBotPodiumTiccmd(player_t *player, ticcmd_t *cmd)
{
if (player->currentwaypoint == NULL)
{
// We've reached the end of our path.
// Simply stop moving.
return;
}
if (K_GetWaypointIsSpawnpoint(player->currentwaypoint) == false)
{
// Hacky flag reuse: slow down before reaching your podium stand.
cmd->forwardmove = MAXPLMOVE * 3 / 4;
}
else
{
cmd->forwardmove = MAXPLMOVE;
}
cmd->buttons |= BT_ACCELERATE;
K_BotPodiumTurning(player, cmd);
}
/*--------------------------------------------------
void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd)
@ -1282,7 +1342,9 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd)
// Remove any existing controls
memset(cmd, 0, sizeof(ticcmd_t));
if (gamestate != GS_LEVEL || !player->mo || player->spectator)
if (player->mo == NULL
|| player->spectator == true
|| G_GamestateUsesLevel() == false)
{
// Not in the level.
return;
@ -1294,6 +1356,12 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd)
return;
}
if (K_PodiumSequence() == true)
{
K_BuildBotPodiumTiccmd(player, cmd);
return;
}
if (!(gametyperules & GTR_BOTS) // No bot behaviors
|| K_GetNumWaypoints() == 0 // No waypoints
|| leveltime <= introtime // During intro camera
@ -1556,6 +1624,9 @@ void K_UpdateBotGameplayVars(player_t *player)
{
const line_t *botController;
player->botvars.controller = UINT16_MAX;
player->botvars.rubberband = FRACUNIT;
if (gamestate != GS_LEVEL || !player->mo)
{
// Not in the level.

View file

@ -13,6 +13,7 @@
#include "g_game.h" // Sink snipe print
#include "k_objects.h"
#include "k_roulette.h"
#include "k_podium.h"
angle_t K_GetCollideAngle(mobj_t *t1, mobj_t *t2)
{
@ -811,6 +812,12 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2)
boolean stungT1 = false;
boolean stungT2 = false;
if (K_PodiumSequence() == true)
{
// Always regular bumps, no ring toss.
return false;
}
// Clash instead of damage if both parties have any of these conditions
t1Condition = (K_IsBigger(t1, t2) == true)
|| (t1->player->invincibilitytimer > 0)

View file

@ -114,6 +114,24 @@ UINT8 K_BotDefaultSkin(void)
return (UINT8)defaultbotskin;
}
/*--------------------------------------------------
UINT8 K_GetGPPlayerCount(UINT8 humans)
See header file for description.
--------------------------------------------------*/
UINT8 K_GetGPPlayerCount(UINT8 humans)
{
UINT8 playerCount = 8;
if (humans > 2)
{
// Add 3 bots per player beyond 2P
playerCount += (humans - 2) * 3;
}
return playerCount;
}
/*--------------------------------------------------
void K_InitGrandPrixBots(void)
@ -198,12 +216,7 @@ void K_InitGrandPrixBots(void)
}
}
if (numplayers > 2)
{
// Add 3 bots per player beyond 2P
playercount += (numplayers-2) * 3;
}
playercount = K_GetGPPlayerCount(numplayers);
wantedbots = playercount - numplayers;
// Create rival list

View file

@ -15,14 +15,18 @@
#include "doomdef.h"
#include "doomstat.h"
#include "k_rank.h" // gpRank_t
#ifdef __cplusplus
extern "C" {
#endif
#define GPEVENT_NONE 0
#define GPEVENT_BONUS 1
#define GPEVENT_SPECIAL 2
typedef enum
{
GPEVENT_NONE = 0,
GPEVENT_BONUS,
GPEVENT_SPECIAL,
} gpEvent_e;
extern struct grandprixinfo
{
@ -34,10 +38,10 @@ extern struct grandprixinfo
boolean masterbots; ///< If true, all bots should be max difficulty (Master Mode)
boolean initalize; ///< If true, we need to initialize a new session.
boolean wonround; ///< If false, then we retry the map instead of going to the next.
UINT8 eventmode; ///< See GPEVENT_ constants
gpEvent_e eventmode; ///< Special event mode, bots are set to spectate and a special gametype is played
gpRank_t rank; ///< Struct containing grading information. (See also: k_rank.h)
} grandprixinfo;
/*--------------------------------------------------
UINT8 K_BotStartingDifficulty(SINT8 value);
@ -81,6 +85,23 @@ INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers);
UINT8 K_BotDefaultSkin(void);
/*--------------------------------------------------
UINT8 K_GetGPPlayerCount(UINT8 humans)
Counts the number of total players,
including humans and bots, to put into
a GP session.
Input Arguments:-
humans - Number of human players.
Return:-
Number of both human players and CPU.
--------------------------------------------------*/
UINT8 K_GetGPPlayerCount(UINT8 humans);
/*--------------------------------------------------
void K_InitGrandPrixBots(void);
@ -170,6 +191,7 @@ void K_PlayerLoseLife(player_t *player);
boolean K_CanChangeRules(boolean allowdemos);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -37,6 +37,8 @@
#include "r_fps.h"
#include "m_random.h"
#include "k_roulette.h"
#include "k_bot.h"
#include "k_rank.h"
//{ Patch Definitions
static patch_t *kp_nodraw;
@ -3005,7 +3007,7 @@ static void K_drawKartPlayerCheck(void)
return;
}
if (stplyr->spectator || stplyr->awayviewtics)
if (stplyr->spectator || stplyr->awayview.tics)
{
return;
}
@ -3254,7 +3256,7 @@ static void K_drawKartNameTags(void)
return;
}
if (stplyr->awayviewtics)
if (stplyr->awayview.tics)
{
return;
}
@ -4815,6 +4817,58 @@ static void K_DrawWaypointDebugger(void)
}
}
static void K_DrawGPRankDebugger(void)
{
gp_rank_e grade = GRADE_E;
char gradeChar = '?';
if (cv_debugrank.value == 0)
{
return;
}
if (stplyr != &players[displayplayers[0]]) // only for p1
{
return;
}
if (grandprixinfo.gp == false)
{
return;
}
grade = K_CalculateGPGrade(&grandprixinfo.rank);
V_DrawThinString(0, 0, V_SNAPTOTOP|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE,
va("POS: %d / %d", grandprixinfo.rank.position, RANK_NEUTRAL_POSITION));
V_DrawThinString(0, 10, V_SNAPTOTOP|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE,
va("PTS: %d / %d", grandprixinfo.rank.winPoints, grandprixinfo.rank.totalPoints));
V_DrawThinString(0, 20, V_SNAPTOTOP|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE,
va("LAPS: %d / %d", grandprixinfo.rank.laps, grandprixinfo.rank.totalLaps));
V_DrawThinString(0, 30, V_SNAPTOTOP|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE,
va("CONTINUES: %d", grandprixinfo.rank.continuesUsed));
V_DrawThinString(0, 40, V_SNAPTOTOP|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE,
va("CAPSULES: %d / %d", grandprixinfo.rank.capsules, grandprixinfo.rank.totalCapsules));
V_DrawThinString(0, 50, V_SNAPTOTOP|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE,
va("RINGS: %d / %d", grandprixinfo.rank.rings, grandprixinfo.rank.totalRings));
V_DrawThinString(0, 60, V_SNAPTOTOP|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE,
va("EMERALD: %s", (grandprixinfo.rank.specialWon == true) ? "YES" : "NO"));
switch (grade)
{
case GRADE_E: { gradeChar = 'E'; break; }
case GRADE_D: { gradeChar = 'D'; break; }
case GRADE_C: { gradeChar = 'C'; break; }
case GRADE_B: { gradeChar = 'B'; break; }
case GRADE_A: { gradeChar = 'A'; break; }
case GRADE_S: { gradeChar = 'S'; break; }
default: { break; }
}
V_DrawThinString(0, 80, V_SNAPTOTOP|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE|V_YELLOWMAP,
va(" ** FINAL GRADE: %c", gradeChar));
}
void K_drawKartHUD(void)
{
boolean islonesome = false;
@ -5086,4 +5140,5 @@ void K_drawKartHUD(void)
K_DrawWaypointDebugger();
K_DrawDirectorDebugger();
K_DrawGPRankDebugger();
}

View file

@ -43,6 +43,7 @@
#include "k_boss.h"
#include "k_specialstage.h"
#include "k_roulette.h"
#include "k_podium.h"
// SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H:
// gamespeed is cc (0 for easy, 1 for normal, 2 for hard)
@ -109,6 +110,12 @@ void K_TimerInit(void)
boolean domodeattack = ((modeattacking != ATTACKING_NONE)
|| (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE));
if (K_PodiumSequence() == true)
{
// Leave it alone for podium
return;
}
// Rooooooolllling staaaaaaart
if ((gametyperules & (GTR_ROLLINGSTART|GTR_CIRCUIT)) == (GTR_ROLLINGSTART|GTR_CIRCUIT))
{
@ -340,6 +347,7 @@ void K_RegisterKartStuff(void)
CV_RegisterVar(&cv_kartdebugnodes);
CV_RegisterVar(&cv_kartdebugcolorize);
CV_RegisterVar(&cv_kartdebugdirector);
CV_RegisterVar(&cv_debugrank);
CV_RegisterVar(&cv_spbtest);
CV_RegisterVar(&cv_gptest);
CV_RegisterVar(&cv_capsuletest);
@ -354,6 +362,12 @@ boolean K_IsPlayerLosing(player_t *player)
INT32 winningpos = 1;
UINT8 i, pcount = 0;
if (K_PodiumSequence() == true)
{
// Need to be in top 3 to win.
return (player->position > 3);
}
if (player->pflags & PF_NOCONTEST)
return true;
@ -1226,6 +1240,11 @@ static boolean K_TryDraft(player_t *player, mobj_t *dest, fixed_t minDist, fixed
angle_t yourangle, theirangle, diff;
#endif
if (K_PodiumSequence() == true)
{
return false;
}
#ifndef EASYDRAFTTEST
// Don't draft on yourself :V
if (dest->player && dest->player == player)
@ -3215,27 +3234,42 @@ fixed_t K_GetKartSpeedFromStat(UINT8 kartspeed)
fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower, boolean dorubberband)
{
const boolean mobjValid = (player->mo != NULL && P_MobjWasRemoved(player->mo) == false);
fixed_t finalspeed = K_GetKartSpeedFromStat(player->kartspeed);
fixed_t finalspeed = 0;
if (gametyperules & GTR_BUMPERS && player->bumpers <= 0)
finalspeed = 3 * finalspeed / 2;
if (player->spheres > 0)
if (K_PodiumSequence() == true)
{
fixed_t sphereAdd = (FRACUNIT/40); // 100% at max
finalspeed = FixedMul(finalspeed, FRACUNIT + (sphereAdd * player->spheres));
// Make 1st reach their podium faster!
finalspeed = K_GetKartSpeedFromStat(max(1, 11 - (player->position * 3)));
// Ignore other speed boosts.
doboostpower = dorubberband = false;
}
if (K_PlayerUsesBotMovement(player))
else
{
// Increase bot speed by 1-10% depending on difficulty
fixed_t add = (player->botvars.difficulty * (FRACUNIT/10)) / DIFFICULTBOT;
finalspeed = FixedMul(finalspeed, FRACUNIT + add);
finalspeed = K_GetKartSpeedFromStat(player->kartspeed);
if (player->bot && player->botvars.rival)
if (gametyperules & GTR_BUMPERS && player->bumpers <= 0)
{
// +10% top speed for the rival
finalspeed = FixedMul(finalspeed, 11*FRACUNIT/10);
finalspeed = 3 * finalspeed / 2;
}
if (player->spheres > 0)
{
fixed_t sphereAdd = (FRACUNIT/40); // 100% at max
finalspeed = FixedMul(finalspeed, FRACUNIT + (sphereAdd * player->spheres));
}
if (K_PlayerUsesBotMovement(player))
{
// Increase bot speed by 1-10% depending on difficulty
fixed_t add = (player->botvars.difficulty * (FRACUNIT/10)) / DIFFICULTBOT;
finalspeed = FixedMul(finalspeed, FRACUNIT + add);
if (player->bot && player->botvars.rival)
{
// +10% top speed for the rival
finalspeed = FixedMul(finalspeed, 11*FRACUNIT/10);
}
}
}
@ -3263,16 +3297,32 @@ fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower, boolean dorubberb
fixed_t K_GetKartAccel(player_t *player)
{
fixed_t k_accel = 121;
UINT8 stat = (9 - player->kartspeed);
k_accel += 17 * (9 - player->kartspeed); // 121 - 257
if (K_PodiumSequence() == true)
{
// Normalize to Metal's accel
stat = 1;
}
k_accel += 17 * stat; // 121 - 257
if (K_PodiumSequence() == true)
{
return FixedMul(k_accel, FRACUNIT / 4);
}
// karma bomb gets 2x acceleration
if ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0)
{
k_accel *= 2;
}
// Marble Garden Top gets 1200% accel
if (player->curshield == KSHIELD_TOP)
{
k_accel *= 12;
}
return FixedMul(k_accel, (FRACUNIT + player->accelboost) / 4);
}
@ -3403,7 +3453,7 @@ fixed_t K_GetNewSpeed(player_t *player)
// Don't calculate the acceleration as ever being above top speed
if (oldspeed > p_speed)
oldspeed = p_speed;
newspeed = FixedDiv(FixedDiv(FixedMul(oldspeed, accelmax - p_accel) + FixedMul(p_speed, p_accel), accelmax), ORIG_FRICTION);
newspeed = FixedDiv(FixedDiv(FixedMul(oldspeed, accelmax - p_accel) + FixedMul(p_speed, p_accel), accelmax), K_PlayerBaseFriction(ORIG_FRICTION));
finalspeed = newspeed - oldspeed;
@ -6975,6 +7025,13 @@ static void K_UpdateEngineSounds(player_t *player)
INT32 targetsnd = 0;
INT32 i;
if (leveltime < 8 || player->spectator || gamestate != GS_LEVEL)
{
// Silence the engines, and reset sound number while we're at it.
player->karthud[khud_enginesnd] = 0;
return;
}
s = (player->kartspeed - 1) / 3;
w = (player->kartweight - 1) / 3;
@ -6987,13 +7044,6 @@ static void K_UpdateEngineSounds(player_t *player)
class = s + (3*w);
if (leveltime < 8 || player->spectator)
{
// Silence the engines, and reset sound number while we're at it.
player->karthud[khud_enginesnd] = 0;
return;
}
#if 0
if ((leveltime % 8) != ((player-players) % 8)) // Per-player offset, to make engines sound distinct!
#else
@ -8471,6 +8521,12 @@ static waypoint_t *K_GetPlayerNextWaypoint(player_t *player)
--------------------------------------------------*/
void K_UpdateDistanceFromFinishLine(player_t *const player)
{
if (K_PodiumSequence() == true)
{
K_UpdatePodiumWaypoints(player);
return;
}
if ((player != NULL) && (player->mo != NULL))
{
waypoint_t *finishline = K_GetFinishLineWaypoint();
@ -8837,6 +8893,15 @@ INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue)
p_speed = min(currentSpeed, (p_maxspeed * 2));
}
if (K_PodiumSequence() == true)
{
// Normalize turning for podium
weightadjust = FixedDiv((p_maxspeed * 3), (p_maxspeed * 3) + (2 * FRACUNIT));
turnfixed = FixedMul(turnfixed, weightadjust);
turnfixed *= 2;
return (turnfixed / FRACUNIT);
}
weightadjust = FixedDiv((p_maxspeed * 3) - p_speed, (p_maxspeed * 3) + (player->kartweight * FRACUNIT));
if (K_PlayerUsesBotMovement(player))
@ -9263,64 +9328,79 @@ void K_KartUpdatePosition(player_t *player)
return;
}
for (i = 0; i < MAXPLAYERS; i++)
if (K_PodiumSequence() == true)
{
if (!playeringame[i] || players[i].spectator || !players[i].mo)
continue;
position = K_GetPodiumPosition(player);
realplayers++;
if (gametyperules & GTR_CIRCUIT)
for (i = 0; i < MAXPLAYERS; i++)
{
if (player->exiting) // End of match standings
{
// Only time matters
if (players[i].realtime < player->realtime)
position++;
}
else
{
// I'm a lap behind this player OR
// My distance to the finish line is higher, so I'm behind
if ((players[i].laps > player->laps)
|| (players[i].distancetofinish < player->distancetofinish))
{
position++;
}
}
if (!playeringame[i] || players[i].spectator || !players[i].mo)
continue;
realplayers++;
}
else
}
else
{
for (i = 0; i < MAXPLAYERS; i++)
{
if (player->exiting) // End of match standings
{
// Only score matters
if (players[i].roundscore > player->roundscore)
position++;
}
else
{
UINT8 myEmeralds = K_NumEmeralds(player);
UINT8 yourEmeralds = K_NumEmeralds(&players[i]);
if (!playeringame[i] || players[i].spectator || !players[i].mo)
continue;
// First compare all points
if (players[i].roundscore > player->roundscore)
realplayers++;
if (gametyperules & GTR_CIRCUIT)
{
if (player->exiting) // End of match standings
{
position++;
// Only time matters
if (players[i].realtime < player->realtime)
position++;
}
else if (players[i].roundscore == player->roundscore)
else
{
// Emeralds are a tie breaker
if (yourEmeralds > myEmeralds)
// I'm a lap behind this player OR
// My distance to the finish line is higher, so I'm behind
if ((players[i].laps > player->laps)
|| (players[i].distancetofinish < player->distancetofinish))
{
position++;
}
else if (yourEmeralds == myEmeralds)
}
}
else
{
if (player->exiting) // End of match standings
{
// Only score matters
if (players[i].roundscore > player->roundscore)
position++;
}
else
{
UINT8 myEmeralds = K_NumEmeralds(player);
UINT8 yourEmeralds = K_NumEmeralds(&players[i]);
// First compare all points
if (players[i].roundscore > player->roundscore)
{
// Bumpers are the second tier tie breaker
if (players[i].bumpers > player->bumpers)
position++;
}
else if (players[i].roundscore == player->roundscore)
{
// Emeralds are a tie breaker
if (yourEmeralds > myEmeralds)
{
position++;
}
else if (yourEmeralds == myEmeralds)
{
// Bumpers are the second tier tie breaker
if (players[i].bumpers > player->bumpers)
{
position++;
}
}
}
}
}
@ -9378,6 +9458,29 @@ void K_KartUpdatePosition(player_t *player)
player->position = position;
}
void K_UpdateAllPlayerPositions(void)
{
INT32 i;
// First loop: Ensure all players' distance to the finish line are all accurate
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo))
{
K_UpdateDistanceFromFinishLine(&players[i]);
}
}
// Second loop: Ensure all player positions reflect everyone's distances
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo))
{
K_KartUpdatePosition(&players[i]);
}
}
}
//
// K_StripItems
//
@ -9917,18 +10020,38 @@ static void K_AirFailsafe(player_t *player)
}
}
//
// K_PlayerBaseFriction
//
fixed_t K_PlayerBaseFriction(fixed_t original)
{
fixed_t frict = original;
if (K_PodiumSequence() == true)
{
frict -= FRACUNIT >> 4;
}
if (frict > FRACUNIT) { frict = FRACUNIT; }
if (frict < 0) { frict = 0; }
return frict;
}
//
// K_AdjustPlayerFriction
//
void K_AdjustPlayerFriction(player_t *player)
{
fixed_t prevfriction = player->mo->friction;
const fixed_t prevfriction = K_PlayerBaseFriction(player->mo->friction);
if (P_IsObjectOnGround(player->mo) == false)
{
return;
}
player->mo->friction = prevfriction;
// Reduce friction after hitting a spring
if (player->tiregrease)
{

View file

@ -139,6 +139,7 @@ INT32 K_GetKartDriftSparkValueForStage(player_t *player, UINT8 stage);
void K_SpawnDriftBoostExplosion(player_t *player, int stage);
void K_SpawnDriftElectricSparks(player_t *player, int color, boolean shockwave);
void K_KartUpdatePosition(player_t *player);
void K_UpdateAllPlayerPositions(void);
SINT8 K_GetTotallyRandomResult(UINT8 useodds);
mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT8 amount);
void K_DropItems(player_t *player);
@ -176,6 +177,7 @@ fixed_t K_3dKartMovement(player_t *player);
boolean K_PlayerEBrake(player_t *player);
SINT8 K_Sliptiding(player_t *player);
boolean K_FastFallBounce(player_t *player);
fixed_t K_PlayerBaseFriction(fixed_t original);
void K_AdjustPlayerFriction(player_t *player);
void K_MoveKartPlayer(player_t *player, boolean onground);
void K_CheckSpectateStatus(void);

View file

@ -201,6 +201,23 @@ boolean M_PrevOpt(void)
return true;
}
static boolean M_GamestateCanOpenMenu(void)
{
switch (gamestate)
{
case GS_INTRO:
case GS_CUTSCENE:
case GS_GAMEEND:
case GS_CREDITS:
case GS_EVALUATION:
case GS_CEREMONY:
return false;
default:
return true;
}
}
//
// M_Responder
//
@ -208,9 +225,9 @@ boolean M_Responder(event_t *ev)
{
menuKey = -1;
if (dedicated || (demo.playback && demo.title)
|| gamestate == GS_INTRO || gamestate == GS_CUTSCENE || gamestate == GS_GAMEEND
|| gamestate == GS_CREDITS || gamestate == GS_EVALUATION)
if (dedicated
|| (demo.playback && demo.title)
|| M_GamestateCanOpenMenu() == false)
{
return false;
}

504
src/k_podium.c Normal file
View file

@ -0,0 +1,504 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) by Sally "TehRealSalt" Cochenour
// Copyright (C) by Kart Krew
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file k_podium.c
/// \brief Grand Prix podium cutscene
#include "k_podium.h"
#include "doomdef.h"
#include "doomstat.h"
#include "d_main.h"
#include "d_netcmd.h"
#include "f_finale.h"
#include "g_game.h"
#include "hu_stuff.h"
#include "r_local.h"
#include "s_sound.h"
#include "i_time.h"
#include "i_video.h"
#include "v_video.h"
#include "w_wad.h"
#include "z_zone.h"
#include "i_system.h"
#include "i_threads.h"
#include "dehacked.h"
#include "g_input.h"
#include "console.h"
#include "m_random.h"
#include "m_misc.h" // moviemode functionality
#include "y_inter.h"
#include "m_cond.h"
#include "p_local.h"
#include "p_setup.h"
#include "st_stuff.h" // hud hiding
#include "fastcmp.h"
#include "lua_hud.h"
#include "lua_hook.h"
#include "k_menu.h"
#include "k_grandprix.h"
#include "k_rank.h"
static struct podiumData_s
{
boolean ranking;
gpRank_t rank;
gp_rank_e grade;
UINT8 state;
UINT8 delay;
UINT8 fade;
} podiumData;
#define PODIUM_STATES (9) // TODO: enum when this actually gets made
/*--------------------------------------------------
boolean K_PodiumSequence(void)
See header file for description.
--------------------------------------------------*/
boolean K_PodiumSequence(void)
{
return (gamestate == GS_CEREMONY);
}
/*--------------------------------------------------
UINT8 K_GetPodiumPosition(player_t *player)
See header file for description.
--------------------------------------------------*/
UINT8 K_GetPodiumPosition(player_t *player)
{
UINT8 position = 1;
INT32 i;
for (i = 0; i < MAXPLAYERS; i++)
{
player_t *other = NULL;
if (playeringame[i] == false)
{
continue;
}
other = &players[i];
if (other->spectator == true)
{
continue;
}
if (other->score > player->score)
{
// Final score is the important part.
position++;
}
else if (other->score == player->score)
{
if (other->bot == false && player->bot == true)
{
// Bots are never as important as players.
position++;
}
else if (i < player - players)
{
// Port priority is the final tie breaker.
position++;
}
}
}
return position;
}
/*--------------------------------------------------
static void K_SetPodiumWaypoint(player_t *const player, waypoint_t *const waypoint)
Changes the player's current and next waypoints, for
use during the podium sequence.
Input Arguments:-
player - The player to update the waypoints of.
waypoint - The new current waypoint.
Return:-
None
--------------------------------------------------*/
static void K_SetPodiumWaypoint(player_t *const player, waypoint_t *const waypoint)
{
// Set the new waypoint.
player->currentwaypoint = waypoint;
if ((waypoint == NULL)
|| (waypoint->nextwaypoints == NULL)
|| (waypoint->numnextwaypoints == 0U))
{
// No waypoint, or no next waypoint.
player->nextwaypoint = NULL;
return;
}
// Simply use the first available next waypoint.
// No need for split paths in these cutscenes.
player->nextwaypoint = waypoint->nextwaypoints[0];
}
/*--------------------------------------------------
void K_InitializePodiumWaypoint(player_t *const player)
See header file for description.
--------------------------------------------------*/
void K_InitializePodiumWaypoint(player_t *const player)
{
if ((player != NULL) && (player->mo != NULL))
{
player->position = K_GetPodiumPosition(player);
if (player->position > 0 && player->position <= MAXPLAYERS)
{
// Initialize our first waypoint to the one that
// matches our position.
K_SetPodiumWaypoint(player, K_GetWaypointFromID(player->position));
}
else
{
// None does, so remove it if we happen to have one.
K_SetPodiumWaypoint(player, NULL);
}
}
}
/*--------------------------------------------------
void K_UpdatePodiumWaypoints(player_t *const player)
See header file for description.
--------------------------------------------------*/
void K_UpdatePodiumWaypoints(player_t *const player)
{
if ((player != NULL) && (player->mo != NULL))
{
if (player->currentwaypoint != NULL)
{
const fixed_t xydist = P_AproxDistance(
player->mo->x - player->currentwaypoint->mobj->x,
player->mo->y - player->currentwaypoint->mobj->y
);
const fixed_t xyzdist = P_AproxDistance(
xydist,
player->mo->z - player->currentwaypoint->mobj->z
);
//const fixed_t speed = P_AproxDistance(player->mo->momx, player->mo->momy);
if (xyzdist <= player->mo->radius + player->currentwaypoint->mobj->radius)
{
// Reached waypoint, go to the next waypoint.
K_SetPodiumWaypoint(player, player->nextwaypoint);
}
}
}
}
/*--------------------------------------------------
boolean K_StartCeremony(void)
See header file for description.
--------------------------------------------------*/
boolean K_StartCeremony(void)
{
INT32 podiumMapNum = nummapheaders;
INT32 i;
if (grandprixinfo.gp == false)
{
return false;
}
if (podiummap
&& ((podiumMapNum = G_MapNumber(podiummap)) < nummapheaders)
&& mapheaderinfo[podiumMapNum]
&& mapheaderinfo[podiumMapNum]->lumpnum != LUMPERROR)
{
P_SetTarget(&titlemapcam.mobj, NULL);
gamemap = podiumMapNum+1;
maptol = mapheaderinfo[gamemap-1]->typeoflevel;
globalweather = mapheaderinfo[gamemap-1]->weather;
// Make sure all of the GAME OVER'd players can spawn
// and be present for the podium
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] && !players[i].spectator && !players[i].bot)
{
players[i].lives = max(1, players[i].lives);
}
}
G_SetGametype(GT_RACE);
G_DoLoadLevelEx(false, GS_CEREMONY);
r_splitscreen = 0; // Only one screen for the ceremony
R_ExecuteSetViewSize();
return true;
}
return false;
}
/*--------------------------------------------------
void K_FinishCeremony(void)
See header file for description.
--------------------------------------------------*/
void K_FinishCeremony(void)
{
if (K_PodiumSequence() == false)
{
return;
}
podiumData.ranking = true;
}
/*--------------------------------------------------
void K_ResetCeremony(void)
See header file for description.
--------------------------------------------------*/
void K_ResetCeremony(void)
{
memset(&podiumData, 0, sizeof(struct podiumData_s));
if (K_PodiumSequence() == false)
{
return;
}
podiumData.rank = grandprixinfo.rank;
podiumData.grade = K_CalculateGPGrade(&podiumData.rank);
}
/*--------------------------------------------------
void K_CeremonyTicker(boolean run)
See header file for description.
--------------------------------------------------*/
void K_CeremonyTicker(boolean run)
{
// don't trigger if doing anything besides idling
if (gameaction != ga_nothing || gamestate != GS_CEREMONY)
{
return;
}
P_TickAltView(&titlemapcam);
if (titlemapcam.mobj != NULL)
{
camera[0].x = titlemapcam.mobj->x;
camera[0].y = titlemapcam.mobj->y;
camera[0].z = titlemapcam.mobj->z;
camera[0].angle = titlemapcam.mobj->angle;
camera[0].aiming = titlemapcam.mobj->pitch;
camera[0].subsector = titlemapcam.mobj->subsector;
}
if (podiumData.ranking == false)
{
return;
}
if (run == true)
{
if (podiumData.fade < 16)
{
podiumData.fade++;
}
else
{
if (podiumData.state < PODIUM_STATES)
{
podiumData.delay++;
if (podiumData.delay > TICRATE/2)
{
podiumData.state++;
podiumData.delay = 0;
}
}
}
}
}
/*--------------------------------------------------
boolean K_CeremonyResponder(event_t *event)
See header file for description.
--------------------------------------------------*/
boolean K_CeremonyResponder(event_t *event)
{
INT32 key = event->data1;
if (podiumData.ranking == false || podiumData.state < PODIUM_STATES)
{
return false;
}
// remap virtual keys (mouse & joystick buttons)
switch (key)
{
case KEY_MOUSE1:
key = KEY_ENTER;
break;
case KEY_MOUSE1 + 1:
key = KEY_BACKSPACE;
break;
case KEY_JOY1:
case KEY_JOY1 + 2:
key = KEY_ENTER;
break;
case KEY_JOY1 + 3:
key = 'n';
break;
case KEY_JOY1 + 1:
key = KEY_BACKSPACE;
break;
case KEY_HAT1:
key = KEY_UPARROW;
break;
case KEY_HAT1 + 1:
key = KEY_DOWNARROW;
break;
case KEY_HAT1 + 2:
key = KEY_LEFTARROW;
break;
case KEY_HAT1 + 3:
key = KEY_RIGHTARROW;
break;
}
if (event->type != ev_keydown)
{
return false;
}
if (key != KEY_ESCAPE && key != KEY_ENTER && key != KEY_BACKSPACE)
{
return false;
}
return true;
}
/*--------------------------------------------------
void K_CeremonyDrawer(void)
See header file for description.
--------------------------------------------------*/
void K_CeremonyDrawer(void)
{
if (podiumData.ranking == true)
{
char gradeChar = '?';
INT32 x = 64;
INT32 y = 48;
INT32 i;
switch (podiumData.grade)
{
case GRADE_E: { gradeChar = 'E'; break; }
case GRADE_D: { gradeChar = 'D'; break; }
case GRADE_C: { gradeChar = 'C'; break; }
case GRADE_B: { gradeChar = 'B'; break; }
case GRADE_A: { gradeChar = 'A'; break; }
case GRADE_S: { gradeChar = 'S'; break; }
default: { break; }
}
V_DrawFadeScreen(0xFF00, podiumData.fade);
for (i = 0; i <= podiumData.state; i++)
{
switch (i)
{
case 1:
{
V_DrawString(x, y, V_ALLOWLOWERCASE,
va("POS: %d / %d", podiumData.rank.position, RANK_NEUTRAL_POSITION)
);
break;
}
case 2:
{
V_DrawString(x, y, V_ALLOWLOWERCASE,
va("PTS: %d / %d", podiumData.rank.winPoints, podiumData.rank.totalPoints)
);
break;
}
case 3:
{
V_DrawString(x, y, V_ALLOWLOWERCASE,
va("LAPS: %d / %d", podiumData.rank.laps, podiumData.rank.totalLaps)
);
break;
}
case 4:
{
V_DrawString(x, y, V_ALLOWLOWERCASE,
va("CONTINUES: %d", podiumData.rank.continuesUsed)
);
break;
}
case 5:
{
V_DrawString(x, y, V_ALLOWLOWERCASE,
va("CAPSULES: %d / %d", podiumData.rank.capsules, podiumData.rank.totalCapsules)
);
break;
}
case 6:
{
V_DrawString(x, y, V_ALLOWLOWERCASE,
va("RINGS: %d / %d", podiumData.rank.rings, podiumData.rank.totalRings)
);
break;
}
case 7:
{
V_DrawString(x, y, V_ALLOWLOWERCASE,
va("EMERALD: %s", (podiumData.rank.specialWon == true) ? "YES" : "NO")
);
break;
}
case 8:
{
V_DrawString(x, y + 10, V_YELLOWMAP|V_ALLOWLOWERCASE,
va(" ** FINAL GRADE: %c", gradeChar)
);
break;
}
case 9:
{
V_DrawThinString(2, BASEVIDHEIGHT - 10, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE,
"Press some button type deal to continue"
);
break;
}
}
y += 10;
}
}
if (timeinmap < 16)
{
// Level fade-in
V_DrawCustomFadeScreen(((levelfadecol == 0) ? "FADEMAP1" : "FADEMAP0"), 31-(timeinmap*2));
}
}

178
src/k_podium.h Normal file
View file

@ -0,0 +1,178 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) by Sally "TehRealSalt" Cochenour
// Copyright (C) by Kart Krew
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file k_podium.h
/// \brief Grand Prix podium cutscene
#ifndef __K_PODIUM__
#define __K_PODIUM__
#include "doomtype.h"
#include "d_event.h"
#include "p_mobj.h"
#ifdef __cplusplus
extern "C" {
#endif
/*--------------------------------------------------
boolean K_PodiumSequence(void);
Returns whenver or not we are in the podium
cutscene mode.
Input Arguments:-
N/A
Return:-
true if we're in GS_CEREMONY, otherwise false.
--------------------------------------------------*/
boolean K_PodiumSequence(void);
/*--------------------------------------------------
UINT8 K_GetPodiumPosition(player_t *player);
Calculates what the player's position would
be at the final standings.
Input Arguments:-
player - The player to do the calculation for.
Return:-
The player's final position, as a number
between 1 and MAXPLAYERS.
--------------------------------------------------*/
UINT8 K_GetPodiumPosition(player_t *player);
/*--------------------------------------------------
void K_InitializePodiumWaypoint(player_t *const player);
Sets a bot's current waypoint to one matching
their final podium position.
Input Arguments:-
player - The podium bot to update.
Return:-
N/A
--------------------------------------------------*/
void K_InitializePodiumWaypoint(player_t *const player);
/*--------------------------------------------------
void K_UpdatePodiumWaypoints(player_t *const player);
Helps a bot move along a predetermined path by
updating their current and next waypoints as
they move. Intended for the podium sequence.
Input Arguments:-
player - The podium bot to update.
Return:-
N/A
--------------------------------------------------*/
void K_UpdatePodiumWaypoints(player_t *const player);
/*--------------------------------------------------
boolean K_StartCeremony(void);
Loads the podium map and changes the gamestate
to the podium cutscene mode.
Input Arguments:-
N/A
Return:-
true if successful, otherwise false. Can fail
if there is no podium map defined.
--------------------------------------------------*/
boolean K_StartCeremony(void);
/*--------------------------------------------------
void K_FinishCeremony(void);
Called at the end of the podium cutscene,
displays the ranking screen and starts
accepting input.
--------------------------------------------------*/
void K_FinishCeremony(void);
/*--------------------------------------------------
void K_ResetCeremony(void);
Called on level load, to reset all of the
podium variables.
--------------------------------------------------*/
void K_ResetCeremony(void);
/*--------------------------------------------------
void K_CeremonyTicker(boolean run);
Ticker function to be ran during the podium
cutscene mode gamestate. Handles updating
the camera.
Input Arguments:-
run - Set to true when we're running a
new game frame.
Return:-
N/A
--------------------------------------------------*/
void K_CeremonyTicker(boolean run);
/*--------------------------------------------------
void K_CeremonyResponder(event_t *ev);
Responder function to be ran during the podium
cutscene mode gamestate. Handles key presses
ending the podium scene.
Input Arguments:-
ev - The player input event.
Return:-
true to end the podium cutscene and return
to the title screen, otherwise false.
--------------------------------------------------*/
boolean K_CeremonyResponder(event_t *ev);
/*--------------------------------------------------
void K_CeremonyDrawer(void);
Handles the ranking screen and other HUD for
the podium cutscene.
--------------------------------------------------*/
void K_CeremonyDrawer(void);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // __K_PODIUM__

416
src/k_rank.c Normal file
View file

@ -0,0 +1,416 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) by Sally "TehRealSalt" Cochenour
// Copyright (C) by Kart Krew
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file k_rank.c
/// \brief Grand Prix mode ranking
#include "k_rank.h"
#include "k_grandprix.h"
#include "k_specialstage.h"
#include "doomdef.h"
#include "d_player.h"
#include "g_game.h"
#include "k_bot.h"
#include "k_kart.h"
#include "m_random.h"
#include "r_things.h"
#include "fastcmp.h"
#include "byteptr.h"
// I was ALMOST tempted to start tearing apart all
// of the map loading code and turning it into C++
// and making it properly split between read-only
// and true level loading and clean up all of the
// global variable garbage it uses ... but I stopped
// myself. So here's code duplication hell instead.
static UINT32 g_rankCapsules_mapthingsPos[UINT16_MAX];
static size_t g_rankCapsules_nummapthings = 0;
static boolean g_rankCapsules_udmf = false;
static UINT32 g_rankCapsules_count = 0;
/*--------------------------------------------------
static void RankCapsules_TextmapCount(size_t size)
Counts the number of map things and records
the structure positions, for the result of
RankCapsules_CountFromMap.
Input Arguments:-
size - Length of the TEXTMAP lump.
Return:-
N/A
--------------------------------------------------*/
static UINT32 RankCapsules_TextmapCount(size_t size)
{
const char *tkn = M_TokenizerRead(0);
UINT8 brackets = 0;
g_rankCapsules_nummapthings = 0;
// Look for namespace at the beginning.
if (!fastcmp(tkn, "namespace"))
{
return false;
}
// Check if namespace is valid.
tkn = M_TokenizerRead(0);
while ((tkn = M_TokenizerRead(0)) && M_TokenizerGetEndPos() < size)
{
// Avoid anything inside bracketed stuff, only look for external keywords.
if (brackets)
{
if (fastcmp(tkn, "}"))
brackets--;
}
else if (fastcmp(tkn, "{"))
brackets++;
// Check for valid fields.
else if (fastcmp(tkn, "thing"))
g_rankCapsules_mapthingsPos[g_rankCapsules_nummapthings++] = M_TokenizerGetEndPos();
}
if (brackets)
{
return false;
}
return true;
}
/*--------------------------------------------------
static void RankCapsules_LoadTextmap(void)
Loads UDMF map data for the result of
RankCapsules_CountFromMap.
--------------------------------------------------*/
static void RankCapsules_LoadTextmap(void)
{
size_t i;
for (i = 0; i < g_rankCapsules_nummapthings; i++)
{
const char *param, *val;
M_TokenizerSetEndPos(g_rankCapsules_mapthingsPos[i]);
param = M_TokenizerRead(0);
if (!fastcmp(param, "{"))
{
continue;
}
while (true)
{
param = M_TokenizerRead(0);
if (fastcmp(param, "}"))
{
break;
}
val = M_TokenizerRead(1);
if (fastcmp(param, "type"))
{
UINT16 type = atol(val);
if (type == mobjinfo[MT_BATTLECAPSULE].doomednum)
{
g_rankCapsules_count++;
}
break;
}
}
}
}
/*--------------------------------------------------
static void RankCapsules_LoadThingsLump(UINT8 *data)
Loads binary map data for the result of
RankCapsules_CountFromMap.
Input Arguments:-
data - Pointer to a THINGS lump.
Return:-
N/A
--------------------------------------------------*/
static void RankCapsules_LoadThingsLump(UINT8 *data)
{
size_t i;
for (i = 0; i < g_rankCapsules_nummapthings; i++)
{
UINT16 type = 0;
data += 2; // x
data += 2; // y
data += 2; // angle
type = READUINT16(data); // type
type &= 4095;
data += 2; // options
if (type == mobjinfo[MT_BATTLECAPSULE].doomednum)
{
g_rankCapsules_count++;
}
}
}
/*--------------------------------------------------
static boolean RankCapsules_LoadMapData(const virtres_t *virt)
Loads either UDMF or binary map data, for the
result of RankCapsules_CountFromMap.
Input Arguments:-
virt - Pointer to the map's virtual resource.
Return:-
true if we could successfully load the map data,
otherwise false.
--------------------------------------------------*/
static boolean RankCapsules_LoadMapData(const virtres_t *virt)
{
virtlump_t *virtthings = NULL;
// Count map data.
if (g_rankCapsules_udmf) // Count how many entries for each type we got in textmap.
{
virtlump_t *textmap = vres_Find(virt, "TEXTMAP");
M_TokenizerOpen((char *)textmap->data);
if (!RankCapsules_TextmapCount(textmap->size))
{
M_TokenizerClose();
return false;
}
}
else
{
virtthings = vres_Find(virt, "THINGS");
if (!virtthings)
{
return false;
}
// Traditional doom map format just assumes the number of elements from the lump sizes.
g_rankCapsules_nummapthings = virtthings->size / (5 * sizeof (INT16));
}
// Load map data.
if (g_rankCapsules_udmf)
{
RankCapsules_LoadTextmap();
M_TokenizerClose();
}
else
{
RankCapsules_LoadThingsLump(virtthings->data);
}
return true;
}
/*--------------------------------------------------
static UINT32 RankCapsules_CountFromMap(const virtres_t *virt)
Counts the number of capsules in a map, without
needing to fully load it.
Input Arguments:-
virt - Pointer to the map's virtual resource.
Return:-
Number of MT_BATTLECAPSULE instances found.
--------------------------------------------------*/
static UINT32 RankCapsules_CountFromMap(const virtres_t *virt)
{
virtlump_t *textmap = vres_Find(virt, "TEXTMAP");
g_rankCapsules_udmf = (textmap != NULL);
g_rankCapsules_count = 0;
if (RankCapsules_LoadMapData(virt) == true)
{
return g_rankCapsules_count;
}
return 0;
}
/*--------------------------------------------------
void K_InitGrandPrixRank(gpRank_t *rankData)
See header file for description.
--------------------------------------------------*/
void K_InitGrandPrixRank(gpRank_t *rankData)
{
UINT8 numHumans = 0;
UINT32 laps = 0;
INT32 i;
memset(rankData, 0, sizeof(gpRank_t));
if (grandprixinfo.cup == NULL)
{
return;
}
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i])
{
if (numHumans < MAXSPLITSCREENPLAYERS && players[i].spectator == false)
{
numHumans++;
}
}
}
// Calculate players
rankData->players = numHumans;
rankData->totalPlayers = K_GetGPPlayerCount(numHumans);
// Initialize to the neutral value.
rankData->position = RANK_NEUTRAL_POSITION;
// Calculate total of points
// (Should this account for all coop players?)
for (i = 0; i < numHumans; i++)
{
rankData->totalPoints += grandprixinfo.cup->numlevels * K_CalculateGPRankPoints(i + 1, rankData->totalPlayers);
}
rankData->totalRings = grandprixinfo.cup->numlevels * numHumans * 20;
for (i = 0; i < grandprixinfo.cup->numlevels; i++)
{
const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[i];
if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum] != NULL)
{
laps += mapheaderinfo[cupLevelNum]->numlaps;
}
}
// +1, since 1st place laps are worth 2 pts.
for (i = 0; i < numHumans+1; i++)
{
rankData->totalLaps += laps;
}
// Search through all of the cup's bonus levels
// for an accurate count of how many capsules they have.
for (i = 0; i < grandprixinfo.cup->numbonus; i++)
{
const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_BONUS + i];
if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum] != NULL)
{
lumpnum_t lp = mapheaderinfo[cupLevelNum]->lumpnum;
virtres_t *virt = NULL;
if (lp == LUMPERROR)
{
continue;
}
virt = vres_GetMap(lp);
if (virt == NULL)
{
continue;
}
rankData->totalCapsules += RankCapsules_CountFromMap(virt);
vres_Free(virt);
}
}
}
/*--------------------------------------------------
gp_rank_e K_CalculateGPGrade(gpRank_t *rankData)
See header file for description.
--------------------------------------------------*/
gp_rank_e K_CalculateGPGrade(gpRank_t *rankData)
{
static const fixed_t gradePercents[GRADE_A] = {
7*FRACUNIT/20, // D: 35% or higher
10*FRACUNIT/20, // C: 50% or higher
14*FRACUNIT/20, // B: 70% or higher
17*FRACUNIT/20 // A: 85% or higher
};
gp_rank_e retGrade = GRADE_E;
const INT32 positionWeight = 150;
const INT32 pointsWeight = 100;
const INT32 lapsWeight = 100;
const INT32 capsulesWeight = 100;
const INT32 ringsWeight = 50;
const INT32 total = positionWeight + pointsWeight + lapsWeight + capsulesWeight + ringsWeight;
const INT32 continuesPenalty = 20;
INT32 ours = 0;
fixed_t percent = 0;
if (rankData->position > 0)
{
const INT32 sc = (rankData->position - 1);
const INT32 loser = (RANK_NEUTRAL_POSITION - 1);
ours += ((loser - sc) * positionWeight) / loser;
}
if (rankData->totalPoints > 0)
{
ours += (rankData->winPoints * pointsWeight) / rankData->totalPoints;
}
if (rankData->totalLaps > 0)
{
ours += (rankData->laps * lapsWeight) / rankData->totalLaps;
}
if (rankData->totalCapsules > 0)
{
ours += (rankData->capsules * capsulesWeight) / rankData->totalCapsules;
}
if (rankData->totalRings > 0)
{
ours += (rankData->rings * ringsWeight) / rankData->totalRings;
}
ours -= rankData->continuesUsed * continuesPenalty;
percent = FixedDiv(ours, total);
for (retGrade = 0; retGrade < GRADE_A; retGrade++)
{
if (percent < gradePercents[retGrade])
{
break;
}
}
if (rankData->specialWon == true)
{
// Winning the Special Stage gives you
// a free grade increase.
retGrade++;
}
return retGrade;
}

96
src/k_rank.h Normal file
View file

@ -0,0 +1,96 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) by Sally "TehRealSalt" Cochenour
// Copyright (C) by Kart Krew
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file k_rank.h
/// \brief Grand Prix mode ranking
#ifndef __K_RANK__
#define __K_RANK__
#include "doomdef.h"
#include "doomstat.h"
#ifdef __cplusplus
extern "C" {
#endif
struct gpRank_t
{
UINT8 players;
UINT8 totalPlayers;
UINT8 position;
UINT32 winPoints;
UINT32 totalPoints;
UINT32 laps;
UINT32 totalLaps;
UINT32 continuesUsed;
UINT32 capsules;
UINT32 totalCapsules;
UINT32 rings;
UINT32 totalRings;
boolean specialWon;
};
typedef enum
{
GRADE_E,
GRADE_D,
GRADE_C,
GRADE_B,
GRADE_A,
GRADE_S
} gp_rank_e;
// 3rd place is neutral, anything below is a penalty
#define RANK_NEUTRAL_POSITION (3)
/*--------------------------------------------------
void K_InitGrandPrixRank(gpRank_t *rankData);
Calculates rank requirements for a GP session.
Input Arguments:-
rankData - Pointer to struct that contains all
of the information required to calculate GP rank.
Return:-
N/A
--------------------------------------------------*/
void K_InitGrandPrixRank(gpRank_t *rankData);
/*--------------------------------------------------
gp_rank_e K_CalculateGPGrade(gpRank_t *rankData);
Calculates the player's grade using the
variables from gpRank.
Input Arguments:-
rankData - struct containing existing rank data.
Return:-
gp_rank_e representing the total grade.
--------------------------------------------------*/
gp_rank_e K_CalculateGPGrade(gpRank_t *rankData);
#ifdef __cplusplus
} // extern "C"
#endif
#endif

View file

@ -235,6 +235,29 @@ INT32 K_GetWaypointID(waypoint_t *waypoint)
return waypointid;
}
/*--------------------------------------------------
waypoint_t *K_GetWaypointFromID(INT32 waypointID)
See header file for description.
--------------------------------------------------*/
waypoint_t *K_GetWaypointFromID(INT32 waypointID)
{
waypoint_t *waypoint = NULL;
size_t i = SIZE_MAX;
for (i = 0; i < numwaypoints; i++)
{
waypoint = &waypointheap[i];
if (K_GetWaypointID(waypoint) == waypointID)
{
return waypoint;
}
}
return NULL;
}
/*--------------------------------------------------
UINT32 K_GetCircuitLength(void)

View file

@ -141,9 +141,25 @@ INT32 K_GetWaypointNextID(waypoint_t *waypoint);
Return:-
The waypoint ID, -1 if there is no waypoint or mobj.
--------------------------------------------------*/
INT32 K_GetWaypointID(waypoint_t *waypoint);
/*--------------------------------------------------
waypoint_t *K_GetWaypointFromID(INT32 waypointID)
Returns the first waypoint with the specified ID.
Input Arguments:-
waypointID - The ID of the waypoint to get
Return:-
The first waypoint with this ID, NULL if the ID doesn't exist at all in the map
--------------------------------------------------*/
waypoint_t *K_GetWaypointFromID(INT32 waypointID);
/*--------------------------------------------------
UINT32 K_GetCircuitLength(void)

View file

@ -482,12 +482,10 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->timeshitprev);
else if (fastcmp(field,"onconveyor"))
lua_pushinteger(L, plr->onconveyor);
else if (fastcmp(field,"awayviewmobj"))
LUA_PushUserdata(L, plr->awayviewmobj, META_MOBJ);
else if (fastcmp(field,"awayviewtics"))
lua_pushinteger(L, plr->awayviewtics);
else if (fastcmp(field,"awayviewaiming"))
lua_pushangle(L, plr->awayviewaiming);
else if (fastcmp(field,"awayviewmobj")) // FIXME: struct
LUA_PushUserdata(L, plr->awayview.mobj, META_MOBJ);
else if (fastcmp(field,"awayviewtics")) // FIXME: struct
lua_pushinteger(L, plr->awayview.tics);
else if (fastcmp(field,"spectator"))
lua_pushboolean(L, plr->spectator);
@ -848,21 +846,19 @@ static int player_set(lua_State *L)
plr->timeshitprev = (UINT8)luaL_checkinteger(L, 3);
else if (fastcmp(field,"onconveyor"))
plr->onconveyor = (INT32)luaL_checkinteger(L, 3);
else if (fastcmp(field,"awayviewmobj"))
else if (fastcmp(field,"awayviewmobj")) // FIXME: struct
{
mobj_t *mo = NULL;
if (!lua_isnil(L, 3))
mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
P_SetTarget(&plr->awayviewmobj, mo);
P_SetTarget(&plr->awayview.mobj, mo);
}
else if (fastcmp(field,"awayviewtics"))
else if (fastcmp(field,"awayviewtics")) // FIXME: struct
{
plr->awayviewtics = (INT32)luaL_checkinteger(L, 3);
if (plr->awayviewtics && !plr->awayviewmobj) // awayviewtics must ALWAYS have an awayviewmobj set!!
P_SetTarget(&plr->awayviewmobj, plr->mo); // but since the script might set awayviewmobj immediately AFTER setting awayviewtics, use player mobj as filler for now.
plr->awayview.tics = (INT32)luaL_checkinteger(L, 3);
if (plr->awayview.tics && !plr->awayview.mobj) // awayviewtics must ALWAYS have an awayviewmobj set!!
P_SetTarget(&plr->awayview.mobj, plr->mo); // but since the script might set awayviewmobj immediately AFTER setting awayviewtics, use player mobj as filler for now.
}
else if (fastcmp(field,"awayviewaiming"))
plr->awayviewaiming = luaL_checkangle(L, 3);
else if (fastcmp(field,"spectator"))
plr->spectator = lua_toboolean(L, 3);
else if (fastcmp(field,"bot"))

View file

@ -212,7 +212,7 @@ int LUA_PushGlobals(lua_State *L, const char *word)
lua_pushstring(L, titlemap);
return 1;
} else if (fastcmp(word,"titlemapinaction")) {
lua_pushboolean(L, (titlemapinaction != TITLEMAP_OFF));
lua_pushboolean(L, titlemapinaction);
return 1;
} else if (fastcmp(word,"bootmap")) {
lua_pushstring(L, bootmap);
@ -223,6 +223,9 @@ int LUA_PushGlobals(lua_State *L, const char *word)
} else if (fastcmp(word,"tutorialmode")) {
lua_pushboolean(L, tutorialmode);
return 1;
} else if (fastcmp(word,"podiummap")) {
lua_pushstring(L, podiummap);
return 1;
// end map vars
// begin CTF colors
} else if (fastcmp(word,"skincolor_redteam")) {

View file

@ -139,7 +139,7 @@ void COM_Lua_f(void);
// #define HAVE_LUA_SEGS
#define ISINLEVEL \
(gamestate == GS_LEVEL || titlemapinaction)
(G_GamestateUsesLevel())
#define INLEVEL if (! ISINLEVEL)\
return luaL_error(L, "This can only be used in a level!");

View file

@ -255,10 +255,7 @@ static void M_DrawRenderStats(void)
perfstatcol_t batchcalls_col = {220, 200, V_PURPLEMAP, batchcalls_row};
boolean rendering = (
gamestate == GS_LEVEL ||
(gamestate == GS_TITLESCREEN && titlemapinaction)
);
boolean rendering = G_GamestateUsesLevel();
draw_row = 10;
M_DrawPerfTiming(&frametime_col);
@ -619,8 +616,9 @@ void M_DrawPerfStats(void)
}
else if (cv_perfstats.value == PS_THINKFRAME) // lua thinkframe
{
if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
if (G_GamestateUsesLevel() == false)
return;
if (vid.width < 640 || vid.height < 400) // low resolution
{
// it's not gonna fit very well..

View file

@ -4165,8 +4165,8 @@ void A_OverlayThink(mobj_t *actor)
{
angle_t viewingangle;
if (players[displayplayers[0]].awayviewtics)
viewingangle = R_PointToAngle2(actor->target->x, actor->target->y, players[displayplayers[0]].awayviewmobj->x, players[displayplayers[0]].awayviewmobj->y);
if (players[displayplayers[0]].awayview.tics)
viewingangle = R_PointToAngle2(actor->target->x, actor->target->y, players[displayplayers[0]].awayview.mobj->x, players[displayplayers[0]].awayview.mobj->y);
else if (!camera[0].chase && players[displayplayers[0]].mo)
viewingangle = R_PointToAngle2(actor->target->x, actor->target->y, players[displayplayers[0]].mo->x, players[displayplayers[0]].mo->y);
else
@ -9390,6 +9390,10 @@ void A_SetScale(mobj_t *actor)
}
locvar1 = FixedMul(locvar1, mapobjectscale); // SRB2Kart
if (target->spawnpoint != NULL)
{
locvar1 = FixedMul(locvar1, target->spawnpoint->scale);
}
target->destscale = locvar1; // destination scale
if (!(locvar2 & 65535))

View file

@ -197,6 +197,8 @@ void P_HaltPlayerOrbit(player_t *player);
void P_ExitPlayerOrbit(player_t *player);
boolean P_PlayerOrbit(player_t *player);
void P_TickAltView(altview_t *view);
void P_MovePlayer(player_t *player);
void P_PlayerThink(player_t *player);
void P_PlayerAfterThink(player_t *player);

View file

@ -47,6 +47,8 @@
#include "k_objects.h"
#include "k_grandprix.h"
#include "k_director.h"
#include "m_easing.h"
#include "k_podium.h"
static CV_PossibleValue_t CV_BobSpeed[] = {{0, "MIN"}, {4*FRACUNIT, "MAX"}, {0, NULL}};
consvar_t cv_movebob = CVAR_INIT ("movebob", "1.0", CV_FLOAT|CV_SAVE, CV_BobSpeed, NULL);
@ -3779,15 +3781,16 @@ void P_CalcChasePostImg(player_t *player, camera_t *thiscam)
{
postimg = postimg_mirror;
}
else if (player->awayviewtics && player->awayviewmobj && !P_MobjWasRemoved(player->awayviewmobj)) // Camera must obviously exist
else if (player->awayview.tics && player->awayview.mobj && !P_MobjWasRemoved(player->awayview.mobj)) // Camera must obviously exist
{
camera_t dummycam;
dummycam.subsector = player->awayviewmobj->subsector;
dummycam.x = player->awayviewmobj->x;
dummycam.y = player->awayviewmobj->y;
dummycam.z = player->awayviewmobj->z;
//dummycam.height = 40*FRACUNIT; // alt view height is 20*FRACUNIT
dummycam.height = 0; // Why? Remote viewpoint cameras have no height.
dummycam.subsector = player->awayview.mobj->subsector;
dummycam.x = player->awayview.mobj->x;
dummycam.y = player->awayview.mobj->y;
dummycam.z = player->awayview.mobj->z;
dummycam.height = 0;
// Are we in water?
if (P_CameraCheckWater(&dummycam))
postimg = postimg_water;
@ -8088,8 +8091,8 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
angle_t viewingangle;
statenum_t curstate = ((mobj->tics == 1) ? (mobj->state->nextstate) : ((statenum_t)(mobj->state-states)));
if (players[displayplayers[0]].awayviewtics)
viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].awayviewmobj->x, players[displayplayers[0]].awayviewmobj->y);
if (players[displayplayers[0]].awayview.tics)
viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].awayview.mobj->x, players[displayplayers[0]].awayview.mobj->y);
else if (!camera[0].chase && players[displayplayers[0]].mo)
viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].mo->x, players[displayplayers[0]].mo->y);
else
@ -8219,8 +8222,8 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
{
angle_t viewingangle;
if (players[displayplayers[0]].awayviewtics)
viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].awayviewmobj->x, players[displayplayers[0]].awayviewmobj->y);
if (players[displayplayers[0]].awayview.tics)
viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].awayview.mobj->x, players[displayplayers[0]].awayview.mobj->y);
else if (!camera[0].chase && players[displayplayers[0]].mo)
viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].mo->x, players[displayplayers[0]].mo->y);
else
@ -8324,8 +8327,8 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
{
angle_t viewingangle;
if (players[displayplayers[0]].awayviewtics)
viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].awayviewmobj->x, players[displayplayers[0]].awayviewmobj->y);
if (players[displayplayers[0]].awayview.tics)
viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].awayview.mobj->x, players[displayplayers[0]].awayview.mobj->y);
else if (!camera[0].chase && players[displayplayers[0]].mo)
viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].mo->x, players[displayplayers[0]].mo->y);
else
@ -9487,6 +9490,66 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
case MT_MONITOR_PART:
Obj_MonitorPartThink(mobj);
break;
case MT_ALTVIEWMAN:
{
mobj->momx = mobj->momy = mobj->momz = 0;
if (mobj->movefactor <= 0)
{
mobj->movefactor = FRACUNIT / TICRATE; // default speed
}
if (mobj->tracer != NULL && P_MobjWasRemoved(mobj->tracer) == false)
{
fixed_t newX = Easing_Linear(mobj->movecount, mobj->extravalue1, mobj->tracer->x);
fixed_t newY = Easing_Linear(mobj->movecount, mobj->extravalue2, mobj->tracer->y);
fixed_t newZ = Easing_Linear(mobj->movecount, mobj->cusval, mobj->tracer->z);
mobj->angle = Easing_Linear(mobj->movecount, mobj->movedir, mobj->tracer->angle);
mobj->pitch = Easing_Linear(mobj->movecount, mobj->lastlook, mobj->tracer->pitch);
mobj->momx = newX - mobj->x;
mobj->momy = newY - mobj->y;
mobj->momz = newZ - mobj->z;
mobj->movecount += mobj->movefactor;
if (mobj->movecount >= FRACUNIT)
{
mobj->movecount = mobj->movecount % FRACUNIT; // time
mobj->movedir = mobj->tracer->angle; // start angle
mobj->lastlook = mobj->tracer->pitch; // start pitch
mobj->extravalue1 = mobj->tracer->x; // start x
mobj->extravalue2 = mobj->tracer->y; // start y
mobj->cusval = mobj->tracer->z; // start z
P_SetTarget(&mobj->tracer, P_GetNextTubeWaypoint(mobj->tracer, false));
}
}
// If target is valid, then we'll focus on it.
if (mobj->target != NULL && P_MobjWasRemoved(mobj->target) == false)
{
mobj->angle = R_PointToAngle2(
mobj->x,
mobj->y,
mobj->target->x,
mobj->target->y
);
mobj->pitch = R_PointToAngle2(
0,
mobj->z,
R_PointToDist2(
mobj->x, mobj->y,
mobj->target->x, mobj->target->y
),
mobj->target->z + (mobj->target->height >> 1)
);
}
}
break;
default:
// check mobj against possible water content, before movement code
P_MobjCheckWater(mobj);
@ -10464,9 +10527,6 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
else
switch (mobj->type)
{
case MT_ALTVIEWMAN:
if (titlemapinaction) mobj->flags &= ~MF_NOTHINK;
break;
case MT_LOCKONINF:
P_SetScale(mobj, (mobj->destscale = 3*mobj->scale));
break;
@ -11661,7 +11721,9 @@ void P_SpawnPlayer(INT32 playernum)
mobj_t *mobj;
if (p->playerstate == PST_REBORN)
G_PlayerReborn(playernum, false);
{
G_PlayerReborn(playernum, (p->jointime <= 1));
}
for (i = 0; i < MAXPLAYERS; i++)
{
@ -11686,7 +11748,8 @@ void P_SpawnPlayer(INT32 playernum)
}
else if (p->bot)
{
if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE)
if (K_PodiumSequence() == false
&& (!(gametyperules & GTR_BOTS) || (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE)))
{
// Bots aren't supposed to be here.
p->spectator = true;
@ -11750,7 +11813,7 @@ void P_SpawnPlayer(INT32 playernum)
p->skincolor = skincolor_blueteam;
}
if (leveltime > introtime)
if (leveltime > introtime && K_PodiumSequence() == false)
p->flashing = K_GetKartFlashing(p); // Babysitting deterrent
mobj = P_SpawnMobj(0, 0, 0, MT_PLAYER);
@ -11895,6 +11958,11 @@ void P_AfterPlayerSpawn(INT32 playernum)
if (CheckForReverseGravity)
P_CheckGravity(mobj, false);
if (K_PodiumSequence() == true)
{
K_InitializePodiumWaypoint(p);
}
}
// spawn it at a playerspawn mapthing
@ -11977,7 +12045,7 @@ void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing)
mobj->angle = angle;
// FAULT
if (leveltime > introtime && !p->spectator)
if (gamestate == GS_LEVEL && leveltime > introtime && !p->spectator)
{
K_DoIngameRespawn(p);
}
@ -12055,6 +12123,7 @@ void P_MovePlayerToStarpost(INT32 playernum)
fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const fixed_t y, const fixed_t dz, const fixed_t offset, const boolean flip, const fixed_t scale)
{
const fixed_t finalScale = FixedMul(scale, mapobjectscale);
const subsector_t *ss = R_PointInSubsector(x, y);
// Axis objects snap to the floor.
@ -12063,9 +12132,9 @@ fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const f
// Establish height.
if (flip)
return P_GetSectorCeilingZAt(ss->sector, x, y) - dz - FixedMul(scale, offset + mobjinfo[mobjtype].height);
return P_GetSectorCeilingZAt(ss->sector, x, y) - dz - FixedMul(finalScale, offset + mobjinfo[mobjtype].height);
else
return P_GetSectorFloorZAt(ss->sector, x, y) + dz + FixedMul(scale, offset);
return P_GetSectorFloorZAt(ss->sector, x, y) + dz + FixedMul(finalScale, offset);
}
fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mthing, const fixed_t x, const fixed_t y)
@ -13364,16 +13433,14 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y, fixed_t z, mobjtype_t i)
{
fixed_t relativise = FixedDiv(mthing->scale, mapobjectscale);
mobj_t *mobj = NULL;
boolean doangle = true;
mobj = P_SpawnMobj(x, y, z, i);
mobj->spawnpoint = mthing;
P_SetScale(mobj, FixedMul(mobj->scale, relativise));
mobj->destscale = FixedMul(mobj->destscale, relativise);
P_SetScale(mobj, FixedMul(mobj->scale, mthing->scale));
mobj->destscale = FixedMul(mobj->destscale, mthing->scale);
if (!P_SetupSpawnedMapThing(mthing, mobj, &doangle))
return mobj;

View file

@ -137,8 +137,7 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEANGLE(save->p, players[i].drawangle);
WRITEANGLE(save->p, players[i].viewrollangle);
WRITEANGLE(save->p, players[i].tilt);
WRITEANGLE(save->p, players[i].awayviewaiming);
WRITEINT32(save->p, players[i].awayviewtics);
WRITEINT32(save->p, players[i].awayview.tics);
WRITEUINT8(save->p, players[i].playerstate);
WRITEUINT32(save->p, players[i].pflags);
@ -179,6 +178,7 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT32(save->p, players[i].realtime);
WRITEUINT8(save->p, players[i].laps);
WRITEUINT8(save->p, players[i].latestlap);
WRITEUINT32(save->p, players[i].lapPoints);
WRITEINT32(save->p, players[i].starpostnum);
WRITEUINT8(save->p, players[i].ctfteam);
@ -197,7 +197,7 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT8(save->p, players[i].splitscreenindex);
if (players[i].awayviewmobj)
if (players[i].awayview.mobj)
flags |= AWAYVIEW;
if (players[i].followmobj)
@ -227,7 +227,7 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT32(save->p, players[i].skybox.centerpoint->mobjnum);
if (flags & AWAYVIEW)
WRITEUINT32(save->p, players[i].awayviewmobj->mobjnum);
WRITEUINT32(save->p, players[i].awayview.mobj->mobjnum);
if (flags & FOLLOWITEM)
WRITEUINT32(save->p, players[i].followmobj->mobjnum);
@ -527,8 +527,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].drawangle = players[i].old_drawangle = READANGLE(save->p);
players[i].viewrollangle = READANGLE(save->p);
players[i].tilt = READANGLE(save->p);
players[i].awayviewaiming = READANGLE(save->p);
players[i].awayviewtics = READINT32(save->p);
players[i].awayview.tics = READINT32(save->p);
players[i].playerstate = READUINT8(save->p);
players[i].pflags = READUINT32(save->p);
@ -569,6 +568,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].realtime = READUINT32(save->p); // integer replacement for leveltime
players[i].laps = READUINT8(save->p); // Number of laps (optional)
players[i].latestlap = READUINT8(save->p);
players[i].lapPoints = READUINT32(save->p);
players[i].starpostnum = READINT32(save->p);
players[i].ctfteam = READUINT8(save->p); // 1 == Red, 2 == Blue
@ -596,7 +596,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].skybox.centerpoint = (mobj_t *)(size_t)READUINT32(save->p);
if (flags & AWAYVIEW)
players[i].awayviewmobj = (mobj_t *)(size_t)READUINT32(save->p);
players[i].awayview.mobj = (mobj_t *)(size_t)READUINT32(save->p);
if (flags & FOLLOWITEM)
players[i].followmobj = (mobj_t *)(size_t)READUINT32(save->p);
@ -4684,12 +4684,12 @@ static void P_RelinkPointers(void)
if (!P_SetTarget(&players[i].skybox.centerpoint, P_FindNewPosition(temp)))
CONS_Debug(DBG_GAMELOGIC, "skybox.centerpoint not found on player %d\n", i);
}
if (players[i].awayviewmobj)
if (players[i].awayview.mobj)
{
temp = (UINT32)(size_t)players[i].awayviewmobj;
players[i].awayviewmobj = NULL;
if (!P_SetTarget(&players[i].awayviewmobj, P_FindNewPosition(temp)))
CONS_Debug(DBG_GAMELOGIC, "awayviewmobj not found on player %d\n", i);
temp = (UINT32)(size_t)players[i].awayview.mobj;
players[i].awayview.mobj = NULL;
if (!P_SetTarget(&players[i].awayview.mobj, P_FindNewPosition(temp)))
CONS_Debug(DBG_GAMELOGIC, "awayview.mobj not found on player %d\n", i);
}
if (players[i].followmobj)
{

View file

@ -101,6 +101,8 @@
#include "k_specialstage.h"
#include "acs/interface.h"
#include "doomstat.h" // MAXMUSNAMES
#include "k_podium.h"
#include "k_rank.h"
// Replay names have time
#if !defined (UNDER_CE)
@ -1278,7 +1280,7 @@ static void P_LoadThings(UINT8 *data)
mt->options = READUINT16(data);
mt->extrainfo = (UINT8)(mt->type >> 12);
Tag_FSet(&mt->tags, 0);
mt->scale = mapobjectscale;
mt->scale = FRACUNIT;
memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args));
memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs));
mt->special = 0;
@ -1783,7 +1785,7 @@ static void ParseTextmapThingParameter(UINT32 i, const char *param, const char *
else if (fastcmp(param, "type"))
mapthings[i].type = atol(val);
else if (fastcmp(param, "scale") || fastcmp(param, "scalex") || fastcmp(param, "scaley"))
mapthings[i].scale = FixedMul(mapobjectscale, FLOAT_TO_FIXED(atof(val)));
mapthings[i].scale = FLOAT_TO_FIXED(atof(val));
// Flags
else if (fastcmp(param, "flip") && fastcmp("true", val))
mapthings[i].options |= MTF_OBJECTFLIP;
@ -2731,7 +2733,7 @@ static void P_LoadTextmap(void)
mt->z = 0;
mt->extrainfo = 0;
Tag_FSet(&mt->tags, 0);
mt->scale = mapobjectscale;
mt->scale = FRACUNIT;
memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args));
memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs));
mt->special = 0;
@ -7083,7 +7085,11 @@ static void P_InitLevelSettings(void)
gamespeed = KARTSPEED_EASY;
franticitems = false;
if (grandprixinfo.gp == true)
if (K_PodiumSequence() == true)
{
; // NOP
}
else if (grandprixinfo.gp == true)
{
if (gametyperules & GTR_CIRCUIT)
{
@ -7359,7 +7365,7 @@ static void P_InitPlayers(void)
players[i].mo = NULL;
if (!(gametyperules & GTR_CIRCUIT))
if (!(gametyperules & GTR_CIRCUIT) && K_PodiumSequence() == false)
{
G_DoReborn(i);
}
@ -7368,6 +7374,8 @@ static void P_InitPlayers(void)
G_SpawnPlayer(i);
}
}
K_UpdateAllPlayerPositions();
}
static void P_InitGametype(void)
@ -7377,6 +7385,26 @@ static void P_InitGametype(void)
spectateGriefed = 0;
K_CashInPowerLevels(); // Pushes power level changes even if intermission was skipped
if (grandprixinfo.gp == true)
{
if (grandprixinfo.initalize == true)
{
K_InitGrandPrixRank(&grandprixinfo.rank);
K_InitGrandPrixBots();
grandprixinfo.initalize = false;
}
else if (grandprixinfo.wonround == true)
{
K_UpdateGrandPrixBots();
grandprixinfo.wonround = false;
}
}
else if (!modeattacking)
{
// We're in a Match Race, use simplistic randomized bots.
K_UpdateMatchRaceBots();
}
P_InitPlayers();
if (modeattacking && !demo.playback)
@ -7419,7 +7447,7 @@ static void P_InitGametype(void)
// Start recording replay in multiplayer with a temp filename
//@TODO I'd like to fix dedis crashing when recording replays for the future too...
if (!demo.playback && multiplayer && !dedicated)
if (gamestate == GS_LEVEL && !demo.playback && multiplayer && !dedicated)
{
char buf[MAX_WADPATH];
char ver[128];
@ -7697,7 +7725,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
// Fade out music here. Deduct 2 tics so the fade volume actually reaches 0.
// But don't halt the music! S_Start will take care of that. This dodges a MIDI crash bug.
if (!(reloadinggamestate || titlemapinaction))
if (!(reloadinggamestate || gamestate != GS_LEVEL))
S_FadeMusic(0, FixedMul(
FixedDiv((F_GetWipeLength(wipedefs[wipe_level_toblack])-2)*NEWTICRATERATIO, NEWTICRATE), MUSICRATE));
@ -7705,7 +7733,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
if (rendermode != render_none)
V_ReloadPalette(); // Set the level palette
if (!(reloadinggamestate || titlemapinaction))
if (!(reloadinggamestate || gamestate != GS_LEVEL))
{
if (ranspecialwipe == 2)
{
@ -7745,8 +7773,11 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
F_RunWipe(wipetype, wipedefs[wipetype], false, ((levelfadecol == 0) ? "FADEMAP1" : "FADEMAP0"), false, false);
}
/*if (!titlemapinaction)
wipegamestate = GS_LEVEL;*/
/*
if (!titlemapinaction)
wipegamestate = GS_LEVEL;
*/
// Close text prompt before freeing the old level
F_EndTextPrompt(false, true);
@ -7948,7 +7979,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
P_MapEnd(); // tm.thing is no longer needed from this point onwards
// Took me 3 hours to figure out why my progression kept on getting overwritten with the titlemap...
if (!titlemapinaction)
if (gamestate == GS_LEVEL)
{
if (!lastmaploaded) // Start a new game?
{
@ -7961,27 +7992,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
lastmaploaded = gamemap; // HAS to be set after saving!!
}
if (reloadinggamestate)
;
else if (grandprixinfo.gp == true)
{
if (grandprixinfo.initalize == true)
{
K_InitGrandPrixBots();
grandprixinfo.initalize = false;
}
else if (grandprixinfo.wonround == true)
{
K_UpdateGrandPrixBots();
grandprixinfo.wonround = false;
}
}
else if (!modeattacking)
{
// We're in a Match Race, use simplistic randomized bots.
K_UpdateMatchRaceBots();
}
if (!fromnetsave) // uglier hack
{ // to make a newly loaded level start on the second frame.
INT32 buf = gametic % BACKUPTICS;

View file

@ -2007,6 +2007,40 @@ static void K_HandleLapIncrement(player_t *player)
}
}
if (player->laps > player->latestlap)
{
if (player->laps > 1)
{
// save best lap for record attack
if (modeattacking && player == &players[consoleplayer])
{
if (curlap < bestlap || bestlap == 0)
{
bestlap = curlap;
}
curlap = 0;
}
// Update power levels for this lap.
K_UpdatePowerLevels(player, player->laps, false);
if (nump > 1 && K_IsPlayerLosing(player) == false)
{
if (nump > 2 && player->position == 1) // 1st place in 1v1 uses thumbs up
{
player->lapPoints += 2;
}
else
{
player->lapPoints++;
}
}
}
player->latestlap = player->laps;
}
// finished race exit setup
if (player->laps > numlaps)
{
@ -2034,32 +2068,8 @@ static void K_HandleLapIncrement(player_t *player)
SetRandomFakePlayerSkin(player, true);
}
}
if (player->laps > player->latestlap)
{
if (player->laps > 1)
{
// save best lap for record attack
if (modeattacking && player == &players[consoleplayer])
{
if (curlap < bestlap || bestlap == 0)
{
bestlap = curlap;
}
curlap = 0;
}
// Update power levels for this lap.
K_UpdatePowerLevels(player, player->laps, false);
}
player->latestlap = player->laps;
}
thwompsactive = true; // Lap 2 effects
lowestLap = P_FindLowestLap();
for (i = 0; i < numlines; i++)
@ -2412,7 +2422,7 @@ static void P_SwitchSkybox(INT32 args, player_t *player, skybox_t *skybox)
}
}
static mobj_t* P_FindObjectTypeFromTag(mobjtype_t type, mtag_t tag)
mobj_t* P_FindObjectTypeFromTag(mobjtype_t type, mtag_t tag)
{
if (udmf)
{
@ -3001,35 +3011,139 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha
case 422: // Cut away to another view
{
mobj_t *altview;
INT32 aim;
altview_t *modifyView = NULL;
mobj_t *newViewMobj = NULL;
if ((!mo || !mo->player) && !titlemapinaction) // only players have views, and title screens
return false;
altview = P_FindObjectTypeFromTag(MT_ALTVIEWMAN, args[0]);
if (!altview || !altview->spawnpoint)
return false;
// If titlemap, set the camera ref for title's thinker
// This is not revoked until overwritten; awayviewtics is ignored
if (titlemapinaction)
titlemapcameraref = altview;
if (gamestate != GS_LEVEL)
{
modifyView = &titlemapcam;
}
else if (mo != NULL && mo->player != NULL)
{
modifyView = &mo->player->awayview;
}
else
{
P_SetTarget(&mo->player->awayviewmobj, altview);
mo->player->awayviewtics = args[1];
return false;
}
aim = (backwardsCompat) ? args[2] : altview->spawnpoint->pitch;
aim = (aim + 360) % 360;
aim *= (ANGLE_90>>8);
aim /= 90;
aim <<= 8;
if (titlemapinaction)
titlemapcameraref->cusval = (angle_t)aim;
newViewMobj = P_FindObjectTypeFromTag(MT_ALTVIEWMAN, args[0]);
if (newViewMobj == NULL || newViewMobj->spawnpoint == NULL)
{
return false;
}
P_SetTarget(&modifyView->mobj, newViewMobj);
if (gamestate != GS_LEVEL)
{
// If titlemap, awayview.tics is ignored
modifyView->tics = -1;
}
else
mo->player->awayviewaiming = (angle_t)aim;
{
modifyView->tics = args[1];
}
if (args[2] != 0)
{
switch (args[2])
{
case TMCAM_FIRST:
case TMCAM_SECOND:
case TMCAM_THIRD:
{
mobj_t *firstPlace = NULL;
INT32 i;
for (i = 0; i < MAXPLAYERS; i++)
{
player_t *player = NULL;
if (playeringame[i] == false)
{
continue;
}
player = &players[i];
if (player->spectator == true)
{
continue;
}
if (player->mo == NULL || P_MobjWasRemoved(player->mo) == true)
{
continue;
}
if (player->position == abs(args[2])) // a bit of a hack
{
firstPlace = player->mo;
break;
}
}
P_SetTarget(
&newViewMobj->target,
firstPlace
);
break;
}
case TMCAM_CONSOLE:
{
mobj_t *consoleMo = NULL;
if (playeringame[consoleplayer] == true)
{
consoleMo = players[consoleplayer].mo;
}
P_SetTarget(
&newViewMobj->target,
consoleMo
);
break;
}
default:
{
P_SetTarget(
&newViewMobj->target,
P_FindMobjFromTID(args[2], NULL, NULL)
);
break;
}
}
}
else
{
P_SetTarget(&newViewMobj->target, NULL);
}
if (args[3] > 0 && args[3] <= NUMTUBEWAYPOINTSEQUENCES)
{
P_SetTarget(
&newViewMobj->tracer,
P_GetFirstTubeWaypoint(args[3] - 1)
);
newViewMobj->movecount = 0; // time
newViewMobj->movedir = newViewMobj->angle; // start angle
newViewMobj->lastlook = newViewMobj->pitch; // start pitch
newViewMobj->extravalue1 = newViewMobj->x; // start x
newViewMobj->extravalue2 = newViewMobj->y; // start y
newViewMobj->cusval = newViewMobj->z; // start z
if (args[4] > 0)
{
newViewMobj->movefactor = FRACUNIT / args[4];
}
else
{
newViewMobj->movefactor = FRACUNIT / TICRATE; // default speed
}
}
else
{
P_SetTarget(&newViewMobj->tracer, NULL);
}
}
break;

View file

@ -519,6 +519,14 @@ typedef enum
TMLOOP_BETA = 1,
} textmaploopendpointtype_t;
typedef enum
{
TMCAM_FIRST = -1,
TMCAM_SECOND = -2,
TMCAM_THIRD = -3,
TMCAM_CONSOLE = -4,
} textmapcamerafollow_t;
// GETSECSPECIAL (specialval, section)
//
// Pulls out the special # from a particular section.
@ -574,6 +582,8 @@ void P_CrossSpecialLine(line_t *line, INT32 side, mobj_t *thing);
void P_PushSpecialLine(line_t *line, mobj_t *thing);
void P_ActivateThingSpecial(mobj_t *mo, mobj_t *source);
mobj_t* P_FindObjectTypeFromTag(mobjtype_t type, mtag_t tag);
//
// Special activation info
//

View file

@ -78,7 +78,7 @@ void Command_Numthinkers_f(void)
thinklistnum_t end = NUM_THINKERLISTS - 1;
thinklistnum_t i;
if (gamestate != GS_LEVEL)
if (G_GamestateUsesLevel() == false)
{
CONS_Printf(M_GetText("You must be in a level to use this.\n"));
return;
@ -149,7 +149,7 @@ void Command_CountMobjs_f(void)
mobjtype_t i;
INT32 count;
if (gamestate != GS_LEVEL)
if (G_GamestateUsesLevel() == false)
{
CONS_Printf(M_GetText("You must be in a level to use this.\n"));
return;
@ -532,10 +532,12 @@ void P_Ticker(boolean run)
// Increment jointime and quittime even if paused
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i])
{
players[i].jointime++;
}
}
if (objectplacing)
{
@ -602,32 +604,16 @@ void P_Ticker(boolean run)
ps_playerthink_time = I_GetPreciseTime();
#define PLAYERCONDITION(i) (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo))
// First loop: Ensure all players' distance to the finish line are all accurate
for (i = 0; i < MAXPLAYERS; i++)
{
if (!PLAYERCONDITION(i))
continue;
K_UpdateDistanceFromFinishLine(&players[i]);
}
// Second loop: Ensure all player positions reflect everyone's distances
for (i = 0; i < MAXPLAYERS; i++)
{
if (!PLAYERCONDITION(i))
continue;
K_KartUpdatePosition(&players[i]);
}
K_UpdateAllPlayerPositions();
// OK! Now that we got all of that sorted, players can think!
for (i = 0; i < MAXPLAYERS; i++)
{
if (!PLAYERCONDITION(i))
if (!(playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)))
continue;
P_PlayerThink(&players[i]);
K_KartPlayerHUDUpdate(&players[i]);
}
#undef PLAYERCONDITION
ps_playerthink_time = I_GetPreciseTime() - ps_playerthink_time;
}
@ -795,10 +781,12 @@ void P_Ticker(boolean run)
}
}
K_UpdateDirector();
// Always move the camera.
P_RunChaseCameras();
if (gamestate == GS_LEVEL)
{
// Move the camera during levels.
K_UpdateDirector();
P_RunChaseCameras();
}
LUA_HOOK(PostThinkFrame);
@ -867,19 +855,11 @@ void P_PreTicker(INT32 frames)
R_UpdateMobjInterpolators();
// First loop: Ensure all players' distance to the finish line are all accurate
for (i = 0; i < MAXPLAYERS; i++)
if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo))
K_UpdateDistanceFromFinishLine(&players[i]);
// Second loop: Ensure all player positions reflect everyone's distances
for (i = 0; i < MAXPLAYERS; i++)
if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo))
K_KartUpdatePosition(&players[i]);
// OK! Now that we got all of that sorted, players can think!
LUA_HOOK(PreThinkFrame);
K_UpdateAllPlayerPositions();
// OK! Now that we got all of that sorted, players can think!
for (i = 0; i < MAXPLAYERS; i++)
if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo))
{

View file

@ -58,6 +58,8 @@
#include "k_terrain.h" // K_SpawnSplashForMobj
#include "k_color.h"
#include "k_follower.h"
#include "k_battle.h"
#include "k_rank.h"
#ifdef HW3SOUND
#include "hardware/hw3sound.h"
@ -1284,7 +1286,7 @@ void P_DoPlayerExit(player_t *player)
if ((gametyperules & GTR_CIRCUIT)) // If in Race Mode, allow
{
K_KartUpdatePosition(player);
K_UpdateAllPlayerPositions();
if (cv_kartvoices.value)
{
@ -1353,6 +1355,7 @@ void P_DoPlayerExit(player_t *player)
if (RINGTOTAL(player) > 0)
{
player->totalring += RINGTOTAL(player);
grandprixinfo.rank.rings += RINGTOTAL(player);
extra = player->totalring / lifethreshold;
@ -1363,6 +1366,16 @@ void P_DoPlayerExit(player_t *player)
player->xtralife = extra;
}
}
if (grandprixinfo.eventmode == GPEVENT_NONE)
{
grandprixinfo.rank.winPoints += K_CalculateGPRankPoints(player->position, grandprixinfo.rank.totalPlayers);
grandprixinfo.rank.laps += player->lapPoints;
}
else if (grandprixinfo.eventmode == GPEVENT_SPECIAL)
{
grandprixinfo.rank.specialWon = true;
}
}
}
}
@ -3670,10 +3683,10 @@ static void P_CalcPostImg(player_t *player)
else
pviewheight = player->mo->z + player->viewheight;
if (player->awayviewtics && player->awayviewmobj && !P_MobjWasRemoved(player->awayviewmobj))
if (player->awayview.tics && player->awayview.mobj && !P_MobjWasRemoved(player->awayview.mobj))
{
sector = player->awayviewmobj->subsector->sector;
pviewheight = player->awayviewmobj->z + 20*FRACUNIT;
sector = player->awayview.mobj->subsector->sector;
pviewheight = player->awayview.mobj->z;
}
for (i = 0; i <= (unsigned)r_splitscreen; i++)
@ -3997,6 +4010,25 @@ DoABarrelRoll (player_t *player)
player->tilt = slope;
}
void P_TickAltView(altview_t *view)
{
if (view->mobj != NULL && P_MobjWasRemoved(view->mobj) == true)
{
P_SetTarget(&view->mobj, NULL); // remove view->mobj asap if invalid
view->tics = 0; // reset to zero
}
if (view->tics > 0)
{
view->tics--;
if (view->tics == 0)
{
P_SetTarget(&view->mobj, NULL);
}
}
}
//
// P_PlayerThink
//
@ -4020,18 +4052,11 @@ void P_PlayerThink(player_t *player)
player->old_drawangle = player->drawangle;
if (player->awayviewmobj && P_MobjWasRemoved(player->awayviewmobj))
{
P_SetTarget(&player->awayviewmobj, NULL); // remove awayviewmobj asap if invalid
player->awayviewtics = 0; // reset to zero
}
P_TickAltView(&player->awayview);
if (player->flashcount)
player->flashcount--;
if (player->awayviewtics && player->awayviewtics != -1)
player->awayviewtics--;
// Track airtime
if (P_IsObjectOnGround(player->mo)
&& !P_PlayerInPain(player)) // This isn't airtime, but it's control loss all the same.

View file

@ -928,7 +928,22 @@ void R_ApplyViewMorph(int s)
angle_t R_ViewRollAngle(const player_t *player)
{
angle_t roll = player->viewrollangle;
angle_t roll = 0;
if (gamestate != GS_LEVEL)
{
// FIXME: The way this is implemented is totally
// incompatible with cameras that aren't directly
// tied to the player. (podium, titlemap,
// MT_ALTVIEWMAN in general)
// All of these player variables should affect their
// camera_t in P_MoveChaseCamera, and then this
// just returns that variable instead.
return 0;
}
roll = player->viewrollangle;
if (cv_tilting.value)
{
@ -1192,10 +1207,10 @@ static void R_SetupAimingFrame(int s)
player_t *player = &players[displayplayers[s]];
camera_t *thiscam = &camera[s];
if (player->awayviewtics)
if (player->awayview.tics)
{
newview->aim = player->awayviewaiming;
newview->angle = player->awayviewmobj->angle;
newview->aim = player->awayview.mobj->pitch;
newview->angle = player->awayview.mobj->angle;
}
else if (thiscam && thiscam->chase)
{
@ -1237,15 +1252,15 @@ void R_SetupFrame(int s)
R_SetupAimingFrame(s);
if (player->awayviewtics)
if (player->awayview.tics)
{
// cut-away view stuff
r_viewmobj = player->awayviewmobj; // should be a MT_ALTVIEWMAN
r_viewmobj = player->awayview.mobj; // should be a MT_ALTVIEWMAN
I_Assert(r_viewmobj != NULL);
newview->x = r_viewmobj->x;
newview->y = r_viewmobj->y;
newview->z = r_viewmobj->z + 20*FRACUNIT;
newview->z = r_viewmobj->z;
R_SetupCommonFrame(player, r_viewmobj->subsector);
}
@ -1306,10 +1321,10 @@ void R_SkyboxFrame(int s)
vector3_t campos = {0,0,0}; // Position of player's actual view point
mobj_t *center = player->skybox.centerpoint;
if (player->awayviewtics) {
campos.x = player->awayviewmobj->x;
campos.y = player->awayviewmobj->y;
campos.z = player->awayviewmobj->z + 20*FRACUNIT;
if (player->awayview.tics) {
campos.x = player->awayview.mobj->x;
campos.y = player->awayview.mobj->y;
campos.z = player->awayview.mobj->z;
} else if (thiscam->chase) {
campos.x = thiscam->x;
campos.y = thiscam->y;
@ -1403,7 +1418,7 @@ boolean R_IsViewpointThirdPerson(player_t *player, boolean skybox)
boolean chasecam = R_ViewpointHasChasecam(player);
// cut-away view stuff
if (player->awayviewtics || skybox)
if (player->awayview.tics || skybox)
return chasecam;
// use outside cam view
else if (!player->spectator && chasecam)

View file

@ -540,9 +540,9 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume)
continue;
}
if (player->awayviewtics)
if (player->awayview.tics)
{
listenmobj[i] = player->awayviewmobj;
listenmobj[i] = player->awayview.mobj;
}
else
{
@ -572,7 +572,7 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume)
continue;
}
if (camera[i].chase && !player->awayviewtics)
if (camera[i].chase && !player->awayview.tics)
{
listener[i].x = camera[i].x;
listener[i].y = camera[i].y;
@ -827,9 +827,9 @@ void S_UpdateSounds(void)
continue;
}
if (player->awayviewtics)
if (player->awayview.tics)
{
listenmobj[i] = player->awayviewmobj;
listenmobj[i] = player->awayview.mobj;
}
else
{
@ -858,7 +858,7 @@ void S_UpdateSounds(void)
continue;
}
if (camera[i].chase && !player->awayviewtics)
if (camera[i].chase && !player->awayview.tics)
{
listener[i].x = camera[i].x;
listener[i].y = camera[i].y;

View file

@ -46,6 +46,7 @@ TYPEDEF (respawnvars_t);
TYPEDEF (botvars_t);
TYPEDEF (skybox_t);
TYPEDEF (itemroulette_t);
TYPEDEF (altview_t);
TYPEDEF (player_t);
// d_clisrv.h
@ -197,6 +198,9 @@ TYPEDEF (t_floor_t);
// k_waypoint.h
TYPEDEF (waypoint_t);
// k_rank.h
TYPEDEF (gpRank_t);
// lua_hudlib_drawlist.h
typedef struct huddrawlist_s *huddrawlist_h;