1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 1993-1996 by id Software, Inc.
4 // Copyright (C) 1998-2000 by DooM Legacy Team.
5 // Copyright (C) 1999-2020 by Sonic Team Junior.
6 //
7 // This program is free software distributed under the
8 // terms of the GNU General Public License, version 2.
9 // See the 'LICENSE' file for more details.
10 //-----------------------------------------------------------------------------
11 /// \file  r_skins.c
12 /// \brief Loading skins
13 
14 #include "doomdef.h"
15 #include "console.h"
16 #include "g_game.h"
17 #include "r_local.h"
18 #include "st_stuff.h"
19 #include "w_wad.h"
20 #include "z_zone.h"
21 #include "m_misc.h"
22 #include "info.h" // spr2names
23 #include "i_video.h" // rendermode
24 #include "i_system.h"
25 #include "r_things.h"
26 #include "r_skins.h"
27 #include "p_local.h"
28 #include "dehacked.h" // get_number (for thok)
29 #include "m_cond.h"
30 #ifdef HWRENDER
31 #include "hardware/hw_md2.h"
32 #endif
33 
34 INT32 numskins = 0;
35 skin_t skins[MAXSKINS];
36 
37 // FIXTHIS: don't work because it must be inistilised before the config load
38 //#define SKINVALUES
39 #ifdef SKINVALUES
40 CV_PossibleValue_t skin_cons_t[MAXSKINS+1];
41 #endif
42 
43 //
44 // P_GetSkinSprite2
45 // For non-super players, tries each sprite2's immediate predecessor until it finds one with a number of frames or ends up at standing.
46 // For super players, does the same as above - but tries the super equivalent for each sprite2 before the non-super version.
47 //
48 
P_GetSkinSprite2(skin_t * skin,UINT8 spr2,player_t * player)49 UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player)
50 {
51 	UINT8 super = 0, i = 0;
52 
53 	if (!skin)
54 		return 0;
55 
56 	if ((playersprite_t)(spr2 & ~FF_SPR2SUPER) >= free_spr2)
57 		return 0;
58 
59 	while (!skin->sprites[spr2].numframes
60 		&& spr2 != SPR2_STND
61 		&& ++i < 32) // recursion limiter
62 	{
63 		if (spr2 & FF_SPR2SUPER)
64 		{
65 			super = FF_SPR2SUPER;
66 			spr2 &= ~FF_SPR2SUPER;
67 			continue;
68 		}
69 
70 		switch(spr2)
71 		{
72 		// Normal special cases.
73 		case SPR2_JUMP:
74 			spr2 = ((player
75 					? player->charflags
76 					: skin->flags)
77 					& SF_NOJUMPSPIN) ? SPR2_SPNG : SPR2_ROLL;
78 			break;
79 		case SPR2_TIRE:
80 			spr2 = ((player
81 					? player->charability
82 					: skin->ability)
83 					== CA_SWIM) ? SPR2_SWIM : SPR2_FLY;
84 			break;
85 		// Use the handy list, that's what it's there for!
86 		default:
87 			spr2 = spr2defaults[spr2];
88 			break;
89 		}
90 
91 		spr2 |= super;
92 	}
93 
94 	if (i >= 32) // probably an infinite loop...
95 		return 0;
96 
97 	return spr2;
98 }
99 
Sk_SetDefaultValue(skin_t * skin)100 static void Sk_SetDefaultValue(skin_t *skin)
101 {
102 	INT32 i;
103 	//
104 	// set default skin values
105 	//
106 	memset(skin, 0, sizeof (skin_t));
107 	snprintf(skin->name,
108 		sizeof skin->name, "skin %u", (UINT32)(skin-skins));
109 	skin->name[sizeof skin->name - 1] = '\0';
110 	skin->wadnum = INT16_MAX;
111 
112 	skin->flags = 0;
113 
114 	strcpy(skin->realname, "Someone");
115 	strcpy(skin->hudname, "???");
116 
117 	skin->starttranscolor = 96;
118 	skin->prefcolor = SKINCOLOR_GREEN;
119 	skin->supercolor = SKINCOLOR_SUPERGOLD1;
120 	skin->prefoppositecolor = 0; // use tables
121 
122 	skin->normalspeed = 36<<FRACBITS;
123 	skin->runspeed = 28<<FRACBITS;
124 	skin->thrustfactor = 5;
125 	skin->accelstart = 96;
126 	skin->acceleration = 40;
127 
128 	skin->ability = CA_NONE;
129 	skin->ability2 = CA2_SPINDASH;
130 	skin->jumpfactor = FRACUNIT;
131 	skin->actionspd = 30<<FRACBITS;
132 	skin->mindash = 15<<FRACBITS;
133 	skin->maxdash = 70<<FRACBITS;
134 
135 	skin->radius = mobjinfo[MT_PLAYER].radius;
136 	skin->height = mobjinfo[MT_PLAYER].height;
137 	skin->spinheight = FixedMul(skin->height, 2*FRACUNIT/3);
138 
139 	skin->shieldscale = FRACUNIT;
140 	skin->camerascale = FRACUNIT;
141 
142 	skin->thokitem = -1;
143 	skin->spinitem = -1;
144 	skin->revitem = -1;
145 	skin->followitem = 0;
146 
147 	skin->highresscale = FRACUNIT;
148 	skin->contspeed = 17;
149 	skin->contangle = 0;
150 
151 	skin->availability = 0;
152 
153 	for (i = 0; i < sfx_skinsoundslot0; i++)
154 		if (S_sfx[i].skinsound != -1)
155 			skin->soundsid[S_sfx[i].skinsound] = i;
156 }
157 
158 //
159 // Initialize the basic skins
160 //
R_InitSkins(void)161 void R_InitSkins(void)
162 {
163 #ifdef SKINVALUES
164 	INT32 i;
165 
166 	for (i = 0; i <= MAXSKINS; i++)
167 	{
168 		skin_cons_t[i].value = 0;
169 		skin_cons_t[i].strvalue = NULL;
170 	}
171 #endif
172 
173 	// no default skin!
174 	numskins = 0;
175 }
176 
R_GetSkinAvailabilities(void)177 UINT32 R_GetSkinAvailabilities(void)
178 {
179 	INT32 s;
180 	UINT32 response = 0;
181 
182 	for (s = 0; s < MAXSKINS; s++)
183 	{
184 		if (skins[s].availability && unlockables[skins[s].availability - 1].unlocked)
185 			response |= (1 << s);
186 	}
187 	return response;
188 }
189 
190 // returns true if available in circumstances, otherwise nope
191 // warning don't use with an invalid skinnum other than -1 which always returns true
R_SkinUsable(INT32 playernum,INT32 skinnum)192 boolean R_SkinUsable(INT32 playernum, INT32 skinnum)
193 {
194 	return ((skinnum == -1) // Simplifies things elsewhere, since there's already plenty of checks for less-than-0...
195 		|| (!skins[skinnum].availability)
196 		|| (((netgame || multiplayer) && playernum != -1) ? (players[playernum].availabilities & (1 << skinnum)) : (unlockables[skins[skinnum].availability - 1].unlocked))
197 		|| (modeattacking) // If you have someone else's run you might as well take a look
198 		|| (Playing() && (R_SkinAvailable(mapheaderinfo[gamemap-1]->forcecharacter) == skinnum)) // Force 1.
199 		|| (netgame && (cv_forceskin.value == skinnum)) // Force 2.
200 		|| (metalrecording && skinnum == 5) // Force 3.
201 		);
202 }
203 
204 // returns true if the skin name is found (loaded from pwad)
205 // warning return -1 if not found
R_SkinAvailable(const char * name)206 INT32 R_SkinAvailable(const char *name)
207 {
208 	INT32 i;
209 
210 	for (i = 0; i < numskins; i++)
211 	{
212 		// search in the skin list
213 		if (stricmp(skins[i].name,name)==0)
214 			return i;
215 	}
216 	return -1;
217 }
218 
219 // network code calls this when a 'skin change' is received
SetPlayerSkin(INT32 playernum,const char * skinname)220 void SetPlayerSkin(INT32 playernum, const char *skinname)
221 {
222 	INT32 i = R_SkinAvailable(skinname);
223 	player_t *player = &players[playernum];
224 
225 	if ((i != -1) && R_SkinUsable(playernum, i))
226 	{
227 		SetPlayerSkinByNum(playernum, i);
228 		return;
229 	}
230 
231 	if (P_IsLocalPlayer(player))
232 		CONS_Alert(CONS_WARNING, M_GetText("Skin '%s' not found.\n"), skinname);
233 	else if(server || IsPlayerAdmin(consoleplayer))
234 		CONS_Alert(CONS_WARNING, M_GetText("Player %d (%s) skin '%s' not found\n"), playernum, player_names[playernum], skinname);
235 
236 	SetPlayerSkinByNum(playernum, 0);
237 }
238 
239 // Same as SetPlayerSkin, but uses the skin #.
240 // network code calls this when a 'skin change' is received
SetPlayerSkinByNum(INT32 playernum,INT32 skinnum)241 void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
242 {
243 	player_t *player = &players[playernum];
244 	skin_t *skin = &skins[skinnum];
245 	UINT16 newcolor = 0;
246 
247 	if (skinnum >= 0 && skinnum < numskins && R_SkinUsable(playernum, skinnum)) // Make sure it exists!
248 	{
249 		player->skin = skinnum;
250 
251 		player->camerascale = skin->camerascale;
252 		player->shieldscale = skin->shieldscale;
253 
254 		player->charability = (UINT8)skin->ability;
255 		player->charability2 = (UINT8)skin->ability2;
256 
257 		player->charflags = (UINT32)skin->flags;
258 
259 		player->thokitem = skin->thokitem < 0 ? (UINT32)mobjinfo[MT_PLAYER].painchance : (UINT32)skin->thokitem;
260 		player->spinitem = skin->spinitem < 0 ? (UINT32)mobjinfo[MT_PLAYER].damage : (UINT32)skin->spinitem;
261 		player->revitem = skin->revitem < 0 ? (mobjtype_t)mobjinfo[MT_PLAYER].raisestate : (UINT32)skin->revitem;
262 		player->followitem = skin->followitem;
263 
264 		if (((player->powers[pw_shield] & SH_NOSTACK) == SH_PINK) && (player->revitem == MT_LHRT || player->spinitem == MT_LHRT || player->thokitem == MT_LHRT)) // Healers can't keep their buff.
265 			player->powers[pw_shield] &= SH_STACK;
266 
267 		player->actionspd = skin->actionspd;
268 		player->mindash = skin->mindash;
269 		player->maxdash = skin->maxdash;
270 
271 		player->normalspeed = skin->normalspeed;
272 		player->runspeed = skin->runspeed;
273 		player->thrustfactor = skin->thrustfactor;
274 		player->accelstart = skin->accelstart;
275 		player->acceleration = skin->acceleration;
276 
277 		player->jumpfactor = skin->jumpfactor;
278 
279 		player->height = skin->height;
280 		player->spinheight = skin->spinheight;
281 
282 		if (!(cv_debug || devparm) && !(netgame || multiplayer || demoplayback))
283 		{
284 			if (playernum == consoleplayer)
285 				CV_StealthSetValue(&cv_playercolor, skin->prefcolor);
286 			else if (playernum == secondarydisplayplayer)
287 				CV_StealthSetValue(&cv_playercolor2, skin->prefcolor);
288 			player->skincolor = newcolor = skin->prefcolor;
289 			if (player->bot && botingame)
290 			{
291 				botskin = (UINT8)(skinnum + 1);
292 				botcolor = skin->prefcolor;
293 			}
294 		}
295 
296 		if (player->followmobj)
297 		{
298 			P_RemoveMobj(player->followmobj);
299 			P_SetTarget(&player->followmobj, NULL);
300 		}
301 
302 		if (player->mo)
303 		{
304 			fixed_t radius = FixedMul(skin->radius, player->mo->scale);
305 			if ((player->powers[pw_carry] == CR_NIGHTSMODE) && (skin->sprites[SPR2_NFLY].numframes == 0)) // If you don't have a sprite for flying horizontally, use the default NiGHTS skin.
306 			{
307 				skin = &skins[DEFAULTNIGHTSSKIN];
308 				player->followitem = skin->followitem;
309 				if (!(cv_debug || devparm) && !(netgame || multiplayer || demoplayback))
310 					newcolor = skin->prefcolor; // will be updated in thinker to flashing
311 			}
312 			player->mo->skin = skin;
313 			if (newcolor)
314 				player->mo->color = newcolor;
315 			P_SetScale(player->mo, player->mo->scale);
316 			player->mo->radius = radius;
317 
318 			P_SetPlayerMobjState(player->mo, player->mo->state-states); // Prevent visual errors when switching between skins with differing number of frames
319 		}
320 		return;
321 	}
322 
323 	if (P_IsLocalPlayer(player))
324 		CONS_Alert(CONS_WARNING, M_GetText("Requested skin %d not found\n"), skinnum);
325 	else if(server || IsPlayerAdmin(consoleplayer))
326 		CONS_Alert(CONS_WARNING, "Player %d (%s) skin %d not found\n", playernum, player_names[playernum], skinnum);
327 	SetPlayerSkinByNum(playernum, 0); // not found put the sonic skin
328 }
329 
330 //
331 // Add skins from a pwad, each skin preceded by 'S_SKIN' marker
332 //
333 
334 // Does the same is in w_wad, but check only for
335 // the first 6 characters (this is so we can have S_SKIN1, S_SKIN2..
336 // for wad editors that don't like multiple resources of the same name)
337 //
W_CheckForSkinMarkerInPwad(UINT16 wadid,UINT16 startlump)338 static UINT16 W_CheckForSkinMarkerInPwad(UINT16 wadid, UINT16 startlump)
339 {
340 	UINT16 i;
341 	const char *S_SKIN = "S_SKIN";
342 	lumpinfo_t *lump_p;
343 
344 	// scan forward, start at <startlump>
345 	if (startlump < wadfiles[wadid]->numlumps)
346 	{
347 		lump_p = wadfiles[wadid]->lumpinfo + startlump;
348 		for (i = startlump; i < wadfiles[wadid]->numlumps; i++, lump_p++)
349 			if (memcmp(lump_p->name,S_SKIN,6)==0)
350 				return i;
351 	}
352 	return INT16_MAX; // not found
353 }
354 
355 #define HUDNAMEWRITE(value) STRBUFCPY(skin->hudname, value)
356 
357 // turn _ into spaces and . into katana dot
358 #define SYMBOLCONVERT(name) for (value = name; *value; value++)\
359 					{\
360 						if (*value == '_') *value = ' ';\
361 						else if (*value == '.') *value = '\x1E';\
362 					}
363 
364 //
365 // Patch skins from a pwad, each skin preceded by 'P_SKIN' marker
366 //
367 
368 // Does the same is in w_wad, but check only for
369 // the first 6 characters (this is so we can have P_SKIN1, P_SKIN2..
370 // for wad editors that don't like multiple resources of the same name)
371 //
W_CheckForPatchSkinMarkerInPwad(UINT16 wadid,UINT16 startlump)372 static UINT16 W_CheckForPatchSkinMarkerInPwad(UINT16 wadid, UINT16 startlump)
373 {
374 	UINT16 i;
375 	const char *P_SKIN = "P_SKIN";
376 	lumpinfo_t *lump_p;
377 
378 	// scan forward, start at <startlump>
379 	if (startlump < wadfiles[wadid]->numlumps)
380 	{
381 		lump_p = wadfiles[wadid]->lumpinfo + startlump;
382 		for (i = startlump; i < wadfiles[wadid]->numlumps; i++, lump_p++)
383 			if (memcmp(lump_p->name,P_SKIN,6)==0)
384 				return i;
385 	}
386 	return INT16_MAX; // not found
387 }
388 
R_LoadSkinSprites(UINT16 wadnum,UINT16 * lump,UINT16 * lastlump,skin_t * skin)389 static void R_LoadSkinSprites(UINT16 wadnum, UINT16 *lump, UINT16 *lastlump, skin_t *skin)
390 {
391 	UINT16 newlastlump;
392 	UINT8 sprite2;
393 
394 	*lump += 1; // start after S_SKIN
395 	*lastlump = W_CheckNumForNamePwad("S_END",wadnum,*lump); // stop at S_END
396 
397 	// old wadding practices die hard -- stop at S_SKIN (or P_SKIN) or S_START if they come before S_END.
398 	newlastlump = W_CheckForSkinMarkerInPwad(wadnum,*lump);
399 	if (newlastlump < *lastlump) *lastlump = newlastlump;
400 	newlastlump = W_CheckForPatchSkinMarkerInPwad(wadnum,*lump);
401 	if (newlastlump < *lastlump) *lastlump = newlastlump;
402 	newlastlump = W_CheckNumForNamePwad("S_START",wadnum,*lump);
403 	if (newlastlump < *lastlump) *lastlump = newlastlump;
404 
405 	// ...and let's handle super, too
406 	newlastlump = W_CheckNumForNamePwad("S_SUPER",wadnum,*lump);
407 	if (newlastlump < *lastlump)
408 	{
409 		newlastlump++;
410 		// load all sprite sets we are aware of... for super!
411 		for (sprite2 = 0; sprite2 < free_spr2; sprite2++)
412 			R_AddSingleSpriteDef(spr2names[sprite2], &skin->sprites[FF_SPR2SUPER|sprite2], wadnum, newlastlump, *lastlump);
413 
414 		newlastlump--;
415 		*lastlump = newlastlump; // okay, make the normal sprite set loading end there
416 	}
417 
418 	// load all sprite sets we are aware of... for normal stuff.
419 	for (sprite2 = 0; sprite2 < free_spr2; sprite2++)
420 		R_AddSingleSpriteDef(spr2names[sprite2], &skin->sprites[sprite2], wadnum, *lump, *lastlump);
421 
422 	if (skin->sprites[0].numframes == 0)
423 		I_Error("R_LoadSkinSprites: no frames found for sprite SPR2_%s\n", spr2names[0]);
424 }
425 
426 // returns whether found appropriate property
R_ProcessPatchableFields(skin_t * skin,char * stoken,char * value)427 static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value)
428 {
429 	// custom translation table
430 	if (!stricmp(stoken, "startcolor"))
431 		skin->starttranscolor = atoi(value);
432 
433 #define FULLPROCESS(field) else if (!stricmp(stoken, #field)) skin->field = get_number(value);
434 	// character type identification
435 	FULLPROCESS(flags)
436 	FULLPROCESS(ability)
437 	FULLPROCESS(ability2)
438 
439 	FULLPROCESS(thokitem)
440 	FULLPROCESS(spinitem)
441 	FULLPROCESS(revitem)
442 	FULLPROCESS(followitem)
443 #undef FULLPROCESS
444 
445 #define GETFRACBITS(field) else if (!stricmp(stoken, #field)) skin->field = atoi(value)<<FRACBITS;
446 	GETFRACBITS(normalspeed)
447 	GETFRACBITS(runspeed)
448 
449 	GETFRACBITS(mindash)
450 	GETFRACBITS(maxdash)
451 	GETFRACBITS(actionspd)
452 
453 	GETFRACBITS(radius)
454 	GETFRACBITS(height)
455 	GETFRACBITS(spinheight)
456 #undef GETFRACBITS
457 
458 #define GETINT(field) else if (!stricmp(stoken, #field)) skin->field = atoi(value);
459 	GETINT(thrustfactor)
460 	GETINT(accelstart)
461 	GETINT(acceleration)
462 	GETINT(contspeed)
463 	GETINT(contangle)
464 #undef GETINT
465 
466 #define GETSKINCOLOR(field) else if (!stricmp(stoken, #field)) \
467 { \
468 	UINT16 color = R_GetColorByName(value); \
469 	skin->field = (color ? color : SKINCOLOR_GREEN); \
470 }
471 	GETSKINCOLOR(prefcolor)
472 	GETSKINCOLOR(prefoppositecolor)
473 #undef GETSKINCOLOR
474 	else if (!stricmp(stoken, "supercolor"))
475 	{
476 		UINT16 color = R_GetSuperColorByName(value);
477 		skin->supercolor = (color ? color : SKINCOLOR_SUPERGOLD1);
478 	}
479 
480 #define GETFLOAT(field) else if (!stricmp(stoken, #field)) skin->field = FLOAT_TO_FIXED(atof(value));
481 	GETFLOAT(jumpfactor)
482 	GETFLOAT(highresscale)
483 	GETFLOAT(shieldscale)
484 	GETFLOAT(camerascale)
485 #undef GETFLOAT
486 
487 #define GETFLAG(field) else if (!stricmp(stoken, #field)) { \
488 	strupr(value); \
489 	if (atoi(value) || value[0] == 'T' || value[0] == 'Y') \
490 		skin->flags |= (SF_##field); \
491 	else \
492 		skin->flags &= ~(SF_##field); \
493 }
494 	// parameters for individual character flags
495 	// these are uppercase so they can be concatenated with SF_
496 	// 1, true, yes are all valid values
497 	GETFLAG(SUPER)
498 	GETFLAG(NOSUPERSPIN)
499 	GETFLAG(NOSPINDASHDUST)
500 	GETFLAG(HIRES)
501 	GETFLAG(NOSKID)
502 	GETFLAG(NOSPEEDADJUST)
503 	GETFLAG(RUNONWATER)
504 	GETFLAG(NOJUMPSPIN)
505 	GETFLAG(NOJUMPDAMAGE)
506 	GETFLAG(STOMPDAMAGE)
507 	GETFLAG(MARIODAMAGE)
508 	GETFLAG(MACHINE)
509 	GETFLAG(DASHMODE)
510 	GETFLAG(FASTEDGE)
511 	GETFLAG(MULTIABILITY)
512 	GETFLAG(NONIGHTSROTATION)
513 	GETFLAG(NONIGHTSSUPER)
514 	GETFLAG(NOSUPERSPRITES)
515 	GETFLAG(NOSUPERJUMPBOOST)
516 	GETFLAG(CANBUSTWALLS)
517 	GETFLAG(NOSHIELDABILITY)
518 #undef GETFLAG
519 
520 	else // let's check if it's a sound, otherwise error out
521 	{
522 		boolean found = false;
523 		sfxenum_t i;
524 		size_t stokenadjust;
525 
526 		// Remove the prefix. (We need to affect an adjusting variable so that we can print error messages if it's not actually a sound.)
527 		if ((stoken[0] == 'D' || stoken[0] == 'd') && (stoken[1] == 'S' || stoken[1] == 's')) // DS*
528 			stokenadjust = 2;
529 		else // sfx_*
530 			stokenadjust = 4;
531 
532 		// Remove the prefix. (We can affect this directly since we're not going to use it again.)
533 		if ((value[0] == 'D' || value[0] == 'd') && (value[1] == 'S' || value[1] == 's')) // DS*
534 			value += 2;
535 		else // sfx_*
536 			value += 4;
537 
538 		// copy name of sounds that are remapped
539 		// for this skin
540 		for (i = 0; i < sfx_skinsoundslot0; i++)
541 		{
542 			if (!S_sfx[i].name)
543 				continue;
544 			if (S_sfx[i].skinsound != -1
545 				&& !stricmp(S_sfx[i].name,
546 					stoken + stokenadjust))
547 			{
548 				skin->soundsid[S_sfx[i].skinsound] =
549 					S_AddSoundFx(value, S_sfx[i].singularity, S_sfx[i].pitch, true);
550 				found = true;
551 			}
552 		}
553 		return found;
554 	}
555 	return true;
556 }
557 
558 //
559 // Find skin sprites, sounds & optional status bar face, & add them
560 //
R_AddSkins(UINT16 wadnum)561 void R_AddSkins(UINT16 wadnum)
562 {
563 	UINT16 lump, lastlump = 0;
564 	char *buf;
565 	char *buf2;
566 	char *stoken;
567 	char *value;
568 	size_t size;
569 	skin_t *skin;
570 	boolean hudname, realname;
571 
572 	//
573 	// search for all skin markers in pwad
574 	//
575 
576 	while ((lump = W_CheckForSkinMarkerInPwad(wadnum, lastlump)) != INT16_MAX)
577 	{
578 		// advance by default
579 		lastlump = lump + 1;
580 
581 		if (numskins >= MAXSKINS)
582 		{
583 			CONS_Debug(DBG_RENDER, "ignored skin (%d skins maximum)\n", MAXSKINS);
584 			continue; // so we know how many skins couldn't be added
585 		}
586 		buf = W_CacheLumpNumPwad(wadnum, lump, PU_CACHE);
587 		size = W_LumpLengthPwad(wadnum, lump);
588 
589 		// for strtok
590 		buf2 = malloc(size+1);
591 		if (!buf2)
592 			I_Error("R_AddSkins: No more free memory\n");
593 		M_Memcpy(buf2,buf,size);
594 		buf2[size] = '\0';
595 
596 		// set defaults
597 		skin = &skins[numskins];
598 		Sk_SetDefaultValue(skin);
599 		skin->wadnum = wadnum;
600 		hudname = realname = false;
601 		// parse
602 		stoken = strtok (buf2, "\r\n= ");
603 		while (stoken)
604 		{
605 			if ((stoken[0] == '/' && stoken[1] == '/')
606 				|| (stoken[0] == '#'))// skip comments
607 			{
608 				stoken = strtok(NULL, "\r\n"); // skip end of line
609 				goto next_token;              // find the real next token
610 			}
611 
612 			value = strtok(NULL, "\r\n= ");
613 
614 			if (!value)
615 				I_Error("R_AddSkins: syntax error in S_SKIN lump# %d(%s) in WAD %s\n", lump, W_CheckNameForNumPwad(wadnum,lump), wadfiles[wadnum]->filename);
616 
617 			// Some of these can't go in R_ProcessPatchableFields because they have side effects for future lines.
618 			// Others can't go in there because we don't want them to be patchable.
619 			if (!stricmp(stoken, "name"))
620 			{
621 				INT32 skinnum = R_SkinAvailable(value);
622 				strlwr(value);
623 				if (skinnum == -1)
624 					STRBUFCPY(skin->name, value);
625 				// the skin name must uniquely identify a single skin
626 				// if the name is already used I make the name 'namex'
627 				// using the default skin name's number set above
628 				else
629 				{
630 					const size_t stringspace =
631 						strlen(value) + sizeof (numskins) + 1;
632 					char *value2 = Z_Malloc(stringspace, PU_STATIC, NULL);
633 					snprintf(value2, stringspace,
634 						"%s%d", value, numskins);
635 					value2[stringspace - 1] = '\0';
636 					if (R_SkinAvailable(value2) == -1)
637 						// I'm lazy so if NEW name is already used I leave the 'skin x'
638 						// default skin name set in Sk_SetDefaultValue
639 						STRBUFCPY(skin->name, value2);
640 					Z_Free(value2);
641 				}
642 
643 				// copy to hudname and fullname as a default.
644 				if (!realname)
645 				{
646 					STRBUFCPY(skin->realname, skin->name);
647 					for (value = skin->realname; *value; value++)
648 					{
649 						if (*value == '_') *value = ' '; // turn _ into spaces.
650 						else if (*value == '.') *value = '\x1E'; // turn . into katana dot.
651 					}
652 				}
653 				if (!hudname)
654 				{
655 					HUDNAMEWRITE(skin->name);
656 					strupr(skin->hudname);
657 					SYMBOLCONVERT(skin->hudname)
658 				}
659 			}
660 			else if (!stricmp(stoken, "realname"))
661 			{ // Display name (eg. "Knuckles")
662 				realname = true;
663 				STRBUFCPY(skin->realname, value);
664 				SYMBOLCONVERT(skin->realname)
665 				if (!hudname)
666 					HUDNAMEWRITE(skin->realname);
667 			}
668 			else if (!stricmp(stoken, "hudname"))
669 			{ // Life icon name (eg. "K.T.E")
670 				hudname = true;
671 				HUDNAMEWRITE(value);
672 				SYMBOLCONVERT(skin->hudname)
673 				if (!realname)
674 					STRBUFCPY(skin->realname, skin->hudname);
675 			}
676 			else if (!stricmp(stoken, "availability"))
677 			{
678 				skin->availability = atoi(value);
679 				if (skin->availability >= MAXUNLOCKABLES)
680 					skin->availability = 0;
681 			}
682 			else if (!R_ProcessPatchableFields(skin, stoken, value))
683 				CONS_Debug(DBG_SETUP, "R_AddSkins: Unknown keyword '%s' in S_SKIN lump #%d (WAD %s)\n", stoken, lump, wadfiles[wadnum]->filename);
684 
685 next_token:
686 			stoken = strtok(NULL, "\r\n= ");
687 		}
688 		free(buf2);
689 
690 		// Add sprites
691 		R_LoadSkinSprites(wadnum, &lump, &lastlump, skin);
692 		//ST_LoadFaceGraphics(numskins); -- nah let's do this elsewhere
693 
694 		R_FlushTranslationColormapCache();
695 
696 		if (!skin->availability) // Safe to print...
697 			CONS_Printf(M_GetText("Added skin '%s'\n"), skin->name);
698 #ifdef SKINVALUES
699 		skin_cons_t[numskins].value = numskins;
700 		skin_cons_t[numskins].strvalue = skin->name;
701 #endif
702 
703 #ifdef HWRENDER
704 		if (rendermode == render_opengl)
705 			HWR_AddPlayerModel(numskins);
706 #endif
707 
708 		numskins++;
709 	}
710 	return;
711 }
712 
713 //
714 // Patch skin sprites
715 //
R_PatchSkins(UINT16 wadnum)716 void R_PatchSkins(UINT16 wadnum)
717 {
718 	UINT16 lump, lastlump = 0;
719 	char *buf;
720 	char *buf2;
721 	char *stoken;
722 	char *value;
723 	size_t size;
724 	skin_t *skin;
725 	boolean noskincomplain, realname, hudname;
726 
727 	//
728 	// search for all skin patch markers in pwad
729 	//
730 
731 	while ((lump = W_CheckForPatchSkinMarkerInPwad(wadnum, lastlump)) != INT16_MAX)
732 	{
733 		INT32 skinnum = 0;
734 
735 		// advance by default
736 		lastlump = lump + 1;
737 
738 		buf = W_CacheLumpNumPwad(wadnum, lump, PU_CACHE);
739 		size = W_LumpLengthPwad(wadnum, lump);
740 
741 		// for strtok
742 		buf2 = malloc(size+1);
743 		if (!buf2)
744 			I_Error("R_PatchSkins: No more free memory\n");
745 		M_Memcpy(buf2,buf,size);
746 		buf2[size] = '\0';
747 
748 		skin = NULL;
749 		noskincomplain = realname = hudname = false;
750 
751 		/*
752 		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)
753 		*/
754 
755 		stoken = strtok(buf2, "\r\n= ");
756 		while (stoken)
757 		{
758 			if ((stoken[0] == '/' && stoken[1] == '/')
759 				|| (stoken[0] == '#'))// skip comments
760 			{
761 				stoken = strtok(NULL, "\r\n"); // skip end of line
762 				goto next_token;              // find the real next token
763 			}
764 
765 			value = strtok(NULL, "\r\n= ");
766 
767 			if (!value)
768 				I_Error("R_PatchSkins: syntax error in P_SKIN lump# %d(%s) in WAD %s\n", lump, W_CheckNameForNumPwad(wadnum,lump), wadfiles[wadnum]->filename);
769 
770 			if (!skin) // Get the name!
771 			{
772 				if (!stricmp(stoken, "name"))
773 				{
774 					strlwr(value);
775 					skinnum = R_SkinAvailable(value);
776 					if (skinnum != -1)
777 						skin = &skins[skinnum];
778 					else
779 					{
780 						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);
781 						noskincomplain = true;
782 					}
783 				}
784 			}
785 			else // Get the properties!
786 			{
787 				// Some of these can't go in R_ProcessPatchableFields because they have side effects for future lines.
788 				if (!stricmp(stoken, "realname"))
789 				{ // Display name (eg. "Knuckles")
790 					realname = true;
791 					STRBUFCPY(skin->realname, value);
792 					SYMBOLCONVERT(skin->realname)
793 					if (!hudname)
794 						HUDNAMEWRITE(skin->realname);
795 				}
796 				else if (!stricmp(stoken, "hudname"))
797 				{ // Life icon name (eg. "K.T.E")
798 					hudname = true;
799 					HUDNAMEWRITE(value);
800 					SYMBOLCONVERT(skin->hudname)
801 					if (!realname)
802 						STRBUFCPY(skin->realname, skin->hudname);
803 				}
804 				else if (!R_ProcessPatchableFields(skin, stoken, value))
805 					CONS_Debug(DBG_SETUP, "R_PatchSkins: Unknown keyword '%s' in P_SKIN lump #%d (WAD %s)\n", stoken, lump, wadfiles[wadnum]->filename);
806 			}
807 
808 			if (!skin)
809 				break;
810 
811 next_token:
812 			stoken = strtok(NULL, "\r\n= ");
813 		}
814 		free(buf2);
815 
816 		if (!skin) // Didn't include a name parameter? What a waste.
817 		{
818 			if (!noskincomplain)
819 				CONS_Debug(DBG_SETUP, "R_PatchSkins: no skin name given in P_SKIN lump #%d (WAD %s)\n", lump, wadfiles[wadnum]->filename);
820 			continue;
821 		}
822 
823 		// Patch sprites
824 		R_LoadSkinSprites(wadnum, &lump, &lastlump, skin);
825 		//ST_LoadFaceGraphics(skinnum); -- nah let's do this elsewhere
826 
827 		R_FlushTranslationColormapCache();
828 
829 		if (!skin->availability) // Safe to print...
830 			CONS_Printf(M_GetText("Patched skin '%s'\n"), skin->name);
831 	}
832 	return;
833 }
834 
835 #undef HUDNAMEWRITE
836 #undef SYMBOLCONVERT
837