Substantial re-engineering for the foundations of hidden characters.

R_SkinUnlock defines the circumstances under which a skin is available. For simplicty's sake, I've currently bound it to an S_SKIN variable so I can toggle it easily, but it WILL be replaced with a hook into the savegame system at some point.
* Currently has three tiers of unlock - freebie (forceskin or modeattacking via a loaded replay), Ringslinger Only, and SP/Coop and Ringslinger.
* I don't know anything about netcode so I basically decided to make R_SkinUnlock relevant only under local circumstances, try as hard as possible to stop bad skin info from getting sent to the server, and then admit defeat once the server has the information. If this is a bad choice, please discipline me and show me how to fix it.
* Character Select now checks for whether the character is hidden or not on menu load and does/undoes it based on that info, but will never touch one disabled via SOC. I also used this opportunity to optimise, checking for/filling out charsel pictures instead of doing it later. (It now also includes special casing for a select screen with zero characters!)
* Mode Attack now hides hidden characters in its character select based on SP rules.

Things that still need to be done:
* ForceSkin_OnChange. Is there a graceful way to handle this?
* No obvious skin name conflicts. Add a salt to the names of hidden skins, and then remove it when they're unhidden?
* The gap between Knuckles' skin number and the first custom character anybody adds will be way too obvious. A seperate hidden skin numbering system? Start at 32 and count up from there? There's a few ways...
This commit is contained in:
toasterbabe 2016-07-12 03:15:58 +01:00
parent ee92e043b9
commit b5108afe16
4 changed files with 88 additions and 61 deletions

View file

@ -1092,7 +1092,7 @@ static void SendNameAndColor(void)
SetPlayerSkinByNum(consoleplayer, 0);
CV_StealthSet(&cv_skin, skins[0].name);
}
else if ((foundskin = R_SkinAvailable(cv_skin.string)) != -1)
else if ((foundskin = R_SkinAvailable(cv_skin.string)) != -1 && R_SkinUnlock(foundskin))
{
boolean notsame;
@ -1139,7 +1139,7 @@ static void SendNameAndColor(void)
// check if player has the skin loaded (cv_skin may have
// the name of a skin that was available in the previous game)
cv_skin.value = R_SkinAvailable(cv_skin.string);
if (cv_skin.value < 0)
if ((cv_skin.value < 0) || !R_SkinUnlock(cv_skin.value))
{
CV_StealthSet(&cv_skin, DEFAULTSKIN);
cv_skin.value = 0;
@ -1217,7 +1217,7 @@ static void SendNameAndColor2(void)
SetPlayerSkinByNum(secondplaya, forcedskin);
CV_StealthSet(&cv_skin2, skins[forcedskin].name);
}
else if ((foundskin = R_SkinAvailable(cv_skin2.string)) != -1)
else if ((foundskin = R_SkinAvailable(cv_skin2.string)) != -1 && R_SkinUnlock(foundskin))
{
boolean notsame;
@ -4002,7 +4002,7 @@ static void Command_Archivetest_f(void)
*/
static void ForceSkin_OnChange(void)
{
if ((server || adminplayer == consoleplayer) && (cv_forceskin.value < -1 || cv_forceskin.value >= numskins))
if ((server || adminplayer == consoleplayer) && (cv_forceskin.value < -1 || cv_forceskin.value >= numskins || !R_SkinUnlock(cv_forceskin.value)))
{
if (cv_forceskin.value == -2)
CV_SetValue(&cv_forceskin, numskins-1);

View file

@ -3429,9 +3429,9 @@ static void M_PatchSkinNameTable(void)
for (j = 0; j < MAXSKINS; j++)
{
if (skins[j].name[0] != '\0')
if (skins[j].name[0] != '\0' && R_SkinUnlock(j))
{
skins_cons_t[j].strvalue = skins[j].name;
skins_cons_t[j].strvalue = skins[j].realname;
skins_cons_t[j].value = j+1;
}
else
@ -4771,14 +4771,42 @@ void M_ForceSaveSlotSelected(INT32 sslot)
static void M_SetupChoosePlayer(INT32 choice)
{
INT32 availablecount = 0;
INT32 i, skinnum;
char *name;
(void)choice;
if (mapheaderinfo[startmap-1] && mapheaderinfo[startmap-1]->forcecharacter[0] != '\0')
for (i = 0; i < 32; i++) // Handle charsels, availability, and unlocks.
{
M_ChoosePlayer(0); //oh for crying out loud just get STARTED, it doesn't matter!
if (PlayerMenu[i].status != IT_DISABLED) // If the character's disabled through SOC, there's nothing we can do for it.
{
name = strtok(Z_StrDup(description[i].skinname), "&");
skinnum = R_SkinAvailable(name);
if ((skinnum != -1) && (R_SkinUnlock(skinnum)))
{
if (PlayerMenu[i].status == (IT_DISABLED|IT_CENTER))
PlayerMenu[i].status = IT_CALL;
if (description[i].picname[0] == '\0')
strncpy(description[i].picname, skins[skinnum].charsel, 8);
}
else // Technically, character select icons without corresponding skins get bundled away behind this too. Sucks to be them.
PlayerMenu[i].status = (IT_DISABLED|IT_CENTER);
Z_Free(name);
if (!(PlayerMenu[i].status & IT_DISABLED)) // If this character is available at all...
availablecount++;
}
}
if (!(availablecount)
|| (mapheaderinfo[startmap-1] && mapheaderinfo[startmap-1]->forcecharacter[0] != '\0'))
{
PlayerMenu[0].status = (IT_CALL|IT_CENTER); // This is a hack to make availablecount not softlock the game. Again, I use IT_CENTER as a dummy flag.
M_ChoosePlayer(0); // oh for crying out loud just get STARTED, it doesn't matter!
return;
}
if (Playing() == false)
{
S_StopMusic();
@ -4798,7 +4826,6 @@ static void M_DrawSetupChoosePlayerMenu(void)
const INT32 my = 24;
patch_t *patch;
INT32 i, o, j, prev, next;
char *picname;
// Black BG
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
@ -4836,7 +4863,7 @@ static void M_DrawSetupChoosePlayerMenu(void)
i--;
if (i < 0)
i = (currentMenu->numitems - 1);
} while (i != j && PlayerMenu[i].status == IT_DISABLED); // Skip over all disabled characters.
} while (i != j && PlayerMenu[i].status & IT_DISABLED);
}
// Get prev character...
@ -4846,7 +4873,7 @@ static void M_DrawSetupChoosePlayerMenu(void)
prev--;
if (prev < 0)
prev = (currentMenu->numitems - 1);
} while (prev != i && PlayerMenu[prev].status == IT_DISABLED); // Skip over all disabled characters.
} while (prev != i && PlayerMenu[prev].status & IT_DISABLED);
if (prev != i) // If there's more than one character available...
{
@ -4857,23 +4884,12 @@ static void M_DrawSetupChoosePlayerMenu(void)
next++;
if (next >= currentMenu->numitems)
next = 0;
} while (next != i && PlayerMenu[next].status == IT_DISABLED); // Skip over all disabled characters.
} while (next != i && PlayerMenu[next].status & IT_DISABLED);
// Draw prev character if it's visible and its number isn't greater than the current one or there's more than two
if ((o < 32) && !((prev == next) && prev > i)) // (prev != i) was previously a part of this, but we don't need to check again after above.
{
picname = description[prev].picname;
if (picname[0] == '\0')
{
picname = strtok(Z_StrDup(description[prev].skinname), "&");
j = R_SkinAvailable(picname);
Z_Free(picname);
if (j == -1)
j = 0;
picname = skins[j].charsel;
strncpy(description[prev].picname, picname, 8); // Only iterate once.
}
patch = W_CachePatchName(picname, PU_CACHE);
patch = W_CachePatchName(description[prev].picname, PU_CACHE);
if (SHORT(patch->width) >= 256)
V_DrawCroppedPatch(8<<FRACBITS, (my + 8)<<FRACBITS, FRACUNIT/2, 0, patch, 0, SHORT(patch->height) - 64 + o*2, SHORT(patch->width), SHORT(patch->height));
else
@ -4884,18 +4900,7 @@ static void M_DrawSetupChoosePlayerMenu(void)
// Draw next character if it's visible and its number isn't less than the current one or there's more than two
if ((o < 128) && !((prev == next) && next < i)) // (next != i) was previously a part of this, but it's implicitly true if (prev != i) is true.
{
picname = description[next].picname;
if (picname[0] == '\0')
{
picname = strtok(Z_StrDup(description[next].skinname), "&");
j = R_SkinAvailable(picname);
Z_Free(picname);
if (j == -1)
j = 0;
picname = skins[j].charsel;
strncpy(description[next].picname, picname, 8); // Only iterate once.
}
patch = W_CachePatchName(picname, PU_CACHE);
patch = W_CachePatchName(description[next].picname, PU_CACHE);
if (SHORT(patch->width) >= 256)
V_DrawCroppedPatch(8<<FRACBITS, (my + 168 - o)<<FRACBITS, FRACUNIT/2, 0, patch, 0, 0, SHORT(patch->width), o*2);
else
@ -4904,24 +4909,13 @@ static void M_DrawSetupChoosePlayerMenu(void)
}
// current character
if (PlayerMenu[i].status == IT_DISABLED) // Prevent flickering.
if (PlayerMenu[i].status & IT_DISABLED) // Prevent flickering.
i = (lastdirection) ? prev : next; // This actually causes duplication at slow scroll speeds (<16FU per tic), but thankfully we always go quickly.
}
if (PlayerMenu[i].status != IT_DISABLED)
if (!(PlayerMenu[i].status & IT_DISABLED))
{
picname = description[i].picname;
if (picname[0] == '\0')
{
picname = strtok(Z_StrDup(description[i].skinname), "&");
j = R_SkinAvailable(picname);
Z_Free(picname);
if (j == -1)
j = 0;
picname = skins[j].charsel;
strncpy(description[i].picname, picname, 8); // Only iterate once.
}
patch = W_CachePatchName(picname, PU_CACHE);
patch = W_CachePatchName(description[i].picname, PU_CACHE);
if (o >= 0 && o <= 32)
{
if (SHORT(patch->width) >= 256)
@ -4956,8 +4950,8 @@ static void M_ChoosePlayer(INT32 choice)
INT32 skinnum;
boolean ultmode = (ultimate_selectable && SP_PlayerDef.prevMenu == &SP_LoadDef && saveSlotSelected == NOSAVESLOT);
// skip this if forcecharacter
if (mapheaderinfo[startmap-1] && mapheaderinfo[startmap-1]->forcecharacter[0] == '\0')
// skip this if forcecharacter or no characters available
if (!(PlayerMenu[choice].status & IT_CENTER))
{
// M_SetupChoosePlayer didn't call us directly, that means we've been properly set up.
char_scroll = itemOn*128*FRACUNIT; // finish scrolling the menu
@ -6503,6 +6497,7 @@ static void M_DrawSetupMultiPlayerMenu(void)
static void M_HandleSetupMultiPlayer(INT32 choice)
{
size_t l;
INT32 prev_setupm_fakeskin;
boolean exitmenu = false; // exit to previous menu and send name change
switch (choice)
@ -6521,7 +6516,14 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
if (itemOn == 2) //player skin
{
S_StartSound(NULL,sfx_menu1); // Tails
setupm_fakeskin--;
prev_setupm_fakeskin = setupm_fakeskin;
do
{
setupm_fakeskin--;
if (setupm_fakeskin < 0)
setupm_fakeskin = numskins-1;
}
while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUnlock(setupm_fakeskin)));
}
else if (itemOn == 1) // player color
{
@ -6534,7 +6536,14 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
if (itemOn == 2) //player skin
{
S_StartSound(NULL,sfx_menu1); // Tails
setupm_fakeskin++;
prev_setupm_fakeskin = setupm_fakeskin;
do
{
setupm_fakeskin++;
if (setupm_fakeskin > numskins-1)
setupm_fakeskin = 0;
}
while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUnlock(setupm_fakeskin)));
}
else if (itemOn == 1) // player color
{
@ -6568,12 +6577,6 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
break;
}
// check skin
if (setupm_fakeskin < 0)
setupm_fakeskin = numskins-1;
if (setupm_fakeskin > numskins-1)
setupm_fakeskin = 0;
// check color
if (setupm_fakecolor < 1)
setupm_fakecolor = MAXSKINCOLORS-1;

View file

@ -2305,6 +2305,8 @@ static void Sk_SetDefaultValue(skin_t *skin)
skin->highresscale = FRACUNIT>>1;
skin->availability = 2;
for (i = 0; i < sfx_skinsoundslot0; i++)
if (S_sfx[i].skinsound != -1)
skin->soundsid[S_sfx[i].skinsound] = i;
@ -2329,6 +2331,17 @@ void R_InitSkins(void)
numskins = 0;
}
// returns true if available in circumstances, otherwise nope
// warning don't use with an invalid skinnum
boolean R_SkinUnlock(INT32 skinnum)
{
return ((skins[skinnum].availability == 2) // SP/Coop is strict
|| (modeattacking) // If you have someone else's run you might as well take a look
|| ((netgame)
&& ((cv_forceskin.value == skinnum) // Forceskin is weak
|| (G_RingSlingerGametype() && (skins[skinnum].availability != 0))))); // Ringslinger is disciplined
}
// returns true if the skin name is found (loaded from pwad)
// warning return -1 if not found
INT32 R_SkinAvailable(const char *name)
@ -2371,7 +2384,8 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
player_t *player = &players[playernum];
skin_t *skin = &skins[skinnum];
if (skinnum >= 0 && skinnum < numskins) // Make sure it exists!
if ((skinnum >= 0 && skinnum < numskins) // Make sure it exists!
&& (!P_IsLocalPlayer(player) || R_SkinUnlock(skinnum))) // ...but is it allowed? We must always allow external players to change skin. The server should vet that...
{
player->skin = skinnum;
if (player->mo)
@ -2612,6 +2626,13 @@ void R_AddSkins(UINT16 wadnum)
GETINT(acceleration)
#undef GETINT
else if (!stricmp(stoken, "availability"))
#ifdef DEVELOP
skin->availability = atoi(value);
#else
;
#endif
// custom translation table
else if (!stricmp(stoken, "startcolor"))
skin->starttranscolor = atoi(value);

View file

@ -102,6 +102,8 @@ typedef struct
sfxenum_t soundsid[NUMSKINSOUNDS]; // sound # in S_sfx table
spritedef_t sprites[NUMPLAYERSPRITES];
UINT8 availability; // lock? safe to put here as is not networked
} skin_t;
// -----------
@ -184,6 +186,7 @@ extern skin_t skins[MAXSKINS + 1];
void SetPlayerSkin(INT32 playernum,const char *skinname);
void SetPlayerSkinByNum(INT32 playernum,INT32 skinnum); // Tails 03-16-2002
boolean R_SkinUnlock(INT32 skinnum);
INT32 R_SkinAvailable(const char *name);
void R_AddSkins(UINT16 wadnum);