Merge branch 'seal-a-carte' into 'master'

Sealed Star re-ordering (resolves #606)

Closes #606

See merge request KartKrew/Kart!1980
This commit is contained in:
James R. 2024-03-03 05:18:26 +00:00
commit b136df4d41
14 changed files with 336 additions and 43 deletions

View file

@ -3933,15 +3933,68 @@ void G_GPCupIntoRoundQueue(cupheader_t *cup, UINT8 setgametype, boolean setencor
// At the end of the Cup is a Rank-restricted treat.
// So we append it to the end of the roundqueue.
// (as long as it exists, of course!)
cupLevelNum = cup->cachedlevels[CUPCACHE_SPECIAL];
if (cupLevelNum < nummapheaders)
{
G_MapIntoRoundQueue(
cupLevelNum,
G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel),
setencore, // if this isn't correct, Got_Mapcmd will fix it
true // Rank-restricted!
);
// Of course, this last minute game design tweak
// has to make things a little complicated. We
// basically just make sure they're dispensed
// at the intended difficulty sequence until
// you've got them all, at which point they
// become their intended order permanently.
// ~toast 010324
cupheader_t *emeraldcup = NULL;
if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found
|| cup->id >= basenumkartcupheaders // custom content
|| M_SecretUnlocked(SECRET_SPECIALATTACK, false)) // true order
{
// Standard order.
emeraldcup = cup;
}
else
{
// Determine order from sealedswaps.
for (i = 0; (i < GDMAX_SEALEDSWAPS && gamedata->sealedswaps[i]); i++)
{
if (gamedata->sealedswaps[i] != grandprixinfo.cup)
continue;
// Repeat visit, grab the same ID.
break;
}
// If there's pending stars, get them from the associated cup order.
if (i < GDMAX_SEALEDSWAPS)
{
emeraldcup = kartcupheaders;
while (emeraldcup)
{
if (emeraldcup->id >= basenumkartcupheaders)
{
emeraldcup = NULL;
break;
}
if (emeraldcup->emeraldnum == i+1)
break;
emeraldcup = emeraldcup->next;
}
}
}
if (emeraldcup)
{
cupLevelNum = emeraldcup->cachedlevels[CUPCACHE_SPECIAL];
if (cupLevelNum < nummapheaders)
{
G_MapIntoRoundQueue(
cupLevelNum,
G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel),
setencore, // if this isn't correct, Got_Mapcmd will fix it
true // Rank-restricted!
);
}
}
}
if (roundqueue.size == 0)

View file

@ -64,6 +64,7 @@ void srb2::save_ng_gamedata()
ng.milestones.majorkeyskipattempted = gamedata->majorkeyskipattempted;
ng.milestones.finishedtutorialchallenge = gamedata->finishedtutorialchallenge;
ng.milestones.enteredtutorialchallenge = gamedata->enteredtutorialchallenge;
ng.milestones.sealedswapalerted = gamedata->sealedswapalerted;
ng.milestones.gonerlevel = gamedata->gonerlevel;
ng.prisons.thisprisoneggpickup = gamedata->thisprisoneggpickup;
ng.prisons.prisoneggstothispickup = gamedata->prisoneggstothispickup;
@ -176,7 +177,7 @@ void srb2::save_ng_gamedata()
}
for (auto cup = kartcupheaders; cup; cup = cup->next)
{
if (cup->windata[0].best_placement == 0)
if (cup->windata[0].best_placement == 0 && cup->windata[1].got_emerald == false)
{
continue;
}
@ -229,6 +230,17 @@ void srb2::save_ng_gamedata()
ng.cups[cupdata.name] = std::move(cupdata);
}
for (int i = 0; (i < GDMAX_SEALEDSWAPS && gamedata->sealedswaps[i]); i++)
{
srb2::GamedataSealedSwapJson sealedswap {};
cupheader_t* cup = gamedata->sealedswaps[i];
sealedswap.name = std::string(cup->name);
ng.sealedswaps.emplace_back(std::move(sealedswap));
}
std::string gamedataname_s {gamedatafilename};
fs::path savepath {fmt::format("{}/{}", srb2home, gamedataname_s)};
fs::path tmpsavepath {fmt::format("{}/{}.tmp", srb2home, gamedataname_s)};
@ -418,6 +430,7 @@ void srb2::load_ng_gamedata()
gamedata->majorkeyskipattempted = js.milestones.majorkeyskipattempted;
gamedata->finishedtutorialchallenge = js.milestones.finishedtutorialchallenge;
gamedata->enteredtutorialchallenge = js.milestones.enteredtutorialchallenge;
gamedata->sealedswapalerted = js.milestones.sealedswapalerted;
gamedata->gonerlevel = js.milestones.gonerlevel;
gamedata->thisprisoneggpickup = js.prisons.thisprisoneggpickup;
gamedata->prisoneggstothispickup = js.prisons.prisoneggstothispickup;
@ -720,6 +733,33 @@ void srb2::load_ng_gamedata()
}
}
size_t sealedswaps_size = js.sealedswaps.size();
for (size_t i = 0; i < std::min((size_t)GDMAX_SEALEDSWAPS, sealedswaps_size); i++)
{
cupheader_t* cup = nullptr;
// Find BASE cups only
for (cup = kartcupheaders; cup; cup = cup->next)
{
if (cup->id >= basenumkartcupheaders)
{
cup = NULL;
break;
}
std::string cupname = std::string(cup->name);
if (cupname == js.sealedswaps[i].name)
{
break;
}
}
if (cup)
{
gamedata->sealedswaps[i] = cup;
}
}
M_FinaliseGameData();
}

View file

@ -70,6 +70,7 @@ struct GamedataMilestonesJson final
bool majorkeyskipattempted;
bool finishedtutorialchallenge;
bool enteredtutorialchallenge;
bool sealedswapalerted;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
GamedataMilestonesJson,
@ -81,7 +82,8 @@ struct GamedataMilestonesJson final
chaokeytutorial,
majorkeyskipattempted,
finishedtutorialchallenge,
enteredtutorialchallenge
enteredtutorialchallenge,
sealedswapalerted
)
};
@ -184,6 +186,13 @@ struct GamedataCupJson final
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataCupJson, name, records)
};
struct GamedataSealedSwapJson final
{
std::string name;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataSealedSwapJson, name)
};
struct GamedataJson final
{
GamedataPlaytimeJson playtime;
@ -203,6 +212,7 @@ struct GamedataJson final
std::unordered_map<std::string, GamedataMapJson> maps;
std::vector<GamedataSprayCanJson> spraycans;
std::unordered_map<std::string, GamedataCupJson> cups;
std::vector<GamedataSealedSwapJson> sealedswaps;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
GamedataJson,
@ -222,7 +232,8 @@ struct GamedataJson final
skins,
maps,
spraycans,
cups
cups,
sealedswaps
)
};

View file

@ -680,6 +680,8 @@ void M_Init(void);
void M_PlayMenuJam(void);
boolean M_ConsiderSealedSwapAlert(void);
void M_OpenVirtualKeyboard(boolean gamepad);
void M_MenuTypingInput(INT32 key);
@ -1347,6 +1349,8 @@ extern struct challengesmenu_s {
boolean chaokeyadd, keywasadded;
UINT8 chaokeyhold;
boolean considersealedswapalert;
boolean requestflip;
UINT16 unlockcount[CMC_MAX];

View file

@ -2823,6 +2823,57 @@ fixed_t M_DrawCupWinData(INT32 rankx, INT32 ranky, cupheader_t *cup, UINT8 diffi
rankx += 19 - (rankw / 2);
cupwindata_t *windata = &(cup->windata[difficulty]);
UINT8 emeraldnum = UINT8_MAX;
if (!noemerald)
{
if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found
|| cup->id >= basenumkartcupheaders // custom content
|| M_SecretUnlocked(SECRET_SPECIALATTACK, true)) // true order
{
// Standard order.
if (windata->got_emerald == true)
{
emeraldnum = cup->emeraldnum;
}
}
else
{
// Determine order from sealedswaps.
UINT8 i;
for (i = 0; (i < GDMAX_SEALEDSWAPS && gamedata->sealedswaps[i]); i++)
{
if (gamedata->sealedswaps[i] != cup)
continue;
break;
}
// If there's pending stars, get them from the associated cup order.
if (i < GDMAX_SEALEDSWAPS)
{
cupheader_t *emeraldcup = kartcupheaders;
while (emeraldcup)
{
if (emeraldcup->id >= basenumkartcupheaders)
{
emeraldcup = NULL;
break;
}
if (emeraldcup->emeraldnum == i+1)
{
if (emeraldcup->windata[difficulty].got_emerald == true)
emeraldnum = i+1;
break;
}
emeraldcup = emeraldcup->next;
}
}
}
}
if (windata->best_placement == 0)
{
if (statsmode)
@ -2832,14 +2883,13 @@ fixed_t M_DrawCupWinData(INT32 rankx, INT32 ranky, cupheader_t *cup, UINT8 diffi
V_DrawCharacter((14-4)/2 + rankx, ranky, '.' | V_GRAYMAP, false);
rankx += 14 + 1;
V_DrawCharacter((12-4)/2 + rankx, ranky, '.' | V_GRAYMAP, false);
if (!noemerald)
{
rankx += 12 + 1;
V_DrawCharacter((12-4)/2 + rankx, ranky, '.' | V_GRAYMAP, false);
}
}
return rankw;
else
{
rankx += 14 + 1;
}
goto windataemeraldmaybe;
}
UINT8 *colormap = NULL;
@ -2934,13 +2984,15 @@ fixed_t M_DrawCupWinData(INT32 rankx, INT32 ranky, cupheader_t *cup, UINT8 diffi
if (charPat)
V_DrawFixedPatch((rankx)*FRACUNIT, (ranky)*FRACUNIT, FRACUNIT, 0, charPat, colormap);
windataemeraldmaybe:
rankx += 12 + 1;
if (noemerald)
;
else if (windata->got_emerald == true)
else if (emeraldnum != UINT8_MAX)
{
if (cup->emeraldnum == 0)
if (emeraldnum == 0)
V_DrawCharacter(rankx+2, ranky+2, '+', false);
else
{
@ -2948,14 +3000,14 @@ fixed_t M_DrawCupWinData(INT32 rankx, INT32 ranky, cupheader_t *cup, UINT8 diffi
if (!flash)
{
UINT16 col = SKINCOLOR_CHAOSEMERALD1 + (cup->emeraldnum-1) % 7;
UINT16 col = SKINCOLOR_CHAOSEMERALD1 + (emeraldnum-1) % 7;
colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE);
}
const char *emname = va(
"%sMAP%c",
(cup->emeraldnum > 7) ? "SUP" : "EME",
(emeraldnum > 7) ? "SUP" : "EME",
colormap ? '\0' : 'B'
);
@ -3116,7 +3168,7 @@ void M_DrawCupSelect(void)
y += 44; //(8 + 100) - (20 + 44)
}
if (windata && windata->best_placement != 0)
if (windata)
{
M_DrawCupWinData(
x,

View file

@ -560,6 +560,35 @@ void M_PlayMenuJam(void)
#undef IsCurrentlyPlaying
boolean M_ConsiderSealedSwapAlert(void)
{
if (gamedata->sealedswapalerted == true)
return false;
if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found
|| M_SecretUnlocked(SECRET_SPECIALATTACK, true)) // true order
{
gamedata->sealedswapalerted = true;
// Don't make a message if no Sealed Stars have yet been found.
if (gamedata->everseenspecial == false)
return false;
M_StartMessage(
"Message from the Stars",
"As if called by fate, the Emeralds you've\n"
"collected return to their rightful places...\n"
"\n"
"The Sealed Stars are now ordered via Cups!\n",
NULL, MM_NOTHING, NULL, NULL
);
return true;
}
return false;
}
void M_ValidateRestoreMenu(void)
{
if (restoreMenu == NULL || restoreMenu == &MAIN_GonerDef)
@ -629,6 +658,11 @@ menu_t *M_SpecificMenuRestore(menu_t *torestore)
M_SetupPlayMenu(-1);
PLAY_CharSelectDef.prevMenu = &MainDef;
if (torestore != &MISC_ChallengesDef)
{
M_ConsiderSealedSwapAlert();
}
return torestore;
}

View file

@ -83,6 +83,7 @@ static struct podiumData_s
sfxenum_t gradeVoice;
cupheader_t *cup;
UINT8 emeraldnum;
boolean fastForward;
@ -102,6 +103,7 @@ void podiumData_s::Init(void)
{
rank = grandprixinfo.rank;
cup = grandprixinfo.cup;
emeraldnum = cup->emeraldnum;
}
else
{
@ -119,6 +121,7 @@ void podiumData_s::Init(void)
cup = cup->next;
}
emeraldnum = 0;
memset(&rank, 0, sizeof(gpRank_t));
rank.skin = players[consoleplayer].skin;
@ -628,12 +631,7 @@ void podiumData_s::Draw(void)
case GPEVENT_SPECIAL:
{
srb2::Draw drawer_emerald = drawer_gametype;
UINT8 emeraldNum = 0;
if (cup != nullptr)
{
emeraldNum = cup->emeraldnum;
}
UINT8 emeraldNum = g_podiumData.emeraldnum;
boolean useWhiteFrame = ((leveltime & 1) || !dta->gotSpecialPrize);
patch_t *emeraldPatch = nullptr;
@ -844,12 +842,7 @@ void podiumData_s::Draw(void)
if (rank.specialWon == true)
{
UINT8 emeraldNum = 0;
if (cup != nullptr)
{
emeraldNum = cup->emeraldnum;
}
UINT8 emeraldNum = g_podiumData.emeraldnum;
const boolean emeraldBlink = (leveltime & 1);
patch_t *emeraldOverlay = nullptr;
@ -1263,6 +1256,60 @@ void K_ResetCeremony(void)
return;
}
cupheader_t *emeraldcup = NULL;
if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found
|| grandprixinfo.cup->id >= basenumkartcupheaders // custom content
|| M_SecretUnlocked(SECRET_SPECIALATTACK, false)) // true order
{
// Standard order.
emeraldcup = grandprixinfo.cup;
}
else
{
// Determine order from sealedswaps.
for (i = 0; i < GDMAX_SEALEDSWAPS; i++)
{
if (gamedata->sealedswaps[i] == NULL)
{
if (g_podiumData.rank.specialWon == true)
{
// First visit! Mark it off.
gamedata->sealedswaps[i] = grandprixinfo.cup;
}
break;
}
if (gamedata->sealedswaps[i] != grandprixinfo.cup)
continue;
// Repeat visit, grab the same ID.
break;
}
// If there's pending stars, apply them to the new cup order.
if (i < GDMAX_SEALEDSWAPS)
{
emeraldcup = kartcupheaders;
while (emeraldcup)
{
if (emeraldcup->id >= basenumkartcupheaders)
{
emeraldcup = NULL;
break;
}
if (emeraldcup->emeraldnum == i+1)
break;
emeraldcup = emeraldcup->next;
}
g_podiumData.emeraldnum = i+1;
}
}
// Write grade, position, and emerald-having-ness for later sessions!
i = (grandprixinfo.masterbots) ? KARTGP_MASTER : grandprixinfo.gamespeed;
@ -1292,9 +1339,9 @@ void K_ResetCeremony(void)
anymerit = true;
}
if (g_podiumData.rank.specialWon == true)
if (g_podiumData.rank.specialWon == true && emeraldcup)
{
grandprixinfo.cup->windata[i].got_emerald = true;
emeraldcup->windata[i].got_emerald = true;
anymerit = true;
}

View file

@ -96,6 +96,7 @@ static UINT8 cheatf_warp(void)
if (success)
{
gamedata->gonerlevel = GDGONER_DONE;
gamedata->sealedswapalerted = true;
G_SetUsedCheats();
}
@ -231,6 +232,7 @@ static UINT8 cheatf_devmode(void)
}
gamedata->gonerlevel = GDGONER_DONE;
gamedata->sealedswapalerted = true;
M_ClearMenus(true);

View file

@ -666,6 +666,7 @@ void M_ClearStats(void)
gamedata->majorkeyskipattempted = false;
gamedata->enteredtutorialchallenge = false;
gamedata->finishedtutorialchallenge = false;
gamedata->sealedswapalerted = false;
gamedata->musicstate = GDMUSIC_NONE;
gamedata->importprofilewins = false;
@ -720,6 +721,8 @@ void M_ClearSecrets(void)
skincolors[i].cache_spraycan = UINT16_MAX;
}
memset(gamedata->sealedswaps, 0, sizeof(gamedata->sealedswaps));
Z_Free(gamedata->challengegrid);
gamedata->challengegrid = NULL;
gamedata->challengegridwidth = 0;

View file

@ -291,6 +291,7 @@ typedef enum {
// This is the largest number of 9s that will fit in UINT32 and UINT16 respectively.
#define GDMAX_RINGS 999999999
#define GDMAX_CHAOKEYS 9999
#define GDMAX_SEALEDSWAPS 7
#define GDCONVERT_ROUNDSTOKEY 14
@ -371,12 +372,15 @@ struct gamedata_t
UINT32 totalrings;
UINT32 totaltumbletime;
// Chao Key condition bypass
// CHAO KEYS AND THEIR GENERATION
UINT32 pendingkeyrounds;
UINT8 pendingkeyroundoffset;
UINT16 keyspending;
UINT16 chaokeys;
// EMERALD REMAPPING
cupheader_t *sealedswaps[GDMAX_SEALEDSWAPS];
// SPECIFIC SPECIAL EVENTS
boolean everloadedaddon;
boolean everfinishedcredits;
@ -387,6 +391,7 @@ struct gamedata_t
boolean majorkeyskipattempted;
boolean enteredtutorialchallenge;
boolean finishedtutorialchallenge;
boolean sealedswapalerted;
gdmusic_t musicstate;
UINT8 gonerlevel;

View file

@ -335,6 +335,7 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu)
challengesmenu.requestnew = false;
challengesmenu.chaokeyadd = false;
challengesmenu.keywasadded = false;
challengesmenu.considersealedswapalert = false;
challengesmenu.chaokeyhold = 0;
challengesmenu.currentunlock = MAXUNLOCKABLES;
challengesmenu.unlockcondition = NULL;
@ -668,10 +669,15 @@ void M_ChallengesTick(void)
if (challengesmenu.currentunlock < MAXUNLOCKABLES
&& challengesmenu.unlockanim == UNLOCKTIME)
{
unlockable_t *ref = &unlockables[challengesmenu.currentunlock];
// Unlock animation... also tied directly to the actual unlock!
gamedata->unlocked[challengesmenu.currentunlock] = true;
M_UpdateUnlockablesAndExtraEmblems(true, true);
if (ref->type == SECRET_SPECIALATTACK)
challengesmenu.considersealedswapalert = true;
// Update shown description just in case..?
if (challengesmenu.unlockcondition)
Z_Free(challengesmenu.unlockcondition);
@ -682,12 +688,10 @@ void M_ChallengesTick(void)
if (challengesmenu.extradata)
{
unlockable_t *ref;
UINT16 bombcolor;
M_UpdateChallengeGridExtraData(challengesmenu.extradata);
ref = &unlockables[challengesmenu.currentunlock];
bombcolor = SKINCOLOR_NONE;
if (ref->color != SKINCOLOR_NONE && ref->color < numskincolors)
@ -747,7 +751,14 @@ void M_ChallengesTick(void)
// Play music the moment control returns.
M_PlayMenuJam();
if (gamedata->chaokeytutorial == false
if (challengesmenu.considersealedswapalert == true
&& M_ConsiderSealedSwapAlert() == true)
{
// No keygen tutorial in this case...
// not ideal but at least unlikely to
// get at same time?? :V
}
else if (gamedata->chaokeytutorial == false
&& challengesmenu.keywasadded == true)
{
M_ChallengesTutorial(CCTUTORIAL_KEYGEN);

View file

@ -116,6 +116,9 @@ void M_StopMessage(INT32 choice)
boolean M_MenuMessageTick(void)
{
if (menuwipe)
return false;
if (menumessage.closing)
{
if (menumessage.closing > MENUMESSAGECLOSE)

View file

@ -6284,6 +6284,14 @@ static boolean P_UnArchiveSPGame(savebuffer_t *save)
{
UINT32 val = READUINT32(save->p);
if (roundqueue.entries[i].rankrestricted && roundqueue.position != i+1)
{
// If this is a Sealed Star that hasn't yet been
// reached, don't be picky about divergance. Just
// use the base game without question. ~toast 010324
continue;
}
mapnum = roundqueue.entries[i].mapnum;
if (mapnum < nummapheaders && mapheaderinfo[mapnum] != NULL)
{

View file

@ -296,9 +296,29 @@ UINT8 P_GetNextEmerald(void)
{
cupheader_t *cup = NULL;
if (grandprixinfo.gp == true)
if (grandprixinfo.gp == true && grandprixinfo.cup)
{
cup = grandprixinfo.cup;
if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found
|| grandprixinfo.cup->id >= basenumkartcupheaders // custom content
|| M_SecretUnlocked(SECRET_SPECIALATTACK, false)) // true order
{
cup = grandprixinfo.cup;
}
else
{
// Determine order from sealedswaps.
UINT8 i;
for (i = 0; (i < GDMAX_SEALEDSWAPS && gamedata->sealedswaps[i]); i++)
{
if (gamedata->sealedswaps[i] != grandprixinfo.cup)
continue;
// Repeat visit, grab the same ID.
break;
}
return i+1;
}
}
if (cup == NULL)