Merge branch 'challenge-final' into 'master'

Challenge Final

See merge request KartKrew/Kart!2140
This commit is contained in:
AJ Martinez 2024-03-24 06:47:32 +00:00
commit bca0b48f45
10 changed files with 291 additions and 47 deletions

View file

@ -3928,6 +3928,7 @@ void SV_StopServer(void)
maketic = gametic+1;
neededtic = maketic;
serverrunning = false;
titlemapinaction = false;
}
// called at singleplayer start and stopdemo

View file

@ -3088,6 +3088,7 @@ static void readcondition(UINT16 set, UINT32 id, char *word2)
else if ((offset=0) || fastcmp(params[0], "FINISHCOOL")
|| (++offset && fastcmp(params[0], "FINISHPERFECT"))
|| (++offset && fastcmp(params[0], "FINISHALLPRISONS"))
|| (++offset && fastcmp(params[0], "SURVIVE"))
|| (++offset && fastcmp(params[0], "NOCONTEST"))
|| (++offset && fastcmp(params[0], "SMASHUFO"))
|| (++offset && fastcmp(params[0], "CHASEDBYSPB")))

View file

@ -2837,7 +2837,7 @@ static void M_DrawCupTitle(INT16 y, levelsearch_t *levelsearch)
if (levelsearch->cup == &dummy_lostandfound)
{
V_DrawCenteredLSTitleLowString(BASEVIDWIDTH/2, y+6, 0, "Lost and Found");
V_DrawCenteredLSTitleLowString(BASEVIDWIDTH/2, y+6, 0, "Lost & Found");
}
else if (levelsearch->cup)
{
@ -6389,7 +6389,7 @@ void M_DrawAddons(void)
// Challenges Menu
static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili)
static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, UINT8 *flashmap, boolean hili)
{
#ifdef DEVELOP
extern consvar_t cv_debugchallenges;
@ -6710,6 +6710,21 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili
}
drawborder:
if (num < MAXUNLOCKABLES && gamedata->unlockpending[num])
{
const INT32 area = (ref->majorunlock) ? 42 : 20;
INT32 val;
for (i = 0; i < area; i++)
{
val = (x + i + challengesmenu.ticker) % 40;
if (val >= 20)
val = 40 - val;
val = (val + 6)/5;
V_DrawFadeFill(x + i, y, 1, area, 0, flashmap[98 + val], 2);
}
}
if (hili)
{
boolean maj = (ref != NULL && ref->majorunlock);
@ -6719,13 +6734,11 @@ drawborder:
buffer[7] = (skullAnimCounter/5) ? '2' : '1';
pat = W_CachePatchName(buffer, PU_CACHE);
colormap = R_GetTranslationColormap(TC_DEFAULT, M_GetCvPlayerColor(0), GTC_MENUCACHE);
V_DrawFixedPatch(
x*FRACUNIT, y*FRACUNIT,
FRACUNIT,
0, pat,
colormap
flashmap
);
}
@ -7114,6 +7127,32 @@ static void M_DrawChallengePreview(INT32 x, INT32 y)
NULL);
break;
}
case SECRET_ADDONS:
{
V_DrawFixedPatch(28*FRACUNIT, (BASEVIDHEIGHT-28)*FRACUNIT,
FRACUNIT,
0, W_CachePatchName("M_ICOADD", PU_CACHE),
NULL);
break;
}
case SECRET_SOUNDTEST:
{
V_DrawFixedPatch(28*FRACUNIT, (BASEVIDHEIGHT-28)*FRACUNIT,
FRACUNIT,
0, W_CachePatchName("M_ICOSTM", PU_CACHE),
NULL);
break;
}
case SECRET_EGGTV:
{
V_DrawFixedPatch(3*FRACUNIT, (BASEVIDHEIGHT-40)*FRACUNIT,
FRACUNIT,
0, W_CachePatchName(
va("RHTVSQN%c", (challengesmenu.ticker & 2) ? '5' : '6'),
PU_CACHE),
NULL);
break;
}
case SECRET_ALTTITLE:
{
x = 8;
@ -7282,12 +7321,14 @@ static void M_DrawChallengeKeys(INT32 tilex, INT32 tiley)
fixed_t keyx = (8+offs)*FRACUNIT, keyy = 0;
const boolean keybuttonpress = (menumessage.active == false && M_MenuExtraHeld(pid) == true);
// Button prompt
K_drawButton(
24 << FRACBITS,
16 << FRACBITS,
0, kp_button_c[1],
menumessage.active == false && M_MenuExtraHeld(pid) == true
keybuttonpress
);
// Metyr of rounds played that contribute to Chao Key generation
@ -7334,6 +7375,20 @@ static void M_DrawChallengeKeys(INT32 tilex, INT32 tiley)
}
}
// Hand
if (challengesmenu.keywasadded == true)
{
INT32 handx = 32 + 16;
if (keybuttonpress == false)
{
// Only animate if it's the focus
handx -= (skullAnimCounter/5);
}
V_DrawScaledPatch(handx, 8, V_FLIP,
W_CachePatchName("M_CURSOR", PU_CACHE));
}
UINT8 keysbeingused = 0;
// The Chao Key swooping animation
@ -7451,6 +7506,103 @@ static void M_DrawChallengeKeys(INT32 tilex, INT32 tiley)
}
}
static void M_DrawChallengeScrollBar(UINT8 *flashmap)
{
const INT32 bary = 4, barh = 1, hiliw = 1;
if (!gamedata->challengegrid || !gamedata->challengegridwidth)
return;
const INT32 barlen = gamedata->challengegridwidth*hiliw;
INT32 barx = (BASEVIDWIDTH - barlen)/2;
if (barlen > 200)
{
// TODO I DONT KNOW IF THE MATHS IS WRONG BUT WE DON'T HAVE
// 200 COLUMNS YET SO KICKING CAN DOWN THE ROAD ~toast 190324
INT32 shif = barlen - 200;
barx -= (shif/2 + (shif * challengesmenu.col)/barlen);
}
// bg
V_DrawFadeFill(barx, bary, barlen, barh, 0, 31, challengetransparentstrength);
// This was a macro for experimentation
#define COLTOPIX(col) (col*hiliw)
//((col * barlen)/gamedata->challengegridwidth)
INT32 hilix, nextstep, i, completionamount, skiplevel;
// selection
hilix = COLTOPIX(challengesmenu.col);
V_DrawFill(barx + hilix, bary-1, hiliw, 1, 0);
V_DrawFill(barx + hilix, bary+barh, hiliw, 1, 0);
INT32 mindiscouragement = 2; // skipping major unlocks is just a LITTLE cringe
if (challengesmenu.unlockcount[CMC_PERCENT] == 100
&& challengesmenu.unlockcount[CMC_MAJORSKIPPED] == 0)
mindiscouragement = 1; // so someone looking for 101% isn't hunting forever
// unbounded so that we can do the last remaining completionamount draw
nextstep = completionamount = skiplevel = 0;
for (i = 0; ; i++)
{
INT32 prevstep = nextstep;
nextstep = (i % CHALLENGEGRIDHEIGHT);
if (prevstep >= nextstep)
{
if (completionamount > 0)
{
if (skiplevel >= mindiscouragement && completionamount == 10)
{
// awareness
completionamount--;
}
V_DrawFadeFill(barx + hilix, bary, hiliw, barh, 0, 1, completionamount);
}
completionamount = skiplevel = 0;
hilix = i/CHALLENGEGRIDHEIGHT;
hilix = COLTOPIX(hilix);
}
// DO NOT DEREFERENCE gamedata->challengegrid[i] UNTIL AFTER THIS
if (i >= gamedata->challengegridwidth*CHALLENGEGRIDHEIGHT)
break;
if (gamedata->challengegrid[i] >= MAXUNLOCKABLES)
continue;
if (gamedata->unlocked[gamedata->challengegrid[i]] && completionamount != -1)
{
completionamount += (10/CHALLENGEGRIDHEIGHT);
unlockable_t *ref = &unlockables[gamedata->challengegrid[i]];
if (skiplevel < 2 && M_Achieved(ref->conditionset - 1) == false)
{
skiplevel = ref->majorunlock ? 2 : 1;
}
}
if (gamedata->unlockpending[gamedata->challengegrid[i]] == false)
continue;
INT32 val = (hilix + challengesmenu.ticker) % 40;
if (val >= 20)
val = 40 - val;
val = (val + 6)/10;
V_DrawFill(barx + hilix, bary, hiliw, barh, flashmap[99 + val]);
// The pending fill overrides everything else.
completionamount = -1;
}
#undef COLTOPIX
}
void M_DrawChallenges(void)
{
INT32 x = currentMenu->x, explodex, selectx = 0, selecty = 0;
@ -7503,6 +7655,8 @@ void M_DrawChallenges(void)
goto challengedesc;
}
UINT8 *flashmap = R_GetTranslationColormap(TC_DEFAULT, M_GetCvPlayerColor(0), GTC_MENUCACHE);
y = currentMenu->y;
V_DrawFadeFill(0, y-2, BASEVIDWIDTH, (challengesgridstep * CHALLENGEGRIDHEIGHT) + 2, 0, 31, challengetransparentstrength);
@ -7556,7 +7710,7 @@ void M_DrawChallenges(void)
continue;
}
M_DrawChallengeTile(i, j, x, y, false);
M_DrawChallengeTile(i, j, x, y, flashmap, false);
}
x -= challengesgridstep;
@ -7571,11 +7725,14 @@ void M_DrawChallenges(void)
if (challengesmenu.fade)
V_DrawFadeScreen(31, challengesmenu.fade);
M_DrawChallengeScrollBar(flashmap);
M_DrawChallengeTile(
challengesmenu.hilix,
challengesmenu.hiliy,
selectx,
selecty,
flashmap,
true);
M_DrawCharSelectExplosions(false, explodex, currentMenu->y);
@ -7881,7 +8038,7 @@ static void M_DrawStatsMaps(void)
else if (mapheaderinfo[mnum]->cup)
str = va("%s CUP", mapheaderinfo[mnum]->cup->realname);
else
str = "LOST AND FOUND";
str = "LOST & FOUND";
V_DrawThinString(20, y, highlightflags, str);
}

View file

@ -733,6 +733,14 @@ void M_StartControlPanel(void)
{
if (gamestate != GS_MENU)
{
if (titlemapinaction)
{
// We clear a LITTLE bit of state, but not a full D_ClearState.
// Just enough to guarantee SV_ResetServer is called before session start.
SV_StopServer();
SV_ResetServer();
}
G_SetGamestate(GS_MENU);
gameaction = ga_nothing;

View file

@ -1125,7 +1125,7 @@ static void M_PrecacheLevelLocks(void)
}
tempstr = va(
"Music: %s Cup %c%u %c",
"Music: %s CUP %c%u %c",
mapheaderinfo[map]->cup->realname,
prefix,
positionid + 1,
@ -1153,7 +1153,7 @@ static void M_PrecacheLevelLocks(void)
tempstr = va(
"Music: %s #%u %c",
(mapheaderinfo[map]->typeoflevel & TOL_TUTORIAL) ? "Tutorial" : "Lost and Found",
(mapheaderinfo[map]->typeoflevel & TOL_TUTORIAL) ? "Tutorial" : "Lost & Found",
positionid + 1,
'A' + j // :D ?
);
@ -1516,8 +1516,10 @@ boolean M_CheckCondition(condition_t *cn, player_t *player)
case UC_UNLOCKPERCENT:
{
// Don't let netgame sessions intefere
// (or have this give a performance hit)
if (Playing())
// or have this give a performance hit
// (This is formulated this way to
// perfectly eclipse M_CheckNetUnlockByID)
if (netgame || demo.playback || Playing())
return false;
UINT16 i, unlocked = cn->extrainfo2, total = 0;
@ -1710,6 +1712,9 @@ boolean M_CheckCondition(condition_t *cn, player_t *player)
&& !(player->pflags & PF_NOCONTEST)
//&& M_NotFreePlay()
&& numtargets >= maptargets);
case UCRP_SURVIVE:
return (player->exiting
&& !(player->pflags & PF_NOCONTEST));
case UCRP_NOCONTEST:
return (player->pflags & PF_NOCONTEST);
@ -2114,7 +2119,7 @@ static const char *M_GetConditionString(condition_t *cn)
switch (cn->type)
{
case UC_PLAYTIME: // Requires total playing time >= x
return va("play for %i:%02i:%02i",
return va("play the game for %i:%02i:%02i",
G_TicsToHours(cn->requirement),
G_TicsToMinutes(cn->requirement, false),
G_TicsToSeconds(cn->requirement));
@ -2165,7 +2170,7 @@ static const char *M_GetConditionString(condition_t *cn)
case UC_GAMECLEAR: // Requires game beaten >= x times
if (cn->requirement > 1)
return va("beat game %d times", cn->requirement);
return va("beat the game %d times", cn->requirement);
else
return va("beat the game");
@ -2316,7 +2321,7 @@ static const char *M_GetConditionString(condition_t *cn)
}
case UC_TOTALMEDALS: // Requires number of emblems >= x
return va("get %d medals", cn->requirement);
return va("get %d Medals", cn->requirement);
case UC_EMBLEM: // Requires emblem x to be obtained
{
@ -2461,14 +2466,14 @@ static const char *M_GetConditionString(condition_t *cn)
case UC_ADDON:
if (!M_SecretUnlocked(SECRET_ADDONS, true))
return NULL;
return "load a custom addon into \"Dr. Robotnik's Ring Racers\"";
return "load a custom addon";
case UC_CREDITS:
return "watch the developer credits all the way from start to finish";
case UC_REPLAY:
return "save a replay after finishing a round";
case UC_CRASH:
if (gamedata->evercrashed)
return "launch \"Dr. Robotnik's Ring Racers\" again after a game crash";
return "re-launch the game after a crash";
return NULL;
case UC_TUTORIALSKIP:
return "successfully skip the Tutorial";
@ -2626,7 +2631,7 @@ static const char *M_GetConditionString(condition_t *cn)
{
if (cup->id != cn->requirement)
continue;
return va("%s%s %s Cup",
return va("%s%s %s CUP",
completetype, orbetter,
(M_CupLocked(cup) ? "???" : cup->realname)
);
@ -2650,6 +2655,8 @@ static const char *M_GetConditionString(condition_t *cn)
return "finish a perfect round";
case UCRP_FINISHALLPRISONS:
return "break every Prison Egg";
case UCRP_SURVIVE:
return "survive";
case UCRP_NOCONTEST:
return "NO CONTEST";
@ -2875,8 +2882,8 @@ char *M_BuildConditionSetString(UINT16 unlockid)
{
if (lastID != cn->id)
{
worklen = 4;
strncat(message, "\nOR ", len);
worklen = 6;
strncat(message, " - OR ", len);
}
else
{

View file

@ -103,6 +103,7 @@ typedef enum
UCRP_FINISHCOOL, // Finish in good standing
UCRP_FINISHPERFECT, // Finish a perfect race
UCRP_FINISHALLPRISONS, // Break all prisons
UCRP_SURVIVE, // Survive
UCRP_NOCONTEST, // No Contest
UCRP_SMASHUFO, // Smash the UFO Catcher

View file

@ -142,6 +142,7 @@ static void M_ChallengesAutoFocus(UINT16 unlockid, boolean fresh)
{
UINT16 i;
INT16 work;
boolean posisvalid = false;
if (unlockid >= MAXUNLOCKABLES && gamedata->pendingkeyrounds > 0
&& (gamedata->chaokeys < GDMAX_CHAOKEYS))
@ -149,28 +150,22 @@ static void M_ChallengesAutoFocus(UINT16 unlockid, boolean fresh)
if (fresh && unlockid >= MAXUNLOCKABLES)
{
UINT16 selection[MAXUNLOCKABLES];
UINT16 numunlocks = 0;
// Get a random available unlockable.
for (i = 0; i < MAXUNLOCKABLES; i++)
if (challengesmenu.currentunlock < MAXUNLOCKABLES)
{
if (!unlockables[i].conditionset)
{
continue;
}
if (!gamedata->unlocked[i])
{
continue;
}
selection[numunlocks++] = i;
// Use the last selected time.
unlockid = challengesmenu.currentunlock;
posisvalid = true;
}
if (!numunlocks)
else
{
// ...OK, get a random unlockable.
UINT16 selection[MAXUNLOCKABLES];
UINT16 numunlocks = 0;
boolean triedrandomlevel = 0;
tryfreshrandom:
// Get a random available unlockable.
for (i = 0; i < MAXUNLOCKABLES; i++)
{
if (!unlockables[i].conditionset)
@ -178,13 +173,43 @@ static void M_ChallengesAutoFocus(UINT16 unlockid, boolean fresh)
continue;
}
// Otherwise we don't care, just pick any non-blank tile
if (triedrandomlevel < 2)
{
// We try for any unlock second
if (!gamedata->unlocked[i])
{
continue;
}
if (triedrandomlevel == 0)
{
// We try for a pending unlock first
if (!gamedata->unlockpending[i])
{
continue;
}
}
}
selection[numunlocks++] = i;
}
}
unlockid = selection[M_RandomKey(numunlocks)];
if (numunlocks == 0)
{
if (triedrandomlevel == 2)
return;
triedrandomlevel++;
goto tryfreshrandom;
}
unlockid = selection[M_RandomKey(numunlocks)];
}
}
challengesmenu.unlockanim = (challengesmenu.pending && !challengesmenu.chaokeyadd ? 0 : MAXUNLOCKTIME);
if (unlockid >= MAXUNLOCKABLES)
return;
@ -192,9 +217,8 @@ static void M_ChallengesAutoFocus(UINT16 unlockid, boolean fresh)
if (challengesmenu.unlockcondition)
Z_Free(challengesmenu.unlockcondition);
challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock);
challengesmenu.unlockanim = (challengesmenu.pending && !challengesmenu.chaokeyadd ? 0 : MAXUNLOCKTIME);
if (gamedata->challengegrid == NULL || challengesmenu.extradata == NULL)
if (gamedata->challengegrid == NULL || challengesmenu.extradata == NULL || posisvalid)
return;
for (i = 0; i < (CHALLENGEGRIDHEIGHT * gamedata->challengegridwidth); i++)
@ -330,6 +354,8 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu)
if (challengesmenu.pending || desiredmenu == NULL)
{
static boolean firstopen = true;
challengesmenu.ticker = 0;
challengesmenu.requestflip = false;
challengesmenu.requestnew = false;
@ -337,9 +363,14 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu)
challengesmenu.keywasadded = false;
challengesmenu.considersealedswapalert = false;
challengesmenu.chaokeyhold = 0;
challengesmenu.currentunlock = MAXUNLOCKABLES;
challengesmenu.unlockcondition = NULL;
if (firstopen)
{
challengesmenu.currentunlock = MAXUNLOCKABLES;
firstopen = false;
}
M_PopulateChallengeGrid();
if (gamedata->challengegrid)
{
@ -548,6 +579,7 @@ void M_ChallengesTick(void)
challengesmenu.chaokeyhold = 0;
challengesmenu.unlockcount[CMC_CHAOANIM]++;
challengesmenu.keywasadded = false; // disappearify the Hand
S_StartSound(NULL, sfx_chchng);
@ -765,6 +797,21 @@ void M_ChallengesTick(void)
}
}
}
if (challengesmenu.currentunlock < MAXUNLOCKABLES
&& gamedata->unlockpending[challengesmenu.currentunlock] == true)
{
UINT16 id = (challengesmenu.hilix * CHALLENGEGRIDHEIGHT) + challengesmenu.hiliy;
if (challengesmenu.extradata
&& challengesmenu.extradata[id].flip != (TILEFLIP_MAX/2))
{
// Only mark visited once flipped
}
else
{
gamedata->unlockpending[challengesmenu.currentunlock] = false;
}
}
}
}
@ -844,6 +891,8 @@ boolean M_ChallengesInputs(INT32 ch)
Z_Free(challengesmenu.extradata);
challengesmenu.extradata = NULL;
if (challengesmenu.unlockcondition)
Z_Free(challengesmenu.unlockcondition);
challengesmenu.unlockcondition = NULL;
return true;

View file

@ -113,7 +113,7 @@ void M_EndModeAttackRun(void)
"Secret Exit",
va(
"No finish time was recorded.\n"
"Secrets don't work in Record modes!\n"
"Secrets don't work in Attack modes!\n"
"Try again in %s.\n",
(gametype == GT_RACE)
? "Grand Prix or Match Race"

View file

@ -7007,6 +7007,23 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
{
trans = tr_trans50;
}
// Non-RNG-advancing equivalent of Obj_SpawnEmeraldSparks
else if (leveltime % 3 == 0)
{
mobj_t *sparkle = P_SpawnMobjFromMobj(
mobj,
M_RandomRange(-mobj->radius/FRACUNIT, mobj->radius/FRACUNIT) * FRACUNIT,
M_RandomRange(-mobj->radius/FRACUNIT, mobj->radius/FRACUNIT) * FRACUNIT,
M_RandomRange(0, mobj->height/FRACUNIT) * FRACUNIT,
MT_SPARK
);
P_SetMobjStateNF(sparkle, mobjinfo[MT_EMERALDSPARK].spawnstate);
sparkle->color = mobj->color;
sparkle->momz += 6 * mapobjectscale * P_MobjFlip(mobj);
P_SetScale(sparkle, 2);
sparkle->destscale = mapobjectscale;
}
if (mobj->reactiontime > 0
&& leveltime > starttime)
@ -13083,7 +13100,9 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj)
}
case MT_SPRAYCAN:
{
if (nummapspraycans == UINT8_MAX || tutorialchallenge == TUTORIALSKIP_INPROGRESS)
if (nummapspraycans == UINT8_MAX
|| modeattacking != ATTACKING_NONE
|| tutorialchallenge == TUTORIALSKIP_INPROGRESS)
{
P_RemoveMobj(mobj);
return false;

View file

@ -880,7 +880,8 @@ static void P_SpawnMapThings(boolean spawnemblems)
Z_Free(loopends);
if (spawnemblems
&& gametype != GT_TUTORIAL)
&& gametype != GT_TUTORIAL
&& !modeattacking)
{
const UINT8 recommendedcans =
#ifdef DEVELOP