mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
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:
parent
d78668e0a6
commit
e7c79ab461
15 changed files with 78 additions and 65 deletions
|
|
@ -1966,13 +1966,13 @@ static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth)
|
|||
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"));
|
||||
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)
|
||||
{
|
||||
|
|
@ -2227,7 +2227,7 @@ void CV_AddValue(consvar_t *var, INT32 increment)
|
|||
|| var->PossibleValue == dummykartspeed_cons_t
|
||||
|| var->PossibleValue == gpdifficulty_cons_t)
|
||||
{
|
||||
if (!M_SecretUnlocked(SECRET_HARDSPEED))
|
||||
if (!M_SecretUnlocked(SECRET_HARDSPEED, false))
|
||||
{
|
||||
max = KARTSPEED_NORMAL+1;
|
||||
if (var->PossibleValue == kartspeed_cons_t)
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@
|
|||
#include "k_boss.h"
|
||||
#include "doomstat.h"
|
||||
#include "s_sound.h" // sfx_syfail
|
||||
#include "m_cond.h" // netUnlocked
|
||||
|
||||
// cl loading screen
|
||||
#include "v_video.h"
|
||||
|
|
@ -3463,6 +3464,11 @@ void SV_ResetServer(void)
|
|||
|
||||
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");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2857,7 +2857,7 @@ static void Command_Map_f(void)
|
|||
{
|
||||
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"));
|
||||
return;
|
||||
|
|
@ -4848,7 +4848,7 @@ void ItemFinder_OnChange(void)
|
|||
if (!cv_itemfinder.value)
|
||||
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"));
|
||||
CV_StealthSetValue(&cv_itemfinder, 0);
|
||||
|
|
|
|||
|
|
@ -573,10 +573,6 @@ extern INT32 luabanks[NUM_LUABANKS];
|
|||
|
||||
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 redscore; ///< Red Team Scores
|
||||
|
||||
|
|
|
|||
12
src/g_game.c
12
src/g_game.c
|
|
@ -204,10 +204,6 @@ UINT8 stagefailed; // Used for GEMS BONUS? Also to see if you beat the stage.
|
|||
|
||||
UINT16 emeralds;
|
||||
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
|
||||
//nightsdata_t ntemprecords;
|
||||
|
|
@ -3309,7 +3305,7 @@ INT16 G_SometimesGetDifferentGametype(UINT8 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.
|
||||
// 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));
|
||||
UINT8 encoremodifier = 0;
|
||||
|
||||
|
|
@ -3870,7 +3866,7 @@ static void G_GetNextMap(void)
|
|||
while (cup)
|
||||
{
|
||||
// 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;
|
||||
gettingresult = 1;
|
||||
|
|
@ -4218,10 +4214,6 @@ static void G_DoContinued(void)
|
|||
// Reset score
|
||||
pl->score = 0;
|
||||
|
||||
// Allow tokens to come back
|
||||
tokenlist = 0;
|
||||
token = 0;
|
||||
|
||||
if (!(netgame || multiplayer || demo.playback || demo.recording || metalrecording || modeattacking) && !usedCheats && cursaveslot > 0)
|
||||
G_SaveGameOver((UINT32)cursaveslot, true);
|
||||
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ boolean K_FollowerUsable(INT32 skinnum)
|
|||
}
|
||||
|
||||
// Use the unlockables table directly
|
||||
// DEFINITELY not M_CheckNetUnlockByID
|
||||
return (boolean)(gamedata->unlocked[i]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1930,7 +1930,7 @@ static void M_DrawCupPreview(INT16 y, cupheader_t *cup)
|
|||
|
||||
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;
|
||||
while (x < BASEVIDWIDTH + pad)
|
||||
|
|
@ -1968,7 +1968,7 @@ static void M_DrawCupTitle(INT16 y, cupheader_t *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);
|
||||
patch_t *icon = W_CachePatchName(cup->icon, PU_CACHE);
|
||||
const char *str = (unlocked ? va("%s Cup", cup->name) : "???");
|
||||
|
|
@ -2036,7 +2036,7 @@ void M_DrawCupSelect(void)
|
|||
|
||||
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);
|
||||
V_DrawScaledPatch(x + 8, y + icony, 0, st);
|
||||
|
|
|
|||
|
|
@ -3362,7 +3362,7 @@ void M_SetupDifficultySelect(INT32 choice)
|
|||
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
|
||||
}
|
||||
|
|
@ -3477,7 +3477,7 @@ static void M_LevelListFromGametype(INT16 gt)
|
|||
|
||||
while (cup)
|
||||
{
|
||||
if (cup->unlockrequired >= MAXUNLOCKABLES || gamedata->unlocked[cup->unlockrequired])
|
||||
if (cup->unlockrequired >= MAXUNLOCKABLES || M_CheckNetUnlockByID(cup->unlockrequired))
|
||||
{
|
||||
highestid = cup->id;
|
||||
if (Playing() && mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == cup)
|
||||
|
|
@ -3611,7 +3611,7 @@ void M_CupSelectHandler(INT32 choice)
|
|||
M_SetMenuDelay(pid);
|
||||
|
||||
if ((!newcup)
|
||||
|| (newcup && newcup->unlockrequired < MAXUNLOCKABLES && !gamedata->unlocked[newcup->unlockrequired])
|
||||
|| (newcup->unlockrequired < MAXUNLOCKABLES && !M_CheckNetUnlockByID(newcup->unlockrequired))
|
||||
|| (newcup->cachedlevels[0] == NEXTMAP_INVALID))
|
||||
{
|
||||
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_server].status = IT_STRING | IT_SUBMENU;
|
||||
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
|
||||
|
|
|
|||
|
|
@ -403,8 +403,6 @@ int LUA_WriteGlobals(lua_State *L, const char *word)
|
|||
skincolor_bluering = (UINT16)luaL_checkinteger(L, 2);
|
||||
else if (fastcmp(word, "emeralds"))
|
||||
emeralds = (UINT16)luaL_checkinteger(L, 2);
|
||||
else if (fastcmp(word, "token"))
|
||||
token = (UINT32)luaL_checkinteger(L, 2);
|
||||
else if (fastcmp(word, "gravity"))
|
||||
gravity = (fixed_t)luaL_checkinteger(L, 2);
|
||||
else if (fastcmp(word, "stoppedclock"))
|
||||
|
|
|
|||
52
src/m_cond.c
52
src/m_cond.c
|
|
@ -27,6 +27,7 @@
|
|||
#include "k_profiles.h"
|
||||
|
||||
gamedata_t *gamedata = NULL;
|
||||
boolean netUnlocked[MAXUNLOCKABLES];
|
||||
|
||||
// Map triggers for linedef executors
|
||||
// 32 triggers, one bit each
|
||||
|
|
@ -442,7 +443,7 @@ void M_ClearSecrets(void)
|
|||
for (i = 0; i < MAXEMBLEMS; ++i)
|
||||
gamedata->collected[i] = false;
|
||||
for (i = 0; i < MAXUNLOCKABLES; ++i)
|
||||
gamedata->unlocked[i] = false;
|
||||
gamedata->unlocked[i] = netUnlocked[i] = false;
|
||||
for (i = 0; i < MAXCONDITIONSETS; ++i)
|
||||
gamedata->achieved[i] = false;
|
||||
|
||||
|
|
@ -728,37 +729,47 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa
|
|||
// 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;
|
||||
|
||||
if (dedicated)
|
||||
return true;
|
||||
|
||||
#if 0
|
||||
(void)type;
|
||||
(void)i;
|
||||
return false; // for quick testing
|
||||
#else
|
||||
|
||||
#ifdef DEVELOP
|
||||
#define CHADYES true
|
||||
#else
|
||||
#define CHADYES false
|
||||
#endif
|
||||
|
||||
for (i = 0; i < MAXUNLOCKABLES; ++i)
|
||||
{
|
||||
if (unlockables[i].type == type && gamedata->unlocked[i] != CHADYES)
|
||||
return !CHADYES;
|
||||
if (unlockables[i].type != type)
|
||||
continue;
|
||||
if ((local && gamedata->unlocked[i])
|
||||
|| M_CheckNetUnlockByID(i))
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
return CHADYES;
|
||||
|
||||
#undef CHADYES
|
||||
return true;
|
||||
|
||||
#endif //if 0
|
||||
}
|
||||
|
||||
UINT8 M_MapLocked(INT32 mapnum)
|
||||
boolean M_MapLocked(INT32 mapnum)
|
||||
{
|
||||
// Don't lock maps in dedicated servers.
|
||||
// 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->unlockrequired < MAXUNLOCKABLES)
|
||||
&& (!gamedata->unlocked[mapheaderinfo[mapnum-1]->cup->unlockrequired]))
|
||||
if (!M_CheckNetUnlockByID(mapheaderinfo[mapnum-1]->cup->unlockrequired))
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((mapheaderinfo[mapnum-1]->unlockrequired < MAXUNLOCKABLES)
|
||||
&& (!gamedata->unlocked[mapheaderinfo[mapnum-1]->unlockrequired]))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
return !M_CheckNetUnlockByID(mapheaderinfo[mapnum-1]->unlockrequired);
|
||||
}
|
||||
|
||||
INT32 M_CountEmblems(void)
|
||||
|
|
|
|||
|
|
@ -158,6 +158,9 @@ typedef struct
|
|||
|
||||
extern gamedata_t *gamedata;
|
||||
|
||||
// Netsynced functional alternative to gamedata->unlocked
|
||||
extern boolean netUnlocked[MAXUNLOCKABLES];
|
||||
|
||||
extern conditionset_t conditionSets[MAXCONDITIONSETS];
|
||||
extern emblem_t emblemlocations[MAXEMBLEMS];
|
||||
extern unlockable_t unlockables[MAXUNLOCKABLES];
|
||||
|
|
@ -191,8 +194,9 @@ UINT8 M_CheckLevelEmblems(void);
|
|||
UINT8 M_CompletionEmblems(void);
|
||||
|
||||
// Checking unlockable status
|
||||
UINT8 M_SecretUnlocked(INT32 type);
|
||||
UINT8 M_MapLocked(INT32 mapnum);
|
||||
boolean M_CheckNetUnlockByID(UINT8 unlockid);
|
||||
boolean M_SecretUnlocked(INT32 type, boolean local);
|
||||
boolean M_MapLocked(INT32 mapnum);
|
||||
INT32 M_CountEmblems(void);
|
||||
|
||||
// Emblem shit
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
#include "p_polyobj.h"
|
||||
#include "lua_script.h"
|
||||
#include "p_slopes.h"
|
||||
#include "m_cond.h" // netUnlocked
|
||||
|
||||
// SRB2Kart
|
||||
#include "k_battle.h"
|
||||
|
|
@ -4600,9 +4601,6 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride)
|
|||
//lastmapsaved = gamemap;
|
||||
lastmaploaded = gamemap;
|
||||
|
||||
tokenlist = 0;
|
||||
token = 0;
|
||||
|
||||
savedata.emeralds = READUINT16(save_p)-357;
|
||||
|
||||
READSTRINGN(save_p, testname, sizeof(testname));
|
||||
|
|
@ -4621,7 +4619,7 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride)
|
|||
|
||||
static void P_NetArchiveMisc(boolean resending)
|
||||
{
|
||||
size_t i;
|
||||
size_t i, j;
|
||||
|
||||
WRITEUINT32(save_p, ARCHIVEBLOCK_MISC);
|
||||
|
||||
|
|
@ -4642,7 +4640,14 @@ static void P_NetArchiveMisc(boolean resending)
|
|||
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);
|
||||
|
||||
|
|
@ -4671,7 +4676,6 @@ static void P_NetArchiveMisc(boolean resending)
|
|||
WRITEUINT8(save_p, globools);
|
||||
}
|
||||
|
||||
WRITEUINT32(save_p, token);
|
||||
WRITEUINT32(save_p, bluescore);
|
||||
WRITEUINT32(save_p, redscore);
|
||||
|
||||
|
|
@ -4767,7 +4771,7 @@ static void P_NetArchiveMisc(boolean resending)
|
|||
|
||||
static inline boolean P_NetUnArchiveMisc(boolean reloading)
|
||||
{
|
||||
size_t i;
|
||||
size_t i, j;
|
||||
size_t numTasks;
|
||||
|
||||
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);
|
||||
|
||||
|
|
@ -4834,7 +4844,6 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading)
|
|||
stoppedclock = !!(globools & (1<<1));
|
||||
}
|
||||
|
||||
token = READUINT32(save_p);
|
||||
bluescore = READUINT32(save_p);
|
||||
redscore = READUINT32(save_p);
|
||||
|
||||
|
|
|
|||
|
|
@ -6807,7 +6807,6 @@ static void P_InitLevelSettings(void)
|
|||
modulothing = 0;
|
||||
|
||||
// special stage tokens, emeralds, and ring total
|
||||
tokenbits = 0;
|
||||
runemeraldmanager = false;
|
||||
emeraldspawndelay = 60*TICRATE;
|
||||
|
||||
|
|
|
|||
|
|
@ -1495,14 +1495,14 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
|
|||
{ // An unlockable itself must be unlocked!
|
||||
INT32 unlockid = triggerline->args[1];
|
||||
|
||||
if ((modifiedgame && !savemoddata) || (netgame || multiplayer))
|
||||
if (modifiedgame && !savemoddata)
|
||||
return false;
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
else if (!(gamedata->unlocked[unlockid]))
|
||||
else if (!(M_CheckNetUnlockByID(unlockid)))
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -171,6 +171,7 @@ UINT8 *R_GetSkinAvailabilities(boolean demolock)
|
|||
if (unlockables[i].type != SECRET_SKIN)
|
||||
continue;
|
||||
|
||||
// NEVER EVER EVER M_CheckNetUnlockByID
|
||||
if (gamedata->unlocked[i] != true && !demolock)
|
||||
continue;
|
||||
|
||||
|
|
@ -250,6 +251,7 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins)
|
|||
}
|
||||
|
||||
// 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]);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue