From 6d0637d39d464f7f52e79ca05cb32f5ada78d02e Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 27 Nov 2022 22:53:29 +0000 Subject: [PATCH] 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!! :face_holding_back_tears: - 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. --- src/command.c | 6 +- src/d_clisrv.c | 53 ++++++++++++---- src/d_clisrv.h | 1 + src/d_main.c | 12 ++-- src/d_netcmd.c | 67 +++++--------------- src/d_player.h | 2 +- src/deh_soc.c | 38 +++++++++++- src/deh_soc.h | 2 + src/dehacked.c | 7 ++- src/doomdef.h | 3 +- src/g_demo.c | 115 +++++++++++++++++++++++++--------- src/g_demo.h | 10 +-- src/g_game.c | 14 +++-- src/k_grandprix.c | 8 +-- src/k_menufunc.c | 3 + src/lua_baselib.c | 4 +- src/lua_mobjlib.c | 2 +- src/lua_playerlib.c | 4 -- src/m_cond.c | 35 +++++++++-- src/m_cond.h | 5 ++ src/p_saveg.c | 14 ++++- src/p_setup.c | 14 +++-- src/r_skins.c | 146 ++++++++++++++++++++++++++++---------------- src/r_skins.h | 10 +-- 24 files changed, 377 insertions(+), 198 deletions(-) diff --git a/src/command.c b/src/command.c index eb1204108..30198c3e2 100644 --- a/src/command.c +++ b/src/command.c @@ -1481,7 +1481,7 @@ boolean CV_CompleteValue(consvar_t *var, const char **valstrp, INT32 *intval) { v = R_SkinAvailable(valstr); - if (!R_SkinUsable(-1, v)) + if (!R_SkinUsable(-1, v, false)) v = -1; goto finish; @@ -1984,7 +1984,7 @@ static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth) if (var == &cv_forceskin) { 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"); return; @@ -2109,7 +2109,7 @@ void CV_AddValue(consvar_t *var, INT32 increment) else if (newvalue >= numskins) newvalue = -1; } while ((oldvalue != newvalue) - && !(R_SkinUsable(-1, newvalue))); + && !(R_SkinUsable(-1, newvalue, false))); } else newvalue = var->value + increment; diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 6ce85db27..2ecea2764 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -824,6 +824,8 @@ static boolean CL_SendJoin(void) for (; i < MAXSPLITSCREENPLAYERS; i++) 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)); } @@ -3565,8 +3567,8 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) return; } - node = (UINT8)READUINT8(*p); - newplayernum = (UINT8)READUINT8(*p); + node = READUINT8(*p); + newplayernum = READUINT8(*p); 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); - console = (UINT8)READUINT8(*p); - splitscreenplayer = (UINT8)READUINT8(*p); + console = READUINT8(*p); + splitscreenplayer = READUINT8(*p); + + for (i = 0; i < MAXAVAILABILITY; i++) + { + newplayer->availabilities[i] = READUINT8(*p); + } // the server is creating my player if (node == mynode) @@ -3690,8 +3697,9 @@ static void Got_RemovePlayer(UINT8 **p, INT32 playernum) static void Got_AddBot(UINT8 **p, INT32 playernum) { INT16 newplayernum; - UINT8 skinnum = 0; + UINT8 i, skinnum = 0; UINT8 difficulty = DIFFICULTBOT; + UINT8 availabilitiesbuffer[MAXAVAILABILITY]; if (playernum != serverplayer && !IsPlayerAdmin(playernum)) { @@ -3704,9 +3712,9 @@ static void Got_AddBot(UINT8 **p, INT32 playernum) return; } - newplayernum = (UINT8)READUINT8(*p); - skinnum = (UINT8)READUINT8(*p); - difficulty = (UINT8)READUINT8(*p); + newplayernum = READUINT8(*p); + skinnum = READUINT8(*p); + difficulty = READUINT8(*p); CONS_Debug(DBG_NETPLAY, "addbot: %d\n", newplayernum); @@ -3720,6 +3728,15 @@ static void Got_AddBot(UINT8 **p, INT32 playernum) 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].bot = true; players[newplayernum].botvars.difficulty = difficulty; @@ -3737,10 +3754,10 @@ static void Got_AddBot(UINT8 **p, INT32 playernum) 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; - UINT8 buf[4 + MAXPLAYERNAME]; + INT32 n, newplayernum, i; + UINT8 buf[4 + MAXPLAYERNAME + MAXAVAILABILITY]; UINT8 *buf_p = buf; 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, playerpernode[node]); // splitscreen num + for (i = 0; i < MAXAVAILABILITY; i++) + { + WRITEUINT8(buf_p, availabilities[i]); + } + playerpernode[node]++; 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 // but I'm not confident enough to remove it entirely in case it breaks something { + UINT8 *availabilitiesbuffer = R_GetSkinAvailabilities(false); SINT8 node = 0; 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; #endif @@ -3971,6 +3994,7 @@ static void HandleConnect(SINT8 node) { char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME + 1]; INT32 i; + UINT8 availabilitiesbuffer[MAXAVAILABILITY]; // Sal: Dedicated mode is INCREDIBLY hacked together. // 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 nodewaiting[node] = (UINT8)(netbuffer->u.clientcfg.localplayers - playerpernode[node]); if (!nodeingame[node]) @@ -4111,7 +4137,8 @@ static void HandleConnect(SINT8 node) SV_SendSaveGame(node, false); // send a complete game state 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; player_joining = true; } diff --git a/src/d_clisrv.h b/src/d_clisrv.h index eb11ca04a..01b9836e4 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -246,6 +246,7 @@ typedef struct UINT8 localplayers; // number of splitscreen players UINT8 mode; char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME]; + UINT8 availabilities[MAXAVAILABILITY]; } ATTRPACK clientconfig_pak; #define SV_SPEEDMASK 0x03 // used to send kartspeed diff --git a/src/d_main.c b/src/d_main.c index bd63cd874..af438e1b4 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -950,11 +950,6 @@ void D_StartTitle(void) for (i = 0; i < MAXPLAYERS; i++) CL_ClearPlayer(i); - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - players[g_localplayers[i]].availabilities = R_GetSkinAvailabilities(); - } - splitscreen = 0; SplitScreen_OnChange(); @@ -1148,6 +1143,10 @@ static void IdentifyVersion(void) #ifdef USE_PATCH_FILE D_AddFile(startupiwads, va(pandf,srb2waddir,PATCHNAME)); #endif +#define UNLOCKTESTING +#if defined(DEVELOP) && defined(UNLOCKTESTING) + D_AddFile(startupiwads, va(pandf,srb2waddir,"unlocks.pk3")); +#endif //// #undef TEXTURESNAME #undef MAPSNAME @@ -1449,6 +1448,9 @@ void D_SRB2Main(void) #ifdef USE_PATCH_FILE mainwads++; // patch.pk3 #endif +#ifdef UNLOCKTESTING + mainwads++; // unlocks.pk3 +#endif #endif //ifndef DEVELOP diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 11c919a05..f6ef19d81 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1408,6 +1408,10 @@ boolean CanChangeSkinWhilePlaying(INT32 playernum) static void ForceAllSkins(INT32 forcedskin) { INT32 i; + + if (demo.playback) + return; // DXD_SKIN should handle all changes for us + for (i = 0; i < MAXPLAYERS; ++i) { if (!playeringame[i]) @@ -1476,6 +1480,8 @@ static void SendNameAndColor(UINT8 n) char buf[MAXPLAYERNAME+12]; char *p; + UINT16 i = 0; + if (splitscreen < n) 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); else if (skincolors[atoi(cv_playercolor[n].defaultvalue)].accessible) CV_StealthSet(&cv_playercolor[n], cv_playercolor[n].defaultvalue); - else { - UINT16 i = 0; - while (ifollowercolor) return; - player->availabilities = R_GetSkinAvailabilities(); - // We'll handle it later if we're not playing. if (!Playing()) return; @@ -1530,7 +1535,7 @@ static void SendNameAndColor(UINT8 n) 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); 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 // the name of a skin that was available in the previous game) 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_skin[n].value = 0; @@ -1590,7 +1595,6 @@ static void SendNameAndColor(UINT8 n) // Finally write out the complete packet and send it off. WRITESTRINGN(p, cv_playername[n].zstring, MAXPLAYERNAME); - WRITEUINT32(p, (UINT32)player->availabilities); WRITEUINT16(p, (UINT16)cv_playercolor[n].value); WRITEUINT8(p, (UINT8)cv_skin[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); - p->availabilities = READUINT32(*cp); color = READUINT16(*cp); skin = READUINT8(*cp); follower = READINT16(*cp); - //CONS_Printf("Recieved follower id %d\n", follower); followercolor = READUINT16(*cp); // set name @@ -1657,56 +1659,20 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) { 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 if (skincolors[p->skincolor].accessible == false) 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) { - 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); return; } } // 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; SetPlayerSkinByNum(playernum, forcedskin); @@ -5729,11 +5695,6 @@ void Command_ExitGame_f(void) for (i = 0; i < MAXPLAYERS; i++) CL_ClearPlayer(i); - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - players[g_localplayers[i]].availabilities = R_GetSkinAvailabilities(); - } - splitscreen = 0; SplitScreen_OnChange(); diff --git a/src/d_player.h b/src/d_player.h index e9ebe7668..36169e850 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -385,7 +385,7 @@ typedef struct player_s UINT16 skincolor; INT32 skin; - UINT32 availabilities; + UINT8 availabilities[MAXAVAILABILITY]; UINT8 fakeskin; // ironman UINT8 lastfakeskin; diff --git a/src/deh_soc.c b/src/deh_soc.c index f23c71970..74a0920e4 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -131,6 +131,36 @@ static float searchfvalue(const char *s) #endif // 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) { UINT8 i; @@ -2140,7 +2170,12 @@ void reademblemdata(MYFILE *f, INT32 num) else if (fastcmp(word, "COLOR")) emblemlocations[num-1].color = get_number(word2); 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); + } else deh_warning("Emblem %d: unknown word '%s'", num, word); } @@ -2344,7 +2379,8 @@ void readunlockable(MYFILE *f, INT32 num) } 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; } else diff --git a/src/deh_soc.h b/src/deh_soc.h index 0d9beccab..92985ad2f 100644 --- a/src/deh_soc.h +++ b/src/deh_soc.h @@ -77,6 +77,8 @@ void readlight(MYFILE *f, INT32 num); void readskincolor(MYFILE *f, INT32 num); void readthing(MYFILE *f, INT32 num); void readfreeslots(MYFILE *f); +void clear_emblems(void); +void clear_unlockables(void); void clear_levels(void); void clear_conditionsets(void); diff --git a/src/dehacked.c b/src/dehacked.c index 99ee7b0e8..1e400a1fa 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -584,12 +584,13 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) } if (clearall || fastcmp(word2, "UNLOCKABLES")) - memset(&unlockables, 0, sizeof(unlockables)); + { + clear_unlockables(); + } if (clearall || fastcmp(word2, "EMBLEMS")) { - memset(&emblemlocations, 0, sizeof(emblemlocations)); - numemblems = 0; + clear_emblems(); } if (clearall || fastcmp(word2, "EXTRAEMBLEMS")) diff --git a/src/doomdef.h b/src/doomdef.h index 8271a4ae2..4f03d0085 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -201,7 +201,8 @@ extern char logfilename[1024]; #define MAXGAMEPADS (MAXSPLITSCREENPLAYERS * 2) // Number of gamepads we'll be allowing #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 MAXCOLORNAME 32 diff --git a/src/g_demo.c b/src/g_demo.c index 8566a4c22..c6ee247e2 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -276,10 +276,8 @@ void G_ReadDemoExtraData(void) { extradata = READUINT8(demo_p); - if (extradata & DXD_PLAYSTATE) + if (extradata & DXD_JOINDATA) { - i = READUINT8(demo_p); - if (!playeringame[p]) { CL_ClearPlayer(p); @@ -288,14 +286,22 @@ void G_ReadDemoExtraData(void) 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.diffincrease = READUINT8(demo_p); // needed to avoid having to duplicate logic players[p].botvars.rival = (boolean)READUINT8(demo_p); - - i &= ~DXD_PST_ISBOT; } + } + if (extradata & DXD_PLAYSTATE) + { + i = READUINT8(demo_p); switch (i) { case DXD_PST_PLAYING: @@ -334,14 +340,6 @@ void G_ReadDemoExtraData(void) K_CheckBumpers(); P_CheckRacers(); } - if (extradata & DXD_RESPAWN) - { - if (players[p].mo) - { - // Is this how this should work..? - K_DoIngameRespawn(&players[p]); - } - } if (extradata & DXD_SKIN) { 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) { WeaponPref_Parse(&demo_p, p); @@ -454,6 +460,21 @@ void G_WriteDemoExtraData(void) WRITEUINT8(demo_p, 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) { UINT8 pst = DXD_PST_PLAYING; @@ -472,19 +493,7 @@ void G_WriteDemoExtraData(void) pst = DXD_PST_SPECTATING; } - if (players[i].bot) - { - pst |= DXD_PST_ISBOT; - } - 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_SKIN) @@ -1218,8 +1227,14 @@ void G_GhostTicker(void) if (ziptic == 0) // Only support player 0 info for now { 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) - 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) g->p++; // We _could_ read this info, but it shouldn't change anything in record attack... if (ziptic & DXD_COLOR) @@ -2248,6 +2263,7 @@ static void G_SaveDemoSkins(UINT8 **pp) { char skin[16]; UINT8 i; + UINT8 *availabilitiesbuffer = R_GetSkinAvailabilities(true); WRITEUINT8((*pp), numskins); for (i = 0; i < numskins; i++) @@ -2262,12 +2278,17 @@ static void G_SaveDemoSkins(UINT8 **pp) WRITEUINT8((*pp), skins[i].kartweight); 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) { char skin[17]; - UINT8 i; + UINT8 i, byte, shif; democharlist_t *skinlist = NULL; (*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; } @@ -2326,6 +2365,8 @@ static void G_SkipDemoSkins(UINT8 **pp) (*pp)++; // kartweight (*pp) += 4; // flags } + + (*pp) += MAXAVAILABILITY; } void G_BeginRecording(void) @@ -2443,6 +2484,11 @@ void G_BeginRecording(void) M_Memcpy(demo_p,name,16); demo_p += 16; + for (j = 0; j < MAXAVAILABILITY; j++) + { + WRITEUINT8(demo_p, player->availabilities[j]); + } + // Skin (now index into demo.skinlist) WRITEUINT8(demo_p, player->skin); WRITEUINT8(demo_p, player->lastfakeskin); @@ -2928,6 +2974,7 @@ void G_DoPlayDemo(char *defdemoname) UINT8 i, p, numslots = 0; lumpnum_t l; char color[MAXCOLORNAME+1],follower[17],mapname[MAXMAPLUMPNAME],*n,*pdemoname; + UINT8 availabilities[MAXPLAYERS][MAXAVAILABILITY]; UINT8 version,subversion; UINT32 randseed[PRNUMCLASS]; char msg[1024]; @@ -3293,6 +3340,11 @@ void G_DoPlayDemo(char *defdemoname) M_Memcpy(player_names[p],demo_p,16); demo_p += 16; + for (i = 0; i < MAXAVAILABILITY; i++) + { + availabilities[p][i] = READUINT8(demo_p); + } + // Skin i = READUINT8(demo_p); @@ -3376,6 +3428,8 @@ void G_DoPlayDemo(char *defdemoname) for (i = 0; i < numslots; i++) { + UINT8 j; + p = slots[i]; 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].charflags = ghostext[p].charflags = demo.skinlist[demo.currentskinid[p]].flags; players[p].lastfakeskin = lastfakeskin[p]; + + for (j = 0; j < MAXAVAILABILITY; j++) + { + players[p].availabilities[j] = availabilities[p][j]; + } } demo.deferstart = true; diff --git a/src/g_demo.h b/src/g_demo.h index a00756a74..ee38cd5d7 100644 --- a/src/g_demo.h +++ b/src/g_demo.h @@ -33,6 +33,7 @@ typedef struct democharlist_s { UINT8 kartspeed; UINT8 kartweight; UINT32 flags; + boolean unlockrequired; } democharlist_t; // Publicly-accessible demo vars @@ -119,20 +120,19 @@ typedef enum extern UINT8 demo_extradata[MAXPLAYERS]; extern UINT8 demo_writerng; -#define DXD_PLAYSTATE 0x01 // state changed between playing, spectating, or not in-game -#define DXD_RESPAWN 0x02 // "respawn" command in console +#define DXD_JOINDATA 0x01 // join-specific data +#define DXD_PLAYSTATE 0x02 // state changed between playing, spectating, or not in-game #define DXD_SKIN 0x04 // skin changed #define DXD_NAME 0x08 // name changed #define DXD_COLOR 0x10 // color 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_SPECTATING 0x02 #define DXD_PST_LEFT 0x03 -#define DXD_PST_ISBOT 0x80 // extra flag - // Record/playback tics void G_ReadDemoExtraData(void); void G_WriteDemoExtraData(void); diff --git a/src/g_game.c b/src/g_game.c index fe73d8ae6..c2ab6d216 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2238,7 +2238,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) UINT8 latestlap; UINT16 skincolor; INT32 skin; - UINT32 availabilities; + UINT8 availabilities[MAXAVAILABILITY]; UINT8 fakeskin; UINT8 lastfakeskin; @@ -2307,7 +2307,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) followercolor = players[player].followercolor; followerskin = players[player].followerskin; - availabilities = players[player].availabilities; + memcpy(availabilities, players[player].availabilities, sizeof(availabilities)); followitem = players[player].followitem; @@ -2432,7 +2432,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->charflags = charflags; p->lastfakeskin = lastfakeskin; - p->availabilities = availabilities; + memcpy(players[player].availabilities, availabilities, sizeof(availabilities)); p->followitem = followitem; p->starpostnum = starpostnum; @@ -2918,7 +2918,7 @@ void G_AddPlayer(INT32 playernum) { player_t *p = &players[playernum]; 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) @@ -3690,6 +3690,10 @@ static void G_UpdateVisited(void) if ((earnedEmblems = M_CompletionEmblems())) 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) @@ -4507,12 +4511,14 @@ void G_SaveGameData(void) return; } +#ifndef DEVELOP if (usedCheats) { free(savebuffer); save_p = savebuffer = NULL; return; } +#endif // Version test diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 6149434c7..221e9d10c 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -231,9 +231,9 @@ void K_InitGrandPrixBots(void) // Rearrange usable bot skins list to prevent gaps for randomised selection 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; - while (usableskins > i && (grabskins[usableskins] == MAXSKINS /*|| K_SkinLocked(grabskins[i])*/)) + while (usableskins > i && (grabskins[usableskins] == MAXSKINS || !R_SkinUsable(-1, grabskins[usableskins], true))) { usableskins--; } @@ -557,9 +557,9 @@ void K_RetireBots(void) // Rearrange usable bot skins list to prevent gaps for randomised selection 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; - while (usableskins > i && (grabskins[usableskins] == MAXSKINS /*|| K_SkinLocked(grabskins[i])*/)) + while (usableskins > i && (grabskins[usableskins] == MAXSKINS || !R_SkinUsable(-1, grabskins[usableskins], true))) usableskins--; grabskins[i] = grabskins[usableskins]; grabskins[usableskins] = MAXSKINS; diff --git a/src/k_menufunc.c b/src/k_menufunc.c index ab43217ff..696c14593 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -2175,6 +2175,9 @@ void M_CharacterSelectInit(void) UINT8 x = skins[i].kartspeed-1; UINT8 y = skins[i].kartweight-1; + if (!R_SkinUsable(g_localplayers[0], i, false)) + continue; + if (setup_chargrid[x][y].numskins >= MAXCLONES) CONS_Alert(CONS_ERROR, "Max character alts reached for %d,%d\n", x+1, y+1); else diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 06fbb789c..c42a5ffcb 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -2351,7 +2351,7 @@ static int lib_rSetPlayerSkin(lua_State *L) 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); SetPlayerSkinByNum(j, i); 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); } - lua_pushboolean(L, R_SkinUsable(j, i)); + lua_pushboolean(L, R_SkinUsable(j, i, false)); return 1; } diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c index b87ad7234..6d4b6b6e4 100644 --- a/src/lua_mobjlib.c +++ b/src/lua_mobjlib.c @@ -669,7 +669,7 @@ static int mobj_set(lua_State *L) for (i = 0; i < numskins; i++) 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]; return 0; } diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 2a3b9e46a..03d299b01 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -408,8 +408,6 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->skincolor); else if (fastcmp(field,"skin")) lua_pushinteger(L, plr->skin); - else if (fastcmp(field,"availabilities")) - lua_pushinteger(L, plr->availabilities); else if (fastcmp(field,"fakeskin")) lua_pushinteger(L, plr->fakeskin); else if (fastcmp(field,"lastfakeskin")) @@ -577,8 +575,6 @@ static int player_set(lua_State *L) } else if (fastcmp(field,"skin")) return NOSET; - else if (fastcmp(field,"availabilities")) - return NOSET; else if (fastcmp(field,"fakeskin")) return NOSET; else if (fastcmp(field,"lastfakeskin")) diff --git a/src/m_cond.c b/src/m_cond.c index 1568d0845..53ff790a6 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -290,11 +290,6 @@ void M_SilentUpdateUnlockablesAndEmblems(void) continue; 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 @@ -372,6 +367,7 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa if (res) ++somethingUnlocked; } + return somethingUnlocked; } @@ -502,6 +498,35 @@ UINT8 M_GotLowEnoughTime(INT32 tictime) 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 // ---------------- diff --git a/src/m_cond.h b/src/m_cond.h index 323c84347..7633cf39b 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -77,6 +77,7 @@ typedef struct UINT8 sprite; ///< emblem sprite to use, 0 - 25 UINT16 color; ///< skincolor to use 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 UINT8 collected; ///< Do you have this emblem? } emblem_t; @@ -100,6 +101,7 @@ typedef struct UINT8 showconditionset; INT16 type; INT16 variable; + char *stringVar; UINT8 nocecho; UINT8 nochecklist; UINT8 unlocked; @@ -177,4 +179,7 @@ const char *M_GetExtraEmblemPatch(extraemblem_t *em, boolean big); UINT8 M_GotEnoughEmblems(INT32 number); 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) diff --git a/src/p_saveg.c b/src/p_saveg.c index 73d4a7d1f..100672534 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -150,7 +150,12 @@ static void P_NetArchivePlayers(void) WRITEUINT8(save_p, players[i].skincolor); 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].lastfakeskin); WRITEUINT32(save_p, players[i].score); @@ -472,7 +477,12 @@ static void P_NetUnArchivePlayers(void) players[i].skincolor = READUINT8(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].lastfakeskin = READUINT8(save_p); players[i].score = READUINT32(save_p); diff --git a/src/p_setup.c b/src/p_setup.c index 92f054eb6..57e90e697 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7559,10 +7559,14 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) nextmapoverride = 0; skipstats = 0; - if (!(netgame || multiplayer || demo.playback) && !majormods) + if (!demo.playback) + { 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); @@ -8036,8 +8040,8 @@ UINT16 P_PartialAddWadFile(const char *wadfilename) // // look for skins // - R_AddSkins(wadnum); // faB: wadfile index in wadfiles[] - R_PatchSkins(wadnum); // toast: PATCH PATCH + R_AddSkins(wadnum, false); // faB: wadfile index in wadfiles[] + R_PatchSkins(wadnum, false); // toast: PATCH PATCH // // edit music defs diff --git a/src/r_skins.c b/src/r_skins.c index a09b22547..e4f3a317c 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -32,6 +32,7 @@ #if 0 #include "k_kart.h" // K_KartResetPlayerColor #endif +#include "k_grandprix.h" // K_CanChangeRules #ifdef HWRENDER #include "hardware/hw_md2.h" #endif @@ -150,36 +151,51 @@ void R_InitSkins(void) for (i = 0; i < numwadfiles; i++) { - R_AddSkins((UINT16)i); - R_PatchSkins((UINT16)i); + R_AddSkins((UINT16)i, true); + R_PatchSkins((UINT16)i, true); R_LoadSpriteInfoLumps(i, wadfiles[i]->numlumps); } ST_ReloadSkinFaceGraphics(); } -UINT32 R_GetSkinAvailabilities(void) +UINT8 *R_GetSkinAvailabilities(boolean demolock) { - UINT8 i; - UINT32 response = 0; + UINT8 i, shif, byte; + INT32 skinid; + static UINT8 responsebuffer[MAXAVAILABILITY]; + + memset(&responsebuffer, 0, sizeof(responsebuffer)); for (i = 0; i < MAXUNLOCKABLES; i++) { - if (unlockables[i].type == SECRET_SKIN && unlockables[i].unlocked) - { - UINT8 s = min(unlockables[i].variable, MAXSKINS); - response |= (1 << s); - } + if (unlockables[i].type != SECRET_SKIN) + continue; + + 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 // 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 useplayerstruct = (Playing() && playernum != -1); UINT8 i; + INT32 skinid; if (skinnum == -1) { @@ -187,55 +203,54 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum) return true; } - if (modeattacking) - { - // If you have someone else's run, you should be able to take a look - return true; - } - - if (netgame && (cv_forceskin.value == skinnum)) + if (K_CanChangeRules(true) && (cv_forceskin.value == skinnum)) { // Being forced to play as this character by the server 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 - 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 needsunlocked = true; break; } } - if (needsunlocked == true) - { - // 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 + if (needsunlocked == false) { // Didn't trip anything, so we can use this character. 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) @@ -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! - 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 @@ -305,7 +338,7 @@ void SetPlayerSkin(INT32 playernum, const char *skinname) INT32 i = R_SkinAvailable(skinname); player_t *player = &players[playernum]; - if ((i != -1) && R_SkinUsable(playernum, i)) + if ((i != -1) && R_SkinUsable(playernum, i, false)) { SetSkin(player, i); return; @@ -316,7 +349,7 @@ void SetPlayerSkin(INT32 playernum, const char *skinname) else if(server || IsPlayerAdmin(consoleplayer)) 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 #. @@ -325,7 +358,7 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum) { 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); return; @@ -336,7 +369,7 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum) else if(server || IsPlayerAdmin(consoleplayer)) 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 @@ -386,11 +419,14 @@ void SetRandomFakePlayerSkin(player_t* player, boolean fast) } else if (skins[i].flags & SF_IRONMAN) continue; - /*if (K_SkinLocked(i)) - continue;*/ + if (!R_SkinUsable(player-players, i, true)) + continue; grabskins[usableskins++] = i; } + if (!usableskins) + I_Error("SetRandomFakePlayerSkin: No valid skins to pick from!?"); + i = grabskins[P_RandomKey(PR_RANDOMSKIN, usableskins)]; 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 // -void R_AddSkins(UINT16 wadnum) +void R_AddSkins(UINT16 wadnum, boolean mainfile) { UINT16 lump, lastlump = 0; char *buf; @@ -810,7 +846,8 @@ next_token: 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 skin_cons_t[numskins].value = numskins; @@ -834,7 +871,7 @@ next_token: // // Patch skin sprites // -void R_PatchSkins(UINT16 wadnum) +void R_PatchSkins(UINT16 wadnum, boolean mainfile) { UINT16 lump, lastlump = 0; char *buf; @@ -976,7 +1013,8 @@ next_token: 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; } diff --git a/src/r_skins.h b/src/r_skins.h index 1cc06cca4..7b32d3e0c 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -84,11 +84,13 @@ void SetPlayerSkinByNum(INT32 playernum,INT32 skinnum); // Tails 03-16-2002 void SetFakePlayerSkin(player_t* player, INT32 skinnum); void SetRandomFakePlayerSkin(player_t* player, boolean fast); void ClearFakePlayerSkin(player_t* player); -boolean R_SkinUsable(INT32 playernum, INT32 skinnum); -UINT32 R_GetSkinAvailabilities(void); +boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins); + +UINT8 *R_GetSkinAvailabilities(boolean demolock); 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);