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; maketic = gametic+1;
neededtic = maketic; neededtic = maketic;
serverrunning = false; serverrunning = false;
titlemapinaction = false;
} }
// called at singleplayer start and stopdemo // 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") else if ((offset=0) || fastcmp(params[0], "FINISHCOOL")
|| (++offset && fastcmp(params[0], "FINISHPERFECT")) || (++offset && fastcmp(params[0], "FINISHPERFECT"))
|| (++offset && fastcmp(params[0], "FINISHALLPRISONS")) || (++offset && fastcmp(params[0], "FINISHALLPRISONS"))
|| (++offset && fastcmp(params[0], "SURVIVE"))
|| (++offset && fastcmp(params[0], "NOCONTEST")) || (++offset && fastcmp(params[0], "NOCONTEST"))
|| (++offset && fastcmp(params[0], "SMASHUFO")) || (++offset && fastcmp(params[0], "SMASHUFO"))
|| (++offset && fastcmp(params[0], "CHASEDBYSPB"))) || (++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) 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) else if (levelsearch->cup)
{ {
@ -6389,7 +6389,7 @@ void M_DrawAddons(void)
// Challenges Menu // 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 #ifdef DEVELOP
extern consvar_t cv_debugchallenges; extern consvar_t cv_debugchallenges;
@ -6710,6 +6710,21 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili
} }
drawborder: 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) if (hili)
{ {
boolean maj = (ref != NULL && ref->majorunlock); boolean maj = (ref != NULL && ref->majorunlock);
@ -6719,13 +6734,11 @@ drawborder:
buffer[7] = (skullAnimCounter/5) ? '2' : '1'; buffer[7] = (skullAnimCounter/5) ? '2' : '1';
pat = W_CachePatchName(buffer, PU_CACHE); pat = W_CachePatchName(buffer, PU_CACHE);
colormap = R_GetTranslationColormap(TC_DEFAULT, M_GetCvPlayerColor(0), GTC_MENUCACHE);
V_DrawFixedPatch( V_DrawFixedPatch(
x*FRACUNIT, y*FRACUNIT, x*FRACUNIT, y*FRACUNIT,
FRACUNIT, FRACUNIT,
0, pat, 0, pat,
colormap flashmap
); );
} }
@ -7114,6 +7127,32 @@ static void M_DrawChallengePreview(INT32 x, INT32 y)
NULL); NULL);
break; 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: case SECRET_ALTTITLE:
{ {
x = 8; x = 8;
@ -7282,12 +7321,14 @@ static void M_DrawChallengeKeys(INT32 tilex, INT32 tiley)
fixed_t keyx = (8+offs)*FRACUNIT, keyy = 0; fixed_t keyx = (8+offs)*FRACUNIT, keyy = 0;
const boolean keybuttonpress = (menumessage.active == false && M_MenuExtraHeld(pid) == true);
// Button prompt // Button prompt
K_drawButton( K_drawButton(
24 << FRACBITS, 24 << FRACBITS,
16 << FRACBITS, 16 << FRACBITS,
0, kp_button_c[1], 0, kp_button_c[1],
menumessage.active == false && M_MenuExtraHeld(pid) == true keybuttonpress
); );
// Metyr of rounds played that contribute to Chao Key generation // 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; UINT8 keysbeingused = 0;
// The Chao Key swooping animation // 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) void M_DrawChallenges(void)
{ {
INT32 x = currentMenu->x, explodex, selectx = 0, selecty = 0; INT32 x = currentMenu->x, explodex, selectx = 0, selecty = 0;
@ -7503,6 +7655,8 @@ void M_DrawChallenges(void)
goto challengedesc; goto challengedesc;
} }
UINT8 *flashmap = R_GetTranslationColormap(TC_DEFAULT, M_GetCvPlayerColor(0), GTC_MENUCACHE);
y = currentMenu->y; y = currentMenu->y;
V_DrawFadeFill(0, y-2, BASEVIDWIDTH, (challengesgridstep * CHALLENGEGRIDHEIGHT) + 2, 0, 31, challengetransparentstrength); V_DrawFadeFill(0, y-2, BASEVIDWIDTH, (challengesgridstep * CHALLENGEGRIDHEIGHT) + 2, 0, 31, challengetransparentstrength);
@ -7556,7 +7710,7 @@ void M_DrawChallenges(void)
continue; continue;
} }
M_DrawChallengeTile(i, j, x, y, false); M_DrawChallengeTile(i, j, x, y, flashmap, false);
} }
x -= challengesgridstep; x -= challengesgridstep;
@ -7571,11 +7725,14 @@ void M_DrawChallenges(void)
if (challengesmenu.fade) if (challengesmenu.fade)
V_DrawFadeScreen(31, challengesmenu.fade); V_DrawFadeScreen(31, challengesmenu.fade);
M_DrawChallengeScrollBar(flashmap);
M_DrawChallengeTile( M_DrawChallengeTile(
challengesmenu.hilix, challengesmenu.hilix,
challengesmenu.hiliy, challengesmenu.hiliy,
selectx, selectx,
selecty, selecty,
flashmap,
true); true);
M_DrawCharSelectExplosions(false, explodex, currentMenu->y); M_DrawCharSelectExplosions(false, explodex, currentMenu->y);
@ -7881,7 +8038,7 @@ static void M_DrawStatsMaps(void)
else if (mapheaderinfo[mnum]->cup) else if (mapheaderinfo[mnum]->cup)
str = va("%s CUP", mapheaderinfo[mnum]->cup->realname); str = va("%s CUP", mapheaderinfo[mnum]->cup->realname);
else else
str = "LOST AND FOUND"; str = "LOST & FOUND";
V_DrawThinString(20, y, highlightflags, str); V_DrawThinString(20, y, highlightflags, str);
} }

View file

@ -733,6 +733,14 @@ void M_StartControlPanel(void)
{ {
if (gamestate != GS_MENU) 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); G_SetGamestate(GS_MENU);
gameaction = ga_nothing; gameaction = ga_nothing;

View file

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

View file

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

View file

@ -142,6 +142,7 @@ static void M_ChallengesAutoFocus(UINT16 unlockid, boolean fresh)
{ {
UINT16 i; UINT16 i;
INT16 work; INT16 work;
boolean posisvalid = false;
if (unlockid >= MAXUNLOCKABLES && gamedata->pendingkeyrounds > 0 if (unlockid >= MAXUNLOCKABLES && gamedata->pendingkeyrounds > 0
&& (gamedata->chaokeys < GDMAX_CHAOKEYS)) && (gamedata->chaokeys < GDMAX_CHAOKEYS))
@ -149,28 +150,22 @@ static void M_ChallengesAutoFocus(UINT16 unlockid, boolean fresh)
if (fresh && unlockid >= MAXUNLOCKABLES) if (fresh && unlockid >= MAXUNLOCKABLES)
{ {
UINT16 selection[MAXUNLOCKABLES]; if (challengesmenu.currentunlock < MAXUNLOCKABLES)
UINT16 numunlocks = 0;
// Get a random available unlockable.
for (i = 0; i < MAXUNLOCKABLES; i++)
{ {
if (!unlockables[i].conditionset) // Use the last selected time.
{ unlockid = challengesmenu.currentunlock;
continue; posisvalid = true;
}
if (!gamedata->unlocked[i])
{
continue;
}
selection[numunlocks++] = i;
} }
else
if (!numunlocks)
{ {
// ...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++) for (i = 0; i < MAXUNLOCKABLES; i++)
{ {
if (!unlockables[i].conditionset) if (!unlockables[i].conditionset)
@ -178,13 +173,43 @@ static void M_ChallengesAutoFocus(UINT16 unlockid, boolean fresh)
continue; 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; 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) if (unlockid >= MAXUNLOCKABLES)
return; return;
@ -192,9 +217,8 @@ static void M_ChallengesAutoFocus(UINT16 unlockid, boolean fresh)
if (challengesmenu.unlockcondition) if (challengesmenu.unlockcondition)
Z_Free(challengesmenu.unlockcondition); Z_Free(challengesmenu.unlockcondition);
challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock); 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; return;
for (i = 0; i < (CHALLENGEGRIDHEIGHT * gamedata->challengegridwidth); i++) for (i = 0; i < (CHALLENGEGRIDHEIGHT * gamedata->challengegridwidth); i++)
@ -330,6 +354,8 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu)
if (challengesmenu.pending || desiredmenu == NULL) if (challengesmenu.pending || desiredmenu == NULL)
{ {
static boolean firstopen = true;
challengesmenu.ticker = 0; challengesmenu.ticker = 0;
challengesmenu.requestflip = false; challengesmenu.requestflip = false;
challengesmenu.requestnew = false; challengesmenu.requestnew = false;
@ -337,9 +363,14 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu)
challengesmenu.keywasadded = false; challengesmenu.keywasadded = false;
challengesmenu.considersealedswapalert = false; challengesmenu.considersealedswapalert = false;
challengesmenu.chaokeyhold = 0; challengesmenu.chaokeyhold = 0;
challengesmenu.currentunlock = MAXUNLOCKABLES;
challengesmenu.unlockcondition = NULL; challengesmenu.unlockcondition = NULL;
if (firstopen)
{
challengesmenu.currentunlock = MAXUNLOCKABLES;
firstopen = false;
}
M_PopulateChallengeGrid(); M_PopulateChallengeGrid();
if (gamedata->challengegrid) if (gamedata->challengegrid)
{ {
@ -548,6 +579,7 @@ void M_ChallengesTick(void)
challengesmenu.chaokeyhold = 0; challengesmenu.chaokeyhold = 0;
challengesmenu.unlockcount[CMC_CHAOANIM]++; challengesmenu.unlockcount[CMC_CHAOANIM]++;
challengesmenu.keywasadded = false; // disappearify the Hand
S_StartSound(NULL, sfx_chchng); 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); Z_Free(challengesmenu.extradata);
challengesmenu.extradata = NULL; challengesmenu.extradata = NULL;
if (challengesmenu.unlockcondition)
Z_Free(challengesmenu.unlockcondition);
challengesmenu.unlockcondition = NULL; challengesmenu.unlockcondition = NULL;
return true; return true;

View file

@ -113,7 +113,7 @@ void M_EndModeAttackRun(void)
"Secret Exit", "Secret Exit",
va( va(
"No finish time was recorded.\n" "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", "Try again in %s.\n",
(gametype == GT_RACE) (gametype == GT_RACE)
? "Grand Prix or Match Race" ? "Grand Prix or Match Race"

View file

@ -7007,6 +7007,23 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
{ {
trans = tr_trans50; 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 if (mobj->reactiontime > 0
&& leveltime > starttime) && leveltime > starttime)
@ -13083,7 +13100,9 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj)
} }
case MT_SPRAYCAN: case MT_SPRAYCAN:
{ {
if (nummapspraycans == UINT8_MAX || tutorialchallenge == TUTORIALSKIP_INPROGRESS) if (nummapspraycans == UINT8_MAX
|| modeattacking != ATTACKING_NONE
|| tutorialchallenge == TUTORIALSKIP_INPROGRESS)
{ {
P_RemoveMobj(mobj); P_RemoveMobj(mobj);
return false; return false;

View file

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