Removed skin->availability

Locked skins now are a specific unlockable type, instead of being tied to the skin's properties.

This has plagued custom gamedata since 2.2 launch. It's extremely obnoxious having to set aside random numbers as dummy unlockables just to ensure that Amy Fang & Metal are unlocked from the start in a custom map pack.

Other changes made to accommodate this:
 - R_GetSkinAvailabilities is now created from the list of unlockables set to skin type. (1st skin unlockable defined is (1), 2nd skin unlockable defined is (1 << 1), etc...)
- The "Added skin x" print shows up when loading addons but not at all for the base game, because the previous behavior of hiding based on if the skin was locked would now require iterating unlockables, which felt wrong to do during that stage of the loading process
 - I noticed in my test wad that Sonic&Tails would give you Sonic&Sonic out if Tails was locked. I fixed that by making both skins required to show the character select option.

Mods that reserved empty dummy unlockables for Amy Fang and Metal won't have to do anything. Mods that wanted to re-lock them behind different requirements will have to update, but in the future they will not have to be in specific slots. Additionally, now Sonic Tails and Knuckles can also be locked for mods.
This commit is contained in:
Sally Coolatta 2021-04-25 07:18:32 -04:00
parent d71b99a56b
commit 8278e621fb
10 changed files with 175 additions and 52 deletions

View file

@ -1475,7 +1475,8 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
if (server && (p != &players[consoleplayer] && p != &players[secondarydisplayplayer])) if (server && (p != &players[consoleplayer] && p != &players[secondarydisplayplayer]))
{ {
boolean kick = false; boolean kick = false;
INT32 s; UINT32 unlockShift = 0;
UINT32 i;
// team colors // team colors
if (G_GametypeHasTeams()) if (G_GametypeHasTeams())
@ -1491,12 +1492,29 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
kick = true; kick = true;
// availabilities // availabilities
for (s = 0; s < MAXSKINS; s++) for (i = 0; i < MAXUNLOCKABLES; i++)
{ {
if (!skins[s].availability && (p->availabilities & (1 << s))) if (unlockables[i].type != SECRET_SKIN)
{
continue;
}
unlockShift++;
}
// If they set an invalid bit to true, then likely a modified client
if (unlockShift < 32) // 32 is the max the data type allows
{
UINT32 illegalMask = UINT32_MAX;
for (i = 0; i < unlockShift; i++)
{
illegalMask &= ~(1 << i);
}
if ((p->availabilities & illegalMask) != 0)
{ {
kick = true; kick = true;
break;
} }
} }

View file

@ -3219,19 +3219,30 @@ void readunlockable(MYFILE *f, INT32 num)
unlockables[num].type = SECRET_WARP; unlockables[num].type = SECRET_WARP;
else if (fastcmp(word2, "SOUNDTEST")) else if (fastcmp(word2, "SOUNDTEST"))
unlockables[num].type = SECRET_SOUNDTEST; unlockables[num].type = SECRET_SOUNDTEST;
else if (fastcmp(word2, "SKIN"))
unlockables[num].type = SECRET_SKIN;
else else
unlockables[num].type = (INT16)i; unlockables[num].type = (INT16)i;
} }
else if (fastcmp(word, "VAR")) else if (fastcmp(word, "VAR"))
{ {
// Support using the actual map name, INT32 skinnum = R_SkinAvailable(word2);
// i.e., Level AB, Level FZ, etc.
// Convert to map number if (skinnum != -1)
if (word2[0] >= 'A' && word2[0] <= 'Z') {
i = M_MapNumber(word2[0], word2[1]); unlockables[num].variable = (INT16)skinnum;
}
else
{
// Support using the actual map name,
// i.e., Level AB, Level FZ, etc.
unlockables[num].variable = (INT16)i; // Convert to map number
if (word2[0] >= 'A' && word2[0] <= 'Z')
i = M_MapNumber(word2[0], word2[1]);
unlockables[num].variable = (INT16)i;
}
} }
else else
deh_warning("Unlockable %d: unknown word '%s'", num+1, word); deh_warning("Unlockable %d: unknown word '%s'", num+1, word);

View file

@ -53,7 +53,6 @@ enum skin {
skin_contspeed, skin_contspeed,
skin_contangle, skin_contangle,
skin_soundsid, skin_soundsid,
skin_availability,
skin_sprites skin_sprites
}; };
static const char *const skin_opt[] = { static const char *const skin_opt[] = {
@ -91,7 +90,6 @@ static const char *const skin_opt[] = {
"contspeed", "contspeed",
"contangle", "contangle",
"soundsid", "soundsid",
"availability",
"sprites", "sprites",
NULL}; NULL};
@ -209,9 +207,6 @@ static int skin_get(lua_State *L)
case skin_soundsid: case skin_soundsid:
LUA_PushUserdata(L, skin->soundsid, META_SOUNDSID); LUA_PushUserdata(L, skin->soundsid, META_SOUNDSID);
break; break;
case skin_availability:
lua_pushinteger(L, skin->availability);
break;
case skin_sprites: case skin_sprites:
LUA_PushLightUserdata(L, skin->sprites, META_SKINSPRITES); LUA_PushLightUserdata(L, skin->sprites, META_SKINSPRITES);
break; break;

View file

@ -132,6 +132,7 @@ typedef struct
#define SECRET_WARP 2 // Selectable warp #define SECRET_WARP 2 // Selectable warp
#define SECRET_SOUNDTEST 3 // Sound Test #define SECRET_SOUNDTEST 3 // Sound Test
#define SECRET_CREDITS 4 // Enables Credits #define SECRET_CREDITS 4 // Enables Credits
#define SECRET_SKIN 5 // Unlocks a skin
// If you have more secrets than these variables allow in your game, // If you have more secrets than these variables allow in your game,
// you seriously need to get a life. // you seriously need to get a life.

View file

@ -8963,7 +8963,7 @@ static void M_CacheCharacterSelectEntry(INT32 i, INT32 skinnum)
static UINT8 M_SetupChoosePlayerDirect(INT32 choice) static UINT8 M_SetupChoosePlayerDirect(INT32 choice)
{ {
INT32 skinnum; INT32 skinnum, botskin;
UINT8 i; UINT8 i;
UINT8 firstvalid = 255, lastvalid = 255; UINT8 firstvalid = 255, lastvalid = 255;
boolean allowed = false; boolean allowed = false;
@ -8995,6 +8995,13 @@ static UINT8 M_SetupChoosePlayerDirect(INT32 choice)
skinnum = description[i].skinnum[0]; skinnum = description[i].skinnum[0];
if ((skinnum != -1) && (R_SkinUsable(-1, skinnum))) if ((skinnum != -1) && (R_SkinUsable(-1, skinnum)))
{ {
botskin = description[i].skinnum[1];
if ((botskin != -1) && (!R_SkinUsable(-1, botskin)))
{
// Bot skin isn't unlocked
continue;
}
// Handling order. // Handling order.
if (firstvalid == 255) if (firstvalid == 255)
firstvalid = i; firstvalid = i;

View file

@ -25,6 +25,7 @@
#include "i_video.h" #include "i_video.h"
#include "z_zone.h" #include "z_zone.h"
#include "lua_hook.h" #include "lua_hook.h"
#include "m_cond.h" // SECRET_SKIN
#ifdef HW3SOUND #ifdef HW3SOUND
#include "hardware/hw3sound.h" #include "hardware/hw3sound.h"
@ -5101,6 +5102,28 @@ void A_SignSpin(mobj_t *actor)
} }
} }
static boolean SignSkinCheck(player_t *player, INT32 num)
{
INT32 i;
if (player != NULL)
{
// Use player's availabilities
return R_SkinUsable(player - players, num);
}
// Player invalid, only show characters that are unlocked from the start.
for (i = 0; i < MAXUNLOCKABLES; i++)
{
if (unlockables[i].type == SECRET_SKIN && unlockables[i].variable == num)
{
return false;
}
}
return true;
}
// Function: A_SignPlayer // Function: A_SignPlayer
// //
// Description: Changes the state of a level end sign to reflect the player that hit it. // Description: Changes the state of a level end sign to reflect the player that hit it.
@ -5161,23 +5184,21 @@ void A_SignPlayer(mobj_t *actor)
// I turned this function into a fucking mess. I'm so sorry. -Lach // I turned this function into a fucking mess. I'm so sorry. -Lach
if (locvar1 == -2) // random skin if (locvar1 == -2) // random skin
{ {
#define skincheck(num) (player ? !R_SkinUsable(player-players, num) : skins[num].availability > 0)
player_t *player = actor->target ? actor->target->player : NULL; player_t *player = actor->target ? actor->target->player : NULL;
UINT8 skinnum; UINT8 skinnum;
UINT8 skincount = 0; UINT8 skincount = 0;
for (skinnum = 0; skinnum < numskins; skinnum++) for (skinnum = 0; skinnum < numskins; skinnum++)
if (!skincheck(skinnum)) if (SignSkinCheck(player, skinnum))
skincount++; skincount++;
skinnum = P_RandomKey(skincount); skinnum = P_RandomKey(skincount);
for (skincount = 0; skincount < numskins; skincount++) for (skincount = 0; skincount < numskins; skincount++)
{ {
if (skincount > skinnum) if (skincount > skinnum)
break; break;
if (skincheck(skincount)) if (!SignSkinCheck(player, skincount))
skinnum++; skinnum++;
} }
skin = &skins[skinnum]; skin = &skins[skinnum];
#undef skincheck
} }
else // specific skin else // specific skin
skin = &skins[locvar1]; skin = &skins[locvar1];

View file

@ -4527,8 +4527,8 @@ boolean P_AddWadFile(const char *wadfilename)
// //
// look for skins // look for skins
// //
R_AddSkins(wadnum); // faB: wadfile index in wadfiles[] R_AddSkins(wadnum, false); // faB: wadfile index in wadfiles[]
R_PatchSkins(wadnum); // toast: PATCH PATCH R_PatchSkins(wadnum, false); // toast: PATCH PATCH
ST_ReloadSkinFaceGraphics(); ST_ReloadSkinFaceGraphics();
// //

View file

@ -148,8 +148,6 @@ static void Sk_SetDefaultValue(skin_t *skin)
skin->contspeed = 17; skin->contspeed = 17;
skin->contangle = 0; skin->contangle = 0;
skin->availability = 0;
for (i = 0; i < sfx_skinsoundslot0; i++) for (i = 0; i < sfx_skinsoundslot0; i++)
if (S_sfx[i].skinsound != -1) if (S_sfx[i].skinsound != -1)
skin->soundsid[S_sfx[i].skinsound] = i; skin->soundsid[S_sfx[i].skinsound] = i;
@ -176,14 +174,34 @@ void R_InitSkins(void)
UINT32 R_GetSkinAvailabilities(void) UINT32 R_GetSkinAvailabilities(void)
{ {
INT32 s;
UINT32 response = 0; UINT32 response = 0;
UINT32 unlockShift = 0;
INT32 i;
for (s = 0; s < MAXSKINS; s++) for (i = 0; i < MAXUNLOCKABLES; i++)
{ {
if (skins[s].availability && unlockables[skins[s].availability - 1].unlocked) if (unlockables[i].type != SECRET_SKIN)
response |= (1 << s); {
continue;
}
if (unlockShift >= 32)
{
// This crash is impossible to trigger as is,
// but it could happen if MAXUNLOCKABLES is ever made higher than 32,
// and someone makes a mod that has 33+ unlockable characters. :V
I_Error("Too many unlockable characters\n");
return 0;
}
if (unlockables[i].unlocked)
{
response |= (1 << unlockShift);
}
unlockShift++;
} }
return response; return response;
} }
@ -191,14 +209,74 @@ UINT32 R_GetSkinAvailabilities(void)
// warning don't use with an invalid skinnum other than -1 which always returns true // warning don't use with an invalid skinnum other than -1 which always returns true
boolean R_SkinUsable(INT32 playernum, INT32 skinnum) boolean R_SkinUsable(INT32 playernum, INT32 skinnum)
{ {
return ((skinnum == -1) // Simplifies things elsewhere, since there's already plenty of checks for less-than-0... INT32 unlockID = -1;
|| (!skins[skinnum].availability) UINT32 unlockShift = 0;
|| (((netgame || multiplayer) && playernum != -1) ? (players[playernum].availabilities & (1 << skinnum)) : (unlockables[skins[skinnum].availability - 1].unlocked)) INT32 i;
|| (modeattacking) // If you have someone else's run you might as well take a look
|| (Playing() && (R_SkinAvailable(mapheaderinfo[gamemap-1]->forcecharacter) == skinnum)) // Force 1. if (skinnum == -1)
|| (netgame && (cv_forceskin.value == skinnum)) // Force 2. {
|| (metalrecording && skinnum == 5) // Force 3. // Simplifies things elsewhere, since there's already plenty of checks for less-than-0...
); return true;
}
if (modeattacking)
{
// If you have someone else's run you might as well take a look
return true;
}
if (Playing() && (R_SkinAvailable(mapheaderinfo[gamemap-1]->forcecharacter) == skinnum))
{
// Force 1.
return true;
}
if (netgame && (cv_forceskin.value == skinnum))
{
// Force 2.
return true;
}
if (metalrecording && skinnum == 5)
{
// Force 3.
return true;
}
// We will now check if this skin is supposed to be locked or not.
for (i = 0; i < MAXUNLOCKABLES; i++)
{
if (unlockables[i].type != SECRET_SKIN)
{
continue;
}
if (unlockables[i].variable == skinnum)
{
unlockID = i;
break;
}
unlockShift++;
}
if (unlockID == -1)
{
// This skin isn't locked at all, we're good.
return true;
}
if ((netgame || multiplayer) && playernum != -1)
{
// We want to check per-player unlockables.
return (players[playernum].availabilities & (1 << unlockShift));
}
else
{
// We want to check our global unlockables.
return (unlockables[unlockID].unlocked);
}
} }
// returns true if the skin name is found (loaded from pwad) // returns true if the skin name is found (loaded from pwad)
@ -558,7 +636,7 @@ static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value)
// //
// Find skin sprites, sounds & optional status bar face, & add them // Find skin sprites, sounds & optional status bar face, & add them
// //
void R_AddSkins(UINT16 wadnum) void R_AddSkins(UINT16 wadnum, boolean mainfile)
{ {
UINT16 lump, lastlump = 0; UINT16 lump, lastlump = 0;
char *buf; char *buf;
@ -673,12 +751,6 @@ void R_AddSkins(UINT16 wadnum)
if (!realname) if (!realname)
STRBUFCPY(skin->realname, skin->hudname); STRBUFCPY(skin->realname, skin->hudname);
} }
else if (!stricmp(stoken, "availability"))
{
skin->availability = atoi(value);
if (skin->availability >= MAXUNLOCKABLES)
skin->availability = 0;
}
else if (!R_ProcessPatchableFields(skin, stoken, value)) else if (!R_ProcessPatchableFields(skin, stoken, value))
CONS_Debug(DBG_SETUP, "R_AddSkins: Unknown keyword '%s' in S_SKIN lump #%d (WAD %s)\n", stoken, lump, wadfiles[wadnum]->filename); CONS_Debug(DBG_SETUP, "R_AddSkins: Unknown keyword '%s' in S_SKIN lump #%d (WAD %s)\n", stoken, lump, wadfiles[wadnum]->filename);
@ -693,7 +765,7 @@ next_token:
R_FlushTranslationColormapCache(); R_FlushTranslationColormapCache();
if (!skin->availability) // Safe to print... if (mainfile == false)
CONS_Printf(M_GetText("Added skin '%s'\n"), skin->name); CONS_Printf(M_GetText("Added skin '%s'\n"), skin->name);
#ifdef SKINVALUES #ifdef SKINVALUES
skin_cons_t[numskins].value = numskins; skin_cons_t[numskins].value = numskins;
@ -713,7 +785,7 @@ next_token:
// //
// Patch skin sprites // Patch skin sprites
// //
void R_PatchSkins(UINT16 wadnum) void R_PatchSkins(UINT16 wadnum, boolean mainfile)
{ {
UINT16 lump, lastlump = 0; UINT16 lump, lastlump = 0;
char *buf; char *buf;
@ -826,7 +898,7 @@ next_token:
R_FlushTranslationColormapCache(); R_FlushTranslationColormapCache();
if (!skin->availability) // Safe to print... if (mainfile == false)
CONS_Printf(M_GetText("Patched skin '%s'\n"), skin->name); CONS_Printf(M_GetText("Patched skin '%s'\n"), skin->name);
} }
return; return;

View file

@ -80,8 +80,6 @@ typedef struct
// contains super versions too // contains super versions too
spritedef_t sprites[NUMPLAYERSPRITES*2]; spritedef_t sprites[NUMPLAYERSPRITES*2];
spriteinfo_t sprinfo[NUMPLAYERSPRITES*2]; spriteinfo_t sprinfo[NUMPLAYERSPRITES*2];
UINT8 availability; // lock?
} skin_t; } skin_t;
/// Externs /// Externs
@ -96,8 +94,8 @@ void SetPlayerSkinByNum(INT32 playernum,INT32 skinnum); // Tails 03-16-2002
boolean R_SkinUsable(INT32 playernum, INT32 skinnum); boolean R_SkinUsable(INT32 playernum, INT32 skinnum);
UINT32 R_GetSkinAvailabilities(void); UINT32 R_GetSkinAvailabilities(void);
INT32 R_SkinAvailable(const char *name); INT32 R_SkinAvailable(const char *name);
void R_PatchSkins(UINT16 wadnum); void R_AddSkins(UINT16 wadnum, boolean mainfile);
void R_AddSkins(UINT16 wadnum); void R_PatchSkins(UINT16 wadnum, boolean mainfile);
UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player); UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player);

View file

@ -547,8 +547,8 @@ void R_InitSprites(void)
R_InitSkins(); R_InitSkins();
for (i = 0; i < numwadfiles; i++) for (i = 0; i < numwadfiles; i++)
{ {
R_AddSkins((UINT16)i); R_AddSkins((UINT16)i, true);
R_PatchSkins((UINT16)i); R_PatchSkins((UINT16)i, true);
R_LoadSpriteInfoLumps(i, wadfiles[i]->numlumps); R_LoadSpriteInfoLumps(i, wadfiles[i]->numlumps);
} }
ST_ReloadSkinFaceGraphics(); ST_ReloadSkinFaceGraphics();