// DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2024 by Vivian "toastergrl" Grannell. // Copyright (C) 2024 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- /// \file r_skins.c /// \brief Loading skins #include "doomdef.h" #include "console.h" #include "g_game.h" #include "r_local.h" #include "st_stuff.h" #include "w_wad.h" #include "z_zone.h" #include "m_misc.h" #include "info.h" // spr2names #include "i_video.h" // rendermode #include "i_system.h" #include "r_things.h" #include "r_skins.h" #include "p_local.h" #include "dehacked.h" // get_number (for thok) #include "m_cond.h" #include "k_kart.h" #include "m_random.h" #include "s_sound.h" #if 0 #include "k_kart.h" // K_KartResetPlayerColor #endif #include "k_grandprix.h" // K_CanChangeRules #include "discord.h" #ifdef HWRENDER #include "hardware/hw_md2.h" #endif INT32 numskins = 0; skin_t skins[MAXSKINS]; unloaded_skin_t *unloadedskins = NULL; // FIXTHIS: don't work because it must be inistilised before the config load //#define SKINVALUES #ifdef SKINVALUES CV_PossibleValue_t skin_cons_t[MAXSKINS+1]; #endif CV_PossibleValue_t Forceskin_cons_t[MAXSKINS+2]; // // P_GetSkinSprite2 // For non-super players, tries each sprite2's immediate predecessor until it finds one with a number of frames or ends up at standing. // For super players, does the same as above - but tries the super equivalent for each sprite2 before the non-super version. // UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player) { UINT8 super = 0, i = 0; (void)player; if (!skin) return 0; if ((playersprite_t)(spr2 & ~FF_SPR2SUPER) >= free_spr2) return 0; while (!skin->sprites[spr2].numframes && spr2 != SPR2_STIL && ++i < 32) // recursion limiter { if (spr2 & FF_SPR2SUPER) { super = FF_SPR2SUPER; spr2 &= ~FF_SPR2SUPER; continue; } switch(spr2) { // Normal special cases. // (none in kart) // Use the handy list, that's what it's there for! default: spr2 = spr2defaults[spr2]; break; } spr2 |= super; } if (i >= 32) // probably an infinite loop... return 0; return spr2; } static void Sk_SetDefaultValue(skin_t *skin) { INT32 i; // // set default skin values // memset(skin, 0, sizeof (skin_t)); snprintf(skin->name, sizeof skin->name, "skin %u", (UINT32)(skin-skins)); skin->name[sizeof skin->name - 1] = '\0'; skin->wadnum = INT16_MAX; skin->flags = 0; strcpy(skin->realname, "Someone"); skin->starttranscolor = 96; skin->prefcolor = SKINCOLOR_GREEN; skin->supercolor = SKINCOLOR_SUPERGOLD1; skin->prefoppositecolor = 0; // use tables skin->kartspeed = 5; skin->kartweight = 5; skin->followitem = 0; skin->highresscale = FRACUNIT; // no specific memset for skinrecord_t as it's already nuked by the full skin_t wipe for (i = 0; i < sfx_skinsoundslot0; i++) if (S_sfx[i].skinsound != -1) skin->soundsid[S_sfx[i].skinsound] = i; } // Grab the default skin #define DEFAULTBOTSKINNAME "eggrobo" UINT8 R_BotDefaultSkin(void) { static INT32 defaultbotskin = -1; if (demo.playback) return R_SkinAvailableEx(DEFAULTBOTSKINNAME, true); if (defaultbotskin == -1) { defaultbotskin = R_SkinAvailableEx(DEFAULTBOTSKINNAME, false); if (defaultbotskin == -1) { // This shouldn't happen, but just in case defaultbotskin = 0; } } return (UINT8)defaultbotskin; } #undef DEFAULTBOTSKINNAME // // Initialize the basic skins // void R_InitSkins(void) { size_t i; // it can be is do before loading config for skin cvar possible value // (... what the fuck did you just say to me? "it can be is do"?) #ifdef SKINVALUES for (i = 0; i <= MAXSKINS; i++) { skin_cons_t[i].value = 0; skin_cons_t[i].strvalue = NULL; } #endif // no default skin! numskins = 0; for (i = 0; i < numwadfiles; i++) { R_AddSkins((UINT16)i, true); R_PatchSkins((UINT16)i, true); R_LoadSpriteInfoLumps(i, wadfiles[i]->numlumps); #ifdef HAVE_DISCORDRPC if (i == mainwads) { g_discord_skins = numskins; } #endif } ST_ReloadSkinFaceGraphics(); M_UpdateConditionSetsPending(); } UINT8 *R_GetSkinAvailabilities(boolean demolock, INT32 botforcecharacter) { UINT16 i; UINT8 shif, byte; INT32 skinid; static UINT8 responsebuffer[MAXAVAILABILITY]; const boolean forbots = (botforcecharacter != -1); memset(&responsebuffer, 0, sizeof(responsebuffer)); for (i = 0; i < MAXUNLOCKABLES; i++) { if (unlockables[i].type != SECRET_SKIN) continue; skinid = M_UnlockableSkinNum(&unlockables[i]); if (skinid < 0 || skinid >= MAXSKINS) continue; if ((forbots ? (M_CheckNetUnlockByID(i) || skinid == botforcecharacter) // Assert the host's lock. : gamedata->unlocked[i]) // Assert the local lock. != true && !demolock) continue; shif = (skinid % 8); byte = (skinid / 8); responsebuffer[byte] |= (1 << shif); } 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 demoskins) { boolean needsunlocked = false; boolean useplayerstruct = ((Playing() || demo.playback) && playernum >= 0); UINT16 i; INT32 skinid; if (skinnum == -1) { // Simplifies things elsewhere, since there's already plenty of checks for less-than-0... return true; } if (K_CanChangeRules(true) && (cv_forceskin.value == skinnum)) { // Being forced to play as this character by the server return true; } if (gametype == GT_TUTORIAL) { // Being forced to play as this character by the tutorial return true; } // Determine if this character is supposed to be unlockable or not if (useplayerstruct && demo.playback) { 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 == 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 host's if it's checking general state if (playernum == -1) return M_CheckNetUnlockByID(i); // Use the unlockables table directly return (boolean)(gamedata->unlocked[i]); } boolean R_CanShowSkinInDemo(INT32 skinnum) { if (modeattacking == ATTACKING_NONE && !(demo.playback && demo.attract)) return true; return R_SkinUsable(-2, skinnum, false); } // Returns a random unlocked skin ID. UINT32 R_GetLocalRandomSkin(void) { UINT8 i, usableskins = 0; UINT8 grabskins[MAXSKINS]; for (i = 0; i < numskins; i++) { if (!R_SkinUsable(-2, i, false)) continue; grabskins[usableskins++] = i; } if (!usableskins) I_Error("R_GetLocalRandomSkin: No valid skins to pick from!?"); return grabskins[M_RandomKey(usableskins)]; } // returns true if the skin name is found (loaded from pwad) // warning return -1 if not found INT32 R_SkinAvailable(const char *name) { return R_SkinAvailableEx(name, true); } INT32 R_SkinAvailableEx(const char *name, boolean demoskins) { INT32 i; UINT32 hash = quickncasehash(name, SKINNAMESIZE); if (demo.playback && demoskins) { for (i = 0; i < demo.numskins; i++) { if (demo.skinlist[i].namehash != hash) continue; if (stricmp(demo.skinlist[i].name,name)!=0) continue; return i; } } for (i = 0; i < numskins; i++) { if (skins[i].namehash != hash) continue; if (stricmp(skins[i].name,name)!=0) continue; return i; } return -1; } // Returns engine class dependent on skin properties engineclass_t R_GetEngineClass(SINT8 speed, SINT8 weight, skinflags_t flags) { if (flags & SF_IRONMAN) return ENGINECLASS_J; speed = (speed - 1) / 3; weight = (weight - 1) / 3; #define LOCKSTAT(stat) \ if (stat < 0) { stat = 0; } \ if (stat > 2) { stat = 2; } LOCKSTAT(speed); LOCKSTAT(weight); #undef LOCKSTAT return (speed + (3*weight)); } // Auxillary function that actually sets the skin static void SetSkin(player_t *player, INT32 skinnum) { if (demo.playback) skinnum = demo.skinlist[skinnum].mapping; skin_t *skin = &skins[skinnum]; player->skin = skinnum; player->followitem = skin->followitem; player->kartspeed = skin->kartspeed; player->kartweight = skin->kartweight; player->charflags = skin->flags; #if 0 if (!CV_CheatsEnabled() && !(netgame || multiplayer || demo.playback)) { for (i = 0; i <= r_splitscreen; i++) { if (playernum == g_localplayers[i]) { CV_StealthSetValue(&cv_playercolor[i], skin->prefcolor); } } player->skincolor = skin->prefcolor; K_KartResetPlayerColor(player); } #endif if (player->followmobj) { P_RemoveMobj(player->followmobj); P_SetTarget(&player->followmobj, NULL); } if (player->mo) { player->mo->skin = skin; P_SetScale(player->mo, player->mo->scale); P_SetPlayerMobjState(player->mo, player->mo->state-states); // Prevent visual errors when switching between skins with differing number of frames } // for replays: We have changed our skin mid-game; let the game know so it can do the same in the replay! demo_extradata[(player-players)] |= DXD_SKIN; #ifdef HAVE_DISCORDRPC if (player - players == consoleplayer) DRPC_UpdatePresence(); #endif } // 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, skincount = (demo.playback ? demo.numskins : numskins); for (i = 0; i < skincount; 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 void SetPlayerSkin(INT32 playernum, const char *skinname) { INT32 i = R_SkinAvailable(skinname); player_t *player = &players[playernum]; if ((i != -1) && R_SkinUsable(playernum, i, false)) { SetSkin(player, i); return; } if (P_IsPartyPlayer(player)) CONS_Alert(CONS_WARNING, M_GetText("Skin '%s' not found.\n"), 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, GetPlayerDefaultSkin(playernum)); } // Same as SetPlayerSkin, but uses the skin #. // network code calls this when a 'skin change' is received void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum) { player_t *player = &players[playernum]; if (skinnum >= 0 && skinnum < numskins && R_SkinUsable(playernum, skinnum, false)) // Make sure it exists! { SetSkin(player, skinnum); return; } if (P_IsPartyPlayer(player)) CONS_Alert(CONS_WARNING, M_GetText("Requested skin %d not found\n"), 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, GetPlayerDefaultSkin(playernum)); // not found put the eggman skin } // Set mo skin but not player_t skin, for ironman void SetFakePlayerSkin(player_t* player, INT32 skinid) { if (player->fakeskin != skinid) { if (player->fakeskin != MAXSKINS) player->lastfakeskin = player->fakeskin; player->fakeskin = skinid; } if (demo.playback) { player->kartspeed = demo.skinlist[skinid].kartspeed; player->kartweight = demo.skinlist[skinid].kartweight; player->charflags = demo.skinlist[skinid].flags; skinid = demo.skinlist[skinid].mapping; } else { player->kartspeed = skins[skinid].kartspeed; player->kartweight = skins[skinid].kartweight; player->charflags = skins[skinid].flags; } player->mo->skin = &skins[skinid]; } // Loudly rerandomize void SetRandomFakePlayerSkin(player_t* player, boolean fast, boolean instant) { INT32 i; UINT8 usableskins = 0, maxskinpick; UINT8 grabskins[MAXSKINS]; maxskinpick = (demo.playback ? demo.numskins : numskins); for (i = 0; i < maxskinpick; i++) { if (i == player->lastfakeskin) continue; if (demo.playback) { if (demo.skinlist[i].flags & SF_IRONMAN) continue; } else if (skins[i].flags & SF_IRONMAN) 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); if (instant) return; if (player->mo && player->spectator == false && !(player->pflags & PF_VOID)) { S_StartSound(player->mo, sfx_kc33); S_StartSound(player->mo, sfx_cdfm44); mobj_t *parent = player->mo; fixed_t baseangle = P_RandomRange(PR_DECORATION, 0, 359); INT32 j; for (j = 0; j < 6; j++) // 0-3 = sides, 4 = top, 5 = bottom { mobj_t *box = P_SpawnMobjFromMobj(parent, 0, 0, 0, MT_MAGICIANBOX); P_SetTarget(&box->target, parent); box->angle = FixedAngle((baseangle + j*90) * FRACUNIT); box->flags2 |= MF2_AMBUSH; box->renderflags |= parent->renderflags; if (fast) { box->extravalue1 = 10; // Rotation rate box->extravalue2 = 5*TICRATE/4; // Lifetime } else { box->extravalue1 = 1; box->extravalue2 = 3*TICRATE/2; } // cusval controls behavior that should run only once, like disappear FX and RF_DONTDRAW handling. // NB: Order of thinker execution matters here! // We want the other sides to inherit the player's "existing" RF_DONTDRAW before the last side writes to it. // See the MT_MAGICIANBOX thinker in p_mobj.c. if (j == 5) box->cusval = 1; else box->cusval = 0; if (j > 3) { P_SetMobjState(box, (j == 4) ? S_MAGICIANBOX_TOP : S_MAGICIANBOX_BOTTOM); box->renderflags |= RF_NOSPLATBILLBOARD; box->angle = FixedAngle(baseangle*FRACUNIT); } } K_SpawnMagicianParticles(player->mo, 10); } } // Return to base skin from an SF_IRONMAN randomization void ClearFakePlayerSkin(player_t* player) { UINT8 skinid; UINT32 flags; if (demo.playback) { skinid = demo.currentskinid[(player-players)]; flags = demo.skinlist[skinid].flags; } else { skinid = player->skin; flags = skins[player->skin].flags; } if ((flags & SF_IRONMAN) && !P_MobjWasRemoved(player->mo)) { SetFakePlayerSkin(player, skinid); if (player->spectator == false && player->mo->hitlag == 0) { S_StartSound(player->mo, sfx_s3k9f); K_SpawnMagicianParticles(player->mo, 5); } } player->fakeskin = MAXSKINS; } // Finds a skin with the closest stats if the expected skin doesn't exist. INT32 GetSkinNumClosestToStats(UINT8 kartspeed, UINT8 kartweight, UINT32 flags, boolean unlock) { INT32 i, closest_skin = 0; UINT8 closest_stats, stat_diff; boolean doflagcheck = true; UINT32 flagcheck = flags; flaglessretry: closest_stats = stat_diff = UINT8_MAX; for (i = 0; i < numskins; i++) { if (!unlock && !R_SkinUsable(-1, i, false)) { continue; } stat_diff = abs(skins[i].kartspeed - kartspeed) + abs(skins[i].kartweight - kartweight); if (doflagcheck && (skins[i].flags & flagcheck) != flagcheck) { continue; } if (stat_diff < closest_stats) { closest_stats = stat_diff; closest_skin = i; } } if (stat_diff && (doflagcheck || closest_stats == UINT8_MAX)) { // Just grab *any* SF_IRONMAN if we don't get it on the first pass. if ((flagcheck & SF_IRONMAN) && (flagcheck != SF_IRONMAN)) { flagcheck = SF_IRONMAN; } doflagcheck = false; goto flaglessretry; } return closest_skin; } // // Add skins from a pwad, each skin preceded by 'S_SKIN' marker // // Does the same is in w_wad, but check only for // the first 6 characters (this is so we can have S_SKIN1, S_SKIN2.. // for wad editors that don't like multiple resources of the same name) // static UINT16 W_CheckForSkinMarkerInPwad(UINT16 wadid, UINT16 startlump) { UINT16 i; const char *S_SKIN = "S_SKIN"; lumpinfo_t *lump_p; // scan forward, start at if (startlump < wadfiles[wadid]->numlumps) { lump_p = wadfiles[wadid]->lumpinfo + startlump; for (i = startlump; i < wadfiles[wadid]->numlumps; i++, lump_p++) if (memcmp(lump_p->name,S_SKIN,6)==0) return i; } return INT16_MAX; // not found } // turn _ into spaces and . into katana dot #define SYMBOLCONVERT(name) for (value = name; *value; value++)\ {\ if (*value == '_') *value = ' ';\ } // // Patch skins from a pwad, each skin preceded by 'P_SKIN' marker // // Does the same is in w_wad, but check only for // the first 6 characters (this is so we can have P_SKIN1, P_SKIN2.. // for wad editors that don't like multiple resources of the same name) // static UINT16 W_CheckForPatchSkinMarkerInPwad(UINT16 wadid, UINT16 startlump) { UINT16 i; const char *P_SKIN = "P_SKIN"; lumpinfo_t *lump_p; // scan forward, start at if (startlump < wadfiles[wadid]->numlumps) { lump_p = wadfiles[wadid]->lumpinfo + startlump; for (i = startlump; i < wadfiles[wadid]->numlumps; i++, lump_p++) if (memcmp(lump_p->name,P_SKIN,6)==0) return i; } return INT16_MAX; // not found } static void R_LoadSkinSprites(UINT16 wadnum, UINT16 *lump, UINT16 *lastlump, skin_t *skin) { UINT16 newlastlump; UINT8 sprite2; *lump += 1; // start after S_SKIN *lastlump = W_CheckNumForNamePwad("S_END",wadnum,*lump); // stop at S_END // old wadding practices die hard -- stop at S_SKIN (or P_SKIN) or S_START if they come before S_END. newlastlump = W_FindNextEmptyInPwad(wadnum,*lump); if (newlastlump < *lastlump) *lastlump = newlastlump; newlastlump = W_CheckForSkinMarkerInPwad(wadnum,*lump); if (newlastlump < *lastlump) *lastlump = newlastlump; newlastlump = W_CheckForPatchSkinMarkerInPwad(wadnum,*lump); if (newlastlump < *lastlump) *lastlump = newlastlump; newlastlump = W_CheckNumForNamePwad("S_START",wadnum,*lump); if (newlastlump < *lastlump) *lastlump = newlastlump; /*// ...and let's handle super, too newlastlump = W_CheckNumForNamePwad("S_SUPER",wadnum,*lump); if (newlastlump < *lastlump) { newlastlump++; // load all sprite sets we are aware of... for super! for (sprite2 = 0; sprite2 < free_spr2; sprite2++) R_AddSingleSpriteDef(spr2names[sprite2], &skin->sprites[FF_SPR2SUPER|sprite2], wadnum, newlastlump, *lastlump); newlastlump--; *lastlump = newlastlump; // okay, make the normal sprite set loading end there }*/ // load all sprite sets we are aware of... for normal stuff. for (sprite2 = 0; sprite2 < free_spr2; sprite2++) R_AddSingleSpriteDef(spr2names[sprite2], &skin->sprites[sprite2], wadnum, *lump, *lastlump); if (skin->sprites[0].numframes == 0) I_Error("R_LoadSkinSprites: no frames found for sprite SPR2_%s\n", spr2names[0]); } // returns whether found appropriate property static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value) { if (!stricmp(stoken, "rivals")) { size_t len = strlen(value); size_t i; char rivalname[SKINNAMESIZE+1] = ""; UINT8 pos = 0; UINT8 numrivals = 0; // Can't use strtok, because the above function's already using it. // Using it causes it to upset the saved pointer, // corrupting the reading for the rest of the file. // So instead we get to crawl through the value, character by character, // and write it down as we go, until we hit a comma or the end of the string. // Yaaay. for (i = 0; i <= len; i++) { if (numrivals >= SKINRIVALS) { break; } if (value[i] == ',' || i == len) { if (pos == 0) continue; STRBUFCPY(skin->rivals[numrivals], rivalname); strlwr(skin->rivals[numrivals]); numrivals++; if (i == len) break; for (; pos > 0; pos--) { rivalname[pos] = '\0'; } continue; } rivalname[pos] = value[i]; pos++; } } // custom translation table else if (!stricmp(stoken, "startcolor")) { skin->starttranscolor = atoi(value); } #define FULLPROCESS(field) else if (!stricmp(stoken, #field)) skin->field = get_number(value); // character type identification FULLPROCESS(flags) FULLPROCESS(followitem) #undef FULLPROCESS #define GETSKINCOLOR(field) else if (!stricmp(stoken, #field)) \ { \ UINT16 color = R_GetColorByName(value); \ skin->field = (color ? color : SKINCOLOR_GREEN); \ } GETSKINCOLOR(prefcolor) GETSKINCOLOR(prefoppositecolor) #undef GETSKINCOLOR else if (!stricmp(stoken, "supercolor")) { UINT16 color = R_GetSuperColorByName(value); skin->supercolor = (color ? color : SKINCOLOR_SUPERGOLD1); } #define GETFLOAT(field) else if (!stricmp(stoken, #field)) skin->field = FLOAT_TO_FIXED(atof(value)); GETFLOAT(highresscale) #undef GETFLOAT #define GETKARTSTAT(field) \ else if (!stricmp(stoken, #field)) \ { \ skin->field = atoi(value); \ if (skin->field < 1) skin->field = 1; \ if (skin->field > 9) skin->field = 9; \ } GETKARTSTAT(kartspeed) GETKARTSTAT(kartweight) #undef GETKARTSTAT #define GETFLAG(field) else if (!stricmp(stoken, #field)) { \ strupr(value); \ if (atoi(value) || value[0] == 'T' || value[0] == 'Y') \ skin->flags |= (SF_##field); \ else \ skin->flags &= ~(SF_##field); \ } // parameters for individual character flags // these are uppercase so they can be concatenated with SF_ // 1, true, yes are all valid values GETFLAG(MACHINE) GETFLAG(IRONMAN) GETFLAG(BADNIK) #undef GETFLAG else // let's check if it's a sound, otherwise error out { boolean found = false; sfxenum_t i; size_t stokenadjust; // Remove the prefix. (We need to affect an adjusting variable so that we can print error messages if it's not actually a sound.) if ((stoken[0] == 'D' || stoken[0] == 'd') && (stoken[1] == 'S' || stoken[1] == 's')) // DS* stokenadjust = 2; else // sfx_* stokenadjust = 4; // Remove the prefix. (We can affect this directly since we're not going to use it again.) if ((value[0] == 'D' || value[0] == 'd') && (value[1] == 'S' || value[1] == 's')) // DS* value += 2; else // sfx_* value += 4; // copy name of sounds that are remapped // for this skin for (i = 0; i < sfx_skinsoundslot0; i++) { if (!S_sfx[i].name) continue; if (S_sfx[i].skinsound != -1 && !stricmp(S_sfx[i].name, stoken + stokenadjust)) { skin->soundsid[S_sfx[i].skinsound] = S_AddSoundFx(value, S_sfx[i].singularity, S_sfx[i].pitch, true); found = true; } } return found; } return true; } // // Find skin sprites, sounds & optional status bar face, & add them // void R_AddSkins(UINT16 wadnum, boolean mainfile) { UINT16 lump, lastlump = 0; char *buf; char *buf2; char *stoken; char *value; size_t size; skin_t *skin; boolean realname; // // search for all skin markers in pwad // while ((lump = W_CheckForSkinMarkerInPwad(wadnum, lastlump)) != INT16_MAX) { // advance by default lastlump = lump + 1; if (numskins >= MAXSKINS) { CONS_Debug(DBG_RENDER, "ignored skin (%d skins maximum)\n", MAXSKINS); continue; // so we know how many skins couldn't be added } buf = W_CacheLumpNumPwad(wadnum, lump, PU_CACHE); size = W_LumpLengthPwad(wadnum, lump); // for strtok buf2 = malloc(size+1); if (!buf2) I_Error("R_AddSkins: No more free memory\n"); M_Memcpy(buf2,buf,size); buf2[size] = '\0'; // set defaults skin = &skins[numskins]; Sk_SetDefaultValue(skin); skin->wadnum = wadnum; realname = false; // parse stoken = strtok (buf2, "\r\n= "); while (stoken) { if ((stoken[0] == '/' && stoken[1] == '/') || (stoken[0] == '#'))// skip comments { stoken = strtok(NULL, "\r\n"); // skip end of line goto next_token; // find the real next token } value = strtok(NULL, "\r\n= "); if (!value) I_Error("R_AddSkins: syntax error in S_SKIN lump# %d(%s) in WAD %s\n", lump, W_CheckNameForNumPwad(wadnum,lump), wadfiles[wadnum]->filename); // Some of these can't go in R_ProcessPatchableFields because they have side effects for future lines. // Others can't go in there because we don't want them to be patchable. if (!stricmp(stoken, "name")) { INT32 skinnum = R_SkinAvailableEx(value, false); strlwr(value); if (skinnum == -1) STRBUFCPY(skin->name, value); // the skin name must uniquely identify a single skin // if the name is already used I make the name 'namex' // using the default skin name's number set above else { const size_t stringspace = strlen(value) + sizeof (numskins) + 1; char *value2 = Z_Malloc(stringspace, PU_STATIC, NULL); snprintf(value2, stringspace, "%s%d", value, numskins); value2[stringspace - 1] = '\0'; if (R_SkinAvailableEx(value2, false) == -1) // I'm lazy so if NEW name is already used I leave the 'skin x' // default skin name set in Sk_SetDefaultValue STRBUFCPY(skin->name, value2); Z_Free(value2); } // copy to hudname and fullname as a default. if (!realname) { STRBUFCPY(skin->realname, skin->name); SYMBOLCONVERT(skin->realname); } } else if (!stricmp(stoken, "realname")) { // Display name (eg. "Knuckles") realname = true; STRBUFCPY(skin->realname, value); SYMBOLCONVERT(skin->realname) } else if (!R_ProcessPatchableFields(skin, stoken, value)) CONS_Debug(DBG_SETUP, "R_AddSkins: Unknown keyword '%s' in S_SKIN lump #%d (WAD %s)\n", stoken, lump, wadfiles[wadnum]->filename); next_token: stoken = strtok(NULL, "\r\n= "); } free(buf2); // Add sprites R_LoadSkinSprites(wadnum, &lump, &lastlump, skin); //ST_LoadFaceGraphics(numskins); -- nah let's do this elsewhere R_FlushTranslationColormapCache(); if (mainfile == false) CONS_Printf(M_GetText("Added skin '%s'\n"), skin->name); #ifdef SKINVALUES skin_cons_t[numskins].value = numskins; skin_cons_t[numskins].strvalue = skin->name; #endif // Update the forceskin possiblevalues Forceskin_cons_t[numskins+1].value = numskins; Forceskin_cons_t[numskins+1].strvalue = skins[numskins].name; #ifdef HWRENDER if (rendermode == render_opengl) HWR_AddPlayerModel(numskins); #endif // Finally, conclude by setting up final properties. skin->namehash = quickncasehash(skin->name, SKINNAMESIZE); { // Check to see if we have any custom skin wins data that we could substitute in. unloaded_skin_t *unloadedskin, *unloadedprev = NULL; for (unloadedskin = unloadedskins; unloadedskin; unloadedprev = unloadedskin, unloadedskin = unloadedskin->next) { if (unloadedskin->namehash != skin->namehash) continue; if (strcasecmp(skin->name, unloadedskin->name) != 0) continue; // Copy in wins, etc. M_Memcpy(&skin->records, &unloadedskin->records, sizeof(skin->records)); // Remove this entry from the chain. if (unloadedprev) { unloadedprev->next = unloadedskin->next; } else { unloadedskins = unloadedskin->next; } // Now... we assign everything which used this pointer the new skin id. UINT8 i; cupheader_t *cup; for (cup = kartcupheaders; cup; cup = cup->next) { for (i = 0; i < KARTGP_MAX; i++) { if (cup->windata[i].best_skin.unloaded != unloadedskin) continue; cup->windata[i].best_skin.id = numskins; cup->windata[i].best_skin.unloaded = NULL; } } unloaded_cupheader_t *unloadedcup; for (unloadedcup = unloadedcupheaders; unloadedcup; unloadedcup = unloadedcup->next) { for (i = 0; i < KARTGP_MAX; i++) { if (unloadedcup->windata[i].best_skin.unloaded != unloadedskin) continue; unloadedcup->windata[i].best_skin.id = numskins; unloadedcup->windata[i].best_skin.unloaded = NULL; } } // Finally, free. Z_Free(unloadedskin); break; } } numskins++; } return; } // // Patch skin sprites // void R_PatchSkins(UINT16 wadnum, boolean mainfile) { UINT16 lump, lastlump = 0; char *buf; char *buf2; char *stoken; char *value; size_t size; skin_t *skin; boolean noskincomplain, realname; // // search for all skin patch markers in pwad // while ((lump = W_CheckForPatchSkinMarkerInPwad(wadnum, lastlump)) != INT16_MAX) { INT32 skinnum = 0; // advance by default lastlump = lump + 1; buf = W_CacheLumpNumPwad(wadnum, lump, PU_CACHE); size = W_LumpLengthPwad(wadnum, lump); // for strtok buf2 = malloc(size+1); if (!buf2) I_Error("R_PatchSkins: No more free memory\n"); M_Memcpy(buf2,buf,size); buf2[size] = '\0'; skin = NULL; noskincomplain = realname = false; /* Parse. Has more phases than the parser in R_AddSkins because it needs to have the patching name first (no default skin name is acceptible for patching, unlike skin creation) */ stoken = strtok(buf2, "\r\n= "); while (stoken) { if ((stoken[0] == '/' && stoken[1] == '/') || (stoken[0] == '#'))// skip comments { stoken = strtok(NULL, "\r\n"); // skip end of line goto next_token; // find the real next token } value = strtok(NULL, "\r\n= "); if (!value) I_Error("R_PatchSkins: syntax error in P_SKIN lump# %d(%s) in WAD %s\n", lump, W_CheckNameForNumPwad(wadnum,lump), wadfiles[wadnum]->filename); if (!skin) // Get the name! { if (!stricmp(stoken, "name")) { strlwr(value); skinnum = R_SkinAvailableEx(value, false); if (skinnum != -1) skin = &skins[skinnum]; else { CONS_Debug(DBG_SETUP, "R_PatchSkins: unknown skin name in P_SKIN lump# %d(%s) in WAD %s\n", lump, W_CheckNameForNumPwad(wadnum,lump), wadfiles[wadnum]->filename); noskincomplain = true; } } } else // Get the properties! { // Some of these can't go in R_ProcessPatchableFields because they have side effects for future lines. if (!stricmp(stoken, "realname")) { // Display name (eg. "Knuckles") realname = true; STRBUFCPY(skin->realname, value); SYMBOLCONVERT(skin->realname) } else if (!R_ProcessPatchableFields(skin, stoken, value)) CONS_Debug(DBG_SETUP, "R_PatchSkins: Unknown keyword '%s' in P_SKIN lump #%d (WAD %s)\n", stoken, lump, wadfiles[wadnum]->filename); } if (!skin) break; next_token: stoken = strtok(NULL, "\r\n= "); } free(buf2); if (!skin) // Didn't include a name parameter? What a waste. { if (!noskincomplain) CONS_Debug(DBG_SETUP, "R_PatchSkins: no skin name given in P_SKIN lump #%d (WAD %s)\n", lump, wadfiles[wadnum]->filename); continue; } // Patch sprites R_LoadSkinSprites(wadnum, &lump, &lastlump, skin); //ST_LoadFaceGraphics(skinnum); -- nah let's do this elsewhere R_FlushTranslationColormapCache(); if (mainfile == false) CONS_Printf(M_GetText("Patched skin '%s'\n"), skin->name); } return; } #undef SYMBOLCONVERT