gzdoom/src/r_data/sprites.cpp

1061 lines
26 KiB
C++
Raw Normal View History

//-----------------------------------------------------------------------------
//
// Copyright 1993-1996 id Software
// Copyright 1999-2016 Randy Heit
// Copyright 2005-2016 Christoph Oelckers
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
//-----------------------------------------------------------------------------
//
2016-03-01 15:47:10 +00:00
#include "doomtype.h"
#include "w_wad.h"
#include "i_system.h"
#include "s_sound.h"
#include "c_console.h"
#include "d_player.h"
#include "d_netinf.h"
#include "gi.h"
#include "colormatcher.h"
#include "c_dispatch.h"
#include "r_defs.h"
#include "v_text.h"
#include "r_data/sprites.h"
#include "r_data/voxels.h"
#include "textures/textures.h"
#include "vm.h"
2016-03-01 15:47:10 +00:00
void gl_InitModels();
2016-03-01 15:47:10 +00:00
// variables used to look up
// and range check thing_t sprites patches
TArray<spritedef_t> sprites;
TArray<spriteframe_t> SpriteFrames;
uint32_t NumStdSprites; // The first x sprites that don't belong to skins.
2016-03-01 15:47:10 +00:00
struct spriteframewithrotate : public spriteframe_t
{
int rotate;
2016-12-08 20:05:53 +00:00
};
2016-03-01 15:47:10 +00:00
// [RH] skin globals
TArray<FPlayerSkin> Skins;
2017-03-08 17:47:52 +00:00
uint8_t OtherGameSkinRemap[256];
2016-03-01 15:47:10 +00:00
PalEntry OtherGameSkinPalette[256];
//===========================================================================
//
// Gets the texture index for a sprite frame
//
//===========================================================================
FTextureID spritedef_t::GetSpriteFrame(int frame, int rot, DAngle ang, bool *mirror)
{
if ((unsigned)frame >= numframes)
{
// If there are no frames at all for this sprite, don't draw it.
return FNullTextureID();
}
else
{
// choose a different rotation based on player view
spriteframe_t *sprframe = &SpriteFrames[spriteframes + frame];
if (rot == -1)
{
if (sprframe->Texture[0] == sprframe->Texture[1])
{
rot = (ang + 45.0 / 2 * 9).BAMs() >> 28;
}
else
{
rot = (ang + (45.0 / 2 * 9 - 180.0 / 16)).BAMs() >> 28;
}
}
if (mirror) *mirror = !!(sprframe->Flip&(1 << rot));
return sprframe->Texture[rot];
}
}
2016-03-01 15:47:10 +00:00
//
// R_InstallSpriteLump
// Local function for R_InitSprites.
//
// [RH] Removed checks for coexistance of rotation 0 with other
// rotations and made it look more like BOOM's version.
//
2016-12-08 20:05:53 +00:00
static bool R_InstallSpriteLump (FTextureID lump, unsigned frame, char rot, bool flipped, spriteframewithrotate *sprtemp, int &maxframe)
2016-03-01 15:47:10 +00:00
{
unsigned rotation;
if (rot >= '0' && rot <= '9')
{
rotation = rot - '0';
}
else if (rot >= 'A')
{
rotation = rot - 'A' + 10;
}
else
{
rotation = 17;
}
if (frame >= MAX_SPRITE_FRAMES || rotation > 16)
{
Printf (TEXTCOLOR_RED "R_InstallSpriteLump: Bad frame characters in lump %s\n", TexMan[lump]->Name.GetChars());
2016-03-01 15:47:10 +00:00
return false;
}
if ((int)frame > maxframe)
maxframe = frame;
if (rotation == 0)
{
// the lump should be used for all rotations
// false=0, true=1, but array initialised to -1
// allows doom to have a "no value set yet" boolean value!
int r;
for (r = 14; r >= 0; r -= 2)
{
if (!sprtemp[frame].Texture[r].isValid())
{
sprtemp[frame].Texture[r] = lump;
if (flipped)
{
sprtemp[frame].Flip |= 1 << r;
}
sprtemp[frame].rotate = false;
}
}
}
else
{
if (rotation <= 8)
{
rotation = (rotation - 1) * 2;
}
else
{
rotation = (rotation - 9) * 2 + 1;
}
if (!sprtemp[frame].Texture[rotation].isValid())
{
// the lump is only used for one rotation
sprtemp[frame].Texture[rotation] = lump;
if (flipped)
{
sprtemp[frame].Flip |= 1 << rotation;
}
sprtemp[frame].rotate = true;
}
}
return true;
}
// [RH] Seperated out of R_InitSpriteDefs()
2016-12-08 20:05:53 +00:00
static void R_InstallSprite (int num, spriteframewithrotate *sprtemp, int &maxframe)
2016-03-01 15:47:10 +00:00
{
int frame;
int framestart;
int rot;
// int undefinedFix;
if (maxframe == -1)
{
sprites[num].numframes = 0;
return;
}
maxframe++;
// [RH] If any frames are undefined, but there are some defined frames, map
// them to the first defined frame. This is a fix for Doom Raider, which actually
// worked with ZDoom 2.0.47, because of a bug here. It does not define frames A,
// B, or C for the sprite PSBG, but because I had sprtemp[].rotate defined as a
// bool, this code never detected that it was not actually present. After switching
// to the unified texture system, this caused it to crash while loading the wad.
// [RH] Let undefined frames actually be blank because LWM uses this in at least
// one of her wads.
// for (frame = 0; frame < maxframe && sprtemp[frame].rotate == -1; ++frame)
// { }
//
// undefinedFix = frame;
for (frame = 0; frame < maxframe; ++frame)
{
switch (sprtemp[frame].rotate)
{
case -1:
// no rotations were found for that frame at all
//I_FatalError ("R_InstallSprite: No patches found for %s frame %c", sprites[num].name, frame+'A');
break;
case 0:
// only the first rotation is needed
for (rot = 1; rot < 16; ++rot)
{
sprtemp[frame].Texture[rot] = sprtemp[frame].Texture[0];
}
// If the frame is flipped, they all should be
if (sprtemp[frame].Flip & 1)
{
sprtemp[frame].Flip = 0xFFFF;
}
break;
case 1:
// must have all 8 frame pairs
for (rot = 0; rot < 8; ++rot)
{
if (!sprtemp[frame].Texture[rot*2+1].isValid())
{
sprtemp[frame].Texture[rot*2+1] = sprtemp[frame].Texture[rot*2];
if (sprtemp[frame].Flip & (1 << (rot*2)))
{
sprtemp[frame].Flip |= 1 << (rot*2+1);
}
}
if (!sprtemp[frame].Texture[rot*2].isValid())
{
sprtemp[frame].Texture[rot*2] = sprtemp[frame].Texture[rot*2+1];
if (sprtemp[frame].Flip & (1 << (rot*2+1)))
{
sprtemp[frame].Flip |= 1 << (rot*2);
}
}
}
for (rot = 0; rot < 16; ++rot)
{
if (!sprtemp[frame].Texture[rot].isValid())
I_FatalError ("R_InstallSprite: Sprite %s frame %c is missing rotations",
sprites[num].name, frame+'A');
}
break;
}
}
for (frame = 0; frame < maxframe; ++frame)
{
if (sprtemp[frame].rotate == -1)
{
memset (&sprtemp[frame].Texture, 0, sizeof(sprtemp[0].Texture));
sprtemp[frame].Flip = 0;
sprtemp[frame].rotate = 0;
}
}
// allocate space for the frames present and copy sprtemp to it
sprites[num].numframes = maxframe;
2017-03-08 17:50:37 +00:00
sprites[num].spriteframes = uint16_t(framestart = SpriteFrames.Reserve (maxframe));
2016-03-01 15:47:10 +00:00
for (frame = 0; frame < maxframe; ++frame)
{
memcpy (SpriteFrames[framestart+frame].Texture, sprtemp[frame].Texture, sizeof(sprtemp[frame].Texture));
SpriteFrames[framestart+frame].Flip = sprtemp[frame].Flip;
SpriteFrames[framestart+frame].Voxel = sprtemp[frame].Voxel;
}
// Let the textures know about the rotations
for (frame = 0; frame < maxframe; ++frame)
{
if (sprtemp[frame].rotate == 1)
{
for (int rot = 0; rot < 16; ++rot)
{
TexMan[sprtemp[frame].Texture[rot]]->Rotations = framestart + frame;
}
}
}
}
//
// R_InitSpriteDefs
// Pass a null terminated list of sprite names
// (4 chars exactly) to be used.
// Builds the sprite rotation matrices to account
// for horizontally flipped sprites.
// Will report an error if the lumps are inconsistant.
// Only called at startup.
//
// Sprite lump names are 4 characters for the actor,
// a letter for the frame, and a number for the rotation.
// A sprite that is flippable will have an additional
// letter/number appended.
// The rotation character can be 0 to signify no rotations.
//
#define TEX_DWNAME(tex) MAKE_ID(tex->Name[0], tex->Name[1], tex->Name[2], tex->Name[3])
void R_InitSpriteDefs ()
{
struct Hasher
{
int Head, Next;
} *hashes;
struct VHasher
{
int Head, Next, Name, Spin;
char Frame;
} *vhashes;
unsigned int i, j, smax, vmax;
uint32_t intname;
2016-03-01 15:47:10 +00:00
2016-12-08 20:05:53 +00:00
spriteframewithrotate sprtemp[MAX_SPRITE_FRAMES];
2016-03-01 15:47:10 +00:00
// Create a hash table to speed up the process
smax = TexMan.NumTextures();
hashes = new Hasher[smax];
memset(hashes, -1, sizeof(Hasher)*smax);
2016-03-01 15:47:10 +00:00
for (i = 0; i < smax; ++i)
{
FTexture *tex = TexMan.ByIndex(i);
if (tex->UseType == FTexture::TEX_Sprite && strlen(tex->Name) >= 6)
{
size_t bucket = TEX_DWNAME(tex) % smax;
hashes[i].Next = hashes[bucket].Head;
hashes[bucket].Head = i;
}
}
// Repeat, for voxels
vmax = Wads.GetNumLumps();
vhashes = new VHasher[vmax];
memset(vhashes, -1, sizeof(VHasher)*vmax);
2016-03-01 15:47:10 +00:00
for (i = 0; i < vmax; ++i)
{
if (Wads.GetLumpNamespace(i) == ns_voxels)
{
char name[9];
size_t namelen;
int spin;
int sign;
Wads.GetLumpName(name, i);
name[8] = 0;
namelen = strlen(name);
if (namelen < 4)
{ // name is too short
continue;
}
if (name[4] != '\0' && name[4] != ' ' && (name[4] < 'A' || name[4] >= 'A' + MAX_SPRITE_FRAMES))
{ // frame char is invalid
continue;
}
spin = 0;
sign = 2; // 2 to convert from deg/halfsec to deg/sec
j = 5;
if (j < namelen && name[j] == '-')
{ // a minus sign is okay, but only before any digits
j++;
sign = -2;
}
for (; j < namelen; ++j)
{ // the remainder to the end of the name must be digits
if (name[j] >= '0' && name[j] <= '9')
{
spin = spin * 10 + name[j] - '0';
}
else
{
break;
}
}
if (j < namelen)
{ // the spin part is invalid
continue;
}
memcpy(&vhashes[i].Name, name, 4);
vhashes[i].Frame = name[4];
vhashes[i].Spin = spin * sign;
size_t bucket = vhashes[i].Name % vmax;
vhashes[i].Next = vhashes[bucket].Head;
vhashes[bucket].Head = i;
}
}
// scan all the lump names for each of the names, noting the highest frame letter.
for (i = 0; i < sprites.Size(); ++i)
{
memset (sprtemp, 0xFF, sizeof(sprtemp));
for (j = 0; j < MAX_SPRITE_FRAMES; ++j)
{
sprtemp[j].Flip = 0;
sprtemp[j].Voxel = NULL;
}
2016-12-08 20:05:53 +00:00
int maxframe = -1;
2016-03-01 15:47:10 +00:00
intname = sprites[i].dwName;
// scan the lumps, filling in the frames for whatever is found
int hash = hashes[intname % smax].Head;
while (hash != -1)
{
FTexture *tex = TexMan[hash];
if (TEX_DWNAME(tex) == intname)
{
2016-12-08 20:05:53 +00:00
bool res = R_InstallSpriteLump (FTextureID(hash), tex->Name[4] - 'A', tex->Name[5], false, sprtemp, maxframe);
2016-03-01 15:47:10 +00:00
if (tex->Name[6] && res)
2016-12-08 20:05:53 +00:00
R_InstallSpriteLump (FTextureID(hash), tex->Name[6] - 'A', tex->Name[7], true, sprtemp, maxframe);
2016-03-01 15:47:10 +00:00
}
hash = hashes[hash].Next;
}
// repeat, for voxels
hash = vhashes[intname % vmax].Head;
while (hash != -1)
{
VHasher *vh = &vhashes[hash];
if (vh->Name == (int)intname)
{
FVoxelDef *voxdef = R_LoadVoxelDef(hash, vh->Spin);
if (voxdef != NULL)
{
if (vh->Frame == ' ' || vh->Frame == '\0')
{ // voxel applies to every sprite frame
for (j = 0; j < MAX_SPRITE_FRAMES; ++j)
{
if (sprtemp[j].Voxel == NULL)
{
sprtemp[j].Voxel = voxdef;
}
}
maxframe = MAX_SPRITE_FRAMES-1;
}
else
{ // voxel applies to a specific frame
j = vh->Frame - 'A';
sprtemp[j].Voxel = voxdef;
maxframe = MAX<int>(maxframe, j);
}
}
}
hash = vh->Next;
}
2016-12-08 20:05:53 +00:00
R_InstallSprite ((int)i, sprtemp, maxframe);
2016-03-01 15:47:10 +00:00
}
delete[] hashes;
delete[] vhashes;
}
//==========================================================================
//
// R_ExtendSpriteFrames
//
// Extends a sprite so that it can hold the desired frame.
//
//==========================================================================
static void R_ExtendSpriteFrames(spritedef_t &spr, int frame)
{
unsigned int i, newstart;
if (spr.numframes >= ++frame)
{ // The sprite already has enough frames, so do nothing.
return;
}
if (spr.numframes == 0 || (spr.spriteframes + spr.numframes == SpriteFrames.Size()))
{ // Sprite's frames are at the end of the array, or it has no frames
// at all, so we can tack the new frames directly on to the end
// of the SpriteFrames array.
newstart = SpriteFrames.Reserve(frame - spr.numframes);
if (spr.numframes == 0)
{
2017-03-08 17:50:37 +00:00
spr.spriteframes = uint16_t(newstart);
2016-03-01 15:47:10 +00:00
}
}
else
{ // We need to allocate space for all the sprite's frames and copy
// the existing ones over to the new space. The old space will be
// lost.
newstart = SpriteFrames.Reserve(frame);
for (i = 0; i < spr.numframes; ++i)
{
SpriteFrames[newstart + i] = SpriteFrames[spr.spriteframes + i];
}
2017-03-08 17:50:37 +00:00
spr.spriteframes = uint16_t(newstart);
2016-03-01 15:47:10 +00:00
newstart += i;
}
// Initialize all new frames to 0.
memset(&SpriteFrames[newstart], 0, sizeof(spriteframe_t)*(frame - spr.numframes));
spr.numframes = frame;
}
//==========================================================================
//
// VOX_AddVoxel
//
// Sets a voxel for a single sprite frame.
//
//==========================================================================
void VOX_AddVoxel(int sprnum, int frame, FVoxelDef *def)
{
R_ExtendSpriteFrames(sprites[sprnum], frame);
SpriteFrames[sprites[sprnum].spriteframes + frame].Voxel = def;
}
// [RH]
// R_InitSkins
// Reads in everything applicable to a skin. The skins should have already
// been counted and had their identifiers assigned to namespaces.
//
#define NUMSKINSOUNDS 17
static const char *skinsoundnames[NUMSKINSOUNDS][2] =
{ // The *painXXX sounds must be the first four
{ "dsplpain", "*pain100" },
{ "dsplpain", "*pain75" },
{ "dsplpain", "*pain50" },
{ "dsplpain", "*pain25" },
{ "dsplpain", "*poison" },
{ "dsoof", "*grunt" },
{ "dsoof", "*land" },
{ "dspldeth", "*death" },
{ "dspldeth", "*wimpydeath" },
{ "dspdiehi", "*xdeath" },
{ "dspdiehi", "*crazydeath" },
{ "dsnoway", "*usefail" },
{ "dsnoway", "*puzzfail" },
{ "dsslop", "*gibbed" },
{ "dsslop", "*splat" },
{ "dspunch", "*fist" },
{ "dsjump", "*jump" }
};
/*
static int skinsorter (const void *a, const void *b)
2016-03-01 15:47:10 +00:00
{
return stricmp (((FPlayerSkin *)a)->name, ((FPlayerSkin *)b)->name);
}
*/
void R_InitSkins (void)
{
FSoundID playersoundrefs[NUMSKINSOUNDS];
spritedef_t temp;
int sndlumps[NUMSKINSOUNDS];
char key[65];
uint32_t intname, crouchname;
unsigned i;
2016-03-01 15:47:10 +00:00
int j, k, base;
int lastlump;
int aliasid;
bool remove;
PClassActor *basetype, *transtype;
2016-03-01 15:47:10 +00:00
key[sizeof(key)-1] = 0;
i = PlayerClasses.Size () - 1;
lastlump = 0;
for (j = 0; j < NUMSKINSOUNDS; ++j)
{
playersoundrefs[j] = skinsoundnames[j][1];
}
while ((base = Wads.FindLump ("S_SKIN", &lastlump, true)) != -1)
{
// The player sprite has 23 frames. This means that the S_SKIN
// marker needs a minimum of 23 lumps after it.
if (base >= Wads.GetNumLumps() - 23 || base == -1)
continue;
i++;
for (j = 0; j < NUMSKINSOUNDS; j++)
sndlumps[j] = -1;
Skins[i].namespc = Wads.GetLumpNamespace (base);
2016-03-01 15:47:10 +00:00
FScanner sc(base);
intname = 0;
crouchname = 0;
remove = false;
basetype = NULL;
transtype = NULL;
// Data is stored as "key = data".
while (sc.GetString ())
{
strncpy (key, sc.String, sizeof(key)-1);
if (!sc.GetString() || sc.String[0] != '=')
{
Printf (PRINT_BOLD, "Bad format for skin %d: %s\n", (int)i, key);
break;
}
sc.GetString ();
if (0 == stricmp (key, "name"))
{
Skins[i].Name = sc.String;
for (j = 0; (unsigned)j < i; j++)
2016-03-01 15:47:10 +00:00
{
if (Skins[i].Name.CompareNoCase(Skins[j].Name) == 0)
2016-03-01 15:47:10 +00:00
{
Skins[i].Name.Format("skin%u", i);
Printf (PRINT_BOLD, "Skin %s duplicated as %s\n", Skins[j].Name.GetChars(), Skins[i].Name.GetChars());
2016-03-01 15:47:10 +00:00
break;
}
}
}
else if (0 == stricmp (key, "sprite"))
{
for (j = 3; j >= 0; j--)
sc.String[j] = toupper (sc.String[j]);
intname = *((uint32_t *)sc.String);
2016-03-01 15:47:10 +00:00
}
else if (0 == stricmp (key, "crouchsprite"))
{
for (j = 3; j >= 0; j--)
sc.String[j] = toupper (sc.String[j]);
crouchname = *((uint32_t *)sc.String);
2016-03-01 15:47:10 +00:00
}
else if (0 == stricmp (key, "face"))
{
Skins[i].Face = FString(sc.String, 3);
2016-03-01 15:47:10 +00:00
}
else if (0 == stricmp (key, "gender"))
{
Skins[i].gender = D_GenderToInt (sc.String);
2016-03-01 15:47:10 +00:00
}
else if (0 == stricmp (key, "scale"))
{
Skins[i].Scale.X = clamp(atof (sc.String), 1./65536, 256.);
Skins[i].Scale.Y = Skins[i].Scale.X;
2016-03-01 15:47:10 +00:00
}
else if (0 == stricmp (key, "game"))
{
if (gameinfo.gametype == GAME_Heretic)
basetype = PClass::FindActor(NAME_HereticPlayer);
2016-03-01 15:47:10 +00:00
else if (gameinfo.gametype == GAME_Strife)
basetype = PClass::FindActor(NAME_StrifePlayer);
2016-03-01 15:47:10 +00:00
else
basetype = PClass::FindActor(NAME_DoomPlayer);
2016-03-01 15:47:10 +00:00
transtype = basetype;
if (stricmp (sc.String, "heretic") == 0)
{
if (gameinfo.gametype & GAME_DoomChex)
{
transtype = PClass::FindActor(NAME_HereticPlayer);
Skins[i].othergame = true;
2016-03-01 15:47:10 +00:00
}
else if (gameinfo.gametype != GAME_Heretic)
{
remove = true;
}
}
else if (stricmp (sc.String, "strife") == 0)
{
if (gameinfo.gametype != GAME_Strife)
{
remove = true;
}
}
else
{
if (gameinfo.gametype == GAME_Heretic)
{
transtype = PClass::FindActor(NAME_DoomPlayer);
Skins[i].othergame = true;
2016-03-01 15:47:10 +00:00
}
else if (!(gameinfo.gametype & GAME_DoomChex))
{
remove = true;
}
}
if (remove)
break;
}
else if (0 == stricmp (key, "class"))
{ // [GRB] Define the skin for a specific player class
int pclass = D_PlayerClassToInt (sc.String);
if (pclass < 0)
{
remove = true;
break;
}
basetype = transtype = PlayerClasses[pclass].Type;
}
else if (key[0] == '*')
{ // Player sound replacment (ZDoom extension)
int lump = Wads.CheckNumForName (sc.String, Skins[i].namespc);
2016-03-01 15:47:10 +00:00
if (lump == -1)
{
lump = Wads.CheckNumForFullName (sc.String, true, ns_sounds);
}
if (lump != -1)
{
if (stricmp (key, "*pain") == 0)
{ // Replace all pain sounds in one go
aliasid = S_AddPlayerSound (Skins[i].Name, Skins[i].gender,
2016-03-01 15:47:10 +00:00
playersoundrefs[0], lump, true);
for (int l = 3; l > 0; --l)
{
S_AddPlayerSoundExisting (Skins[i].Name, Skins[i].gender,
2016-03-01 15:47:10 +00:00
playersoundrefs[l], aliasid, true);
}
}
else
{
int sndref = S_FindSoundNoHash (key);
if (sndref != 0)
{
S_AddPlayerSound (Skins[i].Name, Skins[i].gender, sndref, lump, true);
2016-03-01 15:47:10 +00:00
}
}
}
}
else
{
for (j = 0; j < NUMSKINSOUNDS; j++)
{
if (stricmp (key, skinsoundnames[j][0]) == 0)
{
sndlumps[j] = Wads.CheckNumForName (sc.String, Skins[i].namespc);
2016-03-01 15:47:10 +00:00
if (sndlumps[j] == -1)
{ // Replacement not found, try finding it in the global namespace
sndlumps[j] = Wads.CheckNumForFullName (sc.String, true, ns_sounds);
}
}
}
//if (j == 8)
// Printf ("Funny info for skin %i: %s = %s\n", i, key, sc.String);
}
}
// [GRB] Assume Doom skin by default
if (!remove && basetype == NULL)
{
if (gameinfo.gametype & GAME_DoomChex)
{
basetype = transtype = PClass::FindActor(NAME_DoomPlayer);
2016-03-01 15:47:10 +00:00
}
else if (gameinfo.gametype == GAME_Heretic)
{
basetype = PClass::FindActor(NAME_HereticPlayer);
transtype = PClass::FindActor(NAME_DoomPlayer);
Skins[i].othergame = true;
2016-03-01 15:47:10 +00:00
}
else
{
remove = true;
}
}
if (!remove)
{
auto transdef = ((APlayerPawn*)GetDefaultByType(transtype));
auto basedef = ((APlayerPawn*)GetDefaultByType(basetype));
Skins[i].range0start = transdef->ColorRangeStart;
Skins[i].range0end = transdef->ColorRangeEnd;
2016-03-01 15:47:10 +00:00
remove = true;
for (j = 0; j < (int)PlayerClasses.Size (); j++)
{
auto type = PlayerClasses[j].Type;
auto type_def = ((APlayerPawn*)GetDefaultByType(type));
2016-03-01 15:47:10 +00:00
if (type->IsDescendantOf (basetype) &&
GetDefaultByType(type)->SpawnState->sprite == GetDefaultByType(basetype)->SpawnState->sprite &&
type_def->ColorRangeStart == basedef->ColorRangeStart &&
type_def->ColorRangeEnd == basedef->ColorRangeEnd)
2016-03-01 15:47:10 +00:00
{
PlayerClasses[j].Skins.Push ((int)i);
remove = false;
}
}
}
if (!remove)
{
if (Skins[i].Name.IsEmpty())
Skins[i].Name.Format("skin%u", i);
2016-03-01 15:47:10 +00:00
// Now collect the sprite frames for this skin. If the sprite name was not
// specified, use whatever immediately follows the specifier lump.
if (intname == 0)
{
char name[9];
Wads.GetLumpName (name, base+1);
memcpy(&intname, name, 4);
}
int basens = Wads.GetLumpNamespace(base);
for(int spr = 0; spr<2; spr++)
{
2016-12-08 20:05:53 +00:00
spriteframewithrotate sprtemp[MAX_SPRITE_FRAMES];
2016-03-01 15:47:10 +00:00
memset (sprtemp, 0xFFFF, sizeof(sprtemp));
for (k = 0; k < MAX_SPRITE_FRAMES; ++k)
{
sprtemp[k].Flip = 0;
sprtemp[k].Voxel = NULL;
}
2016-12-08 20:05:53 +00:00
int maxframe = -1;
2016-03-01 15:47:10 +00:00
if (spr == 1)
{
if (crouchname !=0 && crouchname != intname)
{
intname = crouchname;
}
else
{
Skins[i].crouchsprite = -1;
2016-03-01 15:47:10 +00:00
break;
}
}
for (k = base + 1; Wads.GetLumpNamespace(k) == basens; k++)
{
char lname[9];
uint32_t lnameint;
2016-03-01 15:47:10 +00:00
Wads.GetLumpName (lname, k);
memcpy(&lnameint, lname, 4);
if (lnameint == intname)
{
FTextureID picnum = TexMan.CreateTexture(k, FTexture::TEX_SkinSprite);
2016-12-08 20:05:53 +00:00
bool res = R_InstallSpriteLump (picnum, lname[4] - 'A', lname[5], false, sprtemp, maxframe);
2016-03-01 15:47:10 +00:00
if (lname[6] && res)
2016-12-08 20:05:53 +00:00
R_InstallSpriteLump (picnum, lname[6] - 'A', lname[7], true, sprtemp, maxframe);
2016-03-01 15:47:10 +00:00
}
}
if (spr == 0 && maxframe <= 0)
{
Printf (PRINT_BOLD, "Skin %s (#%u) has no frames. Removing.\n", Skins[i].Name.GetChars(), i);
2016-03-01 15:47:10 +00:00
remove = true;
break;
}
Wads.GetLumpName (temp.name, base+1);
temp.name[4] = 0;
int sprno = (int)sprites.Push (temp);
if (spr==0) Skins[i].sprite = sprno;
else Skins[i].crouchsprite = sprno;
2016-12-08 20:05:53 +00:00
R_InstallSprite (sprno, sprtemp, maxframe);
2016-03-01 15:47:10 +00:00
}
}
if (remove)
{
if (i < Skins.Size() - 1)
{
Skins.Delete(i);
}
2016-03-01 15:47:10 +00:00
i--;
continue;
}
// Register any sounds this skin provides
aliasid = 0;
for (j = 0; j < NUMSKINSOUNDS; j++)
{
if (sndlumps[j] != -1)
{
if (j == 0 || sndlumps[j] != sndlumps[j-1])
{
aliasid = S_AddPlayerSound (Skins[i].Name, Skins[i].gender,
2016-03-01 15:47:10 +00:00
playersoundrefs[j], sndlumps[j], true);
}
else
{
S_AddPlayerSoundExisting (Skins[i].Name, Skins[i].gender,
2016-03-01 15:47:10 +00:00
playersoundrefs[j], aliasid, true);
}
}
}
// Make sure face prefix is a full 3 chars
if (Skins[i].Face.Len() < 3)
2016-03-01 15:47:10 +00:00
{
Skins[i].Face = "";
2016-03-01 15:47:10 +00:00
}
}
if (Skins.Size() > PlayerClasses.Size ())
2016-03-01 15:47:10 +00:00
{ // The sound table may have changed, so rehash it.
S_HashSounds ();
S_ShrinkPlayerSoundLists ();
}
}
// [RH] Find a skin by name
int R_FindSkin (const char *name, int pclass)
{
if (stricmp ("base", name) == 0)
{
return pclass;
}
for (unsigned i = PlayerClasses.Size(); i < Skins.Size(); i++)
2016-03-01 15:47:10 +00:00
{
if (Skins[i].Name.CompareNoCase(name) == 0)
2016-03-01 15:47:10 +00:00
{
if (PlayerClasses[pclass].CheckSkin (i))
return i;
else
return pclass;
}
}
return pclass;
}
// [RH] List the names of all installed skins
CCMD (skins)
{
int i;
for (i = PlayerClasses.Size() - 1; i < (int)Skins.Size(); i++)
Printf("% 3d %s\n", i - PlayerClasses.Size() + 1, Skins[i].Name.GetChars());
2016-03-01 15:47:10 +00:00
}
static void R_CreateSkinTranslation (const char *palname)
{
FMemLump lump = Wads.ReadLump (palname);
2017-03-08 17:47:52 +00:00
const uint8_t *otherPal = (uint8_t *)lump.GetMem();
2016-03-01 15:47:10 +00:00
for (int i = 0; i < 256; ++i)
{
OtherGameSkinRemap[i] = ColorMatcher.Pick (otherPal[0], otherPal[1], otherPal[2]);
OtherGameSkinPalette[i] = PalEntry(otherPal[0], otherPal[1], otherPal[2]);
otherPal += 3;
}
}
//
// R_InitSprites
// Called at program start.
//
void R_InitSprites ()
{
int lump, lastlump;
unsigned int i, j;
unsigned numskins;
2016-03-01 15:47:10 +00:00
// [RH] Create a standard translation to map skins between Heretic and Doom
if (gameinfo.gametype == GAME_DoomChex)
{
R_CreateSkinTranslation ("SPALHTIC");
}
else
{
R_CreateSkinTranslation ("SPALDOOM");
}
// [RH] Count the number of skins.
numskins = PlayerClasses.Size ();
lastlump = 0;
while ((lump = Wads.FindLump ("S_SKIN", &lastlump, true)) != -1)
{
numskins++;
}
// [RH] Do some preliminary setup
Skins.Clear();
Skins.Resize(numskins);
2016-03-01 15:47:10 +00:00
for (i = 0; i < numskins; i++)
{ // Assume Doom skin by default
auto type = ((APlayerPawn*)GetDefaultByType(PlayerClasses[0].Type));
Skins[i].range0start = type->ColorRangeStart;
Skins[i].range0end = type->ColorRangeEnd;
Skins[i].Scale = type->Scale;
2016-03-01 15:47:10 +00:00
}
R_InitSpriteDefs ();
R_InitVoxels(); // [RH] Parse VOXELDEF
NumStdSprites = sprites.Size();
R_InitSkins (); // [RH] Finish loading skin data
// [RH] Set up base skin
// [GRB] Each player class has its own base skin
for (i = 0; i < PlayerClasses.Size (); i++)
{
auto basetype = ((APlayerPawn*)GetDefaultByType(PlayerClasses[i].Type));
2016-03-01 15:47:10 +00:00
Skins[i].Name = "Base";
if (basetype->Face == NAME_None)
2016-03-01 15:47:10 +00:00
{
Skins[i].Face = "STF";
2016-03-01 15:47:10 +00:00
}
else
{
Skins[i].Face = basetype->Face;
2016-03-01 15:47:10 +00:00
}
Skins[i].range0start = basetype->ColorRangeStart;
Skins[i].range0end = basetype->ColorRangeEnd;
Skins[i].Scale = basetype->Scale;
Skins[i].sprite = basetype->SpawnState->sprite;
Skins[i].namespc = ns_global;
2016-03-01 15:47:10 +00:00
PlayerClasses[i].Skins.Push (i);
if (memcmp (sprites[Skins[i].sprite].name, "PLAY", 4) == 0)
2016-03-01 15:47:10 +00:00
{
for (j = 0; j < sprites.Size (); j++)
{
if (memcmp (sprites[j].name, deh.PlayerSprite, 4) == 0)
{
Skins[i].sprite = (int)j;
2016-03-01 15:47:10 +00:00
break;
}
}
}
}
// [RH] Sort the skins, but leave base as skin 0
//qsort (&skins[PlayerClasses.Size ()], numskins-PlayerClasses.Size (), sizeof(FPlayerSkin), skinsorter);
gl_InitModels();
2016-03-01 15:47:10 +00:00
}
2017-02-17 21:12:56 +00:00
DEFINE_FIELD_NAMED(FPlayerSkin, Name, SkinName);
DEFINE_FIELD(FPlayerSkin, Face);
DEFINE_FIELD(FPlayerSkin, gender);
DEFINE_FIELD(FPlayerSkin, range0start);
DEFINE_FIELD(FPlayerSkin, range0end);
DEFINE_FIELD(FPlayerSkin, othergame);
DEFINE_FIELD(FPlayerSkin, Scale);
DEFINE_FIELD(FPlayerSkin, sprite);
DEFINE_FIELD(FPlayerSkin, crouchsprite);
DEFINE_FIELD(FPlayerSkin, namespc);