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