1566 lines
39 KiB
C
1566 lines
39 KiB
C
// BLANKART
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 1993-1996 by id Software, Inc.
|
|
// Copyright (C) 1998-2000 by DooM Legacy Team.
|
|
// Copyright (C) 1999-2020 by Sonic Team Junior.
|
|
//
|
|
// 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 "d_main.h" // MAINWAD_CHARS
|
|
#if 0
|
|
#include "k_kart.h" // K_KartResetPlayerColor
|
|
#endif
|
|
#include "qs22j.h"
|
|
#ifdef HWRENDER
|
|
#include "hardware/hw_md2.h"
|
|
#endif
|
|
|
|
#include "k_grandprix.h"
|
|
|
|
#include "discord.h"
|
|
|
|
INT32 numskins = 0;
|
|
skin_t skins[MAXSKINS];
|
|
INT32 skinsorted[MAXSKINS];
|
|
|
|
// 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;
|
|
}
|
|
|
|
// converts the kart player frame in inframe to the equivalent sprite2 + frame
|
|
UINT8 P_KartFrameToSprite2(skin_t *skin, UINT8 inframe, UINT8 *outframe)
|
|
{
|
|
UINT8 spr2, frame = 0;
|
|
#define F(x) case x - 'A':
|
|
switch (inframe & FF_FRAMEMASK)
|
|
{
|
|
F('A') F('B')
|
|
spr2 = SPR2_STIN;
|
|
frame = inframe & 1;
|
|
break;
|
|
F('C') F('D')
|
|
spr2 = SPR2_STIL;
|
|
frame = inframe & 1;
|
|
break;
|
|
F('E') F('F')
|
|
spr2 = SPR2_STIR;
|
|
frame = inframe & 1;
|
|
break;
|
|
F('G')
|
|
spr2 = SPR2_SLWN;
|
|
frame = 1;
|
|
break;
|
|
F('H')
|
|
spr2 = SPR2_SLWL;
|
|
frame = 1;
|
|
break;
|
|
F('I')
|
|
spr2 = SPR2_SLWR;
|
|
frame = 1;
|
|
break;
|
|
F('J')
|
|
spr2 = SPR2_FSTN;
|
|
frame = 1;
|
|
break;
|
|
F('K')
|
|
spr2 = SPR2_FSTL;
|
|
frame = 1;
|
|
break;
|
|
F('L')
|
|
spr2 = SPR2_FSTR;
|
|
frame = 1;
|
|
break;
|
|
F('M') F('N')
|
|
spr2 = SPR2_DRLN;
|
|
frame = inframe & 1;
|
|
break;
|
|
F('O') F('P')
|
|
spr2 = SPR2_DRRN;
|
|
frame = inframe & 1;
|
|
break;
|
|
F('Q')
|
|
spr2 = SPR2_SPIN;
|
|
break;
|
|
F('R')
|
|
spr2 = SPR2_KART;
|
|
break;
|
|
F('S')
|
|
spr2 = SPR2_SIGN;
|
|
break;
|
|
default:
|
|
spr2 = SPR2_KART;
|
|
frame = (inframe & FF_FRAMEMASK) - 18; // not 19! frame 0 is squish
|
|
break;
|
|
}
|
|
#undef F
|
|
if (outframe)
|
|
*outframe = frame;
|
|
return P_GetSkinSprite2(skin, spr2, NULL);
|
|
}
|
|
|
|
// returns the sound flags that should be applied to a voice's sound array
|
|
static INT32 R_GetVoiceArraySoundFlags(const kartvoice_t *voice, const sfxenum_t *array)
|
|
{
|
|
if (array == &voice->overtake || array == &voice->hitem)
|
|
return SF_NOINTERRUPT;
|
|
else if (array == &voice->power)
|
|
return SF_NOINTERRUPT|SF_X8AWAYSOUND;
|
|
else
|
|
return SF_NOINTERRUPT|SF_X2AWAYSOUND;
|
|
}
|
|
|
|
// parses the name of a voice sound array from S_SKIN without the "Voice" prefix
|
|
// returns the length and pointer to the matching voice sound array, or 0 if nothing matched
|
|
static size_t R_ParseVoiceSoundKey(kartvoice_t *voice, const char *name, sfxenum_t **outarray)
|
|
{
|
|
// woah! comma operator!
|
|
#define S(str, ret) if (fasticmp(name, #str)) return *outarray = voice->ret, sizeof(voice->ret)/sizeof(*voice->ret)
|
|
#define S1(str, ret) if (fasticmp(name, #str)) return *outarray = &voice->ret, 1
|
|
S1(WIN, win);
|
|
S1(LOSE, lose);
|
|
S(HURT, pain);
|
|
S(PAIN, pain);
|
|
S(DAMAGE, pain);
|
|
S(ATTACK, attack);
|
|
S(BOOST, boost);
|
|
S1(SLOW, overtake);
|
|
S1(OVERTAKE, overtake);
|
|
S1(PASS, overtake);
|
|
S1(HITEM, hitem);
|
|
S1(HITCONFIRM, hitem);
|
|
S1(GLOAT, power);
|
|
#undef S
|
|
|
|
return 0;
|
|
}
|
|
|
|
sfxenum_t R_GetLegacySkinSound(const kartvoice_t *voice, sfxenum_t sound)
|
|
{
|
|
switch (sound)
|
|
{
|
|
case sfx_kwin: // Win quote
|
|
return voice->win;
|
|
case sfx_klose: // Lose quote
|
|
return voice->lose;
|
|
case sfx_khurt1: // Pain
|
|
case sfx_khurt2:
|
|
return voice->pain[sound - sfx_khurt1];
|
|
case sfx_kattk1: // Offense item taunt
|
|
case sfx_kattk2:
|
|
return voice->attack[sound - sfx_kattk1];
|
|
case sfx_kbost1: // Boost item taunt
|
|
case sfx_kbost2:
|
|
return voice->boost[sound - sfx_kbost1];
|
|
case sfx_kslow: // Overtake taunt
|
|
return voice->overtake;
|
|
case sfx_khitem: // Hit confirm taunt
|
|
return voice->hitem;
|
|
case sfx_kgloat: // Power item taunt
|
|
return voice->power;
|
|
default:
|
|
return sfx_thok;
|
|
}
|
|
}
|
|
|
|
static void Sk_SetDefaultValue(skin_t *skin)
|
|
{
|
|
//
|
|
// 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");
|
|
strncpy(skin->facerank, "MISSING", 9);
|
|
strncpy(skin->facewant, "MISSING", 9);
|
|
strncpy(skin->facemmap, "MISSING", 9);
|
|
skin->starttranscolor = 96;
|
|
skin->prefcolor = SKINCOLOR_GREEN;
|
|
skin->supercolor = SKINCOLOR_SUPER1;
|
|
skin->prefoppositecolor = 0; // use tables
|
|
|
|
skin->kartspeed = 5;
|
|
skin->kartweight = 5;
|
|
|
|
skin->followitem = 0;
|
|
|
|
skin->highresscale = FRACUNIT;
|
|
}
|
|
|
|
static void R_IHateThatHedgehog(UINT16 wadnum);
|
|
|
|
//
|
|
// 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
|
|
|
|
// doesn't seem right but it's what the original did
|
|
memset(skinsorted, 0, sizeof(skinsorted));
|
|
// yes default skin!
|
|
numskins = 1;
|
|
|
|
for (i = 0; i < numwadfiles; i++)
|
|
{
|
|
if (i == MAINWAD_CHARS)
|
|
R_IHateThatHedgehog((UINT16)i);
|
|
R_AddSkins((UINT16)i);
|
|
if (!wadfiles[i]->compatmode)
|
|
R_PatchSkins((UINT16)i);
|
|
R_LoadSpriteInfoLumps(i, wadfiles[i]->numlumps);
|
|
|
|
#ifdef HAVE_DISCORDRPC
|
|
if (i < NUMMAINWADS)
|
|
g_discord_skins = numskins;
|
|
#endif
|
|
}
|
|
ST_ReloadSkinFaceGraphics();
|
|
}
|
|
|
|
UINT8 *R_GetSkinAvailabilities(void)
|
|
{
|
|
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)
|
|
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 responsebuffer;
|
|
}
|
|
|
|
//
|
|
// From callmore's skin select
|
|
// Sort function(s) for sorting skin names
|
|
//
|
|
static int CompareSkinIds(const void *a, const void *b)
|
|
{
|
|
const INT32 val_a = *((const INT32 *)a);
|
|
const INT32 val_b = *((const INT32 *)b);
|
|
if (val_a > val_b)
|
|
return 1;
|
|
else if (val_a < val_b)
|
|
return -1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int CompareSkinNames(const void *a, const void *b)
|
|
{
|
|
const skin_t *in1 = &skins[*(const INT32 *)a];
|
|
const skin_t *in2 = &skins[*(const INT32 *)b];
|
|
return strcmp(in1->realname, in2->realname);
|
|
}
|
|
|
|
static int CompareSkinSpeeds(const void *a, const void *b)
|
|
{
|
|
const skin_t *in1 = &skins[*(const INT32 *)a];
|
|
const skin_t *in2 = &skins[*(const INT32 *)b];
|
|
INT32 temp = 0;
|
|
// check speed
|
|
if (in1->kartspeed < in2->kartspeed)
|
|
return -1;
|
|
else if (in2->kartspeed < in1->kartspeed)
|
|
return 1;
|
|
// then check weight
|
|
if (in1->kartweight < in2->kartweight)
|
|
return -1;
|
|
else if (in2->kartweight < in1->kartweight)
|
|
return 1;
|
|
// then check name
|
|
if ((temp = strcmp(in1->realname, in2->realname)))
|
|
return temp;
|
|
// sort by internal name
|
|
return strcmp(in1->name, in2->name);
|
|
}
|
|
|
|
static int CompareSkinWeights(const void *a, const void *b)
|
|
{
|
|
const skin_t *in1 = &skins[*(const INT32 *)a];
|
|
const skin_t *in2 = &skins[*(const INT32 *)b];
|
|
INT32 temp = 0;
|
|
// check weight
|
|
if (in1->kartweight < in2->kartweight)
|
|
return -1;
|
|
else if (in2->kartweight < in1->kartweight)
|
|
return 1;
|
|
// then check speed
|
|
if (in1->kartspeed < in2->kartspeed)
|
|
return -1;
|
|
else if (in2->kartspeed < in1->kartspeed)
|
|
return 1;
|
|
// then check name
|
|
if ((temp = strcmp(in1->realname, in2->realname)))
|
|
return temp;
|
|
// sort by internal name
|
|
return strcmp(in1->name, in2->name);
|
|
}
|
|
|
|
static int CompareSkinAccels(const void *a, const void *b)
|
|
{
|
|
return CompareSkinSpeeds(b, a);
|
|
}
|
|
|
|
static int CompareSkinHandlings(const void *a, const void *b)
|
|
{
|
|
return CompareSkinWeights(b, a);
|
|
}
|
|
|
|
static int CompareSkinColours(const void *a, const void *b)
|
|
{
|
|
const skin_t *in1 = &skins[*(const INT32 *)a];
|
|
const skin_t *in2 = &skins[*(const INT32 *)b];
|
|
INT32 temp = 0;
|
|
|
|
// check prefcolor
|
|
if (in1->prefcolor < in2->prefcolor)
|
|
return -1;
|
|
else if (in2->prefcolor < in1->prefcolor)
|
|
return 1;
|
|
// then check name
|
|
if ((temp = strcmp(in1->realname, in2->realname)))
|
|
return temp;
|
|
// sort by internal name
|
|
return strcmp(in1->name, in2->name);
|
|
}
|
|
|
|
void SortSkins(void)
|
|
{
|
|
int (*_sortingFunc)(const void *, const void *);
|
|
switch (cv_skinselectsort.value)
|
|
{
|
|
case SKINMENUSORT_NAME:
|
|
_sortingFunc = CompareSkinNames;
|
|
break;
|
|
case SKINMENUSORT_SPEED:
|
|
_sortingFunc = CompareSkinSpeeds;
|
|
break;
|
|
case SKINMENUSORT_WEIGHT:
|
|
_sortingFunc = CompareSkinWeights;
|
|
break;
|
|
case SKINMENUSORT_ACCEL:
|
|
_sortingFunc = CompareSkinAccels;
|
|
break;
|
|
case SKINMENUSORT_HANDLING:
|
|
_sortingFunc = CompareSkinHandlings;
|
|
break;
|
|
case SKINMENUSORT_PREFCOLOR:
|
|
_sortingFunc = CompareSkinColours;
|
|
break;
|
|
default:
|
|
_sortingFunc = CompareSkinIds;
|
|
break;
|
|
}
|
|
qs22j(skinsorted, numskins, sizeof(INT32), _sortingFunc);
|
|
}
|
|
|
|
INT32 FindSortedSkinIndex(INT32 skinnum)
|
|
{
|
|
for (INT32 i = 0; i < numskins; i++)
|
|
{
|
|
if (skinsorted[i] == skinnum) return i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// 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 needsunlocked = false;
|
|
boolean useplayerstruct = (Playing() && playernum != -1);
|
|
UINT8 i;
|
|
INT32 skinid;
|
|
|
|
if (skinnum == -1)
|
|
{
|
|
// Simplifies things elsewhere, since there's already plenty of checks for less-than-0...
|
|
return true;
|
|
}
|
|
|
|
if (modeattacking)
|
|
{
|
|
// If you have someone else's run, you should be able to take a look
|
|
return true;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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)
|
|
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 unlockables table directly
|
|
return (boolean)(unlockables[i].unlocked);
|
|
}
|
|
|
|
// returns true if the skin name is found (loaded from pwad)
|
|
// warning return -1 if not found
|
|
INT32 R_SkinAvailable(const char *name)
|
|
{
|
|
INT32 i;
|
|
|
|
for (i = 0; i < numskins; i++)
|
|
{
|
|
// search in the skin list
|
|
if (fasticmp(skins[i].name,name))
|
|
return i;
|
|
}
|
|
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)
|
|
{
|
|
INT32 i = R_SkinAvailable(skinname);
|
|
player_t *player = &players[playernum];
|
|
|
|
if ((i != -1) && R_SkinUsable(playernum, i))
|
|
{
|
|
SetPlayerSkinByNum(playernum, i);
|
|
return;
|
|
}
|
|
|
|
if (P_IsLocalPlayer(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);
|
|
|
|
SetPlayerSkinByNum(playernum, 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];
|
|
skin_t *skin = &skins[skinnum];
|
|
//UINT16 newcolor = 0;
|
|
//UINT8 i;
|
|
|
|
if (skinnum >= 0 && skinnum < numskins && R_SkinUsable(playernum, skinnum)) // Make sure it exists!
|
|
{
|
|
player->skin = skinnum;
|
|
|
|
player->charflags = (UINT32)skin->flags;
|
|
|
|
player->followitem = skin->followitem;
|
|
|
|
if (player->kartspeedrestat != 0 || player->kartweightrestat != 0)
|
|
{
|
|
player->kartspeed = player->kartspeedrestat;
|
|
player->kartweight = player->kartweightrestat;
|
|
}
|
|
else
|
|
{
|
|
player->kartspeed = skin->kartspeed;
|
|
player->kartweight = skin->kartweight;
|
|
}
|
|
|
|
#if 0
|
|
if (!(cht_debug || devparm) && !(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 = newcolor = skin->prefcolor;
|
|
K_KartResetPlayerColor(player);
|
|
}
|
|
#endif
|
|
|
|
if (player->followmobj)
|
|
{
|
|
P_RemoveMobj(player->followmobj);
|
|
P_SetTarget(&player->followmobj, NULL);
|
|
}
|
|
|
|
if (player->mo)
|
|
{
|
|
R_ApplySkin(player->mo, 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[playernum] |= DXD_SKIN;
|
|
|
|
#ifdef HAVE_DISCORDRPC
|
|
if (player - players == consoleplayer)
|
|
DRPC_UpdatePresence();
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
if (P_IsLocalPlayer(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);
|
|
|
|
SetPlayerSkinByNum(playernum, GetPlayerDefaultSkin(playernum)); // not found, put in the default skin
|
|
}
|
|
|
|
// Gets the corresponding voice ID for a given voice's name.
|
|
// Returns MAXSKINVOICES if nothing is found.
|
|
INT32 R_FindIDForVoice(skin_t *skin, const char* voicename)
|
|
{
|
|
INT32 i;
|
|
|
|
// Scan through our allocated skin voices for the wanted voice.
|
|
|
|
// First pass: Check the actual name.
|
|
for (i = 0; i < skin->numvoices; i++)
|
|
{
|
|
if (strnicmp(voicename, skin->voices[i].name, VOICENAMESIZE) == 0)
|
|
{
|
|
// Found a voice.
|
|
return i;
|
|
}
|
|
}
|
|
|
|
// Second pass: check the display name.
|
|
for (i = 0; i < skin->numvoices; i++)
|
|
{
|
|
// Whatever. Go, my code duplication!
|
|
if (strnicmp(voicename, skin->voices[i].realname, VOICENAMESIZE) == 0)
|
|
{
|
|
// Found a voice.
|
|
return i;
|
|
}
|
|
}
|
|
|
|
// Couldn't find a voice.
|
|
return MAXSKINVOICES;
|
|
}
|
|
|
|
// network code calls this when a 'voice change' is received
|
|
void SetPlayerVoice(INT32 playernum, const char *voicename)
|
|
{
|
|
INT32 voxid = R_FindIDForVoice(&skins[players[playernum].skin], voicename);
|
|
|
|
if (voxid == MAXSKINVOICES)
|
|
{
|
|
// Couldn't find a voice. Default to the skin's default.
|
|
voxid = 0;
|
|
}
|
|
|
|
SetPlayerVoiceByNum(playernum, voxid);
|
|
}
|
|
|
|
// network code calls this when a 'voice change' is received
|
|
void SetPlayerVoiceByNum(INT32 playernum, UINT16 voicenum)
|
|
{
|
|
player_t* player = &players[playernum];
|
|
|
|
if (player->voice_id == voicenum)
|
|
{
|
|
// Hey... this voice is the same as before!
|
|
return;
|
|
}
|
|
|
|
player->voice_id = voicenum;
|
|
|
|
if (!P_MobjWasRemoved(player->mo))
|
|
player->mo->voice = &skins[player->skin].voices[voicenum];
|
|
|
|
// Tell the demo that we've updated our voice_id as well.
|
|
demo_extradata[playernum] |= DXD_SKIN;
|
|
}
|
|
|
|
/** \brief Applies the skin and its default voice.
|
|
|
|
\param mo (mobj_t) the given object the skin and voice are being assigned to
|
|
\param skin (skin_t) the skin to attach to the object. The voice is sourced from this skin
|
|
*/
|
|
void R_ApplySkin(mobj_t *mo, skin_t *skin)
|
|
{
|
|
mo->skin = skin;
|
|
mo->voice = &((skin_t *)mo->skin)->voices[0];
|
|
}
|
|
|
|
/** \brief Applies only the skin.
|
|
|
|
\param mo (mobj_t) the given object the skin is being assigned to
|
|
\param skin (skin_t) the skin to attach to the object
|
|
*/
|
|
void R_ApplySkinOnly(mobj_t *mo, skin_t *skin)
|
|
{
|
|
mo->skin = 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 <startlump>
|
|
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 <startlump>
|
|
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
|
|
}
|
|
|
|
#define NUMKARTFRAMES 19
|
|
#define S(f, s) { f - 'A', SPR2_##s },
|
|
const UINT8 kart2spr2[26][2] = {
|
|
S('A', STIN)
|
|
S('B', STIN)
|
|
S('C', STIL)
|
|
S('D', STIL)
|
|
S('E', STIR)
|
|
S('F', STIR)
|
|
S('J', SLWN)
|
|
S('G', SLWN)
|
|
S('K', SLWL)
|
|
S('H', SLWL)
|
|
S('L', SLWR)
|
|
S('I', SLWR)
|
|
S('A', FSTN)
|
|
S('J', FSTN)
|
|
S('C', FSTL)
|
|
S('K', FSTL)
|
|
S('E', FSTR)
|
|
S('L', FSTR)
|
|
S('M', DRLN)
|
|
S('N', DRLN)
|
|
S('O', DRRN)
|
|
S('P', DRRN)
|
|
S('Q', SPIN)
|
|
S('Q', DEAD)
|
|
S('R', KART)
|
|
S('S', SIGN)
|
|
};
|
|
#undef S
|
|
|
|
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
|
|
*lastlump = min(*lastlump, wadfiles[wadnum]->numlumps); // or end of WAD
|
|
|
|
// 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
|
|
}*/
|
|
|
|
boolean sonic = fastcmp(skin->name, "sonic") && wadnum == MAINWAD_MAIN;
|
|
|
|
if (!wadfiles[wadnum]->compatmode && !sonic)
|
|
{
|
|
// 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);
|
|
}
|
|
else
|
|
{
|
|
// okay, now we have to get fancy...
|
|
// first, just dump all the frames in a temporary def
|
|
size_t i;
|
|
spritedef_t *sd, tmp = sonic ? sprites[SPR_PLAY] : (spritedef_t){0};
|
|
spriteframe_t *sf;
|
|
if (!sonic)
|
|
{
|
|
const char *sprname = W_CheckNameForNumPwad(wadnum, *lump);
|
|
for (newlastlump = *lump; newlastlump < *lastlump; newlastlump++)
|
|
if (memcmp(W_CheckNameForNumPwad(wadnum, newlastlump), sprname, 4))
|
|
break;
|
|
*lastlump = newlastlump;
|
|
R_AddSingleSpriteDef(sprname, &tmp, wadnum, *lump, *lastlump);
|
|
}
|
|
|
|
if (tmp.numframes < NUMKARTFRAMES)
|
|
I_Error("R_LoadSkinSprites: too few frames for kart skin");
|
|
|
|
// now shuffle them around to fit the new SPR2 system
|
|
// iterate over kart2spr2, then add any excess frames to SPR2_KART
|
|
#define SPR2COUNT sizeof(kart2spr2)/sizeof(*kart2spr2)
|
|
for (i = 0; i < SPR2COUNT - NUMKARTFRAMES + tmp.numframes; i++)
|
|
{
|
|
boolean excess = i >= SPR2COUNT;
|
|
sf = &tmp.spriteframes[excess ? i - SPR2COUNT + NUMKARTFRAMES : kart2spr2[i][0]];
|
|
sd = &skin->sprites[excess ? SPR2_KART : kart2spr2[i][1]];
|
|
sd->spriteframes = Z_Realloc(sd->spriteframes, sizeof(*sd->spriteframes) * (sd->numframes+1), PU_STATIC, NULL);
|
|
sd->spriteframes[sd->numframes++] = *sf;
|
|
}
|
|
if (!sonic)
|
|
Z_Free(tmp.spriteframes);
|
|
#undef SPR2COUNT
|
|
|
|
// now we have to rotate the drift frames. i hate this.
|
|
sd = &skin->sprites[SPR2_DRRN];
|
|
for (i = 0; i < sd->numframes; i++)
|
|
{
|
|
sf = &sd->spriteframes[i];
|
|
if (sf->rotate != SRF_3D)
|
|
continue;
|
|
lumpnum_t lastpat = sf->lumppat[7];
|
|
size_t lastid = sf->lumpid[7];
|
|
for (int r = 7; r >= 1; r--)
|
|
{
|
|
sf->lumppat[r] = sf->lumppat[r-1];
|
|
sf->lumpid[r] = sf->lumpid[r-1];
|
|
}
|
|
sf->lumppat[0] = lastpat;
|
|
sf->lumpid[0] = lastid;
|
|
sf->flip = (sf->flip & 0xff00) | ((sf->flip & 0x80) >> 7) | ((sf->flip & 0x7f) << 1);
|
|
}
|
|
sd = &skin->sprites[SPR2_DRLN];
|
|
for (i = 0; i < sd->numframes; i++)
|
|
{
|
|
sf = &sd->spriteframes[i];
|
|
if (sf->rotate != SRF_3D)
|
|
continue;
|
|
lumpnum_t firstpat = sf->lumppat[0];
|
|
size_t firstid = sf->lumpid[0];
|
|
for (int r = 0; r < 8; r++)
|
|
{
|
|
sf->lumppat[r] = sf->lumppat[r+1];
|
|
sf->lumpid[r] = sf->lumpid[r+1];
|
|
}
|
|
sf->lumppat[7] = firstpat;
|
|
sf->lumpid[7] = firstid;
|
|
sf->flip = (sf->flip & 0xff00) | ((sf->flip & 0x01) << 7) | ((sf->flip & 0xfe) >> 1);
|
|
}
|
|
|
|
R_AddKartFaces(skin);
|
|
|
|
skin->flags |= SF_OLDDEATH;
|
|
}
|
|
|
|
if (skin->sprites[0].numframes == 0)
|
|
I_Error("R_LoadSkinSprites: no frames found for sprite SPR2_%s\n", spr2names[0]);
|
|
}
|
|
#undef NUMKARTFRAMES
|
|
|
|
static void R_IHateThatHedgehog(UINT16 wadnum)
|
|
{
|
|
skin_t *skin = &skins[0];
|
|
Sk_SetDefaultValue(skin);
|
|
skin->wadnum = wadnum;
|
|
strcpy(skin->name, "sonic");
|
|
|
|
#ifdef SKINVALUES
|
|
skin_cons_t[0].value = 0;
|
|
skin_cons_t[0].strvalue = skin->name;
|
|
#endif
|
|
Forceskin_cons_t[1].value = 0;
|
|
Forceskin_cons_t[1].strvalue = skin->name;
|
|
|
|
#ifdef HWRENDER
|
|
if (rendermode == render_opengl)
|
|
HWR_AddPlayerModel(0);
|
|
#endif
|
|
}
|
|
|
|
static kartvoice_t *allocvoice;
|
|
|
|
//
|
|
// Allocates a voice onto the dehacked list, and iterates the provided output pointer
|
|
// (should it exist).
|
|
//
|
|
static INT32 R_AllocKartVoice(skin_t *skin, const char* name)
|
|
{
|
|
INT32 vox_id = R_FindIDForVoice(skin, name);
|
|
if (vox_id != MAXSKINVOICES)
|
|
return vox_id; // already allocated
|
|
|
|
if (skin->numvoices == MAXSKINVOICES - 1)
|
|
I_Error("Skin %s: ran out of free voice slots!", skin->name);
|
|
|
|
kartvoice_t *voice = &skin->voices[skin->numvoices];
|
|
strlcpy(voice->name, name, sizeof(voice->name));
|
|
strlwr(voice->name);
|
|
|
|
voice->id = skin->numvoices;
|
|
|
|
voice->win = sfx_thok;
|
|
voice->lose = sfx_thok;
|
|
voice->pain[0] = voice->pain[1] = sfx_thok;
|
|
voice->attack[0] = voice->attack[1] = sfx_thok;
|
|
voice->boost[0] = voice->boost[1] = sfx_thok;
|
|
voice->overtake = sfx_thok;
|
|
voice->hitem = sfx_thok;
|
|
voice->power = sfx_thok;
|
|
|
|
CONS_Printf("Voice '%s' allocated for skin '%s'.\n", voice->name, skin->name);
|
|
|
|
return skin->numvoices++;
|
|
}
|
|
|
|
// returns whether found appropriate property
|
|
static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value)
|
|
{
|
|
boolean compat = wadfiles[skin->wadnum]->compatmode;
|
|
|
|
if (fasticmp(stoken, "rivals"))
|
|
{
|
|
size_t len = strlen(value);
|
|
size_t i;
|
|
char rivalname[SKINNAMESIZE+1] = {0};
|
|
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;
|
|
}
|
|
|
|
if (pos > SKINNAMESIZE)
|
|
{
|
|
CONS_Alert(CONS_ERROR, "Rival names for skin %s are too long.\n", skin->name);
|
|
break;
|
|
}
|
|
|
|
rivalname[pos] = value[i];
|
|
pos++;
|
|
}
|
|
}
|
|
|
|
// custom translation table
|
|
else if (fasticmp(stoken, "startcolor"))
|
|
skin->starttranscolor = compat ? R_GetPaletteRemap(atoi(value)) : atoi(value);
|
|
|
|
#define FULLPROCESS(field) else if (fasticmp(stoken, #field)) skin->field = get_number(value);
|
|
// character type identification
|
|
FULLPROCESS(flags)
|
|
FULLPROCESS(followitem)
|
|
#undef FULLPROCESS
|
|
|
|
#define GETSKINCOLOR(field) else if (fasticmp(stoken, #field)) \
|
|
{ \
|
|
UINT16 color = R_GetColorByName(value); \
|
|
skin->field = (color ? color : SKINCOLOR_GREEN); \
|
|
}
|
|
GETSKINCOLOR(prefcolor)
|
|
GETSKINCOLOR(prefoppositecolor)
|
|
#undef GETSKINCOLOR
|
|
else if (fasticmp(stoken, "supercolor"))
|
|
{
|
|
UINT16 color = R_GetSuperColorByName(value);
|
|
skin->supercolor = (color ? color : SKINCOLOR_SUPER1);
|
|
}
|
|
|
|
#define GETFLOAT(field) else if (fasticmp(stoken, #field)) skin->field = FLOAT_TO_FIXED(atof(value));
|
|
GETFLOAT(highresscale)
|
|
#undef GETFLOAT
|
|
|
|
#define GETKARTSTAT(field) \
|
|
else if (fasticmp(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 (fasticmp(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(OLDDEATH)
|
|
#undef GETFLAG
|
|
|
|
#define GETPATCH(field) else if (compat && fasticmp(stoken, #field)) strncpy(skin->field, value, 8);
|
|
GETPATCH(facerank)
|
|
GETPATCH(facewant)
|
|
GETPATCH(facemmap)
|
|
#undef GETPATCH
|
|
|
|
else if (fasticmp(stoken, "voicename"))
|
|
{
|
|
// Change the current voice.
|
|
allocvoice = &skin->voices[R_AllocKartVoice(skin, value)];
|
|
}
|
|
else if (fasticmp(stoken, "voicerealname"))
|
|
{
|
|
if (allocvoice == NULL)
|
|
I_Error("Skin %s: cannot set voice realname without a voice selected", skin->name);
|
|
|
|
// Set the "realname" field for the current voice.
|
|
STRBUFCPY(allocvoice->realname, value);
|
|
SYMBOLCONVERT(allocvoice->realname);
|
|
}
|
|
else if (!strnicmp(stoken, "voice", 5))
|
|
{
|
|
// (that one gif of patrick drooling where his mouth is his entire face)
|
|
size_t len = strlen(value);
|
|
size_t i;
|
|
char voicename[VOICENAMESIZE] = "";
|
|
UINT8 pos = 0;
|
|
UINT8 numvoices = 0;
|
|
|
|
if (allocvoice == NULL)
|
|
I_Error("Skin %s: cannot set voice sounds without a voice selected", skin->name);
|
|
|
|
sfxenum_t *voicearray;
|
|
size_t voicearraylen = R_ParseVoiceSoundKey(allocvoice, stoken+5, &voicearray);
|
|
if (voicearraylen == 0)
|
|
return false;
|
|
|
|
for (i = 0; i <= len; i++)
|
|
{
|
|
if (value[i] == ',' || i == len)
|
|
{
|
|
// Automatically allocate any defined voice sounds.
|
|
// Freeslotted voice clips won't ever belong to a skin.
|
|
sfxenum_t j;
|
|
const char *sfxname = voicename;
|
|
if (toupper(voicename[0]) == 'D' && toupper(voicename[1]) == 'S')
|
|
sfxname += 2;
|
|
|
|
j = S_AddSoundFx(sfxname, true);
|
|
if (j == sfx_None)
|
|
I_Error("Skin %s: out of skin sound slots", skin->name);
|
|
S_sfx[j].flags = R_GetVoiceArraySoundFlags(allocvoice, voicearray);
|
|
|
|
voicearray[numvoices++] = j;
|
|
if (numvoices >= voicearraylen && i != len)
|
|
I_Error("Skin %s: voice array %s exceeds max length of %zu", skin->name, stoken, voicearraylen);
|
|
|
|
memset(voicename, 0, sizeof (voicename));
|
|
pos = 0;
|
|
|
|
continue;
|
|
}
|
|
|
|
voicename[pos] = value[i];
|
|
pos++;
|
|
}
|
|
}
|
|
else // let's check if it's a legacy skinsound, otherwise error out
|
|
{
|
|
if (fasticmp(stoken, "DSKTALK"))
|
|
return true; // SILENCE!
|
|
|
|
sfxenum_t i;
|
|
for (i = sfx_kwin; i <= sfx_kgloat; i++)
|
|
if (fasticmp(stoken+2, S_sfx[i].name))
|
|
break;
|
|
if (i > sfx_kgloat)
|
|
return false;
|
|
|
|
if (allocvoice == NULL)
|
|
{
|
|
allocvoice = &skin->voices[R_AllocKartVoice(skin, "default")];
|
|
strcpy(allocvoice->realname, "Default");
|
|
}
|
|
else if (!fastcmp(allocvoice->name, "default"))
|
|
{
|
|
// an incentive to read the wiki page
|
|
I_Error("Skin %s: cannot use legacy skinsounds to define voices", skin->name);
|
|
}
|
|
|
|
static const char *tonewname[] = {
|
|
"WIN",
|
|
"LOSE",
|
|
"PAIN",
|
|
"PAIN",
|
|
"ATTACK",
|
|
"ATTACK",
|
|
"BOOST",
|
|
"BOOST",
|
|
"SLOW",
|
|
"HITEM",
|
|
"GLOAT",
|
|
};
|
|
|
|
sfxenum_t *voicearray;
|
|
size_t len = R_ParseVoiceSoundKey(allocvoice, tonewname[i - sfx_kwin], &voicearray);
|
|
if (len == 0)
|
|
return false;
|
|
|
|
// grab the flags now before messing with the pointer
|
|
INT32 flags = R_GetVoiceArraySoundFlags(allocvoice, voicearray);
|
|
|
|
if (i == sfx_khurt2 || i == sfx_kattk2 || i == sfx_kbost2)
|
|
voicearray++;
|
|
|
|
*voicearray = S_AddSoundFx(value+2, true);
|
|
if (*voicearray != sfx_None)
|
|
S_sfx[*voicearray].flags = flags;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// Find skin sprites, sounds & optional status bar face, & add them
|
|
//
|
|
void R_AddSkins(UINT16 wadnum)
|
|
{
|
|
UINT16 lump, lastlump = 0;
|
|
char *buf;
|
|
char *buf2;
|
|
char *stoken;
|
|
char *value;
|
|
size_t size;
|
|
skin_t *skin;
|
|
boolean realname;
|
|
boolean iscompatskin = false;
|
|
|
|
//
|
|
// 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");
|
|
memcpy(buf2,buf,size);
|
|
buf2[size] = '\0';
|
|
|
|
// set defaults
|
|
skin = &skins[numskins];
|
|
allocvoice = NULL;
|
|
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
|
|
{
|
|
strtok(NULL, "\r\n"); // skip end of line
|
|
stoken = strtok(NULL, "\r\n= ");
|
|
continue; // 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 (fasticmp(stoken, "name"))
|
|
{
|
|
INT32 skinnum = R_SkinAvailable(value);
|
|
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_SkinAvailable(value2) == -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 (fasticmp(stoken, "realname"))
|
|
{ // Display name (eg. "Knuckles")
|
|
realname = true;
|
|
STRBUFCPY(skin->realname, value);
|
|
SYMBOLCONVERT(skin->realname)
|
|
}
|
|
else if (!R_ProcessPatchableFields(skin, stoken, value))
|
|
CONS_Alert(CONS_WARNING, "R_AddSkins: Unknown keyword '%s' in S_SKIN lump #%d (WAD %s)\n", stoken, lump, wadfiles[wadnum]->filename);
|
|
|
|
stoken = strtok(NULL, "\r\n= ");
|
|
}
|
|
free(buf2);
|
|
|
|
if (skin->numvoices == 0)
|
|
I_Error("Skin %s has no voices!", skin->name);
|
|
|
|
// Add sprites
|
|
R_LoadSkinSprites(wadnum, &lump, &lastlump, skin);
|
|
//ST_LoadFaceGraphics(numskins); -- nah let's do this elsewhere
|
|
|
|
R_FlushTranslationColormapCache();
|
|
|
|
iscompatskin = wadfiles[skin->wadnum]->compatmode;
|
|
|
|
CONS_Printf(M_GetText("Added skin '%s' %s\n"), skin->name, iscompatskin ? "in compatibility mode" : "");
|
|
|
|
#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
|
|
|
|
skinsorted[numskins] = numskins;
|
|
numskins++;
|
|
}
|
|
SortSkins();
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Patch skin sprites
|
|
//
|
|
void R_PatchSkins(UINT16 wadnum)
|
|
{
|
|
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");
|
|
memcpy(buf2,buf,size);
|
|
buf2[size] = '\0';
|
|
|
|
skin = NULL;
|
|
allocvoice = 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
|
|
{
|
|
strtok(NULL, "\r\n"); // skip end of line
|
|
stoken = strtok(NULL, "\r\n= ");
|
|
continue; // 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 (fasticmp(stoken, "name"))
|
|
{
|
|
strlwr(value);
|
|
skinnum = R_SkinAvailable(value);
|
|
if (skinnum != -1)
|
|
{
|
|
skin = &skins[skinnum];
|
|
}
|
|
else
|
|
{
|
|
CONS_Alert(CONS_WARNING, "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 (fasticmp(stoken, "realname"))
|
|
{ // Display name (eg. "Knuckles")
|
|
realname = true;
|
|
STRBUFCPY(skin->realname, value);
|
|
SYMBOLCONVERT(skin->realname)
|
|
}
|
|
else if (fasticmp(stoken, "rivals"))
|
|
{
|
|
size_t len = strlen(value);
|
|
size_t i;
|
|
char rivalname[SKINNAMESIZE] = "";
|
|
UINT8 pos = 0;
|
|
UINT8 numrivals = 0;
|
|
|
|
// Can't use strtok, because this 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)
|
|
{
|
|
STRBUFCPY(skin->rivals[numrivals], rivalname);
|
|
strlwr(skin->rivals[numrivals]);
|
|
numrivals++;
|
|
|
|
memset(rivalname, 0, sizeof (rivalname));
|
|
pos = 0;
|
|
|
|
continue;
|
|
}
|
|
|
|
rivalname[pos] = value[i];
|
|
pos++;
|
|
}
|
|
}
|
|
else if (!R_ProcessPatchableFields(skin, stoken, value))
|
|
CONS_Alert(CONS_WARNING, "R_PatchSkins: Unknown keyword '%s' in P_SKIN lump #%d (WAD %s)\n", stoken, lump, wadfiles[wadnum]->filename);
|
|
}
|
|
|
|
if (!skin)
|
|
break;
|
|
|
|
stoken = strtok(NULL, "\r\n= ");
|
|
}
|
|
free(buf2);
|
|
|
|
if (!skin) // Didn't include a name parameter? What a waste.
|
|
{
|
|
if (!noskincomplain)
|
|
CONS_Alert(CONS_WARNING, "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();
|
|
|
|
CONS_Printf(M_GetText("Patched skin '%s'\n"), skin->name);
|
|
}
|
|
return;
|
|
}
|
|
|
|
#undef SYMBOLCONVERT
|