From 3028839f2ffe3aecc9f8491b2f882d5934f1eee5 Mon Sep 17 00:00:00 2001 From: NepDisk Date: Thu, 11 Dec 2025 20:44:22 -0500 Subject: [PATCH] Port most of SECRET_SKIN and player->availabilities changes from RR based on https://git.do.srb2.org/KartKrew/RingRacers/-/commit/6d0637d39d464f7f52e79ca05cb32f5ada78d02e --- src/d_clisrv.c | 36 +++++++++++---- src/d_clisrv.h | 1 + src/d_main.cpp | 5 -- src/d_netcmd.c | 54 +++++----------------- src/d_player.h | 2 +- src/deh_soc.c | 38 ++++++++++++++- src/deh_soc.h | 2 + src/dehacked.c | 7 +-- src/doomdef.h | 2 + src/g_demo.c | 46 +++++++++++++++--- src/g_game.c | 6 +-- src/k_bot.cpp | 10 ++++ src/lua_playerlib.c | 8 ++-- src/m_cond.c | 52 +++++++++++++++------ src/m_cond.h | 5 ++ src/p_saveg.c | 7 ++- src/r_skins.c | 110 +++++++++++++++++++++++++++++++------------- src/r_skins.h | 2 +- 18 files changed, 269 insertions(+), 124 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 3ea643ed7..706fc008d 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -894,6 +894,9 @@ 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(), MAXAVAILABILITY*sizeof(UINT8)); + + return HSendPacket(servernode, false, 0, sizeof (clientconfig_pak)); } @@ -3759,8 +3762,8 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) return; } - node = (UINT8)READUINT8(*p); - newplayernum = (UINT8)READUINT8(*p); + node = READUINT8(*p); + newplayernum = READUINT8(*p); if (newplayernum+1 > doomcom->numslots) doomcom->numslots = (INT16)(newplayernum+1); @@ -3771,8 +3774,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); + } G_AddPlayer(newplayernum, console); @@ -3892,10 +3900,10 @@ static void Got_AddBot(UINT8 **p, INT32 playernum) K_SetBot(newplayernum, skinnum, difficulty, style); } -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; @@ -3978,6 +3986,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); @@ -4121,9 +4134,11 @@ 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(); + 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; } @@ -4217,6 +4232,7 @@ static void HandleConnect(SINT8 node) doomdata_t *netbuffer = DOOMCOM_DATA(doomcom); 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..... @@ -4341,6 +4357,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]) @@ -4375,7 +4393,7 @@ 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 f08977d01..38bd1bb72 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -284,6 +284,7 @@ struct clientconfig_pak UINT8 localplayers; // number of splitscreen players UINT8 mode; char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME]; + UINT8 availabilities[MAXAVAILABILITY]; } ATTRPACK; #define SV_SPEEDMASK 0x03 // used to send kartspeed diff --git a/src/d_main.cpp b/src/d_main.cpp index 34125f128..768da5367 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -1174,11 +1174,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(); diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 877aedf64..011aba33b 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1723,6 +1723,10 @@ UINT8 CanChangeSkin(INT32 playernum) static void ForceAllSkins(INT32 forcedskin) { INT32 i, j; + + if (demo.playback) + return; // DXD_SKIN should handle all changes for us + for (i = 0; i < MAXPLAYERS; ++i) { if (!playeringame[i]) @@ -1793,6 +1797,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 @@ -1810,9 +1816,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 (iname)) return; - player->availabilities = R_GetSkinAvailabilities(); - // We'll handle it later if we're not playing. if (!Playing()) return; @@ -2063,7 +2068,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); WRITEUINT16(p, (UINT16)cv_skin[n].value); WRITEINT32(p, (INT32)cv_follower[n].value); @@ -2105,7 +2109,6 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) } READSTRINGN(*cp, name, MAXPLAYERNAME); - p->availabilities = READUINT32(*cp); color = READUINT16(*cp); skin = READUINT16(*cp); follower = READINT32(*cp); @@ -2143,36 +2146,6 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) if (skincolors[p->skincolor].accessible == false) kick = true; - // availabilities - // TODO: Port commit from RR: 6d0637d3 - /* - 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); @@ -2182,7 +2155,7 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) } // 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); @@ -6631,11 +6604,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 c138a0ea2..932197544 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -498,7 +498,7 @@ struct player_t INT32 skin; UINT16 voice_id; - UINT32 availabilities; + UINT8 availabilities[MAXAVAILABILITY]; UINT8 kartspeed; // Kart speed stat between 1 and 9 UINT8 kartweight; // Kart weight stat between 1 and 9 diff --git a/src/deh_soc.c b/src/deh_soc.c index 70e4dab0d..e15ac9818 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -134,6 +134,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; @@ -2679,7 +2709,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); } @@ -2883,7 +2918,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)G_MapNumber(word2); } else diff --git a/src/deh_soc.h b/src/deh_soc.h index c40481fd1..5a63800c7 100644 --- a/src/deh_soc.h +++ b/src/deh_soc.h @@ -86,6 +86,8 @@ void readlight(MYFILE *f, INT32 num); void readskincolor(MYFILE *f, INT32 num, boolean mainfile); 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 967c06b5f..9c52b3a7f 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -846,12 +846,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 c2ac08628..44e275781 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -199,6 +199,8 @@ extern char logfilename[1024]; #define MAXSKINS 4096 #define MAXFOLLOWERS UINT16_MAX +#define MAXSKINUNAVAILABLE 128 +#define MAXAVAILABILITY MAXSKINUNAVAILABLE/8 #define COLORRAMPSIZE 16 #define MAXCOLORNAME 32 diff --git a/src/g_demo.c b/src/g_demo.c index b4b3aefb0..a79155330 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -110,7 +110,7 @@ demoghost *ghosts = NULL; // DEMO RECORDING // -#define DEMOVERSION 0x000E +#define DEMOVERSION 0x000F #define DEMOHEADER "\xF0" "BlanReplay" "\x0F" #define DF_GHOST 0x01 // This demo contains ghost data too! @@ -228,6 +228,7 @@ typedef struct UINT8 kartspeed; UINT8 kartweight; mobjtype_t followitem; + UINT8 availabilities[MAXAVAILABILITY]; } demoplayer_t; typedef struct @@ -550,6 +551,7 @@ static void G_FreeExtraZipTic(ziptic_t *zt) static UINT8 *G_ReadRawExtraData(extradata_t *extra, UINT8 *dp, UINT16 version) { + INT32 i; UINT8 extradata; extra->playernum = READUINT8(dp); boolean kart = version <= 0x0002; @@ -591,6 +593,14 @@ static UINT8 *G_ReadRawExtraData(extradata_t *extra, UINT8 *dp, UINT16 version) if (extradata & DXD_JOINDATA) { + if (version >= 0x000F) + { + for (i = 0; i < MAXAVAILABILITY; i++) + { + players[extra->playernum].availabilities[i] = READUINT8(dp); + } + } + extra->joindata.bot = !!(READUINT8(dp)); if (extra->joindata.bot) { @@ -693,6 +703,8 @@ static headerstatus_e G_ReadDemoHeader(UINT8 *dp, demoheader_t *header) boolean serverinfo = true; boolean rapreset = true; // + extended serverinfo length boolean dubs = true; // Multiple voices + boolean availabilities = true; // Store player availabilities + INT32 i; // these may not be present in old demo formats, so initialize them // also initialize them so the header can be free'd without issues @@ -730,6 +742,8 @@ static headerstatus_e G_ReadDemoHeader(UINT8 *dp, demoheader_t *header) /* FALLTHRU */ case 0x000D: dubs = false; + case 0x000E: + availabilities = false; break; default: // too old, cannot support. @@ -739,7 +753,7 @@ static headerstatus_e G_ReadDemoHeader(UINT8 *dp, demoheader_t *header) else if (!memcmp(startdp, "\xF0" "KartReplay" "\x0F", 12)) { dubs = rapreset = raflag = false; - serverinfo = false; + serverinfo = availabilities = false; switch (header->demoversion) { case 0x0001: // SRB2Kart 1.0.x (only staff ghosts supported) @@ -832,7 +846,7 @@ readfiles: header->numfiles = READUINT8(dp); if (header->numfiles) header->files = malloc(sizeof(demofile_t) * header->numfiles); - for (UINT8 i = 0; i < header->numfiles; i++) + for (i = 0; i < header->numfiles; i++) { char filename[MAX_WADPATH]; READSTRINGL(dp, filename, MAX_WADPATH); @@ -891,7 +905,7 @@ skipfiles: header->numcvars = READUINT16(dp); if (header->numcvars) header->cvars = malloc(sizeof(democvar_t) * header->numcvars); - for (UINT16 i = 0; i < header->numcvars; i++) + for (i = 0; i < header->numcvars; i++) { if (kart) { @@ -994,6 +1008,11 @@ skipfiles: plr->kartspeed = READUINT8(dp); plr->kartweight = READUINT8(dp); plr->followitem = !kart ? READUINT32(dp) : MT_NULL; + + for (i = 0; i < MAXAVAILABILITY; i++) + { + plr->availabilities[i] = availabilities ? READUINT8(dp) : 0; + } } // Sigh ... it's an empty demo. Again. @@ -1342,7 +1361,7 @@ void G_ReadDemoExtraData(void) void G_WriteDemoExtraData(void) { - INT32 i; + INT32 i, j; for (i = 0; i < MAXPLAYERS; i++) { if (demo_extradata[i]) @@ -1352,6 +1371,11 @@ void G_WriteDemoExtraData(void) if (demo_extradata[i] & DXD_JOINDATA) { + for (j = 0; j < MAXAVAILABILITY; j++) + { + WRITEUINT8(demobuf.p, players[i].availabilities[i]); + } + WRITEUINT8(demobuf.p, (UINT8)players[i].bot); if (players[i].bot) { @@ -2821,7 +2845,7 @@ void G_RecordMetal(void) void G_BeginRecording(void) { - UINT8 i, p; + UINT8 i, j, p; char name[MAXCOLORNAME+1]; player_t *player = &players[consoleplayer]; @@ -3005,6 +3029,11 @@ void G_BeginRecording(void) // And mobjtype_t is best with UINT32 too... WRITEUINT32(demobuf.p, player->followitem); + + for (j = 0; j < MAXAVAILABILITY; j++) + { + WRITEUINT8(demobuf.p, player->availabilities[j]); + } } } @@ -3895,6 +3924,11 @@ void G_DoPlayDemo(char *defdemoname) // Followitem player->followitem = plr->followitem; + + for (i = 0; i < MAXAVAILABILITY; i++) + { + player->availabilities[i] = plr->availabilities[i]; + } } // end of player read (the 0xFF marker) diff --git a/src/g_game.c b/src/g_game.c index f1124f1c3..e72ac20a3 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2713,7 +2713,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) UINT16 skincolor; INT32 skin; UINT16 voice; - UINT32 availabilities; + UINT8 availabilities[MAXAVAILABILITY]; tic_t jointime; @@ -2783,7 +2783,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) jitterlegacy = players[player].jitterlegacy; - availabilities = players[player].availabilities; + memcpy(availabilities, players[player].availabilities, sizeof(availabilities)); charflags = players[player].charflags; @@ -2967,7 +2967,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->kartweight = kartweight; // p->charflags = charflags; - p->availabilities = availabilities; + memcpy(players[player].availabilities, availabilities, sizeof(availabilities)); p->followitem = followitem; p->starpostx = starpostx; diff --git a/src/k_bot.cpp b/src/k_bot.cpp index f242d9381..8b30f6f15 100644 --- a/src/k_bot.cpp +++ b/src/k_bot.cpp @@ -147,6 +147,7 @@ void K_SetNameForBot(UINT8 newplayernum, const char *realname) --------------------------------------------------*/ void K_SetBot(UINT8 newplayernum, UINT16 skinnum, UINT8 difficulty, botStyle_e style) { + INT32 i; CONS_Debug(DBG_NETPLAY, "addbot: %d\n", newplayernum); G_AddPlayer(newplayernum, newplayernum); @@ -154,6 +155,15 @@ void K_SetBot(UINT8 newplayernum, UINT16 skinnum, UINT8 difficulty, botStyle_e s if (newplayernum+1 > doomcom->numslots) doomcom->numslots = (INT16)(newplayernum+1); + // 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]; + } + } + playernode[newplayernum] = servernode; players[newplayernum].splitscreenindex = 0; diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 0c80d390e..b8e91b364 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -1191,9 +1191,9 @@ static int player_get(lua_State *L) case player_voice_id: lua_pushinteger(L, plr->voice_id); break; - case player_availabilities: + /*case player_availabilities: lua_pushinteger(L, plr->availabilities); - break; + break;*/ case player_score: lua_pushinteger(L, plr->score); break; @@ -1518,8 +1518,8 @@ static int player_set(lua_State *L) return NOSET; case player_voice_id: return NOSET; - case player_availabilities: - return NOSET; + /*case player_availabilities: + return NOSET;*/ case player_score: plr->score = luaL_checkinteger(L, 3); break; diff --git a/src/m_cond.c b/src/m_cond.c index 4406a56a3..0574cc3ec 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -48,17 +48,17 @@ extraemblem_t extraemblems[MAXEXTRAEMBLEMS] = // Default Unlockables unlockable_t unlockables[MAXUNLOCKABLES] = { - // Name, Objective, Showing Conditionset, ConditionSet, Unlock Type, Variable, NoCecho, NoChecklist - /* 01 */ {"Egg Cup", "", -1, 1, SECRET_NONE, 0, false, false, 0}, - /* 02 */ {"Chao Cup", "", -1, 2, SECRET_NONE, 0, false, false, 0}, - /* 03 */ {"SMK Cup", "", 2, 3, SECRET_NONE, 0, false, false, 0}, + // Name, Objective, Showing Conditionset, ConditionSet, Unlock Type, Variable, NoCecho, NoChecklist, Unlocked + /* 01 */ {"Egg Cup", "", -1, 1, SECRET_NONE, 0, "", false, false, 0}, + /* 02 */ {"Chao Cup", "", -1, 2, SECRET_NONE, 0, "", false, false, 0}, + /* 03 */ {"SMK Cup", "", 2, 3, SECRET_NONE, 0, "", false, false, 0}, - /* 04 */ {"Hard Game Speed", "", -1, 4, SECRET_HARDSPEED, 0, false, false, 0}, - /* 05 */ {"Encore Mode", "", 4, 5, SECRET_ENCORE, 0, false, false, 0}, - /* 06 */ {"Hell Attack", "", 6, 6, SECRET_HELLATTACK, 0, false, false, 0}, + /* 04 */ {"Hard Game Speed", "", -1, 4, SECRET_HARDSPEED, 0, "", false, false, 0}, + /* 05 */ {"Encore Mode", "", 4, 5, SECRET_ENCORE, 0, "", false, false, 0}, + /* 06 */ {"Hell Attack", "", 6, 6, SECRET_HELLATTACK, 0, "", false, false, 0}, - /* 07 */ {"Record Attack", "", -1, -1, SECRET_TIMEATTACK, 0, true, true, 0}, - /* 08 */ {"Capsule Attack", "", -1, -1, SECRET_ITEMBREAKER, 0, true, true, 0}, + /* 07 */ {"Record Attack", "", -1, -1, SECRET_TIMEATTACK, 0, "" ,true, true, 0}, + /* 08 */ {"Capsule Attack", "", -1, -1, SECRET_ITEMBREAKER, 0, "", true, true, 0}, }; // Number of emblems and extra emblems @@ -344,11 +344,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 @@ -555,6 +550,35 @@ UINT8 M_GotLowEnoughTime(INT32 tictime) */ } +// 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 d937c6190..2b88baa0f 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -84,6 +84,7 @@ struct emblem_t 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? }; @@ -107,6 +108,7 @@ struct unlockable_t UINT8 showconditionset; INT16 type; INT16 variable; + char *stringVar; UINT8 nocecho; UINT8 nochecklist; UINT8 unlocked; @@ -185,6 +187,9 @@ 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); + // vvvvvvvvvv anti-integer promotion, do not remove #define M_Achieved(a) ((unsigned)(a) >= MAXCONDITIONSETS || conditionSets[a].achieved) diff --git a/src/p_saveg.c b/src/p_saveg.c index 6c93d1339..1f2d35678 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -477,7 +477,12 @@ static void P_NetSyncPlayers(savebuffer_t *save) SYNC(players[i].skincolor); SYNC(players[i].skin); SYNC(players[i].voice_id); - SYNC(players[i].availabilities); + + for (j = 0; j < MAXAVAILABILITY; j++) + { + SYNC(players[i].availabilities[j]); + } + SYNC(players[i].score); SYNC(players[i].lives); SYNC(players[i].xtralife); diff --git a/src/r_skins.c b/src/r_skins.c index edad64554..ee9f9e3ba 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -36,6 +36,8 @@ #include "hardware/hw_md2.h" #endif +#include "k_grandprix.h" + #include "discord.h" INT32 numskins = 0; @@ -305,21 +307,34 @@ void R_InitSkins(void) ST_ReloadSkinFaceGraphics(); } -UINT32 R_GetSkinAvailabilities(void) +UINT8 *R_GetSkinAvailabilities(void) { - 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) - { - UINT16 s = min(unlockables[i].variable, MAXSKINS); - response |= (1 << s); - } + if (unlockables[i].type != SECRET_SKIN) + continue; + + if (unlockables[i].unlocked != true) + continue; + + skinid = M_UnlockableSkinNum(&unlockables[i]); + + if (skinid < 0 || skinid >= MAXSKINUNAVAILABLE) + continue; + + shif = (skinid % 8); + byte = (skinid / 8); + + responsebuffer[byte] |= (1 << shif); } - return response; + return responsebuffer; } // @@ -461,7 +476,9 @@ INT32 FindSortedSkinIndex(INT32 skinnum) boolean R_SkinUsable(INT32 playernum, INT32 skinnum) { boolean needsunlocked = false; + boolean useplayerstruct = (Playing() && playernum != -1); UINT8 i; + INT32 skinid; if (skinnum == -1) { @@ -475,7 +492,7 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum) 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; @@ -488,36 +505,45 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum) return (skinnum == metalskin); } + if (skinnum >= MAXSKINUNAVAILABLE) + { + // Keeping our packet size nice and sane in the wake of MAXSKINS increase, as suggested by toaster + return true; + } + // Determine if this character is supposed to be unlockable or not for (i = 0; i < MAXUNLOCKABLES; i++) { - if (unlockables[i].type == SECRET_SKIN && unlockables[i].variable == skinnum) - { - // i is now the unlockable index, we can use this later - needsunlocked = true; - break; - } + 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) @@ -535,6 +561,24 @@ INT32 R_SkinAvailable(const char *name) return -1; } +// 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)) + { + return i; + } + } + + I_Error("All characters are locked!"); + return 0; +} + // network code calls this when a 'skin change' is received void SetPlayerSkin(INT32 playernum, const char *skinname) { @@ -552,7 +596,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); - SetPlayerSkinByNum(playernum, 0); + SetPlayerSkinByNum(playernum, GetPlayerDefaultSkin(playernum)); } // Same as SetPlayerSkin, but uses the skin #. @@ -621,7 +665,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); - SetPlayerSkinByNum(playernum, 0); // not found, put in the default skin + SetPlayerSkinByNum(playernum, GetPlayerDefaultSkin(playernum)); // not found, put in the default skin } // Gets the corresponding voice ID for a given voice's name. diff --git a/src/r_skins.h b/src/r_skins.h index 7c38471d4..5097b0f73 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -140,7 +140,7 @@ INT32 FindSortedSkinIndex(INT32 skinnum); void SetPlayerSkin(INT32 playernum,const char *skinname); void SetPlayerSkinByNum(INT32 playernum,INT32 skinnum); // Tails 03-16-2002 boolean R_SkinUsable(INT32 playernum, INT32 skinnum); -UINT32 R_GetSkinAvailabilities(void); +UINT8 *R_GetSkinAvailabilities(void); INT32 R_SkinAvailable(const char *name); void R_PatchSkins(UINT16 wadnum); void R_AddSkins(UINT16 wadnum);