Unlockable skins (in a way friendly to #define MAXSKINS 255)

Mammoth commit, sorry. I only realised halfway through writing it that SECRET_SKIN was only partially merged.

Ports from 2.2:
- Merge SECRET_SKIN (STJr/SRB2!1474)
    - Default skin is now handled by checking all skins for unlock status, and I_Erroring if none are available
    - Don't show skin names on game startup, to keep our secrets hidden
    - Unlockables now have string variables zallocated.
         - For skin names rather than numbers.
    - Correctly clean up memory when freeing unlockables and emblems.

Bespoke code:
- For temporary testing. `unlocks.pk3`
    - Using this for rapid testing gameboot SOC instead of patch.pk3 because of the intent to turn that into scripts.pk3
- Don't not save gamedata in DEVELOP builds, even if you've used cheats!
- `player->availabilities` is now an array of UINT8
    - (MAXSKINS + 7)/8 entries, or 32 bytes.
    - Included with XD_ADDPLAYER instead of XD_NAMEANDCOLOR.
         - Simplifies a lot of logic with respect to demos, skin changes mid-game, etc.
             - Seriously, there's a lot of random places in the code that just iterate over MAXSPLITSCREENPLAYERS and g_localplayers to update availabilities in real time in a way that's not particularly netsafe...
         - Lines up with the plan for handling unlocks when returning to menus.
         - Was included with XD_ADDBOT, but that actually overruns the netxcmd buffer at first mapload with 7 bots. We might need to consider expanding the size of the netxcmd buffer...
    - In demos, can be interpreted as both relative to the original replay and the current skin list depending on boolean context provided to R_SkinUsable.
    - Used for SF_IRONMAN (and will crash if all other skins are inaccessible).
- Grand Prix bot randomisation uses the host's unlocks.
- Don't show locked characters on the fancy new character select.
-  DXD_JOINDATA for demos
    - Replaces the dual-purpose behaviour of DXD_PLAYSTATE
    - Contains availabilities
    - Handles bot material in a different way
- Forceskin restrictions
    - Won't run in demos, because it's assumed recorded DXD_SKIN will handle all the conversions the original match had
    - Won't run if K_CanChangeRules says no
- Correctly set `mapvisited` on level visit, even in [fake gasp] MULTIPLAYER/NETGAMES!! 🥹
- Added updating unlockables and extra emblems on `mapvisited` update.
    - Currently fails to produce the cecho, but that'll be stripped out entirely in a future commit so I'm not bothered.
This commit is contained in:
toaster 2022-11-27 22:53:29 +00:00
parent 8179b39773
commit 6d0637d39d
24 changed files with 377 additions and 198 deletions

View file

@ -1481,7 +1481,7 @@ boolean CV_CompleteValue(consvar_t *var, const char **valstrp, INT32 *intval)
{ {
v = R_SkinAvailable(valstr); v = R_SkinAvailable(valstr);
if (!R_SkinUsable(-1, v)) if (!R_SkinUsable(-1, v, false))
v = -1; v = -1;
goto finish; goto finish;
@ -1984,7 +1984,7 @@ static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth)
if (var == &cv_forceskin) if (var == &cv_forceskin)
{ {
INT32 skin = R_SkinAvailable(value); INT32 skin = R_SkinAvailable(value);
if ((stricmp(value, "None")) && ((skin == -1) || !R_SkinUsable(-1, skin))) if ((stricmp(value, "None")) && ((skin == -1) || !R_SkinUsable(-1, skin, false)))
{ {
CONS_Printf("Please provide a valid skin name (\"None\" disables).\n"); CONS_Printf("Please provide a valid skin name (\"None\" disables).\n");
return; return;
@ -2109,7 +2109,7 @@ void CV_AddValue(consvar_t *var, INT32 increment)
else if (newvalue >= numskins) else if (newvalue >= numskins)
newvalue = -1; newvalue = -1;
} while ((oldvalue != newvalue) } while ((oldvalue != newvalue)
&& !(R_SkinUsable(-1, newvalue))); && !(R_SkinUsable(-1, newvalue, false)));
} }
else else
newvalue = var->value + increment; newvalue = var->value + increment;

View file

@ -824,6 +824,8 @@ static boolean CL_SendJoin(void)
for (; i < MAXSPLITSCREENPLAYERS; i++) for (; i < MAXSPLITSCREENPLAYERS; i++)
strncpy(netbuffer->u.clientcfg.names[i], va("Player %c", 'A' + i), MAXPLAYERNAME); strncpy(netbuffer->u.clientcfg.names[i], va("Player %c", 'A' + i), MAXPLAYERNAME);
memcpy(&netbuffer->u.clientcfg.availabilities, R_GetSkinAvailabilities(false), MAXAVAILABILITY*sizeof(UINT8));
return HSendPacket(servernode, false, 0, sizeof (clientconfig_pak)); return HSendPacket(servernode, false, 0, sizeof (clientconfig_pak));
} }
@ -3565,8 +3567,8 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
return; return;
} }
node = (UINT8)READUINT8(*p); node = READUINT8(*p);
newplayernum = (UINT8)READUINT8(*p); newplayernum = READUINT8(*p);
CONS_Debug(DBG_NETPLAY, "addplayer: %d %d\n", node, newplayernum); CONS_Debug(DBG_NETPLAY, "addplayer: %d %d\n", node, newplayernum);
@ -3587,8 +3589,13 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
READSTRINGN(*p, player_names[newplayernum], MAXPLAYERNAME); READSTRINGN(*p, player_names[newplayernum], MAXPLAYERNAME);
console = (UINT8)READUINT8(*p); console = READUINT8(*p);
splitscreenplayer = (UINT8)READUINT8(*p); splitscreenplayer = READUINT8(*p);
for (i = 0; i < MAXAVAILABILITY; i++)
{
newplayer->availabilities[i] = READUINT8(*p);
}
// the server is creating my player // the server is creating my player
if (node == mynode) if (node == mynode)
@ -3690,8 +3697,9 @@ static void Got_RemovePlayer(UINT8 **p, INT32 playernum)
static void Got_AddBot(UINT8 **p, INT32 playernum) static void Got_AddBot(UINT8 **p, INT32 playernum)
{ {
INT16 newplayernum; INT16 newplayernum;
UINT8 skinnum = 0; UINT8 i, skinnum = 0;
UINT8 difficulty = DIFFICULTBOT; UINT8 difficulty = DIFFICULTBOT;
UINT8 availabilitiesbuffer[MAXAVAILABILITY];
if (playernum != serverplayer && !IsPlayerAdmin(playernum)) if (playernum != serverplayer && !IsPlayerAdmin(playernum))
{ {
@ -3704,9 +3712,9 @@ static void Got_AddBot(UINT8 **p, INT32 playernum)
return; return;
} }
newplayernum = (UINT8)READUINT8(*p); newplayernum = READUINT8(*p);
skinnum = (UINT8)READUINT8(*p); skinnum = READUINT8(*p);
difficulty = (UINT8)READUINT8(*p); difficulty = READUINT8(*p);
CONS_Debug(DBG_NETPLAY, "addbot: %d\n", newplayernum); CONS_Debug(DBG_NETPLAY, "addbot: %d\n", newplayernum);
@ -3720,6 +3728,15 @@ static void Got_AddBot(UINT8 **p, INT32 playernum)
playernode[newplayernum] = servernode; playernode[newplayernum] = servernode;
// todo find a way to have all auto unlocked for dedicated
if (playeringame[0])
{
for (i = 0; i < MAXAVAILABILITY; i++)
{
players[newplayernum].availabilities[i] = players[0].availabilities[i];
}
}
players[newplayernum].splitscreenindex = 0; players[newplayernum].splitscreenindex = 0;
players[newplayernum].bot = true; players[newplayernum].bot = true;
players[newplayernum].botvars.difficulty = difficulty; players[newplayernum].botvars.difficulty = difficulty;
@ -3737,10 +3754,10 @@ static void Got_AddBot(UINT8 **p, INT32 playernum)
LUA_HookInt(newplayernum, HOOK(PlayerJoin)); LUA_HookInt(newplayernum, HOOK(PlayerJoin));
} }
static boolean SV_AddWaitingPlayers(SINT8 node, const char *name, const char *name2, const char *name3, const char *name4) static boolean SV_AddWaitingPlayers(SINT8 node, UINT8 *availabilities, const char *name, const char *name2, const char *name3, const char *name4)
{ {
INT32 n, newplayernum; INT32 n, newplayernum, i;
UINT8 buf[4 + MAXPLAYERNAME]; UINT8 buf[4 + MAXPLAYERNAME + MAXAVAILABILITY];
UINT8 *buf_p = buf; UINT8 *buf_p = buf;
boolean newplayer = false; boolean newplayer = false;
@ -3823,6 +3840,11 @@ static boolean SV_AddWaitingPlayers(SINT8 node, const char *name, const char *na
WRITEUINT8(buf_p, nodetoplayer[node]); // consoleplayer WRITEUINT8(buf_p, nodetoplayer[node]); // consoleplayer
WRITEUINT8(buf_p, playerpernode[node]); // splitscreen num WRITEUINT8(buf_p, playerpernode[node]); // splitscreen num
for (i = 0; i < MAXAVAILABILITY; i++)
{
WRITEUINT8(buf_p, availabilities[i]);
}
playerpernode[node]++; playerpernode[node]++;
SendNetXCmd(XD_ADDPLAYER, buf, buf_p - buf); SendNetXCmd(XD_ADDPLAYER, buf, buf_p - buf);
@ -3886,9 +3908,10 @@ boolean SV_SpawnServer(void)
// strictly speaking, i'm not convinced the following is necessary // strictly speaking, i'm not convinced the following is necessary
// but I'm not confident enough to remove it entirely in case it breaks something // but I'm not confident enough to remove it entirely in case it breaks something
{ {
UINT8 *availabilitiesbuffer = R_GetSkinAvailabilities(false);
SINT8 node = 0; SINT8 node = 0;
for (; node < MAXNETNODES; node++) for (; node < MAXNETNODES; node++)
result |= SV_AddWaitingPlayers(node, cv_playername[0].zstring, cv_playername[1].zstring, cv_playername[2].zstring, cv_playername[3].zstring); result |= SV_AddWaitingPlayers(node, availabilitiesbuffer, cv_playername[0].zstring, cv_playername[1].zstring, cv_playername[2].zstring, cv_playername[3].zstring);
} }
return result; return result;
#endif #endif
@ -3971,6 +3994,7 @@ static void HandleConnect(SINT8 node)
{ {
char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME + 1]; char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME + 1];
INT32 i; INT32 i;
UINT8 availabilitiesbuffer[MAXAVAILABILITY];
// Sal: Dedicated mode is INCREDIBLY hacked together. // Sal: Dedicated mode is INCREDIBLY hacked together.
// If a server filled out, then it'd overwrite the host and turn everyone into weird husks..... // If a server filled out, then it'd overwrite the host and turn everyone into weird husks.....
@ -4077,6 +4101,8 @@ static void HandleConnect(SINT8 node)
} }
} }
memcpy(availabilitiesbuffer, netbuffer->u.clientcfg.availabilities, sizeof(availabilitiesbuffer));
// client authorised to join // client authorised to join
nodewaiting[node] = (UINT8)(netbuffer->u.clientcfg.localplayers - playerpernode[node]); nodewaiting[node] = (UINT8)(netbuffer->u.clientcfg.localplayers - playerpernode[node]);
if (!nodeingame[node]) if (!nodeingame[node])
@ -4111,7 +4137,8 @@ static void HandleConnect(SINT8 node)
SV_SendSaveGame(node, false); // send a complete game state SV_SendSaveGame(node, false); // send a complete game state
DEBFILE("send savegame\n"); DEBFILE("send savegame\n");
} }
SV_AddWaitingPlayers(node, names[0], names[1], names[2], names[3]);
SV_AddWaitingPlayers(node, availabilitiesbuffer, names[0], names[1], names[2], names[3]);
joindelay += cv_joindelay.value * TICRATE; joindelay += cv_joindelay.value * TICRATE;
player_joining = true; player_joining = true;
} }

View file

@ -246,6 +246,7 @@ typedef struct
UINT8 localplayers; // number of splitscreen players UINT8 localplayers; // number of splitscreen players
UINT8 mode; UINT8 mode;
char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME]; char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME];
UINT8 availabilities[MAXAVAILABILITY];
} ATTRPACK clientconfig_pak; } ATTRPACK clientconfig_pak;
#define SV_SPEEDMASK 0x03 // used to send kartspeed #define SV_SPEEDMASK 0x03 // used to send kartspeed

View file

@ -950,11 +950,6 @@ void D_StartTitle(void)
for (i = 0; i < MAXPLAYERS; i++) for (i = 0; i < MAXPLAYERS; i++)
CL_ClearPlayer(i); CL_ClearPlayer(i);
for (i = 0; i < MAXSPLITSCREENPLAYERS; i++)
{
players[g_localplayers[i]].availabilities = R_GetSkinAvailabilities();
}
splitscreen = 0; splitscreen = 0;
SplitScreen_OnChange(); SplitScreen_OnChange();
@ -1148,6 +1143,10 @@ static void IdentifyVersion(void)
#ifdef USE_PATCH_FILE #ifdef USE_PATCH_FILE
D_AddFile(startupiwads, va(pandf,srb2waddir,PATCHNAME)); D_AddFile(startupiwads, va(pandf,srb2waddir,PATCHNAME));
#endif #endif
#define UNLOCKTESTING
#if defined(DEVELOP) && defined(UNLOCKTESTING)
D_AddFile(startupiwads, va(pandf,srb2waddir,"unlocks.pk3"));
#endif
//// ////
#undef TEXTURESNAME #undef TEXTURESNAME
#undef MAPSNAME #undef MAPSNAME
@ -1449,6 +1448,9 @@ void D_SRB2Main(void)
#ifdef USE_PATCH_FILE #ifdef USE_PATCH_FILE
mainwads++; // patch.pk3 mainwads++; // patch.pk3
#endif #endif
#ifdef UNLOCKTESTING
mainwads++; // unlocks.pk3
#endif
#endif //ifndef DEVELOP #endif //ifndef DEVELOP

View file

@ -1408,6 +1408,10 @@ boolean CanChangeSkinWhilePlaying(INT32 playernum)
static void ForceAllSkins(INT32 forcedskin) static void ForceAllSkins(INT32 forcedskin)
{ {
INT32 i; INT32 i;
if (demo.playback)
return; // DXD_SKIN should handle all changes for us
for (i = 0; i < MAXPLAYERS; ++i) for (i = 0; i < MAXPLAYERS; ++i)
{ {
if (!playeringame[i]) if (!playeringame[i])
@ -1476,6 +1480,8 @@ static void SendNameAndColor(UINT8 n)
char buf[MAXPLAYERNAME+12]; char buf[MAXPLAYERNAME+12];
char *p; char *p;
UINT16 i = 0;
if (splitscreen < n) if (splitscreen < n)
return; // can happen if skin4/color4/name4 changed return; // can happen if skin4/color4/name4 changed
@ -1493,9 +1499,10 @@ static void SendNameAndColor(UINT8 n)
CV_StealthSetValue(&cv_playercolor[n], skins[player->skin].prefcolor); CV_StealthSetValue(&cv_playercolor[n], skins[player->skin].prefcolor);
else if (skincolors[atoi(cv_playercolor[n].defaultvalue)].accessible) else if (skincolors[atoi(cv_playercolor[n].defaultvalue)].accessible)
CV_StealthSet(&cv_playercolor[n], cv_playercolor[n].defaultvalue); CV_StealthSet(&cv_playercolor[n], cv_playercolor[n].defaultvalue);
else { else
UINT16 i = 0; {
while (i<numskincolors && !skincolors[i].accessible) i++; while (i < numskincolors && !skincolors[i].accessible)
i++;
CV_StealthSetValue(&cv_playercolor[n], (i != numskincolors) ? i : SKINCOLOR_BLUE); CV_StealthSetValue(&cv_playercolor[n], (i != numskincolors) ? i : SKINCOLOR_BLUE);
} }
} }
@ -1512,8 +1519,6 @@ static void SendNameAndColor(UINT8 n)
&& cv_followercolor[n].value == player->followercolor) && cv_followercolor[n].value == player->followercolor)
return; return;
player->availabilities = R_GetSkinAvailabilities();
// We'll handle it later if we're not playing. // We'll handle it later if we're not playing.
if (!Playing()) if (!Playing())
return; return;
@ -1530,7 +1535,7 @@ static void SendNameAndColor(UINT8 n)
K_KartResetPlayerColor(player); K_KartResetPlayerColor(player);
if ((foundskin = R_SkinAvailable(cv_skin[n].string)) != -1 && R_SkinUsable(playernum, foundskin)) if ((foundskin = R_SkinAvailable(cv_skin[n].string)) != -1 && R_SkinUsable(playernum, foundskin, false))
{ {
SetPlayerSkin(playernum, cv_skin[n].string); SetPlayerSkin(playernum, cv_skin[n].string);
CV_StealthSet(&cv_skin[n], skins[foundskin].name); CV_StealthSet(&cv_skin[n], skins[foundskin].name);
@ -1575,7 +1580,7 @@ static void SendNameAndColor(UINT8 n)
// check if player has the skin loaded (cv_skin may have // check if player has the skin loaded (cv_skin may have
// the name of a skin that was available in the previous game) // the name of a skin that was available in the previous game)
cv_skin[n].value = R_SkinAvailable(cv_skin[n].string); cv_skin[n].value = R_SkinAvailable(cv_skin[n].string);
if ((cv_skin[n].value < 0) || !R_SkinUsable(playernum, cv_skin[n].value)) if ((cv_skin[n].value < 0) || !R_SkinUsable(playernum, cv_skin[n].value, false))
{ {
CV_StealthSet(&cv_skin[n], DEFAULTSKIN); CV_StealthSet(&cv_skin[n], DEFAULTSKIN);
cv_skin[n].value = 0; cv_skin[n].value = 0;
@ -1590,7 +1595,6 @@ static void SendNameAndColor(UINT8 n)
// Finally write out the complete packet and send it off. // Finally write out the complete packet and send it off.
WRITESTRINGN(p, cv_playername[n].zstring, MAXPLAYERNAME); WRITESTRINGN(p, cv_playername[n].zstring, MAXPLAYERNAME);
WRITEUINT32(p, (UINT32)player->availabilities);
WRITEUINT16(p, (UINT16)cv_playercolor[n].value); WRITEUINT16(p, (UINT16)cv_playercolor[n].value);
WRITEUINT8(p, (UINT8)cv_skin[n].value); WRITEUINT8(p, (UINT8)cv_skin[n].value);
WRITEINT16(p, (INT16)cv_follower[n].value); WRITEINT16(p, (INT16)cv_follower[n].value);
@ -1632,11 +1636,9 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
} }
READSTRINGN(*cp, name, MAXPLAYERNAME); READSTRINGN(*cp, name, MAXPLAYERNAME);
p->availabilities = READUINT32(*cp);
color = READUINT16(*cp); color = READUINT16(*cp);
skin = READUINT8(*cp); skin = READUINT8(*cp);
follower = READINT16(*cp); follower = READINT16(*cp);
//CONS_Printf("Recieved follower id %d\n", follower);
followercolor = READUINT16(*cp); followercolor = READUINT16(*cp);
// set name // set name
@ -1657,56 +1659,20 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
{ {
boolean kick = false; boolean kick = false;
// team colors
if (G_GametypeHasTeams())
{
if (p->ctfteam == 1 && p->skincolor != skincolor_redteam)
kick = true;
else if (p->ctfteam == 2 && p->skincolor != skincolor_blueteam)
kick = true;
}
// don't allow inaccessible colors // don't allow inaccessible colors
if (skincolors[p->skincolor].accessible == false) if (skincolors[p->skincolor].accessible == false)
kick = true; kick = true;
// availabilities
for (i = 0; i < MAXSKINS; i++)
{
UINT32 playerhasunlocked = (p->availabilities & (1 << i));
boolean islocked = false;
UINT8 j;
for (j = 0; j < MAXUNLOCKABLES; j++)
{
if (unlockables[j].type != SECRET_SKIN)
continue;
if (unlockables[j].variable == i)
{
islocked = true;
break;
}
}
if (islocked == false && playerhasunlocked == true)
{
// hacked client that enabled every bit
kick = true;
break;
}
}
if (kick) if (kick)
{ {
CONS_Alert(CONS_WARNING, M_GetText("Illegal color change received from %s (team: %d), color: %d)\n"), player_names[playernum], p->ctfteam, p->skincolor); CONS_Alert(CONS_WARNING, M_GetText("Illegal color change received from %s, color: %d)\n"), player_names[playernum], p->skincolor);
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL);
return; return;
} }
} }
// set skin // set skin
if (cv_forceskin.value >= 0 && (netgame || multiplayer)) // Server wants everyone to use the same player if (cv_forceskin.value >= 0 && K_CanChangeRules(true)) // Server wants everyone to use the same player
{ {
const INT32 forcedskin = cv_forceskin.value; const INT32 forcedskin = cv_forceskin.value;
SetPlayerSkinByNum(playernum, forcedskin); SetPlayerSkinByNum(playernum, forcedskin);
@ -5729,11 +5695,6 @@ void Command_ExitGame_f(void)
for (i = 0; i < MAXPLAYERS; i++) for (i = 0; i < MAXPLAYERS; i++)
CL_ClearPlayer(i); CL_ClearPlayer(i);
for (i = 0; i < MAXSPLITSCREENPLAYERS; i++)
{
players[g_localplayers[i]].availabilities = R_GetSkinAvailabilities();
}
splitscreen = 0; splitscreen = 0;
SplitScreen_OnChange(); SplitScreen_OnChange();

View file

@ -385,7 +385,7 @@ typedef struct player_s
UINT16 skincolor; UINT16 skincolor;
INT32 skin; INT32 skin;
UINT32 availabilities; UINT8 availabilities[MAXAVAILABILITY];
UINT8 fakeskin; // ironman UINT8 fakeskin; // ironman
UINT8 lastfakeskin; UINT8 lastfakeskin;

View file

@ -131,6 +131,36 @@ static float searchfvalue(const char *s)
#endif #endif
// These are for clearing all of various things // These are for clearing all of various things
void clear_emblems(void)
{
INT32 i;
for (i = 0; i < MAXEMBLEMS; ++i)
{
Z_Free(emblemlocations[i].level);
emblemlocations[i].level = NULL;
Z_Free(emblemlocations[i].stringVar);
emblemlocations[i].stringVar = NULL;
}
memset(&emblemlocations, 0, sizeof(emblemlocations));
numemblems = 0;
}
void clear_unlockables(void)
{
INT32 i;
for (i = 0; i < MAXUNLOCKABLES; ++i)
{
Z_Free(unlockables[i].stringVar);
unlockables[i].stringVar = NULL;
}
memset(&unlockables, 0, sizeof(unlockables));
}
void clear_conditionsets(void) void clear_conditionsets(void)
{ {
UINT8 i; UINT8 i;
@ -2140,7 +2170,12 @@ void reademblemdata(MYFILE *f, INT32 num)
else if (fastcmp(word, "COLOR")) else if (fastcmp(word, "COLOR"))
emblemlocations[num-1].color = get_number(word2); emblemlocations[num-1].color = get_number(word2);
else if (fastcmp(word, "VAR")) else if (fastcmp(word, "VAR"))
{
Z_Free(emblemlocations[num-1].stringVar);
emblemlocations[num-1].stringVar = Z_StrDup(word2);
emblemlocations[num-1].var = get_number(word2); emblemlocations[num-1].var = get_number(word2);
}
else else
deh_warning("Emblem %d: unknown word '%s'", num, word); deh_warning("Emblem %d: unknown word '%s'", num, word);
} }
@ -2344,7 +2379,8 @@ void readunlockable(MYFILE *f, INT32 num)
} }
else if (fastcmp(word, "VAR")) else if (fastcmp(word, "VAR"))
{ {
// TODO: different field for level name string Z_Free(unlockables[num].stringVar);
unlockables[num].stringVar = Z_StrDup(word2);
unlockables[num].variable = (INT16)i; unlockables[num].variable = (INT16)i;
} }
else else

View file

@ -77,6 +77,8 @@ void readlight(MYFILE *f, INT32 num);
void readskincolor(MYFILE *f, INT32 num); void readskincolor(MYFILE *f, INT32 num);
void readthing(MYFILE *f, INT32 num); void readthing(MYFILE *f, INT32 num);
void readfreeslots(MYFILE *f); void readfreeslots(MYFILE *f);
void clear_emblems(void);
void clear_unlockables(void);
void clear_levels(void); void clear_levels(void);
void clear_conditionsets(void); void clear_conditionsets(void);

View file

@ -584,12 +584,13 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
} }
if (clearall || fastcmp(word2, "UNLOCKABLES")) if (clearall || fastcmp(word2, "UNLOCKABLES"))
memset(&unlockables, 0, sizeof(unlockables)); {
clear_unlockables();
}
if (clearall || fastcmp(word2, "EMBLEMS")) if (clearall || fastcmp(word2, "EMBLEMS"))
{ {
memset(&emblemlocations, 0, sizeof(emblemlocations)); clear_emblems();
numemblems = 0;
} }
if (clearall || fastcmp(word2, "EXTRAEMBLEMS")) if (clearall || fastcmp(word2, "EXTRAEMBLEMS"))

View file

@ -201,7 +201,8 @@ extern char logfilename[1024];
#define MAXGAMEPADS (MAXSPLITSCREENPLAYERS * 2) // Number of gamepads we'll be allowing #define MAXGAMEPADS (MAXSPLITSCREENPLAYERS * 2) // Number of gamepads we'll be allowing
#define MAXSKINS UINT8_MAX #define MAXSKINS UINT8_MAX
#define SKINNAMESIZE 16 // Moved from r_skins.h as including that particular header causes issues later down the line. #define SKINNAMESIZE 16
#define MAXAVAILABILITY ((MAXSKINS + 7)/8)
#define COLORRAMPSIZE 16 #define COLORRAMPSIZE 16
#define MAXCOLORNAME 32 #define MAXCOLORNAME 32

View file

@ -276,10 +276,8 @@ void G_ReadDemoExtraData(void)
{ {
extradata = READUINT8(demo_p); extradata = READUINT8(demo_p);
if (extradata & DXD_PLAYSTATE) if (extradata & DXD_JOINDATA)
{ {
i = READUINT8(demo_p);
if (!playeringame[p]) if (!playeringame[p])
{ {
CL_ClearPlayer(p); CL_ClearPlayer(p);
@ -288,14 +286,22 @@ void G_ReadDemoExtraData(void)
players[p].spectator = true; players[p].spectator = true;
} }
if ((players[p].bot = !!(i & DXD_PST_ISBOT))) for (i = 0; i < MAXAVAILABILITY; i++)
{
players[p].availabilities[i] = READUINT8(demo_p);
}
players[p].bot = !!(READUINT8(demo_p));
if (players[p].bot)
{ {
players[p].botvars.difficulty = READUINT8(demo_p); players[p].botvars.difficulty = READUINT8(demo_p);
players[p].botvars.diffincrease = READUINT8(demo_p); // needed to avoid having to duplicate logic players[p].botvars.diffincrease = READUINT8(demo_p); // needed to avoid having to duplicate logic
players[p].botvars.rival = (boolean)READUINT8(demo_p); players[p].botvars.rival = (boolean)READUINT8(demo_p);
i &= ~DXD_PST_ISBOT;
} }
}
if (extradata & DXD_PLAYSTATE)
{
i = READUINT8(demo_p);
switch (i) { switch (i) {
case DXD_PST_PLAYING: case DXD_PST_PLAYING:
@ -334,14 +340,6 @@ void G_ReadDemoExtraData(void)
K_CheckBumpers(); K_CheckBumpers();
P_CheckRacers(); P_CheckRacers();
} }
if (extradata & DXD_RESPAWN)
{
if (players[p].mo)
{
// Is this how this should work..?
K_DoIngameRespawn(&players[p]);
}
}
if (extradata & DXD_SKIN) if (extradata & DXD_SKIN)
{ {
UINT8 skinid; UINT8 skinid;
@ -397,6 +395,14 @@ void G_ReadDemoExtraData(void)
} }
} }
} }
if (extradata & DXD_RESPAWN)
{
if (players[p].mo)
{
// Is this how this should work..?
K_DoIngameRespawn(&players[p]);
}
}
if (extradata & DXD_WEAPONPREF) if (extradata & DXD_WEAPONPREF)
{ {
WeaponPref_Parse(&demo_p, p); WeaponPref_Parse(&demo_p, p);
@ -454,6 +460,21 @@ void G_WriteDemoExtraData(void)
WRITEUINT8(demo_p, i); WRITEUINT8(demo_p, i);
WRITEUINT8(demo_p, demo_extradata[i]); WRITEUINT8(demo_p, demo_extradata[i]);
if (demo_extradata[i] & DXD_JOINDATA)
{
for (j = 0; j < MAXAVAILABILITY; j++)
{
WRITEUINT8(demo_p, players[i].availabilities[i]);
}
WRITEUINT8(demo_p, (UINT8)players[i].bot);
if (players[i].bot)
{
WRITEUINT8(demo_p, players[i].botvars.difficulty);
WRITEUINT8(demo_p, players[i].botvars.diffincrease); // needed to avoid having to duplicate logic
WRITEUINT8(demo_p, (UINT8)players[i].botvars.rival);
}
}
if (demo_extradata[i] & DXD_PLAYSTATE) if (demo_extradata[i] & DXD_PLAYSTATE)
{ {
UINT8 pst = DXD_PST_PLAYING; UINT8 pst = DXD_PST_PLAYING;
@ -472,19 +493,7 @@ void G_WriteDemoExtraData(void)
pst = DXD_PST_SPECTATING; pst = DXD_PST_SPECTATING;
} }
if (players[i].bot)
{
pst |= DXD_PST_ISBOT;
}
WRITEUINT8(demo_p, pst); WRITEUINT8(demo_p, pst);
if (pst & DXD_PST_ISBOT)
{
WRITEUINT8(demo_p, players[i].botvars.difficulty);
WRITEUINT8(demo_p, players[i].botvars.diffincrease); // needed to avoid having to duplicate logic
WRITEUINT8(demo_p, (UINT8)players[i].botvars.rival);
}
} }
//if (demo_extradata[i] & DXD_RESPAWN) has no extra data //if (demo_extradata[i] & DXD_RESPAWN) has no extra data
if (demo_extradata[i] & DXD_SKIN) if (demo_extradata[i] & DXD_SKIN)
@ -1218,8 +1227,14 @@ void G_GhostTicker(void)
if (ziptic == 0) // Only support player 0 info for now if (ziptic == 0) // Only support player 0 info for now
{ {
ziptic = READUINT8(g->p); ziptic = READUINT8(g->p);
if (ziptic & DXD_JOINDATA)
{
g->p += MAXAVAILABILITY;
if (READUINT8(g->p) != 0)
I_Error("Ghost is not a record attack ghost (bot JOINDATA)");
}
if (ziptic & DXD_PLAYSTATE && READUINT8(g->p) != DXD_PST_PLAYING) if (ziptic & DXD_PLAYSTATE && READUINT8(g->p) != DXD_PST_PLAYING)
I_Error("Ghost is not a record attack ghost PLAYSTATE"); //@TODO lmao don't blow up like this I_Error("Ghost is not a record attack ghost (has PLAYSTATE)");
if (ziptic & DXD_SKIN) if (ziptic & DXD_SKIN)
g->p++; // We _could_ read this info, but it shouldn't change anything in record attack... g->p++; // We _could_ read this info, but it shouldn't change anything in record attack...
if (ziptic & DXD_COLOR) if (ziptic & DXD_COLOR)
@ -2248,6 +2263,7 @@ static void G_SaveDemoSkins(UINT8 **pp)
{ {
char skin[16]; char skin[16];
UINT8 i; UINT8 i;
UINT8 *availabilitiesbuffer = R_GetSkinAvailabilities(true);
WRITEUINT8((*pp), numskins); WRITEUINT8((*pp), numskins);
for (i = 0; i < numskins; i++) for (i = 0; i < numskins; i++)
@ -2262,12 +2278,17 @@ static void G_SaveDemoSkins(UINT8 **pp)
WRITEUINT8((*pp), skins[i].kartweight); WRITEUINT8((*pp), skins[i].kartweight);
WRITEUINT32((*pp), skins[i].flags); WRITEUINT32((*pp), skins[i].flags);
} }
for (i = 0; i < MAXAVAILABILITY; i++)
{
WRITEUINT8((*pp), availabilitiesbuffer[i]);
}
} }
static democharlist_t *G_LoadDemoSkins(UINT8 **pp, UINT8 *worknumskins, boolean getclosest) static democharlist_t *G_LoadDemoSkins(UINT8 **pp, UINT8 *worknumskins, boolean getclosest)
{ {
char skin[17]; char skin[17];
UINT8 i; UINT8 i, byte, shif;
democharlist_t *skinlist = NULL; democharlist_t *skinlist = NULL;
(*worknumskins) = READUINT8((*pp)); (*worknumskins) = READUINT8((*pp));
@ -2310,6 +2331,24 @@ static democharlist_t *G_LoadDemoSkins(UINT8 **pp, UINT8 *worknumskins, boolean
} }
} }
for (byte = 0; byte < MAXAVAILABILITY; byte++)
{
UINT8 availabilitiesbuffer = READUINT8((*pp));
for (shif = 0; shif < 8; shif++)
{
i = (byte*8) + shif;
if (i >= (*worknumskins))
break;
if (availabilitiesbuffer & (1 << shif))
{
skinlist[i].unlockrequired = true;
}
}
}
return skinlist; return skinlist;
} }
@ -2326,6 +2365,8 @@ static void G_SkipDemoSkins(UINT8 **pp)
(*pp)++; // kartweight (*pp)++; // kartweight
(*pp) += 4; // flags (*pp) += 4; // flags
} }
(*pp) += MAXAVAILABILITY;
} }
void G_BeginRecording(void) void G_BeginRecording(void)
@ -2443,6 +2484,11 @@ void G_BeginRecording(void)
M_Memcpy(demo_p,name,16); M_Memcpy(demo_p,name,16);
demo_p += 16; demo_p += 16;
for (j = 0; j < MAXAVAILABILITY; j++)
{
WRITEUINT8(demo_p, player->availabilities[j]);
}
// Skin (now index into demo.skinlist) // Skin (now index into demo.skinlist)
WRITEUINT8(demo_p, player->skin); WRITEUINT8(demo_p, player->skin);
WRITEUINT8(demo_p, player->lastfakeskin); WRITEUINT8(demo_p, player->lastfakeskin);
@ -2928,6 +2974,7 @@ void G_DoPlayDemo(char *defdemoname)
UINT8 i, p, numslots = 0; UINT8 i, p, numslots = 0;
lumpnum_t l; lumpnum_t l;
char color[MAXCOLORNAME+1],follower[17],mapname[MAXMAPLUMPNAME],*n,*pdemoname; char color[MAXCOLORNAME+1],follower[17],mapname[MAXMAPLUMPNAME],*n,*pdemoname;
UINT8 availabilities[MAXPLAYERS][MAXAVAILABILITY];
UINT8 version,subversion; UINT8 version,subversion;
UINT32 randseed[PRNUMCLASS]; UINT32 randseed[PRNUMCLASS];
char msg[1024]; char msg[1024];
@ -3293,6 +3340,11 @@ void G_DoPlayDemo(char *defdemoname)
M_Memcpy(player_names[p],demo_p,16); M_Memcpy(player_names[p],demo_p,16);
demo_p += 16; demo_p += 16;
for (i = 0; i < MAXAVAILABILITY; i++)
{
availabilities[p][i] = READUINT8(demo_p);
}
// Skin // Skin
i = READUINT8(demo_p); i = READUINT8(demo_p);
@ -3376,6 +3428,8 @@ void G_DoPlayDemo(char *defdemoname)
for (i = 0; i < numslots; i++) for (i = 0; i < numslots; i++)
{ {
UINT8 j;
p = slots[i]; p = slots[i];
if (players[p].mo) if (players[p].mo)
{ {
@ -3392,6 +3446,11 @@ void G_DoPlayDemo(char *defdemoname)
players[p].kartweight = ghostext[p].kartweight = demo.skinlist[demo.currentskinid[p]].kartweight; players[p].kartweight = ghostext[p].kartweight = demo.skinlist[demo.currentskinid[p]].kartweight;
players[p].charflags = ghostext[p].charflags = demo.skinlist[demo.currentskinid[p]].flags; players[p].charflags = ghostext[p].charflags = demo.skinlist[demo.currentskinid[p]].flags;
players[p].lastfakeskin = lastfakeskin[p]; players[p].lastfakeskin = lastfakeskin[p];
for (j = 0; j < MAXAVAILABILITY; j++)
{
players[p].availabilities[j] = availabilities[p][j];
}
} }
demo.deferstart = true; demo.deferstart = true;

View file

@ -33,6 +33,7 @@ typedef struct democharlist_s {
UINT8 kartspeed; UINT8 kartspeed;
UINT8 kartweight; UINT8 kartweight;
UINT32 flags; UINT32 flags;
boolean unlockrequired;
} democharlist_t; } democharlist_t;
// Publicly-accessible demo vars // Publicly-accessible demo vars
@ -119,20 +120,19 @@ typedef enum
extern UINT8 demo_extradata[MAXPLAYERS]; extern UINT8 demo_extradata[MAXPLAYERS];
extern UINT8 demo_writerng; extern UINT8 demo_writerng;
#define DXD_PLAYSTATE 0x01 // state changed between playing, spectating, or not in-game #define DXD_JOINDATA 0x01 // join-specific data
#define DXD_RESPAWN 0x02 // "respawn" command in console #define DXD_PLAYSTATE 0x02 // state changed between playing, spectating, or not in-game
#define DXD_SKIN 0x04 // skin changed #define DXD_SKIN 0x04 // skin changed
#define DXD_NAME 0x08 // name changed #define DXD_NAME 0x08 // name changed
#define DXD_COLOR 0x10 // color changed #define DXD_COLOR 0x10 // color changed
#define DXD_FOLLOWER 0x20 // follower was changed #define DXD_FOLLOWER 0x20 // follower was changed
#define DXD_WEAPONPREF 0x40 // netsynced playsim settings were changed #define DXD_RESPAWN 0x40 // "respawn" command in console
#define DXD_WEAPONPREF 0x80 // netsynced playsim settings were changed
#define DXD_PST_PLAYING 0x01 #define DXD_PST_PLAYING 0x01
#define DXD_PST_SPECTATING 0x02 #define DXD_PST_SPECTATING 0x02
#define DXD_PST_LEFT 0x03 #define DXD_PST_LEFT 0x03
#define DXD_PST_ISBOT 0x80 // extra flag
// Record/playback tics // Record/playback tics
void G_ReadDemoExtraData(void); void G_ReadDemoExtraData(void);
void G_WriteDemoExtraData(void); void G_WriteDemoExtraData(void);

View file

@ -2238,7 +2238,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
UINT8 latestlap; UINT8 latestlap;
UINT16 skincolor; UINT16 skincolor;
INT32 skin; INT32 skin;
UINT32 availabilities; UINT8 availabilities[MAXAVAILABILITY];
UINT8 fakeskin; UINT8 fakeskin;
UINT8 lastfakeskin; UINT8 lastfakeskin;
@ -2307,7 +2307,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
followercolor = players[player].followercolor; followercolor = players[player].followercolor;
followerskin = players[player].followerskin; followerskin = players[player].followerskin;
availabilities = players[player].availabilities; memcpy(availabilities, players[player].availabilities, sizeof(availabilities));
followitem = players[player].followitem; followitem = players[player].followitem;
@ -2432,7 +2432,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
p->charflags = charflags; p->charflags = charflags;
p->lastfakeskin = lastfakeskin; p->lastfakeskin = lastfakeskin;
p->availabilities = availabilities; memcpy(players[player].availabilities, availabilities, sizeof(availabilities));
p->followitem = followitem; p->followitem = followitem;
p->starpostnum = starpostnum; p->starpostnum = starpostnum;
@ -2918,7 +2918,7 @@ void G_AddPlayer(INT32 playernum)
{ {
player_t *p = &players[playernum]; player_t *p = &players[playernum];
p->playerstate = PST_REBORN; p->playerstate = PST_REBORN;
demo_extradata[playernum] |= DXD_PLAYSTATE|DXD_COLOR|DXD_NAME|DXD_SKIN|DXD_FOLLOWER; // Set everything demo_extradata[playernum] |= DXD_JOINDATA|DXD_PLAYSTATE|DXD_COLOR|DXD_NAME|DXD_SKIN|DXD_FOLLOWER; // Set everything
} }
void G_ExitLevel(void) void G_ExitLevel(void)
@ -3690,6 +3690,10 @@ static void G_UpdateVisited(void)
if ((earnedEmblems = M_CompletionEmblems())) if ((earnedEmblems = M_CompletionEmblems()))
CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for level completion.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : ""); CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for level completion.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : "");
if (M_UpdateUnlockablesAndExtraEmblems())
S_StartSound(NULL, sfx_ncitem);
G_SaveGameData();
} }
static boolean CanSaveLevel(INT32 mapnum) static boolean CanSaveLevel(INT32 mapnum)
@ -4507,12 +4511,14 @@ void G_SaveGameData(void)
return; return;
} }
#ifndef DEVELOP
if (usedCheats) if (usedCheats)
{ {
free(savebuffer); free(savebuffer);
save_p = savebuffer = NULL; save_p = savebuffer = NULL;
return; return;
} }
#endif
// Version test // Version test

View file

@ -231,9 +231,9 @@ void K_InitGrandPrixBots(void)
// Rearrange usable bot skins list to prevent gaps for randomised selection // Rearrange usable bot skins list to prevent gaps for randomised selection
for (i = 0; i < usableskins; i++) for (i = 0; i < usableskins; i++)
{ {
if (!(grabskins[i] == MAXSKINS /*|| K_SkinLocked(grabskins[i])*/)) if (!(grabskins[i] == MAXSKINS || !R_SkinUsable(-1, grabskins[i], true)))
continue; continue;
while (usableskins > i && (grabskins[usableskins] == MAXSKINS /*|| K_SkinLocked(grabskins[i])*/)) while (usableskins > i && (grabskins[usableskins] == MAXSKINS || !R_SkinUsable(-1, grabskins[usableskins], true)))
{ {
usableskins--; usableskins--;
} }
@ -557,9 +557,9 @@ void K_RetireBots(void)
// Rearrange usable bot skins list to prevent gaps for randomised selection // Rearrange usable bot skins list to prevent gaps for randomised selection
for (i = 0; i < usableskins; i++) for (i = 0; i < usableskins; i++)
{ {
if (!(grabskins[i] == MAXSKINS /*|| K_SkinLocked(grabskins[i])*/)) if (!(grabskins[i] == MAXSKINS || !R_SkinUsable(-1, grabskins[i], true)))
continue; continue;
while (usableskins > i && (grabskins[usableskins] == MAXSKINS /*|| K_SkinLocked(grabskins[i])*/)) while (usableskins > i && (grabskins[usableskins] == MAXSKINS || !R_SkinUsable(-1, grabskins[usableskins], true)))
usableskins--; usableskins--;
grabskins[i] = grabskins[usableskins]; grabskins[i] = grabskins[usableskins];
grabskins[usableskins] = MAXSKINS; grabskins[usableskins] = MAXSKINS;

View file

@ -2175,6 +2175,9 @@ void M_CharacterSelectInit(void)
UINT8 x = skins[i].kartspeed-1; UINT8 x = skins[i].kartspeed-1;
UINT8 y = skins[i].kartweight-1; UINT8 y = skins[i].kartweight-1;
if (!R_SkinUsable(g_localplayers[0], i, false))
continue;
if (setup_chargrid[x][y].numskins >= MAXCLONES) if (setup_chargrid[x][y].numskins >= MAXCLONES)
CONS_Alert(CONS_ERROR, "Max character alts reached for %d,%d\n", x+1, y+1); CONS_Alert(CONS_ERROR, "Max character alts reached for %d,%d\n", x+1, y+1);
else else

View file

@ -2351,7 +2351,7 @@ static int lib_rSetPlayerSkin(lua_State *L)
return luaL_error(L, "skin %s (argument 2) is not loaded", skinname); return luaL_error(L, "skin %s (argument 2) is not loaded", skinname);
} }
if (!R_SkinUsable(j, i)) if (!R_SkinUsable(j, i, false))
return luaL_error(L, "skin %d (argument 2) not usable - check with R_SkinUsable(player_t, skin) first.", i); return luaL_error(L, "skin %d (argument 2) not usable - check with R_SkinUsable(player_t, skin) first.", i);
SetPlayerSkinByNum(j, i); SetPlayerSkinByNum(j, i);
return 0; return 0;
@ -2381,7 +2381,7 @@ static int lib_rSkinUsable(lua_State *L)
return luaL_error(L, "skin %s (argument 2) is not loaded", skinname); return luaL_error(L, "skin %s (argument 2) is not loaded", skinname);
} }
lua_pushboolean(L, R_SkinUsable(j, i)); lua_pushboolean(L, R_SkinUsable(j, i, false));
return 1; return 1;
} }

View file

@ -669,7 +669,7 @@ static int mobj_set(lua_State *L)
for (i = 0; i < numskins; i++) for (i = 0; i < numskins; i++)
if (fastcmp(skins[i].name, skin)) if (fastcmp(skins[i].name, skin))
{ {
if (!mo->player || R_SkinUsable(mo->player-players, i)) if (!mo->player || R_SkinUsable(mo->player-players, i, false))
mo->skin = &skins[i]; mo->skin = &skins[i];
return 0; return 0;
} }

View file

@ -408,8 +408,6 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->skincolor); lua_pushinteger(L, plr->skincolor);
else if (fastcmp(field,"skin")) else if (fastcmp(field,"skin"))
lua_pushinteger(L, plr->skin); lua_pushinteger(L, plr->skin);
else if (fastcmp(field,"availabilities"))
lua_pushinteger(L, plr->availabilities);
else if (fastcmp(field,"fakeskin")) else if (fastcmp(field,"fakeskin"))
lua_pushinteger(L, plr->fakeskin); lua_pushinteger(L, plr->fakeskin);
else if (fastcmp(field,"lastfakeskin")) else if (fastcmp(field,"lastfakeskin"))
@ -577,8 +575,6 @@ static int player_set(lua_State *L)
} }
else if (fastcmp(field,"skin")) else if (fastcmp(field,"skin"))
return NOSET; return NOSET;
else if (fastcmp(field,"availabilities"))
return NOSET;
else if (fastcmp(field,"fakeskin")) else if (fastcmp(field,"fakeskin"))
return NOSET; return NOSET;
else if (fastcmp(field,"lastfakeskin")) else if (fastcmp(field,"lastfakeskin"))

View file

@ -290,11 +290,6 @@ void M_SilentUpdateUnlockablesAndEmblems(void)
continue; continue;
unlockables[i].unlocked = M_Achieved(unlockables[i].conditionset - 1); unlockables[i].unlocked = M_Achieved(unlockables[i].conditionset - 1);
} }
for (i = 0; i < MAXSPLITSCREENPLAYERS; i++)
{
players[g_localplayers[i]].availabilities = R_GetSkinAvailabilities();
}
} }
// Emblem unlocking shit // Emblem unlocking shit
@ -372,6 +367,7 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa
if (res) if (res)
++somethingUnlocked; ++somethingUnlocked;
} }
return somethingUnlocked; return somethingUnlocked;
} }
@ -502,6 +498,35 @@ UINT8 M_GotLowEnoughTime(INT32 tictime)
return true; return true;
} }
// Gets the skin number for a SECRET_SKIN unlockable.
INT32 M_UnlockableSkinNum(unlockable_t *unlock)
{
if (unlock->type != SECRET_SKIN)
{
// This isn't a skin unlockable...
return -1;
}
if (unlock->stringVar && strcmp(unlock->stringVar, ""))
{
// Get the skin from the string.
INT32 skinnum = R_SkinAvailable(unlock->stringVar);
if (skinnum != -1)
{
return skinnum;
}
}
if (unlock->variable >= 0 && unlock->variable < numskins)
{
// Use the number directly.
return unlock->variable;
}
// Invalid skin unlockable.
return -1;
}
// ---------------- // ----------------
// Misc Emblem shit // Misc Emblem shit
// ---------------- // ----------------

View file

@ -77,6 +77,7 @@ typedef struct
UINT8 sprite; ///< emblem sprite to use, 0 - 25 UINT8 sprite; ///< emblem sprite to use, 0 - 25
UINT16 color; ///< skincolor to use UINT16 color; ///< skincolor to use
INT32 var; ///< If needed, specifies information on the target amount to achieve (or target skin) INT32 var; ///< If needed, specifies information on the target amount to achieve (or target skin)
char *stringVar; ///< String version
char hint[110]; ///< Hint for emblem hints menu char hint[110]; ///< Hint for emblem hints menu
UINT8 collected; ///< Do you have this emblem? UINT8 collected; ///< Do you have this emblem?
} emblem_t; } emblem_t;
@ -100,6 +101,7 @@ typedef struct
UINT8 showconditionset; UINT8 showconditionset;
INT16 type; INT16 type;
INT16 variable; INT16 variable;
char *stringVar;
UINT8 nocecho; UINT8 nocecho;
UINT8 nochecklist; UINT8 nochecklist;
UINT8 unlocked; UINT8 unlocked;
@ -177,4 +179,7 @@ const char *M_GetExtraEmblemPatch(extraemblem_t *em, boolean big);
UINT8 M_GotEnoughEmblems(INT32 number); UINT8 M_GotEnoughEmblems(INT32 number);
UINT8 M_GotLowEnoughTime(INT32 tictime); UINT8 M_GotLowEnoughTime(INT32 tictime);
INT32 M_UnlockableSkinNum(unlockable_t *unlock);
INT32 M_EmblemSkinNum(emblem_t *emblem);
#define M_Achieved(a) ((a) >= MAXCONDITIONSETS || conditionSets[a].achieved) #define M_Achieved(a) ((a) >= MAXCONDITIONSETS || conditionSets[a].achieved)

View file

@ -150,7 +150,12 @@ static void P_NetArchivePlayers(void)
WRITEUINT8(save_p, players[i].skincolor); WRITEUINT8(save_p, players[i].skincolor);
WRITEINT32(save_p, players[i].skin); WRITEINT32(save_p, players[i].skin);
WRITEUINT32(save_p, players[i].availabilities);
for (j = 0; j < MAXAVAILABILITY; j++)
{
WRITEUINT8(save_p, players[i].availabilities[j]);
}
WRITEUINT8(save_p, players[i].fakeskin); WRITEUINT8(save_p, players[i].fakeskin);
WRITEUINT8(save_p, players[i].lastfakeskin); WRITEUINT8(save_p, players[i].lastfakeskin);
WRITEUINT32(save_p, players[i].score); WRITEUINT32(save_p, players[i].score);
@ -472,7 +477,12 @@ static void P_NetUnArchivePlayers(void)
players[i].skincolor = READUINT8(save_p); players[i].skincolor = READUINT8(save_p);
players[i].skin = READINT32(save_p); players[i].skin = READINT32(save_p);
players[i].availabilities = READUINT32(save_p);
for (j = 0; i < MAXAVAILABILITY; j++)
{
players[i].availabilities[j] = READUINT8(save_p);
}
players[i].fakeskin = READUINT8(save_p); players[i].fakeskin = READUINT8(save_p);
players[i].lastfakeskin = READUINT8(save_p); players[i].lastfakeskin = READUINT8(save_p);
players[i].score = READUINT32(save_p); players[i].score = READUINT32(save_p);

View file

@ -7559,10 +7559,14 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
nextmapoverride = 0; nextmapoverride = 0;
skipstats = 0; skipstats = 0;
if (!(netgame || multiplayer || demo.playback) && !majormods) if (!demo.playback)
{
mapheaderinfo[gamemap-1]->mapvisited |= MV_VISITED; mapheaderinfo[gamemap-1]->mapvisited |= MV_VISITED;
else if (!demo.playback)
mapheaderinfo[gamemap-1]->mapvisited |= MV_MP; // you want to record that you've been there this session, but not permanently if (M_UpdateUnlockablesAndExtraEmblems())
S_StartSound(NULL, sfx_ncitem);
G_SaveGameData();
}
G_AddMapToBuffer(gamemap-1); G_AddMapToBuffer(gamemap-1);
@ -8036,8 +8040,8 @@ UINT16 P_PartialAddWadFile(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
// //
// edit music defs // edit music defs

View file

@ -32,6 +32,7 @@
#if 0 #if 0
#include "k_kart.h" // K_KartResetPlayerColor #include "k_kart.h" // K_KartResetPlayerColor
#endif #endif
#include "k_grandprix.h" // K_CanChangeRules
#ifdef HWRENDER #ifdef HWRENDER
#include "hardware/hw_md2.h" #include "hardware/hw_md2.h"
#endif #endif
@ -150,36 +151,51 @@ void R_InitSkins(void)
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();
} }
UINT32 R_GetSkinAvailabilities(void) UINT8 *R_GetSkinAvailabilities(boolean demolock)
{ {
UINT8 i; UINT8 i, shif, byte;
UINT32 response = 0; INT32 skinid;
static UINT8 responsebuffer[MAXAVAILABILITY];
memset(&responsebuffer, 0, sizeof(responsebuffer));
for (i = 0; i < MAXUNLOCKABLES; i++) for (i = 0; i < MAXUNLOCKABLES; i++)
{ {
if (unlockables[i].type == SECRET_SKIN && unlockables[i].unlocked) if (unlockables[i].type != SECRET_SKIN)
{ continue;
UINT8 s = min(unlockables[i].variable, MAXSKINS);
response |= (1 << s); if (unlockables[i].unlocked != true && !demolock)
} continue;
skinid = M_UnlockableSkinNum(&unlockables[i]);
if (skinid < 0 || skinid >= MAXSKINS)
continue;
shif = (skinid % 8);
byte = (skinid / 8);
responsebuffer[byte] |= (1 << shif);
} }
return response; return responsebuffer;
} }
// returns true if available in circumstances, otherwise nope // returns true if available in circumstances, otherwise nope
// 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, boolean demoskins)
{ {
boolean needsunlocked = false; boolean needsunlocked = false;
boolean useplayerstruct = (Playing() && playernum != -1);
UINT8 i; UINT8 i;
INT32 skinid;
if (skinnum == -1) if (skinnum == -1)
{ {
@ -187,55 +203,54 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum)
return true; return true;
} }
if (modeattacking) if (K_CanChangeRules(true) && (cv_forceskin.value == skinnum))
{
// If you have someone else's run, you should be able to take a look
return true;
}
if (netgame && (cv_forceskin.value == skinnum))
{ {
// Being forced to play as this character by the server // Being forced to play as this character by the server
return true; return true;
} }
if (metalrecording)
{
// Recording a Metal Sonic race
const INT32 metalskin = R_SkinAvailable("metalsonic");
return (skinnum == metalskin);
}
// Determine if this character is supposed to be unlockable or not // Determine if this character is supposed to be unlockable or not
for (i = 0; i < MAXUNLOCKABLES; i++) if (useplayerstruct && demo.playback)
{ {
if (unlockables[i].type == SECRET_SKIN && unlockables[i].variable == skinnum) if (!demoskins)
skinnum = demo.skinlist[skinnum].mapping;
needsunlocked = demo.skinlist[skinnum].unlockrequired;
}
else
{
for (i = 0; i < MAXUNLOCKABLES; i++)
{ {
if (unlockables[i].type != SECRET_SKIN)
continue;
skinid = M_UnlockableSkinNum(&unlockables[i]);
if (skinid != skinnum)
continue;
// i is now the unlockable index, we can use this later // i is now the unlockable index, we can use this later
needsunlocked = true; needsunlocked = true;
break; break;
} }
} }
if (needsunlocked == true) if (needsunlocked == false)
{
// You can use this character IF you have it unlocked.
if ((netgame || multiplayer) && playernum != -1)
{
// Use the netgame synchronized unlocks.
return (boolean)(!(players[playernum].availabilities & (1 << skinnum)));
}
else
{
// Use the unlockables table directly
return (boolean)(unlockables[i].unlocked);
}
}
else
{ {
// Didn't trip anything, so we can use this character. // Didn't trip anything, so we can use this character.
return true; return true;
} }
// Ok, you can use this character IF you have it unlocked.
if (useplayerstruct)
{
// Use the netgame synchronized unlocks.
UINT8 shif = (skinnum % 8);
UINT8 byte = (skinnum / 8);
return !!(players[playernum].availabilities[byte] & (1 << shif));
}
// Use the unlockables table directly
return (boolean)(unlockables[i].unlocked);
} }
// returns true if the skin name is found (loaded from pwad) // returns true if the skin name is found (loaded from pwad)
@ -296,7 +311,25 @@ static void SetSkin(player_t *player, INT32 skinnum)
} }
// for replays: We have changed our skin mid-game; let the game know so it can do the same in the replay! // for replays: We have changed our skin mid-game; let the game know so it can do the same in the replay!
demo_extradata[playernum] |= DXD_SKIN; demo_extradata[(player-players)] |= DXD_SKIN;
}
// Gets the player to the first usuable skin in the game.
// (If your mod locked them all, then you kinda stupid)
static INT32 GetPlayerDefaultSkin(INT32 playernum)
{
INT32 i;
for (i = 0; i < numskins; i++)
{
if (R_SkinUsable(playernum, i, false))
{
return i;
}
}
I_Error("All characters are locked!");
return 0;
} }
// network code calls this when a 'skin change' is received // network code calls this when a 'skin change' is received
@ -305,7 +338,7 @@ void SetPlayerSkin(INT32 playernum, const char *skinname)
INT32 i = R_SkinAvailable(skinname); INT32 i = R_SkinAvailable(skinname);
player_t *player = &players[playernum]; player_t *player = &players[playernum];
if ((i != -1) && R_SkinUsable(playernum, i)) if ((i != -1) && R_SkinUsable(playernum, i, false))
{ {
SetSkin(player, i); SetSkin(player, i);
return; return;
@ -316,7 +349,7 @@ void SetPlayerSkin(INT32 playernum, const char *skinname)
else if(server || IsPlayerAdmin(consoleplayer)) else if(server || IsPlayerAdmin(consoleplayer))
CONS_Alert(CONS_WARNING, M_GetText("Player %d (%s) skin '%s' not found\n"), playernum, player_names[playernum], skinname); CONS_Alert(CONS_WARNING, M_GetText("Player %d (%s) skin '%s' not found\n"), playernum, player_names[playernum], skinname);
SetSkin(player, 0); SetSkin(player, GetPlayerDefaultSkin(playernum));
} }
// Same as SetPlayerSkin, but uses the skin #. // Same as SetPlayerSkin, but uses the skin #.
@ -325,7 +358,7 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
{ {
player_t *player = &players[playernum]; player_t *player = &players[playernum];
if (skinnum >= 0 && skinnum < numskins && R_SkinUsable(playernum, skinnum)) // Make sure it exists! if (skinnum >= 0 && skinnum < numskins && R_SkinUsable(playernum, skinnum, false)) // Make sure it exists!
{ {
SetSkin(player, skinnum); SetSkin(player, skinnum);
return; return;
@ -336,7 +369,7 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
else if(server || IsPlayerAdmin(consoleplayer)) else if(server || IsPlayerAdmin(consoleplayer))
CONS_Alert(CONS_WARNING, "Player %d (%s) skin %d not found\n", playernum, player_names[playernum], skinnum); CONS_Alert(CONS_WARNING, "Player %d (%s) skin %d not found\n", playernum, player_names[playernum], skinnum);
SetSkin(player, 0); // not found put the eggman skin SetSkin(player, GetPlayerDefaultSkin(playernum)); // not found put the eggman skin
} }
// Set mo skin but not player_t skin, for ironman // Set mo skin but not player_t skin, for ironman
@ -386,11 +419,14 @@ void SetRandomFakePlayerSkin(player_t* player, boolean fast)
} }
else if (skins[i].flags & SF_IRONMAN) else if (skins[i].flags & SF_IRONMAN)
continue; continue;
/*if (K_SkinLocked(i)) if (!R_SkinUsable(player-players, i, true))
continue;*/ continue;
grabskins[usableskins++] = i; grabskins[usableskins++] = i;
} }
if (!usableskins)
I_Error("SetRandomFakePlayerSkin: No valid skins to pick from!?");
i = grabskins[P_RandomKey(PR_RANDOMSKIN, usableskins)]; i = grabskins[P_RandomKey(PR_RANDOMSKIN, usableskins)];
SetFakePlayerSkin(player, i); SetFakePlayerSkin(player, i);
@ -662,7 +698,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;
@ -810,7 +846,8 @@ next_token:
R_FlushTranslationColormapCache(); R_FlushTranslationColormapCache();
CONS_Printf(M_GetText("Added skin '%s'\n"), skin->name); if (mainfile == false)
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;
@ -834,7 +871,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;
@ -976,7 +1013,8 @@ next_token:
R_FlushTranslationColormapCache(); R_FlushTranslationColormapCache();
CONS_Printf(M_GetText("Patched skin '%s'\n"), skin->name); if (mainfile == false)
CONS_Printf(M_GetText("Patched skin '%s'\n"), skin->name);
} }
return; return;
} }

View file

@ -84,11 +84,13 @@ void SetPlayerSkinByNum(INT32 playernum,INT32 skinnum); // Tails 03-16-2002
void SetFakePlayerSkin(player_t* player, INT32 skinnum); void SetFakePlayerSkin(player_t* player, INT32 skinnum);
void SetRandomFakePlayerSkin(player_t* player, boolean fast); void SetRandomFakePlayerSkin(player_t* player, boolean fast);
void ClearFakePlayerSkin(player_t* player); void ClearFakePlayerSkin(player_t* player);
boolean R_SkinUsable(INT32 playernum, INT32 skinnum); boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins);
UINT32 R_GetSkinAvailabilities(void);
UINT8 *R_GetSkinAvailabilities(boolean demolock);
INT32 R_SkinAvailable(const char *name); INT32 R_SkinAvailable(const char *name);
void R_PatchSkins(UINT16 wadnum);
void R_AddSkins(UINT16 wadnum); void R_PatchSkins(UINT16 wadnum, boolean mainfile);
void R_AddSkins(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);