More universal Course restriction based on progression

All courses are restricted in Match Race/Time Attack/Online if not visited in GP UNLESS:
- It is the first Race Course of a Cup
- Course has "NoVisitNeeded = True`
    - The Controls Tutorial and Test Run are the only two stages that will need this

The above replaces:
- A lot of restricted courses having to be marked with `FinishNeeded = True`
    - Hidden Palace
    - Sealed Stars 1-14
        - Once Special Mode is unlocked, it will now be possible to practice Sealed Stars before rematching them in GP
- Almost all Tutorial-specific behaviour, since it was heinously hacky

HOWEVER, `FinishNeeded = True` was left in specifically for future releases, and I reserve the right to use it on Adventure Example again before launch.
This commit is contained in:
toaster 2024-01-07 13:57:05 +00:00
parent 601b39378d
commit 83366b6507
8 changed files with 54 additions and 50 deletions

View file

@ -1372,13 +1372,6 @@ void readlevelheader(MYFILE *f, char * name)
else
mapheaderinfo[num]->menuflags &= ~LF2_HIDEINMENU;
}
else if (fastcmp(word, "HIDEINSTATS"))
{
if (i || word2[0] == 'T' || word2[0] == 'Y')
mapheaderinfo[num]->menuflags |= LF2_HIDEINSTATS;
else
mapheaderinfo[num]->menuflags &= ~LF2_HIDEINSTATS;
}
else if (fastcmp(word, "NOTIMEATTACK") || fastcmp(word, "NORECORDATTACK"))
{ // RECORDATTACK is an accepted alias
if (i || word2[0] == 'T' || word2[0] == 'Y')
@ -1393,6 +1386,13 @@ void readlevelheader(MYFILE *f, char * name)
else
mapheaderinfo[num]->menuflags &= ~LF2_FINISHNEEDED;
}
else if (fastcmp(word, "NOVISITNEEDED"))
{
if (i || word2[0] == 'T' || word2[0] == 'Y')
mapheaderinfo[num]->menuflags |= LF2_NOVISITNEEDED;
else
mapheaderinfo[num]->menuflags &= ~LF2_NOVISITNEEDED;
}
else if (fastcmp(word, "GRAVITY"))
mapheaderinfo[num]->gravity = FLOAT_TO_FIXED(atof(word2));
else if (fastcmp(word, "DESTROYOBJECTSFORCHALLENGES"))

View file

@ -6853,8 +6853,8 @@ struct int_const_s const INT_CONST[] = {
{"LF_SUBTRACTNUM",LF_SUBTRACTNUM},
// And map flags
{"LF2_HIDEINMENU",LF2_HIDEINMENU},
{"LF2_HIDEINSTATS",LF2_HIDEINSTATS},
{"LF2_NOTIMEATTACK",LF2_NOTIMEATTACK},
{"LF2_NOVISITNEEDED",LF2_NOVISITNEEDED},
{"LF2_FINISHNEEDED",LF2_FINISHNEEDED},
// Emeralds

View file

@ -572,8 +572,8 @@ struct mapheader_t
#define LF_SUBTRACTNUM (1<<3) ///< Use subtractive position number (for bright levels)
#define LF2_HIDEINMENU (1<<0) ///< Hide in the multiplayer menu
#define LF2_HIDEINSTATS (1<<1) ///< Hide in the statistics screen
#define LF2_NOTIMEATTACK (1<<2) ///< Hide this map in Time Attack modes
#define LF2_NOTIMEATTACK (1<<1) ///< Hide this map in Time Attack modes
#define LF2_NOVISITNEEDED (1<<2) ///< Map does not require visitation to be selectable
#define LF2_FINISHNEEDED (1<<3) ///< Not available in Time Attack modes until you beat the level
extern mapheader_t** mapheaderinfo;

View file

@ -205,6 +205,17 @@ void M_PickMenuBGMap(void)
continue;
}
if (!(mapheaderinfo[i]->menuflags & LF2_NOVISITNEEDED)
&& !(mapheaderinfo[i]->records.mapvisited & MV_VISITED)
&& !(
mapheaderinfo[i]->cup
&& mapheaderinfo[i]->cup->cachedlevels[0] == i
))
{
// Not visited OR head of cup
continue;
}
if (M_MapLocked(i + 1) == true)
{
// We haven't earned this one.
@ -7177,7 +7188,7 @@ static void M_DrawStatsMaps(void)
for (i = 0; i < nummapheaders; i++)
{
// Check for no visibility
if (!mapheaderinfo[i] || (mapheaderinfo[i]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINSTATS|LF2_HIDEINMENU)))
if (!mapheaderinfo[i] || (mapheaderinfo[i]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINMENU)))
continue;
// Has to be accessible via time attack

View file

@ -1094,7 +1094,7 @@ static void M_PrecacheLevelLocks(void)
{
if (!mapheaderinfo[mapcheck] || mapheaderinfo[mapcheck]->cup != NULL)
continue;
if (mapheaderinfo[mapcheck]->menuflags & (LF2_HIDEINSTATS|LF2_HIDEINMENU))
if (mapheaderinfo[mapcheck]->menuflags & LF2_HIDEINMENU)
continue;
if (((mapheaderinfo[mapcheck]->typeoflevel & TOL_TUTORIAL) == TOL_TUTORIAL)
!= ((mapheaderinfo[map]->typeoflevel & TOL_TUTORIAL) == TOL_TUTORIAL))
@ -1475,7 +1475,7 @@ boolean M_CheckCondition(condition_t *cn, player_t *player)
{
for (i = 0; i < basenummapheaders; i++)
{
if (!mapheaderinfo[i] || mapheaderinfo[i]->menuflags & (LF2_HIDEINSTATS|LF2_HIDEINMENU))
if (!mapheaderinfo[i] || mapheaderinfo[i]->menuflags & (LF2_HIDEINMENU))
continue;
total++;
@ -1957,7 +1957,7 @@ static char *M_BuildConditionTitle(UINT16 map)
{
char *title, *ref;
if (((mapheaderinfo[map]->menuflags & LF2_FINISHNEEDED)
if ((!(mapheaderinfo[map]->menuflags & LF2_NOVISITNEEDED)
// the following is intentionally not MV_BEATEN, just in case the title is for "Finish a round on X"
&& !(mapheaderinfo[map]->records.mapvisited & MV_VISITED))
|| M_MapLocked(map+1))

View file

@ -21,13 +21,22 @@ static boolean M_StatisticsAddMap(UINT16 map, cupheader_t *cup, boolean *headere
return false;
// Check for no visibility
if (mapheaderinfo[map]->menuflags & (LF2_HIDEINSTATS|LF2_HIDEINMENU))
if (mapheaderinfo[map]->menuflags & LF2_HIDEINMENU)
return false;
// Check for visitation
// (M_CanShowLevelInList also checks being the first map in a cup,
// but we don't need to do this here because it'd just muddy stats!)
if (!(mapheaderinfo[map]->menuflags & LF2_NOVISITNEEDED)
&& !(mapheaderinfo[map]->records.mapvisited & MV_VISITED))
return false;
#if 0
// Check for completion
if ((mapheaderinfo[map]->menuflags & LF2_FINISHNEEDED)
&& !(mapheaderinfo[map]->records.mapvisited & MV_BEATEN))
return false;
#endif
// Check for unlock
if (M_MapLocked(map+1))
@ -80,11 +89,7 @@ static void M_StatisticsMaps(void)
headerexists = false;
for (i = 0; i < nummapheaders; i++)
{
if (M_StatisticsAddMap(i, NULL, &headerexists, true))
{
if (!(mapheaderinfo[i]->records.mapvisited & MV_BEATEN))
break;
}
M_StatisticsAddMap(i, NULL, &headerexists, true);
}
if ((i = statisticsmenu.numextramedals) != 0)

View file

@ -77,11 +77,11 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch)
if (levelsearch->timeattack && (mapheaderinfo[mapnum]->menuflags & LF2_NOTIMEATTACK))
return false;
cupheader_t *cup = (levelsearch->cup == &dummy_lostandfound) ? NULL : levelsearch->cup;
// Don't permit cup when no cup requested (also no dupes in time attack)
if (levelsearch->cupmode)
{
cupheader_t *cup = (levelsearch->cup == &dummy_lostandfound) ? NULL : levelsearch->cup;
if ((!cup || levelsearch->timeattack)
&& mapheaderinfo[mapnum]->cup != cup)
return false;
@ -90,6 +90,12 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch)
// Finally, the most complex check: does the map have lock conditions?
if (levelsearch->checklocked)
{
// Check for visitation
if (!(mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
&& !(mapheaderinfo[mapnum]->records.mapvisited & MV_VISITED)
&& !(cup && cup->cachedlevels[0] == mapnum))
return false;
// Check for completion
if ((mapheaderinfo[mapnum]->menuflags & LF2_FINISHNEEDED)
&& !(mapheaderinfo[mapnum]->records.mapvisited & MV_BEATEN))
@ -128,22 +134,9 @@ UINT16 M_CountLevelsToShowInList(levelsearch_t *levelsearch)
for (i = 0; i < nummapheaders; i++)
{
if (M_CanShowLevelInList(i, levelsearch))
{
count++;
// Tutorial will only show what you've made your way to
if (!levelsearch->checklocked)
continue;
if (!levelsearch->tutorial)
continue;
if (i >= basenummapheaders)
continue;
if (mapheaderinfo[i]->records.mapvisited & MV_BEATEN)
continue;
break;
}
if (!M_CanShowLevelInList(i, levelsearch))
continue;
count++;
}
return count;
@ -206,13 +199,6 @@ UINT16 M_GetNextLevelInList(UINT16 mapnum, UINT8 *i, levelsearch_t *levelsearch)
}
else
{
// Tutorial will only show what you've made your way to
if (levelsearch->checklocked
&& levelsearch->tutorial
&& mapnum < basenummapheaders
&& !(mapheaderinfo[mapnum]->records.mapvisited & MV_BEATEN))
return NEXTMAP_INVALID;
mapnum++;
while (!M_CanShowLevelInList(mapnum, levelsearch) && mapnum < nummapheaders)
mapnum++;

View file

@ -1393,11 +1393,13 @@ static boolean S_SoundTestDefLocked(musicdef_t *def)
mapheader_t *header = mapheaderinfo[def->sequence.map];
// Is the level tied to SP progression?
if ((
(header->menuflags & (LF2_FINISHNEEDED|LF2_HIDEINMENU))
|| (def->sequence.altref == ALTREF_REQUIRESBEATEN) // Associated music only when completed
)
// Visitation required?
if (!(header->menuflags & LF2_NOVISITNEEDED)
&& !(header->records.mapvisited & MV_VISITED))
return true;
// Associated music only when completed
if ((def->sequence.altref == ALTREF_REQUIRESBEATEN)
&& !(header->records.mapvisited & MV_BEATEN))
return true;