Merge branch 'master' into recorrect-ringbox-buff-mistake

This commit is contained in:
VelocitOni 2025-07-24 23:47:13 -04:00
commit 9726c999ce
18 changed files with 614 additions and 171 deletions

View file

@ -2965,7 +2965,8 @@ static void readcondition(UINT16 set, UINT32 id, char *word2)
|| (++offset && fastcmp(params[0], "REPLAY")) || (++offset && fastcmp(params[0], "REPLAY"))
|| (++offset && fastcmp(params[0], "CRASH")) || (++offset && fastcmp(params[0], "CRASH"))
|| (++offset && fastcmp(params[0], "TUTORIALSKIP")) || (++offset && fastcmp(params[0], "TUTORIALSKIP"))
|| (++offset && fastcmp(params[0], "TUTORIALDONE"))) || (++offset && fastcmp(params[0], "TUTORIALDONE"))
|| (++offset && fastcmp(params[0], "PLAYGROUNDROUTE")))
{ {
//PARAMCHECK(1); //PARAMCHECK(1);
ty = UC_ADDON + offset; ty = UC_ADDON + offset;
@ -3633,6 +3634,11 @@ void readmaincfg(MYFILE *f, boolean mainfile)
titlemap = Z_StrDup(word2); titlemap = Z_StrDup(word2);
titlechanged = true; titlechanged = true;
} }
else if (fastcmp(word, "TUTORIALPLAYGROUNDMAP"))
{
Z_Free(tutorialplaygroundmap);
tutorialplaygroundmap = Z_StrDup(word2);
}
else if (fastcmp(word, "TUTORIALCHALLENGEMAP")) else if (fastcmp(word, "TUTORIALCHALLENGEMAP"))
{ {
Z_Free(tutorialchallengemap); Z_Free(tutorialchallengemap);

View file

@ -280,6 +280,7 @@ extern boolean looptitle;
extern char * bootmap; //bootmap for loading a map on startup extern char * bootmap; //bootmap for loading a map on startup
extern char * podiummap; // map to load for podium extern char * podiummap; // map to load for podium
extern char * tutorialplaygroundmap; // map to load for playground
extern char * tutorialchallengemap; // map to load for tutorial skip extern char * tutorialchallengemap; // map to load for tutorial skip
extern UINT8 tutorialchallenge; extern UINT8 tutorialchallenge;
#define TUTORIALSKIP_NONE 0 #define TUTORIALSKIP_NONE 0

View file

@ -504,7 +504,7 @@ static void F_IntroDrawScene(void)
} }
// Joyeaux Anniversaire // Joyeaux Anniversaire
V_DrawCenteredMenuString(BASEVIDWIDTH/2, 174 - (textoffs/FRACUNIT), (trans<<V_ALPHASHIFT)|V_SUBTRACT, "2013 - 11 years - 2024"); V_DrawCenteredMenuString(BASEVIDWIDTH/2, 174 - (textoffs/FRACUNIT), (trans<<V_ALPHASHIFT)|V_SUBTRACT, "2013 - 12 years - 2025");
// Joyeaux Adressaire // Joyeaux Adressaire
V_DrawCenteredMenuString(BASEVIDWIDTH/2, 184 - (textoffs/FRACUNIT), (trans<<V_ALPHASHIFT)|V_SUBTRACT, "kartkrew.org"); V_DrawCenteredMenuString(BASEVIDWIDTH/2, 184 - (textoffs/FRACUNIT), (trans<<V_ALPHASHIFT)|V_SUBTRACT, "kartkrew.org");

View file

@ -179,6 +179,7 @@ boolean looptitle = true;
char * bootmap = NULL; //bootmap for loading a map on startup char * bootmap = NULL; //bootmap for loading a map on startup
char * podiummap = NULL; // map to load for podium char * podiummap = NULL; // map to load for podium
char * tutorialplaygroundmap = NULL; // map to load for playground
char * tutorialchallengemap = NULL; // map to load for tutorial skip char * tutorialchallengemap = NULL; // map to load for tutorial skip
UINT8 tutorialchallenge = TUTORIALSKIP_NONE; UINT8 tutorialchallenge = TUTORIALSKIP_NONE;
@ -5076,7 +5077,7 @@ void G_EndGame(void)
// Only do evaluation and credits in singleplayer contexts // Only do evaluation and credits in singleplayer contexts
if (!netgame) if (!netgame)
{ {
if (gametype == GT_TUTORIAL) if (gametype == GT_TUTORIAL && gamedata->gonerlevel < GDGONER_DONE)
{ {
// Tutorial was finished // Tutorial was finished
gamedata->tutorialdone = true; gamedata->tutorialdone = true;
@ -5129,6 +5130,13 @@ void G_EndGame(void)
return; return;
} }
if (gametype == GT_TUTORIAL && M_GameAboutToStart() && restoreMenu == NULL)
{
// Playground Hack
F_StartIntro();
return;
}
// Time to return to the menu. // Time to return to the menu.
Command_ExitGame_f(); Command_ExitGame_f();
} }

View file

@ -83,6 +83,7 @@ void srb2::save_ng_gamedata()
ng.milestones.enteredtutorialchallenge = gamedata->enteredtutorialchallenge; ng.milestones.enteredtutorialchallenge = gamedata->enteredtutorialchallenge;
ng.milestones.sealedswapalerted = gamedata->sealedswapalerted; ng.milestones.sealedswapalerted = gamedata->sealedswapalerted;
ng.milestones.tutorialdone = gamedata->tutorialdone; ng.milestones.tutorialdone = gamedata->tutorialdone;
ng.milestones.playgroundroute = gamedata->playgroundroute;
ng.milestones.gonerlevel = gamedata->gonerlevel; ng.milestones.gonerlevel = gamedata->gonerlevel;
ng.prisons.thisprisoneggpickup = gamedata->thisprisoneggpickup; ng.prisons.thisprisoneggpickup = gamedata->thisprisoneggpickup;
ng.prisons.prisoneggstothispickup = gamedata->prisoneggstothispickup; ng.prisons.prisoneggstothispickup = gamedata->prisoneggstothispickup;
@ -471,6 +472,7 @@ void srb2::load_ng_gamedata()
gamedata->enteredtutorialchallenge = js.milestones.enteredtutorialchallenge; gamedata->enteredtutorialchallenge = js.milestones.enteredtutorialchallenge;
gamedata->sealedswapalerted = js.milestones.sealedswapalerted; gamedata->sealedswapalerted = js.milestones.sealedswapalerted;
gamedata->tutorialdone = js.milestones.tutorialdone; gamedata->tutorialdone = js.milestones.tutorialdone;
gamedata->playgroundroute = js.milestones.playgroundroute;
gamedata->gonerlevel = js.milestones.gonerlevel; gamedata->gonerlevel = js.milestones.gonerlevel;
gamedata->thisprisoneggpickup = js.prisons.thisprisoneggpickup; gamedata->thisprisoneggpickup = js.prisons.thisprisoneggpickup;

View file

@ -96,6 +96,7 @@ struct GamedataMilestonesJson final
bool enteredtutorialchallenge; bool enteredtutorialchallenge;
bool sealedswapalerted; bool sealedswapalerted;
bool tutorialdone; bool tutorialdone;
bool playgroundroute;
SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
GamedataMilestonesJson, GamedataMilestonesJson,
@ -109,7 +110,8 @@ struct GamedataMilestonesJson final
finishedtutorialchallenge, finishedtutorialchallenge,
enteredtutorialchallenge, enteredtutorialchallenge,
sealedswapalerted, sealedswapalerted,
tutorialdone tutorialdone,
playgroundroute
) )
}; };

View file

@ -14125,8 +14125,10 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
{ {
// Set up bail charge, provided we have something to bail with (any rings or item resource). // Set up bail charge, provided we have something to bail with (any rings or item resource).
boolean grounded = P_IsObjectOnGround(player->mo); boolean grounded = P_IsObjectOnGround(player->mo);
onground && player->tumbleBounces == 0 ? player->bailcharge += 2 : player->bailcharge++; // charge twice as fast on the ground // onground && player->tumbleBounces == 0 ? player->bailcharge += 2 : player->bailcharge++; // charge twice as fast on the ground
if ((P_PlayerInPain(player) && player->bailcharge == 1) || (grounded && P_PlayerInPain(player) && player->bailcharge == 2)) // this is brittle .. player->bailcharge += 2;
// if ((P_PlayerInPain(player) && player->bailcharge == 1) || (grounded && P_PlayerInPain(player) && player->bailcharge == 2)) // this is brittle ..
if (player->bailcharge == 2)
{ {
mobj_t *bail = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_BAILCHARGE); mobj_t *bail = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_BAILCHARGE);
S_StartSound(bail, sfx_gshb9); // I tried to use info.c, but you can't play sounds on mobjspawn via A_PlaySound S_StartSound(bail, sfx_gshb9); // I tried to use info.c, but you can't play sounds on mobjspawn via A_PlaySound

View file

@ -140,6 +140,7 @@ typedef enum
MBF_SOUNDLESS = 1<<1, // do not play base menu sounds MBF_SOUNDLESS = 1<<1, // do not play base menu sounds
MBF_NOLOOPENTRIES = 1<<2, // do not loop M_NextOpt/M_PrevOpt MBF_NOLOOPENTRIES = 1<<2, // do not loop M_NextOpt/M_PrevOpt
MBF_DRAWBGWHILEPLAYING = 1<<3, // run backroutine() outside of GS_MENU MBF_DRAWBGWHILEPLAYING = 1<<3, // run backroutine() outside of GS_MENU
MBF_CANTRESTORE = 1<<4, // Do not use in restoreMenu
} menubehaviourflags_t; } menubehaviourflags_t;
struct menuitem_t struct menuitem_t
@ -221,7 +222,7 @@ typedef enum
quitkart quitkart
} main_e; } main_e;
extern menuitem_t MAIN_Goner[]; extern menu_t MAIN_GonerAccessibilityDef;
extern menu_t MAIN_GonerDef; extern menu_t MAIN_GonerDef;
void M_GonerTick(void); void M_GonerTick(void);
@ -229,9 +230,12 @@ void M_GonerBGTick(void);
void M_GonerBGImplyPassageOfTime(void); void M_GonerBGImplyPassageOfTime(void);
void M_DrawGonerBack(void); void M_DrawGonerBack(void);
void M_GonerProfile(INT32 choice); void M_GonerProfile(INT32 choice);
void M_GonerChoice(INT32 choice);
void M_GonerTutorial(INT32 choice); void M_GonerTutorial(INT32 choice);
void M_GonerPlayground(INT32 choice);
void M_GonerResetLooking(int type); void M_GonerResetLooking(int type);
void M_GonerCheckLooking(void); void M_GonerCheckLooking(void);
void M_GonerResetText(boolean completely);
void M_GonerGDQ(boolean opinion); void M_GonerGDQ(boolean opinion);
boolean M_GonerMusicPlayable(void); boolean M_GonerMusicPlayable(void);

View file

@ -662,7 +662,7 @@ boolean M_ConsiderSealedSwapAlert(void)
void M_ValidateRestoreMenu(void) void M_ValidateRestoreMenu(void)
{ {
if (restoreMenu == NULL || restoreMenu == &MAIN_GonerDef) if (restoreMenu == NULL || (restoreMenu->behaviourflags & MBF_CANTRESTORE))
restoreMenu = &MainDef; restoreMenu = &MainDef;
} }
@ -829,17 +829,10 @@ void M_StartControlPanel(void)
if (gamedata != NULL if (gamedata != NULL
&& gamedata->gonerlevel < GDGONER_OUTRO && gamedata->gonerlevel < GDGONER_OUTRO
&& gamestartchallenge < MAXUNLOCKABLES) && M_GameAboutToStart())
{ {
// See M_GameTrulyStarted gamedata->gonerlevel = GDGONER_OUTRO;
if ( M_GonerBGImplyPassageOfTime();
gamedata->unlockpending[gamestartchallenge]
|| gamedata->unlocked[gamestartchallenge]
)
{
gamedata->gonerlevel = GDGONER_OUTRO;
M_GonerBGImplyPassageOfTime();
}
} }
if (M_GameTrulyStarted() == false) if (M_GameTrulyStarted() == false)
@ -847,7 +840,7 @@ void M_StartControlPanel(void)
// Are you ready for the First Boot Experience? // Are you ready for the First Boot Experience?
M_ResetOptions(); M_ResetOptions();
currentMenu = &MAIN_GonerDef; currentMenu = &MAIN_GonerAccessibilityDef;
restoreMenu = NULL; restoreMenu = NULL;
M_PlayMenuJam(); M_PlayMenuJam();

View file

@ -216,6 +216,9 @@ int LUA_PushGlobals(lua_State *L, const char *word)
} else if (fastcmp(word,"podiummap")) { } else if (fastcmp(word,"podiummap")) {
lua_pushstring(L, podiummap); lua_pushstring(L, podiummap);
return 1; return 1;
} else if (fastcmp(word,"tutorialplaygroundmap")) {
lua_pushstring(L, tutorialplaygroundmap);
return 1;
} else if (fastcmp(word,"tutorialchallengemap")) { } else if (fastcmp(word,"tutorialchallengemap")) {
lua_pushstring(L, tutorialchallengemap); lua_pushstring(L, tutorialchallengemap);
return 1; return 1;

View file

@ -660,9 +660,7 @@ void M_ClearStats(void)
gamedata->chaokeytutorial = false; gamedata->chaokeytutorial = false;
gamedata->majorkeyskipattempted = false; gamedata->majorkeyskipattempted = false;
gamedata->enteredtutorialchallenge = false; gamedata->enteredtutorialchallenge = false;
gamedata->finishedtutorialchallenge = false;
gamedata->sealedswapalerted = false; gamedata->sealedswapalerted = false;
gamedata->tutorialdone = false;
gamedata->musicstate = GDMUSIC_NONE; gamedata->musicstate = GDMUSIC_NONE;
gamedata->importprofilewins = false; gamedata->importprofilewins = false;
@ -777,6 +775,10 @@ void M_ClearSecrets(void)
gamedata->chaokeys = GDINIT_CHAOKEYS; gamedata->chaokeys = GDINIT_CHAOKEYS;
gamedata->prisoneggstothispickup = GDINIT_PRISONSTOPRIZE; gamedata->prisoneggstothispickup = GDINIT_PRISONSTOPRIZE;
gamedata->tutorialdone = false;
gamedata->playgroundroute = false;
gamedata->finishedtutorialchallenge = false;
gamedata->gonerlevel = GDGONER_INIT; gamedata->gonerlevel = GDGONER_INIT;
} }
@ -1755,6 +1757,8 @@ boolean M_CheckCondition(condition_t *cn, player_t *player)
return (gamedata->finishedtutorialchallenge == true); return (gamedata->finishedtutorialchallenge == true);
case UC_TUTORIALDONE: case UC_TUTORIALDONE:
return (gamedata->tutorialdone == true); return (gamedata->tutorialdone == true);
case UC_PLAYGROUND:
return (gamedata->playgroundroute == true);
case UC_PASSWORD: case UC_PASSWORD:
return (cn->stringvar == NULL); return (cn->stringvar == NULL);
@ -2641,6 +2645,8 @@ static const char *M_GetConditionString(condition_t *cn)
return "successfully skip the Tutorial"; return "successfully skip the Tutorial";
case UC_TUTORIALDONE: case UC_TUTORIALDONE:
return "complete the Tutorial"; return "complete the Tutorial";
case UC_PLAYGROUND:
return "pick the Playground";
case UC_PASSWORD: case UC_PASSWORD:
return "enter a secret password"; return "enter a secret password";
@ -3470,6 +3476,27 @@ boolean M_GameTrulyStarted(void)
return (gamedata->gonerlevel == GDGONER_DONE); return (gamedata->gonerlevel == GDGONER_DONE);
} }
boolean M_GameAboutToStart(void)
{
// Fail safe
if (gamedata == NULL)
return false;
// Not set
if (gamestartchallenge >= MAXUNLOCKABLES)
return true;
// An unfortunate sidestep, but sync is important.
if (netgame)
return true;
// Pending unlocked, but not unlocked
return (
gamedata->unlockpending[gamestartchallenge]
&& !gamedata->unlocked[gamestartchallenge]
);
}
boolean M_CheckNetUnlockByID(UINT16 unlockid) boolean M_CheckNetUnlockByID(UINT16 unlockid)
{ {
if (unlockid >= MAXUNLOCKABLES if (unlockid >= MAXUNLOCKABLES

View file

@ -68,6 +68,7 @@ typedef enum
UC_CRASH, // Hee ho ! UC_CRASH, // Hee ho !
UC_TUTORIALSKIP, // Complete the Tutorial Challenge UC_TUTORIALSKIP, // Complete the Tutorial Challenge
UC_TUTORIALDONE, // Complete the Tutorial at all UC_TUTORIALDONE, // Complete the Tutorial at all
UC_PLAYGROUND, // Go to the playground instead..?
UC_PASSWORD, // Type in something funny UC_PASSWORD, // Type in something funny
@ -301,7 +302,7 @@ typedef enum {
#define GDCONVERT_ROUNDSTOKEY 5 #define GDCONVERT_ROUNDSTOKEY 5
#define GDINIT_CHAOKEYS 10 // Start with 10 Chao Keys !! #define GDINIT_CHAOKEYS 0 // Start with ZERO Chao Keys. You get NONE. fizzy lifting dink
#define GDINIT_PRISONSTOPRIZE 15 // 15 Prison Eggs to your [Wild Prize] !! #define GDINIT_PRISONSTOPRIZE 15 // 15 Prison Eggs to your [Wild Prize] !!
typedef enum { typedef enum {
@ -395,6 +396,7 @@ struct gamedata_t
boolean finishedtutorialchallenge; boolean finishedtutorialchallenge;
boolean sealedswapalerted; boolean sealedswapalerted;
boolean tutorialdone; boolean tutorialdone;
boolean playgroundroute;
gdmusic_t musicstate; gdmusic_t musicstate;
UINT8 gonerlevel; UINT8 gonerlevel;
@ -470,6 +472,7 @@ extern UINT16 gamestartchallenge;
boolean M_CheckNetUnlockByID(UINT16 unlockid); boolean M_CheckNetUnlockByID(UINT16 unlockid);
boolean M_SecretUnlocked(INT32 type, boolean local); boolean M_SecretUnlocked(INT32 type, boolean local);
boolean M_GameTrulyStarted(void); boolean M_GameTrulyStarted(void);
boolean M_GameAboutToStart(void);
boolean M_CupLocked(cupheader_t *cup); boolean M_CupLocked(cupheader_t *cup);
boolean M_CupSecondRowLocked(void); boolean M_CupSecondRowLocked(void);
boolean M_MapLocked(UINT16 mapnum); boolean M_MapLocked(UINT16 mapnum);

View file

@ -28,6 +28,7 @@
#include "doomdef.h" #include "doomdef.h"
#include "doomstat.h" #include "doomstat.h"
#include "doomtype.h" #include "doomtype.h"
#include "f_finale.h"
#include "g_game.h" #include "g_game.h"
#include "k_menu.h" #include "k_menu.h"
#include "m_cheat.h" #include "m_cheat.h"
@ -613,6 +614,7 @@ void f_devmode()
void f_proceed() void f_proceed()
{ {
#if 0
gamedata->gonerlevel = GDGONER_DONE; gamedata->gonerlevel = GDGONER_DONE;
gamedata->finishedtutorialchallenge = true; gamedata->finishedtutorialchallenge = true;
M_UpdateUnlockablesAndExtraEmblems(true, true); M_UpdateUnlockablesAndExtraEmblems(true, true);
@ -621,6 +623,11 @@ void f_proceed()
S_StartSound(0, sfx_kc42); S_StartSound(0, sfx_kc42);
G_SaveGameData(); G_SaveGameData();
#else
F_StartIntro();
M_ClearMenus(true);
M_GonerResetText(true);
#endif
} }
}; // namespace }; // namespace

View file

@ -101,7 +101,7 @@ void M_QuitSRB2(INT32 choice)
(void)choice; (void)choice;
if (M_GameTrulyStarted()) if (!M_GameAboutToStart() && M_GameTrulyStarted())
{ {
INT32 mrand = M_RandomKey(sizeof(quitsounds) / sizeof(INT32)); INT32 mrand = M_RandomKey(sizeof(quitsounds) / sizeof(INT32));
if (quitsounds[mrand]) if (quitsounds[mrand])

View file

@ -27,17 +27,109 @@
#include "../m_pw.h" #include "../m_pw.h"
#include "../z_zone.h" #include "../z_zone.h"
#include <forward_list> #include <deque>
#include "../core/string.h" #include "../core/string.h"
static void M_GonerDrawer(void); static void M_GonerDrawer(void);
static void M_GonerChoiceDrawer(void);
static void M_GonerConclude(INT32 choice); static void M_GonerConclude(INT32 choice);
static boolean M_GonerInputs(INT32 ch); static boolean M_GonerInputs(INT32 ch);
menuitem_t MAIN_Goner[] = static menuitem_t MAIN_GonerAccessibility[] =
{ {
{IT_STRING | IT_CALL, NULL, NULL, NULL, {.routine = M_QuitSRB2}, 0, 0}, // will be replaced {IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0},
};
static UINT32 goneraccessibilitytick = 0;
//#define HANDSTRAIN
#ifdef HANDSTRAIN
static void M_GonerHandStrain(INT32 ch)
{
if (ch != MA_YES)
return;
CV_StealthSet(&cv_kickstartaccel[0], "On");
}
#endif
static void M_GonerPhotosensitivity(INT32 ch)
{
if (ch == MA_YES)
{
CV_StealthSet(&cv_reducevfx, "Yes");
CV_StealthSet(&cv_screenshake, "Off");
CV_StealthSet(&cv_tilting, "Off");
}
#ifdef HANDSTRAIN
M_StartMessage("Hand strain warning",
"You may be required to press many buttons\n"
"at once in order to control your Ring Racer.\n"
"\n"
"There is an option for your Accel input\n"
"to \"lock\" on after being held for 1 second.\n"
"Would you like to turn it on?\n"
, &M_GonerHandStrain, MM_YESNO, "Yes, I want Accel to \"lock\"", "No thanks");
#endif
}
static void M_GonerAccessibilityTick(void)
{
if (goneraccessibilitytick)
{
if (!menumessage.active && !menutransition.dest)
{
M_SetupNextMenu(&MAIN_GonerDef, true);
M_GonerTick(); // tick once, for safety
}
return;
}
goneraccessibilitytick++;
M_StartMessage("Photosensitivity warning",
"This game has ""\x87""flashing lights and high-contrast\n"
"patterns.""\x80"" Listen to your body, and\n"
"stop playing if you feel unwell.\n"
"\n"
"There is a ""\x88""special mode""\x80"" to reduce some\n"
"visual effects. Would you like to turn it on?\n"
, &M_GonerPhotosensitivity, MM_YESNO, "Yes, reduce effects", "No thanks");
return;
}
static void M_GonerAccessibilityDrawer(void)
{
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
}
menu_t MAIN_GonerAccessibilityDef = {
sizeof (MAIN_GonerAccessibility) / sizeof (menuitem_t),
NULL,
0,
MAIN_GonerAccessibility,
26, 160,
0, 0,
MBF_CANTRESTORE,
"_GONER",
0, 0,
M_GonerAccessibilityDrawer,
NULL,
M_GonerAccessibilityTick,
NULL,
NULL,
NULL,
};
static menuitem_t MAIN_Goner[] =
{
{IT_STRING | IT_CVAR | IT_CV_STRING, "PASSWORD",
"ATTEMPT ADMINISTRATOR ACCESS.", NULL,
{.cvar = &cv_dummyextraspassword}, 0, 0},
{IT_STRING | IT_CALL, "EXIT PROGRAM", {IT_STRING | IT_CALL, "EXIT PROGRAM",
"CONCLUDE OBSERVATIONS NOW.", NULL, "CONCLUDE OBSERVATIONS NOW.", NULL,
@ -55,9 +147,9 @@ menuitem_t MAIN_Goner[] =
"ASSIGN VEHICLE INPUTS.", NULL, "ASSIGN VEHICLE INPUTS.", NULL,
{.routine = M_GonerProfile}, 0, 0}, {.routine = M_GonerProfile}, 0, 0},
{IT_STRING | IT_CALL, "BEGIN TUTORIAL", {IT_STRING | IT_CALL, "MAKE CHOICE",
"PREPARE FOR INTEGRATION.", NULL, "PREPARE FOR INTEGRATION?", NULL,
{.routine = M_GonerTutorial}, 0, 0}, {.routine = M_GonerChoice}, 0, 0},
{IT_STRING | IT_CALL, "START GAME", {IT_STRING | IT_CALL, "START GAME",
"I WILL SUCCEED.", NULL, "I WILL SUCCEED.", NULL,
@ -71,7 +163,7 @@ menu_t MAIN_GonerDef = {
MAIN_Goner, MAIN_Goner,
26, 160, 26, 160,
0, sizeof (MAIN_Goner) / sizeof (menuitem_t), // extra2 is final width 0, sizeof (MAIN_Goner) / sizeof (menuitem_t), // extra2 is final width
MBF_UD_LR_FLIPPED, MBF_CANTRESTORE|MBF_UD_LR_FLIPPED,
"_GONER", "_GONER",
0, 0, 0, 0,
M_GonerDrawer, M_GonerDrawer,
@ -82,9 +174,57 @@ menu_t MAIN_GonerDef = {
M_GonerInputs, M_GonerInputs,
}; };
static menuitem_t MAIN_GonerChoice[] =
{
{IT_STRING | IT_CALL, "Tails' way",
"As a child scientist, Tails has recorded bits\n"
"and pieces of an adventure he and Eggman went\n"
"on while trying out their new Ring Racers.\n"
"\n"
"This is a structured, back-to-basics tutorial\n"
"that will likely take ""\x88""10-20 minutes""\x80"" of your time.",
NULL, {.routine = M_GonerTutorial}, 0, 0},
//{IT_STRING, NULL, NULL, NULL, {.routine = M_QuitSRB2}, 0, 0}, // will be replaced
{IT_STRING | IT_CALL, "Eggman's way",
"As a childlike scientist, Eggman has turned the\n"
"wrecked Egg Carrier into a giant skatepark,\n"
"dotted with fun collectables to test drivers.\n"
"\n"
"You can ""\x88""exit immediately""\x80"" and get to racing...\n"
"or spend ""\x88""as long as you want""\x80"" in the playground!",
NULL, {.routine = M_GonerPlayground}, 0, 0},
};
static menu_t MAIN_GonerChoiceDef = {
sizeof (MAIN_GonerChoice) / sizeof (menuitem_t),
&MAIN_GonerDef,
0,
MAIN_GonerChoice,
26, 160,
0, 0,
MBF_CANTRESTORE|MBF_UD_LR_FLIPPED|MBF_NOLOOPENTRIES,
"_GONER",
0, 0,
M_GonerChoiceDrawer,
M_DrawGonerBack,
NULL,
NULL,
NULL,
NULL,
};
namespace namespace
{ {
typedef enum
{
GONERCHOICE_TAILS = 0,
//GONERCHOICE_NONEBINEY,
GONERCHOICE_EGGMAN
} gonerchoices_t;
typedef enum typedef enum
{ {
GONERSPEAKER_EGGMAN = 0, GONERSPEAKER_EGGMAN = 0,
@ -200,8 +340,8 @@ public:
}; };
}; };
std::forward_list<GonerChatLine> LinesToDigest; std::deque<GonerChatLine> LinesToDigest;
std::forward_list<GonerChatLine> LinesOutput; std::deque<GonerChatLine> LinesOutput;
class GonerBGData class GonerBGData
{ {
@ -429,16 +569,6 @@ void Miles_Electric_Lower()
int goner_levelworking = GDGONER_INIT; int goner_levelworking = GDGONER_INIT;
bool goner_gdq = false; bool goner_gdq = false;
void M_GonerResetText(void)
{
goner_typewriter.ClearText();
LinesToDigest.clear();
LinesOutput.clear();
goner_scroll = 0;
goner_scrollend = -1;
}
static void Initial_Control_Info(void) static void Initial_Control_Info(void)
{ {
if (cv_currprofile.value != -1) if (cv_currprofile.value != -1)
@ -453,24 +583,13 @@ static void Initial_Control_Info(void)
) )
); );
if (LinesToDigest.empty()) LinesToDigest.push_back(line);
{
LinesToDigest.emplace_front(line);
return;
}
LinesToDigest.emplace_after(
LinesToDigest.begin(),
line
);
} }
void M_AddGonerLines(void) void M_AddGonerLines(void)
{ {
SRB2_ASSERT(LinesToDigest.empty()); SRB2_ASSERT(LinesToDigest.empty());
auto _ = srb2::finally([]() { LinesToDigest.reverse(); });
static bool leftoff = false; static bool leftoff = false;
goner_delay = TICRATE; goner_delay = TICRATE;
@ -480,7 +599,7 @@ void M_AddGonerLines(void)
{ {
if (!MAIN_Goner[0].mvar2) if (!MAIN_Goner[0].mvar2)
{ {
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0,
"Metal Sonic. Are you online?"); "Metal Sonic. Are you online?");
} }
@ -488,10 +607,10 @@ void M_AddGonerLines(void)
if (leftoff) if (leftoff)
{ {
LinesToDigest.emplace_front(0, Miles_Look_Camera); LinesToDigest.emplace_back(0, Miles_Look_Camera);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0,
"It must have run into some sort of error..."); "It must have run into some sort of error...");
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0,
"Don't worry, your settings so far are saved. "\ "Don't worry, your settings so far are saved. "\
"Let's pick up where we left off."); "Let's pick up where we left off.");
@ -506,40 +625,40 @@ void M_AddGonerLines(void)
{ {
case GDGONER_VIDEO: case GDGONER_VIDEO:
{ {
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/2, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/2,
"Take a close look, Miles. Moments ago he was at my throat! "\ "Take a close look, Miles. Moments ago he was at my throat! "\
"Now he's docile as can be on that operating table."); "Now he's docile as can be on that operating table.");
LinesToDigest.emplace_front(0, Miles_Look_Camera); LinesToDigest.emplace_back(0, Miles_Look_Camera);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0,
"I don't feel very safe!"); "I don't feel very safe!");
LinesToDigest.emplace_front(0, Miles_Electric_Lower); LinesToDigest.emplace_back(0, Miles_Electric_Lower);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/4, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/4,
"But its programming is definitely locked down..."); "But its programming is definitely locked down...");
LinesToDigest.emplace_front(0, Miles_Look_Electric); LinesToDigest.emplace_back(0, Miles_Look_Electric);
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0,
"You've given me quite the headache, Metal. "\ "You've given me quite the headache, Metal. "\
"Thankfully, Tails caught you in the act."); "Thankfully, Tails caught you in the act.");
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/5, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/5,
"Wait, I'm getting weird readings over the network."); "Wait, I'm getting weird readings over the network.");
LinesToDigest.emplace_front(0, Miles_Look_Camera); LinesToDigest.emplace_back(0, Miles_Look_Camera);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0,
"Metal Sonic is the unit labeled \"MS-1\", right?"); "Metal Sonic is the unit labeled \"MS-1\", right?");
LinesToDigest.emplace_front(0, Miles_Look_Electric); LinesToDigest.emplace_back(0, Miles_Look_Electric);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE,
"The ""\x87""viewport""\x80"" and ""\x87""audio""\x80"" "\ "The ""\x87""viewport""\x80"" and ""\x87""audio""\x80"" "\
"config looks like it got messed up."); "config looks like it got messed up.");
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0,
"So you're right. I wonder if it has anything to do with that outburst."); "So you're right. I wonder if it has anything to do with that outburst.");
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0,
"Alright, Metal! I don't remember your specifications offhand. "\ "Alright, Metal! I don't remember your specifications offhand. "\
"First things first, go ahead and set up your "\ "First things first, go ahead and set up your "\
"\x87""Video Options""\x80"" yourself."); "\x87""Video Options""\x80"" yourself.");
LinesToDigest.emplace_front(0, Initial_Control_Info); LinesToDigest.emplace_back(0, Initial_Control_Info);
break; break;
} }
@ -547,101 +666,101 @@ void M_AddGonerLines(void)
{ {
if (!leftoff) if (!leftoff)
{ {
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0,
"Ah, you can see us now. Good."); "Ah, you can see us now. Good.");
} }
LinesToDigest.emplace_front(0, Miles_Look_Camera); LinesToDigest.emplace_back(0, Miles_Look_Camera);
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0,
"Now, calibrate your ""\x87""Sound Options""\x80""."); "Now, calibrate your ""\x87""Sound Options""\x80"".");
LinesToDigest.emplace_front(0, Miles_Electric_Lower); LinesToDigest.emplace_back(0, Miles_Electric_Lower);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0,
"You always make your stuff so loud by default, Eggman. It might need a moment."); "You always make your stuff so loud by default, Eggman. It might need a moment.");
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0,
"Not Metal! He always needed to be stealthy. But go on, set your sliders."); "Not Metal! He always needed to be stealthy. But go on, set your sliders.");
LinesToDigest.emplace_front(0, Miles_Look_Electric); LinesToDigest.emplace_back(0, Miles_Look_Electric);
break; break;
} }
case GDGONER_PROFILE: case GDGONER_PROFILE:
{ {
if (!leftoff) if (!leftoff)
{ {
LinesToDigest.emplace_front(0, Miles_Look_Electric); LinesToDigest.emplace_back(0, Miles_Look_Electric);
LinesToDigest.emplace_front(0, Miles_Look_Camera); LinesToDigest.emplace_back(0, Miles_Look_Camera);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/2,
"Oh! Let's tell Metal about our project!"); "Oh! Let's tell Metal about our project!");
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0,
"Of course. I and my lab assista-"); "Of course. I and my lab assista-");
LinesToDigest.emplace_front(0, Miles_Electric_Lower); LinesToDigest.emplace_back(0, Miles_Electric_Lower);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0,
"Lab PARTNER."); "Lab PARTNER.");
LinesToDigest.emplace_front(0, Miles_Look_Electric); LinesToDigest.emplace_back(0, Miles_Look_Electric);
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0,
"Irrelevant!"); "Irrelevant!");
} }
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/4, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/4,
"We made a machine together, Tails and I. "\ "We made a machine together, Tails and I. "\
"It's called a \"""\x82""Ring Racer""\x80""\"."); "It's called a \"""\x82""Ring Racer""\x80""\".");
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE,
"At its core, it is designed to utilise the boundless potential "\ "At its core, it is designed to utilise the boundless potential "\
"of the ""\x83""High Voltage Ring""\x80""."); "of the ""\x83""High Voltage Ring""\x80"".");
LinesToDigest.emplace_front(0, Miles_Look_Camera); LinesToDigest.emplace_back(0, Miles_Look_Camera);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE,
"We made this special ""\x83""Ring""\x80"" by combining the power of tens of "\ "We made this special ""\x83""Ring""\x80"" by combining the power of tens of "\
"thousands of ordinary ""\x82""Rings""\x80""."); "thousands of ordinary ""\x82""Rings""\x80"".");
LinesToDigest.emplace_front(0, Miles_Electric_Lower); LinesToDigest.emplace_back(0, Miles_Electric_Lower);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/2,
"We recorded some of our testing for you, MS-1. Maybe your neural "\ "We recorded some of our testing for you, MS-1. Maybe your neural "\
"network could train on some less violent data for once."); "network could train on some less violent data for once.");
LinesToDigest.emplace_front(0, Miles_Look_Electric); LinesToDigest.emplace_back(0, Miles_Look_Electric);
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/4, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/4,
"While that's uploading, why don't you set up your ""\x87""Profile Card""\x80""?"); "While that's uploading, why don't you set up your ""\x87""Profile Card""\x80""?");
LinesToDigest.emplace_front(0, Miles_Electric_Lower); LinesToDigest.emplace_back(0, Miles_Electric_Lower);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0,
"Yes! That's one of my contributions."); "Yes! That's one of my contributions.");
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0,
"(I'm too used to my systems being designed for me alone...)"); "(I'm too used to my systems being designed for me alone...)");
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0,
"Every racer carries one, to contain their personal settings."); "Every racer carries one, to contain their personal settings.");
LinesToDigest.emplace_front(0, Miles_Look_Electric); LinesToDigest.emplace_back(0, Miles_Look_Electric);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0,
"It helps get your ""\x87""controls""\x80"" set up nice and quickly, "\ "It helps get your ""\x87""controls""\x80"" set up nice and quickly, "\
"when starting your vehicle and navigating the menu."); "when starting your vehicle and navigating the menu.");
LinesToDigest.emplace_front(0, Miles_Look_Camera); LinesToDigest.emplace_back(0, Miles_Look_Camera);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0,
"And it helps track your wins, too."); "And it helps track your wins, too.");
LinesToDigest.emplace_front(0, Miles_Look_Electric); LinesToDigest.emplace_back(0, Miles_Look_Electric);
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/5, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/5,
"Bragging rights. My idea!"); "Bragging rights. My idea!");
LinesToDigest.emplace_front(0, Miles_Look_Camera); LinesToDigest.emplace_back(0, Miles_Look_Camera);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/2,
"You can make the ID and player tag on there anything you want."); "You can make the ID and player tag on there anything you want.");
LinesToDigest.emplace_front(0, Miles_Electric_Lower); LinesToDigest.emplace_back(0, Miles_Electric_Lower);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/2,
"Mine says \"Nine Tails\". That's the name of my original character! "\ "Mine says \"Nine Tails\". That's the name of my original character! "\
"He's like me if I never met my ""\x84""brother""\x80"". He'd use cool "\ "He's like me if I never met my ""\x84""brother""\x80"". He'd use cool "\
"robotics, but be kind of mean to protect himself..."); "robotics, but be kind of mean to protect himself...");
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/5, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/5,
"Mine says \"Robotnik\". You can't beat a classic."); "Mine says \"Robotnik\". You can't beat a classic.");
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/2,
"And I'm not sure if you'll need it, but we always tell new drivers to "\ "And I'm not sure if you'll need it, but we always tell new drivers to "\
"look at the ""\x87""Accessibility""\x80"" settings. Often there's some "\ "look at the ""\x87""Accessibility""\x80"" settings. Often there's some "\
"feature they're not expecting. Maybe you'd be surprised too?"); "feature they're not expecting. Maybe you'd be surprised too?");
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0,
"So go on, do your ""\x87""Profile Setup""\x80""!"); "So go on, do your ""\x87""Profile Setup""\x80""!");
break; break;
@ -650,31 +769,42 @@ void M_AddGonerLines(void)
{ {
if (!leftoff) if (!leftoff)
{ {
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/2, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/2,
"Now that that's been set up, you can use your ""\x87""Profile controls""\x80"" on menus from here on out, too."); "Now that that's been set up, you can use your ""\x87""Profile controls""\x80"" on menus from here on out, too.");
LinesToDigest.emplace_front(0, Miles_Look_Electric); LinesToDigest.emplace_back(0, Miles_Look_Electric);
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/5, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/5,
"Miles. How's the upload going?"); "Miles. How's the upload going?");
LinesToDigest.emplace_front(0, Miles_Look_Camera); LinesToDigest.emplace_back(0, Miles_Look_Camera);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0,
"Just finished."); "Just finished.");
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0,
"Perfect."); "Perfect.");
} }
LinesToDigest.emplace_front(0, Miles_Electric_Lower); LinesToDigest.emplace_back(0, Miles_Electric_Lower);
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0,
"Now, Metal... it's important you pay attention."); "Now, Metal... it's important you pay attention.");
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/5, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/5,
"It's time to ""\x87""begin your Tutorial""\x80""!"); "We have a ""\x88""choice""\x80"" ready for you.");
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0,
"Remember, MS-1. Even when you move on from this setup, you "\ "You can play back our testing data as a sort of ""\x82""tutorial""\x80"\
"can always change your ""\x87""Options""\x80"" at any time from the menu."); " and learn the core parts of driving in a safe environment...");
LinesToDigest.emplace_front(0, Miles_Look_Electric);
LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/5,
"...or if you're too headstrong and want to figure things out"\
" for yourself, we can let you loose in our ""\x85""playground""\x80""!");
LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/2,
"If you do run into trouble, the ""\x82""tutorial""\x80"" can"\
" always be found in the ""\x87""Extras""\x80"" menu later on.");
LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0,
"Either way, MS-1. Even when you move on from this setup,"\
" you can always change your ""\x87""Options""\x80"" at any time.");
LinesToDigest.emplace_back(0, Miles_Look_Electric);
break; break;
} }
@ -682,37 +812,35 @@ void M_AddGonerLines(void)
{ {
if (!leftoff) if (!leftoff)
{ {
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/3, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/3,
"And... the training data is completed."); "And... the training data is completed.");
} }
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/2,
"It's kind of funny, actually."); "It's kind of funny, actually.");
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/3, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/3,
"Oh? Care to elucidate, Prower?"); "Oh? Care to elucidate, Prower?");
LinesToDigest.emplace_front(0, Miles_Look_Camera); LinesToDigest.emplace_back(0, Miles_Look_Camera);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/2,
"No matter how much time we took getting here, a machine like "\ "No matter how much time we took getting here, a machine like "\
"Metal can play it back in minutes."); "Metal can play it back in minutes.");
LinesToDigest.emplace_front(0, Miles_Electric_Lower); LinesToDigest.emplace_back(0, Miles_Electric_Lower);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/2,
"It could have been five days or five years of development on "\ "It could have been five days or five years of development on "\
"our ""\x82""Ring Racers""\x80"", and that would barely matter to it."); "our ""\x82""Ring Racers""\x80"", and that would barely matter to it.");
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/4, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/4,
"Ha! As if. I'd like to think our partnership hasn't felt "\ "Ha! As if. I'd like to think our partnership hasn't felt "\
"particularly protracted."); "particularly protracted.");
LinesToDigest.emplace_front(0, Miles_Look_Electric); LinesToDigest.emplace_back(0, Miles_Look_Electric);
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/2, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/2,
"But yes. Perhaps now you have a better appreciation of what "\ "But yes. Perhaps now you have a better appreciation of what "\
"we're building here, Metal."); "we're building here, Metal.");
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/2, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/5,
"If you need to learn more, you can always come back to the Tutorial later in the ""\x87""Extras""\x80"" menu.");
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/5,
"Now, I'm willing to let bygones be bygones."); "Now, I'm willing to let bygones be bygones.");
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/2, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/2,
"As long as you keep your violence to the track, I'll be "\ "As long as you keep your violence to the track, I'll be "\
"giving you your autonomy back in a moment."); "giving you your autonomy back in a moment.");
LinesToDigest.emplace_front(0, Miles_Electric_Lower); LinesToDigest.emplace_back(0, Miles_Electric_Lower);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0,
"We've kept the keys from you long enough!"); "We've kept the keys from you long enough!");
break; break;
} }
@ -720,7 +848,7 @@ void M_AddGonerLines(void)
break; break;
default: default:
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0,
"I am error"); "I am error");
} }
@ -839,22 +967,25 @@ void M_GonerTick(void)
first = true; // a lie, but only slightly... first = true; // a lie, but only slightly...
// Handle rewinding if you clear your gamedata. // Handle rewinding if you clear your gamedata.
M_GonerResetText(); M_GonerResetText(true);
goner_background = GonerBGData();
goner_levelworking = GDGONER_INIT;
} }
M_GonerResetLooking(GDGONER_INIT); M_GonerResetLooking(GDGONER_INIT);
if (first) if (first)
{ {
goner_background = GonerBGData();
first = goner_gdq = false; first = goner_gdq = false;
#if 0
MAIN_Goner[0] = MAIN_Goner[0] =
{IT_STRING | IT_CVAR | IT_CV_STRING, "PASSWORD", {IT_STRING | IT_CVAR | IT_CV_STRING, "PASSWORD",
"ATTEMPT ADMINISTRATOR ACCESS.", NULL, "ATTEMPT ADMINISTRATOR ACCESS.", NULL,
{.cvar = &cv_dummyextraspassword}, 0, 0}; {.cvar = &cv_dummyextraspassword}, 0, 0};
#endif
MAIN_Goner[0].mvar2 = 0;
if (gamedata->gonerlevel < GDGONER_INTRO) if (gamedata->gonerlevel < GDGONER_INTRO)
gamedata->gonerlevel = GDGONER_INTRO; gamedata->gonerlevel = GDGONER_INTRO;
@ -1218,6 +1349,130 @@ static void M_GonerDrawer(void)
M_DrawHorizontalMenu(); M_DrawHorizontalMenu();
} }
static void M_GonerChoiceDrawer(void)
{
srb2::Draw drawer = srb2::Draw();
const INT32 lex = (24 + BASEVIDWIDTH/2)/2;
if (itemOn == GONERCHOICE_TAILS)
{
drawer
.size((BASEVIDWIDTH/2) + 25, BASEVIDHEIGHT)
.fill(60);
drawer
.xy((BASEVIDWIDTH/2) + 40 + 1, 28+3)
.colormap(SKINCOLOR_ORANGE)
.flags(V_FLIP)
.patch("MENUPLTR");
drawer
.xy(lex, 28)
.font(srb2::Draw::Font::kGamemode)
.align(srb2::Draw::Align::kCenter)
.text(currentMenu->menuitems[itemOn].text);
drawer
.xy(8, 72)
.font(srb2::Draw::Font::kThin)
.align(srb2::Draw::Align::kLeft)
.text(currentMenu->menuitems[itemOn].tooltip);
drawer
.xy(lex, 154)
.font(srb2::Draw::Font::kFreeplay)
.align(srb2::Draw::Align::kCenter)
.text("(unlocks 20 )");
drawer
.xy(lex, 154+14)
.font(srb2::Draw::Font::kThin)
.align(srb2::Draw::Align::kCenter)
.flags(V_TRANSLUCENT)
.text("+ more surprises to find");
drawer
.xy(lex + 26, 154-4)
.patch("UN_CHA00");
}
else if (itemOn == GONERCHOICE_EGGMAN)
{
drawer
.x((BASEVIDWIDTH/2) - 24)
.size((BASEVIDWIDTH/2) + 24, BASEVIDHEIGHT)
.fill(44);
drawer
.xy((BASEVIDWIDTH/2) - 40, 28+3)
.colormap(SKINCOLOR_RED)
.patch("MENUPLTR");
drawer
.xy(BASEVIDWIDTH - lex, 28)
.font(srb2::Draw::Font::kGamemode)
.align(srb2::Draw::Align::kCenter)
.text(currentMenu->menuitems[itemOn].text);
drawer
.xy(BASEVIDWIDTH - 8, 72)
.font(srb2::Draw::Font::kThin)
.align(srb2::Draw::Align::kRight)
.text(currentMenu->menuitems[itemOn].tooltip);
drawer
.xy(BASEVIDWIDTH - lex, 154)
.font(srb2::Draw::Font::kFreeplay)
.align(srb2::Draw::Align::kCenter)
.text("(unlocks Addons/Online)");
drawer
.xy(BASEVIDWIDTH - lex, 154+14)
.font(srb2::Draw::Font::kThin)
.align(srb2::Draw::Align::kCenter)
.flags(V_TRANSLUCENT)
.text("the other way has these too, just later");
}
// Un-highlighteds done this weird way because of GONERCHOICE_NONEBINEY
if (itemOn != GONERCHOICE_TAILS)
{
drawer
.size(20, BASEVIDHEIGHT)
.fill(60);
drawer
.xy(25, 39)
.font(srb2::Draw::Font::kFreeplay)
.align(srb2::Draw::Align::kLeft)
.text(currentMenu->menuitems[GONERCHOICE_TAILS].text);
drawer
.xy(20 - 3 - (skullAnimCounter/5), 39+6)
.patch("CUPARROW");
}
if (itemOn != GONERCHOICE_EGGMAN)
{
drawer
.x(BASEVIDWIDTH - 20)
.size(20, BASEVIDHEIGHT)
.fill(44);
drawer
.xy(BASEVIDWIDTH - 25, 39)
.font(srb2::Draw::Font::kFreeplay)
.align(srb2::Draw::Align::kRight)
.text(currentMenu->menuitems[GONERCHOICE_EGGMAN].text);
drawer
.xy((BASEVIDWIDTH - 20 + 3) + (skullAnimCounter/5), 39+6)
.flags(V_FLIP)
.patch("CUPARROW");
}
}
// --- // ---
void M_GonerProfile(INT32 choice) void M_GonerProfile(INT32 choice)
@ -1245,19 +1500,16 @@ void M_GonerProfile(INT32 choice)
M_GonerResetLooking(GDGONER_PROFILE); M_GonerResetLooking(GDGONER_PROFILE);
} }
static void M_GonerSurveyResponse(INT32 ch) static void M_GonerTutorialResponse(INT32 ch)
{ {
if (ch != MA_YES) if (ch != MA_YES)
return; return;
if (gamedata->gonerlevel < GDGONER_OUTRO) M_GonerTutorial(0);
gamedata->gonerlevel = GDGONER_OUTRO;
} }
void M_GonerTutorial(INT32 choice) void M_GonerChoice(INT32 choice)
{ {
(void)choice;
if (cv_currprofile.value == -1) if (cv_currprofile.value == -1)
{ {
const INT32 maxp = PR_GetNumProfiles(); const INT32 maxp = PR_GetNumProfiles();
@ -1270,6 +1522,57 @@ void M_GonerTutorial(INT32 choice)
PR_ApplyProfile(profilen, 0); PR_ApplyProfile(profilen, 0);
} }
if (gamedata->gonerlevel >= GDGONER_OUTRO)
{
M_StartMessage("First Boot Tutorial",
"You've already played the Tutorial!\n"
"Do you want to see it again?",
&M_GonerTutorialResponse, MM_YESNO, "I'd love to", "Not right now");
return;
}
M_SetupNextMenu(&MAIN_GonerChoiceDef, false);
}
static void M_GonerSurveyResponse(INT32 ch)
{
if (ch != MA_YES)
return;
if (gamedata->gonerlevel < GDGONER_OUTRO)
gamedata->gonerlevel = GDGONER_OUTRO;
if (currentMenu == &MAIN_GonerChoiceDef)
{
if (itemOn == GONERCHOICE_EGGMAN)
{
gamedata->playgroundroute = true;
gamedata->gonerlevel = GDGONER_DONE;
F_StartIntro();
M_ClearMenus(true);
M_GonerResetText(false);
return;
}
M_GoBack(0);
}
}
static void M_GonerSurvey(INT32 choice)
{
(void)choice;
// The game is incapable of progression, but I can't bring myself to put an I_Error here.
M_StartMessage("First Boot Error",
"YOU ACCEPT EVERYTHING THAT\nWILL HAPPEN FROM NOW ON.",
&M_GonerSurveyResponse, MM_YESNO, "I agree", "Cancel");
}
void M_GonerTutorial(INT32 choice)
{
(void)choice;
// Please also see M_LevelSelectInit as called in extras-1.c // Please also see M_LevelSelectInit as called in extras-1.c
levellist.netgame = false; levellist.netgame = false;
levellist.canqueue = false; levellist.canqueue = false;
@ -1279,22 +1582,61 @@ void M_GonerTutorial(INT32 choice)
if (!M_LevelListFromGametype(GT_TUTORIAL) && gamedata->gonerlevel < GDGONER_OUTRO) if (!M_LevelListFromGametype(GT_TUTORIAL) && gamedata->gonerlevel < GDGONER_OUTRO)
{ {
// The game is incapable of progression, but I can't bring myself to put an I_Error here. M_GonerSurvey(0);
M_StartMessage("Agreement", return;
"YOU ACCEPT EVERYTHING THAT WILL HAPPEN FROM NOW ON.",
&M_GonerSurveyResponse, MM_YESNO, "I agree", "Cancel");
} }
} }
void M_GonerPlayground(INT32 choice)
{
(void)choice;
UINT16 playgroundmap = NEXTMAP_INVALID;
if (tutorialplaygroundmap)
playgroundmap = G_MapNumber(tutorialplaygroundmap);
if (playgroundmap >= nummapheaders)
{
M_GonerSurvey(0);
return;
}
multiplayer = true;
M_MenuToLevelPreamble(0, false);
D_MapChange(
playgroundmap+1,
GT_TUTORIAL,
false,
true,
0,
false,
false
);
M_ClearMenus(true);
restoreMenu = NULL; // Playground Hack
// need to do all this here because it will skip returning to goner and there are circumstances (game close) where DoCompleted won't be called
gamedata->gonerlevel = GDGONER_DONE;
gamedata->playgroundroute = true;
gamedata->deferredsave = true;
M_GonerResetText(true);
}
static void M_GonerConclude(INT32 choice) static void M_GonerConclude(INT32 choice)
{ {
(void)choice; (void)choice;
gamedata->gonerlevel = GDGONER_DONE; gamedata->gonerlevel = GDGONER_DONE;
if (gamedata->chaokeys < 20)
gamedata->chaokeys = 20;
F_StartIntro(); F_StartIntro();
M_ClearMenus(true); M_ClearMenus(true);
M_GonerResetText(); M_GonerResetText(true);
} }
void M_GonerGDQ(boolean opinion) void M_GonerGDQ(boolean opinion)
@ -1311,24 +1653,23 @@ void M_GonerGDQ(boolean opinion)
if (opinion) // Save The Animals if (opinion) // Save The Animals
{ {
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/2, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/2,
"Why wouldn't you save the frames..?"); "Why wouldn't you save the frames..?");
LinesToDigest.emplace_front(0, Miles_Look_Camera); LinesToDigest.emplace_back(0, Miles_Look_Camera);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0,
"Don't mind him. Good luck on the run!"); "Don't mind him. Good luck on the run!");
LinesToDigest.emplace_front(0, Miles_Look_Electric); LinesToDigest.emplace_back(0, Miles_Look_Electric);
} }
else // Save The Frames else // Save The Frames
{ {
LinesToDigest.emplace_front(0, Miles_Electric_Lower); LinesToDigest.emplace_back(0, Miles_Electric_Lower);
LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/2,
"But what about all the little animals..."); "But what about all the little animals...");
LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0,
"It's just logical. I know you'll conquer this run."); "It's just logical. I know you'll conquer this run.");
} }
LinesToDigest.reverse();
if (gamedata->gonerlevel <= GDGONER_TUTORIAL) if (gamedata->gonerlevel <= GDGONER_TUTORIAL)
{ {
@ -1380,3 +1721,18 @@ static boolean M_GonerInputs(INT32 ch)
return false; return false;
} }
void M_GonerResetText(boolean completely)
{
goner_typewriter.ClearText();
LinesToDigest.clear();
LinesOutput.clear();
goner_scroll = 0;
goner_scrollend = -1;
if (!completely)
return;
goner_levelworking = GDGONER_INIT;
}

View file

@ -83,6 +83,12 @@ void M_StartEditProfile(INT32 c)
PR_InitNewProfile(); // initialize the new profile. PR_InitNewProfile(); // initialize the new profile.
optionsmenu.profile = PR_GetProfile(optionsmenu.profilen); optionsmenu.profile = PR_GetProfile(optionsmenu.profilen);
if (cv_kickstartaccel[0].value)
{
// Primarily for Goner but should help with standard set-up too
optionsmenu.profile->kickstartaccel = true;
}
// copy this profile's controls into optionsmenu so that we can edit controls without changing them directly. // copy this profile's controls into optionsmenu so that we can edit controls without changing them directly.
// we do this so that we don't edit a profile's controls in real-time and end up doing really weird shit. // we do this so that we don't edit a profile's controls in real-time and end up doing really weird shit.
memcpy(&optionsmenu.tempcontrols, optionsmenu.profile->controls, sizeof(gamecontroldefault)); memcpy(&optionsmenu.tempcontrols, optionsmenu.profile->controls, sizeof(gamecontroldefault));

View file

@ -561,7 +561,8 @@ void M_EndGame(INT32 choice)
if (!Playing()) if (!Playing())
return; return;
if (M_GameTrulyStarted() == false) if (M_GameTrulyStarted() == false
|| M_GameAboutToStart() == true) // Playground Hack
{ {
// No returning to the title screen. // No returning to the title screen.
M_QuitSRB2(-1); M_QuitSRB2(-1);

View file

@ -120,6 +120,7 @@
#include "k_credits.h" #include "k_credits.h"
#include "k_objects.h" #include "k_objects.h"
#include "p_deepcopy.h" #include "p_deepcopy.h"
#include "k_color.h" // K_ColorUsable
// Replay names have time // Replay names have time
#if !defined (UNDER_CE) #if !defined (UNDER_CE)
@ -8046,7 +8047,7 @@ static void P_ShuffleTeams(void)
static void P_InitPlayers(void) static void P_InitPlayers(void)
{ {
INT32 i, skin = -1, follower = -1; INT32 i, skin = -1, follower = -1, col = SKINCOLOR_NONE;
// Make sure objectplace is OFF when you first start the level! // Make sure objectplace is OFF when you first start the level!
OP_ResetObjectplace(); OP_ResetObjectplace();
@ -8060,7 +8061,28 @@ static void P_InitPlayers(void)
// Get skin from name. // Get skin from name.
if (mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->relevantskin[0]) if (mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->relevantskin[0])
{ {
skin = R_SkinAvailable(mapheaderinfo[gamemap-1]->relevantskin); if (strcmp(mapheaderinfo[gamemap-1]->relevantskin, "_PROFILE") == 0)
{
profile_t *p = PR_GetProfile(cv_ttlprofilen.value);
if (p && !netgame)
{
skin = R_SkinAvailable(p->skinname);
if (!R_SkinUsable(g_localplayers[0], skin, false))
{
skin = GetSkinNumClosestToStats(skins[skin].kartspeed, skins[skin].kartweight, skins[skin].flags, false);
}
if (K_ColorUsable(static_cast<skincolornum_t>(p->color), false, true) == true)
{
col = p->color;
}
}
}
else
{
skin = R_SkinAvailable(mapheaderinfo[gamemap-1]->relevantskin);
}
} }
else else
{ {
@ -8097,7 +8119,7 @@ static void P_InitPlayers(void)
if (skin != -1) if (skin != -1)
{ {
SetPlayerSkinByNum(i, skin); SetPlayerSkinByNum(i, skin);
players[i].skincolor = skins[skin].prefcolor; players[i].skincolor = (col != SKINCOLOR_NONE) ? col : skins[skin].prefcolor;
players[i].followerskin = follower; players[i].followerskin = follower;
if (follower != -1) if (follower != -1)