M_CheckNetUnlockByID

System for netsyncing unlocks, inspired by but with nowhere near as many moving parts as (STJr/SRB2!1756).
* `gamedata->unlocked[MAXUNLOCKABLES]` is duplicated to `netUnlocked[MAXUNLOCKABLES]` (or all `true` in `dedicated`
* New `local` boolean for M_SecretUnlocked
* Removed last vestiges of SRB2 special stage token code because it occupied the spot in the netsave we wanted to use.
* Correct typing of multiple `m_cond` functions that returned `boolean` constants as `UINT8`s.
This commit is contained in:
toaster 2022-12-09 17:26:52 +00:00
parent d78668e0a6
commit e7c79ab461
15 changed files with 78 additions and 65 deletions

View file

@ -1966,13 +1966,13 @@ static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth)
return; return;
} }
if (var == &cv_kartencore && !M_SecretUnlocked(SECRET_ENCORE)) if (var == &cv_kartencore && !M_SecretUnlocked(SECRET_ENCORE, false))
{ {
CONS_Printf(M_GetText("You haven't unlocked Encore Mode yet!\n")); CONS_Printf(M_GetText("You haven't unlocked Encore Mode yet!\n"));
return; return;
} }
if (var == &cv_kartspeed && !M_SecretUnlocked(SECRET_HARDSPEED)) if (var == &cv_kartspeed && !M_SecretUnlocked(SECRET_HARDSPEED, false))
{ {
if (!stricmp(value, "Hard") || atoi(value) >= KARTSPEED_HARD) if (!stricmp(value, "Hard") || atoi(value) >= KARTSPEED_HARD)
{ {
@ -2227,7 +2227,7 @@ void CV_AddValue(consvar_t *var, INT32 increment)
|| var->PossibleValue == dummykartspeed_cons_t || var->PossibleValue == dummykartspeed_cons_t
|| var->PossibleValue == gpdifficulty_cons_t) || var->PossibleValue == gpdifficulty_cons_t)
{ {
if (!M_SecretUnlocked(SECRET_HARDSPEED)) if (!M_SecretUnlocked(SECRET_HARDSPEED, false))
{ {
max = KARTSPEED_NORMAL+1; max = KARTSPEED_NORMAL+1;
if (var->PossibleValue == kartspeed_cons_t) if (var->PossibleValue == kartspeed_cons_t)

View file

@ -57,6 +57,7 @@
#include "k_boss.h" #include "k_boss.h"
#include "doomstat.h" #include "doomstat.h"
#include "s_sound.h" // sfx_syfail #include "s_sound.h" // sfx_syfail
#include "m_cond.h" // netUnlocked
// cl loading screen // cl loading screen
#include "v_video.h" #include "v_video.h"
@ -3463,6 +3464,11 @@ void SV_ResetServer(void)
CV_RevertNetVars(); CV_RevertNetVars();
// Copy our unlocks to a place where net material can grab at/overwrite them safely.
// (permits all unlocks in dedicated)
for (i = 0; i < MAXUNLOCKABLES; i++)
netUnlocked[i] = (dedicated || gamedata->unlocked[i]);
DEBFILE("\n-=-=-=-=-=-=-= Server Reset =-=-=-=-=-=-=-\n\n"); DEBFILE("\n-=-=-=-=-=-=-= Server Reset =-=-=-=-=-=-=-\n\n");
} }

View file

@ -2857,7 +2857,7 @@ static void Command_Map_f(void)
{ {
newencoremode = !newencoremode; newencoremode = !newencoremode;
if (!M_SecretUnlocked(SECRET_ENCORE) && newencoremode == true && !usingcheats) if (!M_SecretUnlocked(SECRET_ENCORE, false) && newencoremode == true && !usingcheats)
{ {
CONS_Alert(CONS_NOTICE, M_GetText("You haven't unlocked Encore Mode yet!\n")); CONS_Alert(CONS_NOTICE, M_GetText("You haven't unlocked Encore Mode yet!\n"));
return; return;
@ -4848,7 +4848,7 @@ void ItemFinder_OnChange(void)
if (!cv_itemfinder.value) if (!cv_itemfinder.value)
return; // it's fine. return; // it's fine.
if (!M_SecretUnlocked(SECRET_ITEMFINDER)) if (!M_SecretUnlocked(SECRET_ITEMFINDER, true))
{ {
CONS_Printf(M_GetText("You haven't earned this yet.\n")); CONS_Printf(M_GetText("You haven't earned this yet.\n"));
CV_StealthSetValue(&cv_itemfinder, 0); CV_StealthSetValue(&cv_itemfinder, 0);

View file

@ -573,10 +573,6 @@ extern INT32 luabanks[NUM_LUABANKS];
extern INT32 nummaprings; //keep track of spawned rings/coins extern INT32 nummaprings; //keep track of spawned rings/coins
extern UINT32 token; ///< Number of tokens collected in a level
extern UINT32 tokenlist; ///< List of tokens collected
extern boolean gottoken; ///< Did you get a token? Used for end of act
extern INT32 tokenbits; ///< Used for setting token bits
extern UINT32 bluescore; ///< Blue Team Scores extern UINT32 bluescore; ///< Blue Team Scores
extern UINT32 redscore; ///< Red Team Scores extern UINT32 redscore; ///< Red Team Scores

View file

@ -204,10 +204,6 @@ UINT8 stagefailed; // Used for GEMS BONUS? Also to see if you beat the stage.
UINT16 emeralds; UINT16 emeralds;
INT32 luabanks[NUM_LUABANKS]; INT32 luabanks[NUM_LUABANKS];
UINT32 token; // Number of tokens collected in a level
UINT32 tokenlist; // List of tokens collected
boolean gottoken; // Did you get a token? Used for end of act
INT32 tokenbits; // Used for setting token bits
// Temporary holding place for nights data for the current map // Temporary holding place for nights data for the current map
//nightsdata_t ntemprecords; //nightsdata_t ntemprecords;
@ -3309,7 +3305,7 @@ INT16 G_SometimesGetDifferentGametype(UINT8 prefgametype)
// Most of the gametype references in this condition are intentionally not prefgametype. // Most of the gametype references in this condition are intentionally not prefgametype.
// This is so a server CAN continue playing a gametype if they like the taste of it. // This is so a server CAN continue playing a gametype if they like the taste of it.
// The encore check needs prefgametype so can't use G_RaceGametype... // The encore check needs prefgametype so can't use G_RaceGametype...
boolean encorepossible = ((M_SecretUnlocked(SECRET_ENCORE) || encorescramble == 1) boolean encorepossible = ((M_SecretUnlocked(SECRET_ENCORE, false) || encorescramble == 1)
&& ((gametyperules|gametypedefaultrules[prefgametype]) & GTR_CIRCUIT)); && ((gametyperules|gametypedefaultrules[prefgametype]) & GTR_CIRCUIT));
UINT8 encoremodifier = 0; UINT8 encoremodifier = 0;
@ -3870,7 +3866,7 @@ static void G_GetNextMap(void)
while (cup) while (cup)
{ {
// Not unlocked? Grab the next result afterwards // Not unlocked? Grab the next result afterwards
if (!marathonmode && cup->unlockrequired < MAXUNLOCKABLES && !gamedata->unlocked[cup->unlockrequired]) if (!marathonmode && M_CheckNetUnlockByID(cup->unlockrequired))
{ {
cup = cup->next; cup = cup->next;
gettingresult = 1; gettingresult = 1;
@ -4218,10 +4214,6 @@ static void G_DoContinued(void)
// Reset score // Reset score
pl->score = 0; pl->score = 0;
// Allow tokens to come back
tokenlist = 0;
token = 0;
if (!(netgame || multiplayer || demo.playback || demo.recording || metalrecording || modeattacking) && !usedCheats && cursaveslot > 0) if (!(netgame || multiplayer || demo.playback || demo.recording || metalrecording || modeattacking) && !usedCheats && cursaveslot > 0)
G_SaveGameOver((UINT32)cursaveslot, true); G_SaveGameOver((UINT32)cursaveslot, true);

View file

@ -79,6 +79,7 @@ boolean K_FollowerUsable(INT32 skinnum)
} }
// Use the unlockables table directly // Use the unlockables table directly
// DEFINITELY not M_CheckNetUnlockByID
return (boolean)(gamedata->unlocked[i]); return (boolean)(gamedata->unlocked[i]);
} }

View file

@ -1930,7 +1930,7 @@ static void M_DrawCupPreview(INT16 y, cupheader_t *cup)
V_DrawFill(0, y, BASEVIDWIDTH, 54, 31); V_DrawFill(0, y, BASEVIDWIDTH, 54, 31);
if (cup && (cup->unlockrequired >= MAXUNLOCKABLES || gamedata->unlocked[cup->unlockrequired])) if (cup && (cup->unlockrequired >= MAXUNLOCKABLES || M_CheckNetUnlockByID(cup->unlockrequired)))
{ {
i = (cupgrid.previewanim / 82) % cup->numlevels; i = (cupgrid.previewanim / 82) % cup->numlevels;
while (x < BASEVIDWIDTH + pad) while (x < BASEVIDWIDTH + pad)
@ -1968,7 +1968,7 @@ static void M_DrawCupTitle(INT16 y, cupheader_t *cup)
if (cup) if (cup)
{ {
boolean unlocked = (cup->unlockrequired >= MAXUNLOCKABLES || gamedata->unlocked[cup->unlockrequired]); boolean unlocked = (cup->unlockrequired >= MAXUNLOCKABLES || M_CheckNetUnlockByID(cup->unlockrequired));
UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE); UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE);
patch_t *icon = W_CachePatchName(cup->icon, PU_CACHE); patch_t *icon = W_CachePatchName(cup->icon, PU_CACHE);
const char *str = (unlocked ? va("%s Cup", cup->name) : "???"); const char *str = (unlocked ? va("%s Cup", cup->name) : "???");
@ -2036,7 +2036,7 @@ void M_DrawCupSelect(void)
V_DrawScaledPatch(x, y, 0, patch); V_DrawScaledPatch(x, y, 0, patch);
if (iconcup->unlockrequired < MAXUNLOCKABLES && !gamedata->unlocked[iconcup->unlockrequired]) if (iconcup->unlockrequired < MAXUNLOCKABLES && !M_CheckNetUnlockByID(iconcup->unlockrequired))
{ {
patch_t *st = W_CachePatchName(va("ICONST0%d", (cupgrid.previewanim % 4) + 1), PU_CACHE); patch_t *st = W_CachePatchName(va("ICONST0%d", (cupgrid.previewanim % 4) + 1), PU_CACHE);
V_DrawScaledPatch(x + 8, y + icony, 0, st); V_DrawScaledPatch(x + 8, y + icony, 0, st);

View file

@ -3362,7 +3362,7 @@ void M_SetupDifficultySelect(INT32 choice)
PLAY_RaceDifficultyDef.lastOn = drace_cupselect; // Select cup select by default. PLAY_RaceDifficultyDef.lastOn = drace_cupselect; // Select cup select by default.
} }
if (M_SecretUnlocked(SECRET_ENCORE)) if (M_SecretUnlocked(SECRET_ENCORE, false))
{ {
PLAY_RaceDifficulty[drace_encore].status = IT_STRING2|IT_CVAR; // Encore on/off PLAY_RaceDifficulty[drace_encore].status = IT_STRING2|IT_CVAR; // Encore on/off
} }
@ -3477,7 +3477,7 @@ static void M_LevelListFromGametype(INT16 gt)
while (cup) while (cup)
{ {
if (cup->unlockrequired >= MAXUNLOCKABLES || gamedata->unlocked[cup->unlockrequired]) if (cup->unlockrequired >= MAXUNLOCKABLES || M_CheckNetUnlockByID(cup->unlockrequired))
{ {
highestid = cup->id; highestid = cup->id;
if (Playing() && mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == cup) if (Playing() && mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == cup)
@ -3611,7 +3611,7 @@ void M_CupSelectHandler(INT32 choice)
M_SetMenuDelay(pid); M_SetMenuDelay(pid);
if ((!newcup) if ((!newcup)
|| (newcup && newcup->unlockrequired < MAXUNLOCKABLES && !gamedata->unlocked[newcup->unlockrequired]) || (newcup->unlockrequired < MAXUNLOCKABLES && !M_CheckNetUnlockByID(newcup->unlockrequired))
|| (newcup->cachedlevels[0] == NEXTMAP_INVALID)) || (newcup->cachedlevels[0] == NEXTMAP_INVALID))
{ {
S_StartSound(NULL, sfx_s3kb2); S_StartSound(NULL, sfx_s3kb2);
@ -4614,7 +4614,7 @@ void M_InitOptions(INT32 choice)
OPTIONS_MainDef.menuitems[mopt_gameplay].status = IT_STRING | IT_SUBMENU; OPTIONS_MainDef.menuitems[mopt_gameplay].status = IT_STRING | IT_SUBMENU;
OPTIONS_MainDef.menuitems[mopt_server].status = IT_STRING | IT_SUBMENU; OPTIONS_MainDef.menuitems[mopt_server].status = IT_STRING | IT_SUBMENU;
OPTIONS_GameplayDef.menuitems[gopt_encore].status = OPTIONS_GameplayDef.menuitems[gopt_encore].status =
(M_SecretUnlocked(SECRET_ENCORE) ? (IT_STRING | IT_CVAR) : IT_DISABLED); (M_SecretUnlocked(SECRET_ENCORE, false) ? (IT_STRING | IT_CVAR) : IT_DISABLED);
} }
OPTIONS_DataDef.menuitems[dopt_erase].status = (gamestate == GS_MENU OPTIONS_DataDef.menuitems[dopt_erase].status = (gamestate == GS_MENU

View file

@ -403,8 +403,6 @@ int LUA_WriteGlobals(lua_State *L, const char *word)
skincolor_bluering = (UINT16)luaL_checkinteger(L, 2); skincolor_bluering = (UINT16)luaL_checkinteger(L, 2);
else if (fastcmp(word, "emeralds")) else if (fastcmp(word, "emeralds"))
emeralds = (UINT16)luaL_checkinteger(L, 2); emeralds = (UINT16)luaL_checkinteger(L, 2);
else if (fastcmp(word, "token"))
token = (UINT32)luaL_checkinteger(L, 2);
else if (fastcmp(word, "gravity")) else if (fastcmp(word, "gravity"))
gravity = (fixed_t)luaL_checkinteger(L, 2); gravity = (fixed_t)luaL_checkinteger(L, 2);
else if (fastcmp(word, "stoppedclock")) else if (fastcmp(word, "stoppedclock"))

View file

@ -27,6 +27,7 @@
#include "k_profiles.h" #include "k_profiles.h"
gamedata_t *gamedata = NULL; gamedata_t *gamedata = NULL;
boolean netUnlocked[MAXUNLOCKABLES];
// Map triggers for linedef executors // Map triggers for linedef executors
// 32 triggers, one bit each // 32 triggers, one bit each
@ -442,7 +443,7 @@ void M_ClearSecrets(void)
for (i = 0; i < MAXEMBLEMS; ++i) for (i = 0; i < MAXEMBLEMS; ++i)
gamedata->collected[i] = false; gamedata->collected[i] = false;
for (i = 0; i < MAXUNLOCKABLES; ++i) for (i = 0; i < MAXUNLOCKABLES; ++i)
gamedata->unlocked[i] = false; gamedata->unlocked[i] = netUnlocked[i] = false;
for (i = 0; i < MAXCONDITIONSETS; ++i) for (i = 0; i < MAXCONDITIONSETS; ++i)
gamedata->achieved[i] = false; gamedata->achieved[i] = false;
@ -728,37 +729,47 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa
// Quick unlock checks // Quick unlock checks
// ------------------- // -------------------
UINT8 M_SecretUnlocked(INT32 type) boolean M_CheckNetUnlockByID(UINT8 unlockid)
{
if (unlockid >= MAXUNLOCKABLES)
{
return true; // default permit
}
if (netgame)
{
return netUnlocked[unlockid];
}
return gamedata->unlocked[unlockid];
}
boolean M_SecretUnlocked(INT32 type, boolean local)
{ {
INT32 i; INT32 i;
if (dedicated)
return true;
#if 0 #if 0
(void)type; (void)type;
(void)i; (void)i;
return false; // for quick testing return false; // for quick testing
#else #else
#ifdef DEVELOP
#define CHADYES true
#else
#define CHADYES false
#endif
for (i = 0; i < MAXUNLOCKABLES; ++i) for (i = 0; i < MAXUNLOCKABLES; ++i)
{ {
if (unlockables[i].type == type && gamedata->unlocked[i] != CHADYES) if (unlockables[i].type != type)
return !CHADYES; continue;
if ((local && gamedata->unlocked[i])
|| M_CheckNetUnlockByID(i))
continue;
return false;
} }
return CHADYES;
#undef CHADYES return true;
#endif //if 0 #endif //if 0
} }
UINT8 M_MapLocked(INT32 mapnum) boolean M_MapLocked(INT32 mapnum)
{ {
// Don't lock maps in dedicated servers. // Don't lock maps in dedicated servers.
// That just makes hosts' lives hell. // That just makes hosts' lives hell.
@ -774,16 +785,11 @@ UINT8 M_MapLocked(INT32 mapnum)
if (mapheaderinfo[mapnum-1]->cup) if (mapheaderinfo[mapnum-1]->cup)
{ {
if ((mapheaderinfo[mapnum-1]->cup->unlockrequired < MAXUNLOCKABLES) if (!M_CheckNetUnlockByID(mapheaderinfo[mapnum-1]->cup->unlockrequired))
&& (!gamedata->unlocked[mapheaderinfo[mapnum-1]->cup->unlockrequired]))
return true; return true;
} }
if ((mapheaderinfo[mapnum-1]->unlockrequired < MAXUNLOCKABLES) return !M_CheckNetUnlockByID(mapheaderinfo[mapnum-1]->unlockrequired);
&& (!gamedata->unlocked[mapheaderinfo[mapnum-1]->unlockrequired]))
return true;
return false;
} }
INT32 M_CountEmblems(void) INT32 M_CountEmblems(void)

View file

@ -158,6 +158,9 @@ typedef struct
extern gamedata_t *gamedata; extern gamedata_t *gamedata;
// Netsynced functional alternative to gamedata->unlocked
extern boolean netUnlocked[MAXUNLOCKABLES];
extern conditionset_t conditionSets[MAXCONDITIONSETS]; extern conditionset_t conditionSets[MAXCONDITIONSETS];
extern emblem_t emblemlocations[MAXEMBLEMS]; extern emblem_t emblemlocations[MAXEMBLEMS];
extern unlockable_t unlockables[MAXUNLOCKABLES]; extern unlockable_t unlockables[MAXUNLOCKABLES];
@ -191,8 +194,9 @@ UINT8 M_CheckLevelEmblems(void);
UINT8 M_CompletionEmblems(void); UINT8 M_CompletionEmblems(void);
// Checking unlockable status // Checking unlockable status
UINT8 M_SecretUnlocked(INT32 type); boolean M_CheckNetUnlockByID(UINT8 unlockid);
UINT8 M_MapLocked(INT32 mapnum); boolean M_SecretUnlocked(INT32 type, boolean local);
boolean M_MapLocked(INT32 mapnum);
INT32 M_CountEmblems(void); INT32 M_CountEmblems(void);
// Emblem shit // Emblem shit

View file

@ -35,6 +35,7 @@
#include "p_polyobj.h" #include "p_polyobj.h"
#include "lua_script.h" #include "lua_script.h"
#include "p_slopes.h" #include "p_slopes.h"
#include "m_cond.h" // netUnlocked
// SRB2Kart // SRB2Kart
#include "k_battle.h" #include "k_battle.h"
@ -4600,9 +4601,6 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride)
//lastmapsaved = gamemap; //lastmapsaved = gamemap;
lastmaploaded = gamemap; lastmaploaded = gamemap;
tokenlist = 0;
token = 0;
savedata.emeralds = READUINT16(save_p)-357; savedata.emeralds = READUINT16(save_p)-357;
READSTRINGN(save_p, testname, sizeof(testname)); READSTRINGN(save_p, testname, sizeof(testname));
@ -4621,7 +4619,7 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride)
static void P_NetArchiveMisc(boolean resending) static void P_NetArchiveMisc(boolean resending)
{ {
size_t i; size_t i, j;
WRITEUINT32(save_p, ARCHIVEBLOCK_MISC); WRITEUINT32(save_p, ARCHIVEBLOCK_MISC);
@ -4642,7 +4640,14 @@ static void P_NetArchiveMisc(boolean resending)
WRITEUINT32(save_p, pig); WRITEUINT32(save_p, pig);
} }
WRITEUINT32(save_p, tokenlist); for (i = 0; i < MAXUNLOCKABLES;)
{
UINT8 btemp = 0;
for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
btemp |= (netUnlocked[j+i] << j);
WRITEUINT8(save_p, btemp);
i += j;
}
WRITEUINT8(save_p, encoremode); WRITEUINT8(save_p, encoremode);
@ -4671,7 +4676,6 @@ static void P_NetArchiveMisc(boolean resending)
WRITEUINT8(save_p, globools); WRITEUINT8(save_p, globools);
} }
WRITEUINT32(save_p, token);
WRITEUINT32(save_p, bluescore); WRITEUINT32(save_p, bluescore);
WRITEUINT32(save_p, redscore); WRITEUINT32(save_p, redscore);
@ -4767,7 +4771,7 @@ static void P_NetArchiveMisc(boolean resending)
static inline boolean P_NetUnArchiveMisc(boolean reloading) static inline boolean P_NetUnArchiveMisc(boolean reloading)
{ {
size_t i; size_t i, j;
size_t numTasks; size_t numTasks;
if (READUINT32(save_p) != ARCHIVEBLOCK_MISC) if (READUINT32(save_p) != ARCHIVEBLOCK_MISC)
@ -4801,7 +4805,13 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading)
} }
} }
tokenlist = READUINT32(save_p); for (i = 0; i < MAXUNLOCKABLES;)
{
UINT8 rtemp = READUINT8(save_p);
for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
netUnlocked[j+i] = ((rtemp >> j) & 1);
i += j;
}
encoremode = (boolean)READUINT8(save_p); encoremode = (boolean)READUINT8(save_p);
@ -4834,7 +4844,6 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading)
stoppedclock = !!(globools & (1<<1)); stoppedclock = !!(globools & (1<<1));
} }
token = READUINT32(save_p);
bluescore = READUINT32(save_p); bluescore = READUINT32(save_p);
redscore = READUINT32(save_p); redscore = READUINT32(save_p);

View file

@ -6807,7 +6807,6 @@ static void P_InitLevelSettings(void)
modulothing = 0; modulothing = 0;
// special stage tokens, emeralds, and ring total // special stage tokens, emeralds, and ring total
tokenbits = 0;
runemeraldmanager = false; runemeraldmanager = false;
emeraldspawndelay = 60*TICRATE; emeraldspawndelay = 60*TICRATE;

View file

@ -1495,14 +1495,14 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
{ // An unlockable itself must be unlocked! { // An unlockable itself must be unlocked!
INT32 unlockid = triggerline->args[1]; INT32 unlockid = triggerline->args[1];
if ((modifiedgame && !savemoddata) || (netgame || multiplayer)) if (modifiedgame && !savemoddata)
return false; return false;
else if (unlockid <= 0 || unlockid > MAXUNLOCKABLES) // limited by unlockable count else if (unlockid <= 0 || unlockid > MAXUNLOCKABLES) // limited by unlockable count
{ {
CONS_Debug(DBG_GAMELOGIC, "Unlockable check (sidedef %hu): bad unlockable ID %d\n", triggerline->sidenum[0], unlockid); CONS_Debug(DBG_GAMELOGIC, "Unlockable check (sidedef %hu): bad unlockable ID %d\n", triggerline->sidenum[0], unlockid);
return false; return false;
} }
else if (!(gamedata->unlocked[unlockid])) else if (!(M_CheckNetUnlockByID(unlockid)))
return false; return false;
} }
break; break;

View file

@ -171,6 +171,7 @@ UINT8 *R_GetSkinAvailabilities(boolean demolock)
if (unlockables[i].type != SECRET_SKIN) if (unlockables[i].type != SECRET_SKIN)
continue; continue;
// NEVER EVER EVER M_CheckNetUnlockByID
if (gamedata->unlocked[i] != true && !demolock) if (gamedata->unlocked[i] != true && !demolock)
continue; continue;
@ -250,6 +251,7 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins)
} }
// Use the unlockables table directly // Use the unlockables table directly
// NOTE: M_CheckNetUnlockByID would be correct in many circumstances... but not all. TODO figure out how to discern.
return (boolean)(gamedata->unlocked[i]); return (boolean)(gamedata->unlocked[i]);
} }