1 // Emacs style mode select -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id: p_info.c 1564 2020-12-19 06:21:07Z wesleyjohnson $
5 //
6 // Copyright(C) 2000 Simon Howard
7 // Copyright (C) 2001-2011 by DooM Legacy Team.
8 //
9 // This program is free software; you can redistribute it and/or modify
10 // it under the terms of the GNU General Public License as published by
11 // the Free Software Foundation; either version 2 of the License, or
12 // (at your option) any later version.
13 //
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 //
23 // $Log: p_info.c,v $
24 // Revision 1.15 2001/12/26 17:24:46 hurdler
25 // Update Linux version
26 //
27 // Revision 1.14 2001/08/14 00:36:26 hurdler
28 // Revision 1.13 2001/08/13 22:53:39 stroggonmeth
29 //
30 // Revision 1.12 2001/08/06 23:57:09 stroggonmeth
31 // Removed portal code, improved 3D floors in hardware mode.
32 //
33 // Revision 1.11 2001/08/02 19:15:59 bpereira
34 // fix player reset in secret level of doom2
35 //
36 // Revision 1.10 2001/06/30 15:06:01 bpereira
37 // fixed wrong next level name in intermission
38 //
39 // Revision 1.9 2001/05/16 21:21:14 bpereira
40 // Revision 1.8 2001/05/07 20:27:16 stroggonmeth
41 // Revision 1.7 2001/03/21 18:24:38 stroggonmeth
42 //
43 // Revision 1.6 2001/01/25 22:15:43 bpereira
44 // added heretic support
45 //
46 // Revision 1.5 2000/11/11 13:59:45 bpereira
47 // Revision 1.4 2000/11/09 17:56:20 stroggonmeth
48 // Revision 1.3 2000/11/06 20:52:16 bpereira
49 //
50 // Revision 1.2 2000/11/03 11:48:39 hurdler
51 // Fix compiling problem under win32 with 3D-Floors and FragglScript (to verify!)
52 //
53 // Revision 1.1 2000/11/03 02:00:44 stroggonmeth
54 // Added p_info.c and p_info.h
55 //
56 //
57 //--------------------------------------------------------------------------
58 //
59 // Level info.
60 //
61 // Under smmu, level info is stored in the level marker: ie. "mapxx"
62 // or "exmx" lump. This contains new info such as: the level name, music
63 // lump to be played, par time etc.
64 //
65 // By Simon Howard
66 //
67 //-----------------------------------------------------------------------------
68
69 #include "doomincl.h"
70 #include "doomstat.h"
71 #include "command.h"
72 #include "dehacked.h"
73 #include "dstrings.h"
74 #include "p_setup.h"
75 #include "p_info.h"
76 #include "p_mobj.h"
77 #include "t_script.h"
78 #include "w_wad.h"
79 #include "z_zone.h"
80 #include "p_local.h"
81
82 //----------------------------------------------------------------------------
83 //
84 // Helper functions
85 //
86
P_LowerCase(char * line)87 void P_LowerCase(char *line)
88 {
89 char *temp;
90
91 for(temp=line; *temp; temp++)
92 *temp = tolower((unsigned char)*temp);
93 }
94
P_StripSpaces(char * line)95 void P_StripSpaces(char *line)
96 {
97 char *temp;
98
99 temp = line+strlen(line)-1;
100
101 while(*temp == ' ')
102 {
103 *temp = '\0';
104 temp--;
105 }
106 }
107
108 #if 0
109 // [WDJ] found to be unused, 12/5/2009
110 static void P_RemoveComments(char *line)
111 {
112 char *temp = line;
113
114 while(*temp)
115 {
116 if(*temp=='/' && *(temp+1)=='/')
117 {
118 *temp = '\0'; return;
119 }
120 temp++;
121 }
122 }
123 #endif
124
125 #if 0
126 static void P_RemoveEqualses(char *line)
127 {
128 char *temp;
129
130 temp = line;
131
132 while(*temp)
133 {
134 if(*temp == '=')
135 {
136 *temp = ' ';
137 }
138 temp++;
139 }
140 }
141 #endif
142
143 //----------------------------------------------------------------------------
144 //
145 // Level vars: level variables in the [level info] section.
146 //
147 // Takes the form:
148 // [variable name] = [value]
149 //
150 // '=' sign is optional: all equals signs are internally turned to spaces
151 //
152
153 char *info_interpic;
154 char *info_levelname;
155 int info_partime;
156 char *info_music;
157 char *info_skyname;
158 char *info_creator;
159 char *info_levelpic;
160 char *info_nextlevel;
161 char *info_nextsecret;
162 char *info_intertext = NULL;
163 char *info_backdrop;
164 char *info_weapons;
165 int info_scripts; // has the current level got scripts?
166 int gravity;
167
168 enum
169 {
170 IVT_STRING,
171 IVT_INT,
172 IVT_CONSOLECMD, // SoM: W00t I RULE
173 IVT_END
174 };
175
176 typedef struct
177 {
178 int type;
179 const char *name;
180 void *variable;
181 } levelvar_t;
182
183 levelvar_t levelvars[]=
184 {
185 {IVT_STRING, "levelpic", &info_levelpic},
186 {IVT_STRING, "levelname", &info_levelname},
187 {IVT_INT, "partime", &info_partime},
188 {IVT_STRING, "music", &info_music},
189 {IVT_STRING, "skyname", &info_skyname},
190 {IVT_STRING, "creator", &info_creator},
191 {IVT_STRING, "interpic", &info_interpic},
192 {IVT_STRING, "nextlevel", &info_nextlevel},
193 {IVT_STRING, "nextsecret", &info_nextsecret},
194 {IVT_INT, "gravity", &gravity},
195 {IVT_STRING, "inter-backdrop",&info_backdrop},
196 {IVT_STRING, "defaultweapons",&info_weapons},
197 {IVT_CONSOLECMD,"consolecmd", NULL},
198 {IVT_END, 0, 0}
199 };
200
P_ParseLevelVar(char * cmd)201 void P_ParseLevelVar(char *cmd)
202 {
203 char *equals;
204 char *varname;
205 levelvar_t* current;
206
207 if(!*cmd) return;
208
209 // right, first find the variable name
210 // [WDJ] Replace code that could overrun strings.
211 // This code does not copy, cannot overrun strings.
212 // Ignore the equals sign, parse around it.
213 varname = cmd; // varname is first
214 // leading spaces already removed by caller
215 equals = varname;
216 while(*equals && *equals != ' ' && *equals != '=') equals++; // span the name
217 *equals++ = '\0'; // terminate varname
218
219 // find what it equals
220 while(*equals && (*equals == ' ' || *equals == '=')) equals++; // span the '='
221 // it is valid for equals to be null string
222
223 current = levelvars;
224
225 while(current->type != IVT_END)
226 {
227 if(!strcasecmp(current->name, varname))
228 {
229 switch(current->type)
230 {
231 case IVT_STRING:
232 // Just drop the previous string value, it may be const.
233 // Recover such strings at end-level
234 *(char**)current->variable // +5 for safety
235 = Z_Malloc(strlen(equals)+5, PU_LEVEL, NULL);
236 strcpy(*(char**)current->variable, equals);
237 break;
238
239 case IVT_INT:
240 *(int*)current->variable = atoi(equals);
241 break;
242
243 case IVT_CONSOLECMD:
244 {
245 // consolecmd = <string>
246 // pass the string as a console command
247 char t[256];
248 snprintf(t, 255, "%s\n", equals);
249 t[255] = '\0';
250 COM_BufAddText(t);
251 }
252 break;
253 }
254 break; // exit loop, only one name will match
255 }
256 current++;
257 }
258 }
259
260
261 // clear all the level variables so that none are left over from a
262 // previous level
263
isExMy(char * name)264 int isExMy(char * name)
265 {
266 // if(strlen(name) != 4) goto not_ExMy; // redundant
267 if( name[4] != '\0' ) // this also confirms strlen
268 goto not_ExMy;
269 if( (name[0] != 'E' && name[0] != 'e') || (name[2] != 'M' && name[2] != 'm') )
270 goto not_ExMy;
271 if( !isnumchar(name[1]) || !isnumchar(name[3]) )
272 goto not_ExMy;
273
274 return 1;
275
276 not_ExMy:
277 return 0;
278 }
279
isMAPxy(char * name)280 int isMAPxy(char * name)
281 {
282 // if(strlen(name) != 5) goto not_map; // redundant
283 if( name[5] != '\0' ) // this also confirms strlen
284 goto not_map;
285 if( (name[0] != 'M' && name[0] != 'm') || (name[1] != 'A' && name[1] != 'a') || (name[2] != 'P' && name[2] != 'p') )
286 goto not_map;
287 if( !isnumchar(name[3]) || !isnumchar(name[4]) )
288 goto not_map;
289
290 return 1;
291
292 not_map:
293 return 0;
294 }
295
P_Clear_LevelVars(void)296 void P_Clear_LevelVars(void)
297 {
298 info_levelname = info_skyname = info_levelpic = info_interpic = "";
299 info_music = "";
300 info_creator = "unknown";
301 info_partime = -1;
302
303 if(gamemode == doom2_commercial && isExMy(level_mapname))
304 {
305 static char nextlevel[10];
306 info_nextlevel = nextlevel;
307
308 // set the next episode
309 strcpy(nextlevel, level_mapname);
310 nextlevel[3] ++;
311 if(nextlevel[3] > '9') // next episode
312 {
313 nextlevel[3] = '1';
314 nextlevel[1] ++;
315 }
316
317 info_music = level_mapname;
318 }
319 else
320 info_nextlevel = "";
321
322 info_nextsecret = "";
323
324 info_weapons = "";
325 gravity = FRACUNIT; // default gravity
326
327 if(info_intertext)
328 {
329 Z_Free(info_intertext);
330 info_intertext = NULL;
331 }
332
333 info_backdrop = NULL;
334
335 T_Clear_Scripts();
336 info_scripts = false;
337 }
338
339 //----------------------------------------------------------------------------
340 //
341 // P_ParseScriptLine
342 //
343 // FraggleScript: if we are reading in script lines, we add the new lines
344 // into the levelscript
345 //
346
347 //SoM: Dynamic limit set by lumpsize...
348 int maxscriptsize = -1;
349
P_ParseScriptLine(char * line)350 void P_ParseScriptLine(char *line)
351 {
352 // [WDJ] FIXME ???
353 if(fs_levelscript.data[0] == 0)
354 {
355 Z_Free(fs_levelscript.data);
356 fs_levelscript.data = Z_Malloc(maxscriptsize, PU_LEVEL, 0);
357 fs_levelscript.data[0] = '\0';
358 }
359
360 #ifdef DEBUGFILE
361 if( debugfile )
362 fprintf( debugfile, "SL: %s\n", line );
363 #endif
364
365 int lslen = strlen(fs_levelscript.data);
366 int slen = strlen(line);
367
368 // [WDJ] account for \n and 0 term
369 if( (lslen+slen+2) > maxscriptsize)
370 {
371 I_SoftError("Script is larger than levelscript buffer, %i char line rejected.\n", slen);
372 return;
373 }
374
375 // add the new line to the current data
376 // (ugh) sprintf(fs_levelscript.data, "%s%s\n", fs_levelscript.data, line);
377 memcpy(&fs_levelscript.data[lslen], line, slen);
378 fs_levelscript.data[lslen + slen ] = '\n';
379 fs_levelscript.data[lslen + slen + 1] = '\0';
380 }
381
382 //-------------------------------------------------------------------------
383 //
384 // P_ParseInterText
385 //
386 // Add line to the custom intertext
387 //
388
P_ParseInterText(char * line)389 void P_ParseInterText(char *line)
390 {
391 while(*line==' ') line++;
392 if(!*line) return;
393
394 // [WDJ] Add safety term, use safe copy.
395 if(info_intertext)
396 {
397 int textlen = strlen(info_intertext);
398 int avail = maxscriptsize - textlen;
399 if( avail > 2 )
400 {
401 // newline
402 info_intertext[textlen] = '\n';
403 textlen ++;
404 avail --;
405
406 // add line to end
407 strncpy(&info_intertext[textlen], line, avail);
408 }
409 }
410 else
411 {
412 // allocate 1 extra for safety term
413 info_intertext = Z_Malloc(maxscriptsize+1, PU_STATIC, 0);
414 info_intertext[maxscriptsize-1] = 0; // overrun detector
415 strncpy(info_intertext, line, maxscriptsize);
416 }
417 info_intertext[maxscriptsize] = 0; // safety term
418
419 if( info_intertext[maxscriptsize-1] != 0 ) // overrun detected
420 {
421 I_SoftError("Intermission text overruns buffer, truncated.\n");
422 }
423 }
424
425 //---------------------------------------------------------------------------
426 //
427 // Setup/Misc. Functions
428 //
429
430 boolean default_weaponowned[NUMWEAPONS];
431
432 // Init weapon positions, dependent upon info.
P_InitWeapons(void)433 void P_InitWeapons(void)
434 {
435 char *s;
436
437 memset(default_weaponowned, 0, sizeof(default_weaponowned));
438
439 s = info_weapons;
440
441 while(*s)
442 {
443 switch(*s)
444 {
445 case '3': default_weaponowned[wp_shotgun] = true; break;
446 case '4': default_weaponowned[wp_chaingun] = true; break;
447 case '5': default_weaponowned[wp_missile] = true; break;
448 case '6': default_weaponowned[wp_plasma] = true; break;
449 case '7': default_weaponowned[wp_bfg] = true; break;
450 case '8': default_weaponowned[wp_supershotgun] = true; break;
451 default: break;
452 }
453 s++;
454 }
455 }
456
457 #if 0
458 // [WDJ] 8/26/2011 moved inline
459 //SoM: Moved from hu_stuff.c
460 #define HU_TITLE (text[HUSTR_E1M1_NUM + (gameepisode-1)*9+gamemap-1])
461 #define HU_TITLE2 (text[HUSTR_1_NUM + gamemap-1])
462 #define HU_TITLEP (text[PHUSTR_1_NUM + gamemap-1])
463 #define HU_TITLET (text[THUSTR_1_NUM + gamemap-1])
464 #define HU_TITLEH (text[HERETIC_E1M1_NUM + (gameepisode-1)*9+gamemap-1])
465 #endif
466
467 static char * levelname;
468
469 // Determine game specific level name.
470 // From P_SetupLevel, level_mapname.
471 // Called by P_Load_LevelInfo
P_FindLevelName(void)472 void P_FindLevelName(void)
473 {
474 // Determine the level name.
475 // There are a number of sources from which it can come from,
476 // getting the right one is the tricky bit =).
477
478 if(*info_levelname)
479 {
480 // info level name from level lump (p_info.c) ?
481 levelname = info_levelname;
482 }
483 else if(!newlevel || deh_loaded)
484 {
485 // not a new level or dehacked level names
486 if( gamemode == heretic )
487 {
488 // Heretic shareware, Heretic, Blasphemer
489 levelname = text[HERETIC_E1M1_NUM + (gameepisode-1)*9+gamemap-1];
490 }
491 else if(isMAPxy(level_mapname))
492 {
493 switch( gamedesc_id )
494 {
495 case GDESC_plutonia:
496 // Plutonia
497 levelname = text[PHUSTR_1_NUM + gamemap-1];
498 break;
499 case GDESC_tnt:
500 // TNT
501 levelname = text[THUSTR_1_NUM + gamemap-1];
502 break;
503 default:
504 // Doom2, FreeDoom, FreeDM
505 levelname = text[HUSTR_1_NUM + gamemap-1];
506 break;
507 }
508 }
509 else if(isExMy(level_mapname))
510 {
511 // Doom shareware, Doom, UltDoom, UltFreeDoom, ChexQuest
512 levelname = text[HUSTR_E1M1_NUM + (gameepisode-1)*9+gamemap-1];
513 }
514 else
515 levelname = level_mapname;
516 }
517 else // otherwise just put "new level"
518 {
519 static char newlevelstr[50];
520
521 sprintf(newlevelstr, "%s: new level", level_mapname);
522 levelname = newlevelstr;
523 }
524 }
525
526 //-------------------------------------------------------------------------
527 //
528 // P_ParseInfoCmd
529 //
530 // We call the relevant function to deal with the line we are given,
531 // based on info_readtype. If we get a section divider ([] bracketed) we
532 // change readtype.
533 //
534
535 enum
536 {
537 RT_LEVELINFO,
538 RT_SCRIPT,
539 RT_OTHER,
540 RT_INTERTEXT
541 } info_readtype; // global parameter to I_ParseInfoCmd
542
543
P_ParseInfoCmd(char * line)544 void P_ParseInfoCmd(char *line)
545 {
546 if(!*line)
547 return;
548
549 if(info_readtype != RT_SCRIPT) // not for scripts
550 {
551 // P_LowerCase(line);
552 while(*line == ' ') line++;
553 if(!*line) return;
554 if((line[0] == '/' && line[1] == '/') // comment
555 || line[0] == '#' || line[0] == ';') return;
556 }
557
558 if(*line == '[') // a new section seperator
559 {
560 line++;
561 if(!strncasecmp(line, "level info", 10))
562 info_readtype = RT_LEVELINFO;
563 else if(!strncasecmp(line, "scripts", 7))
564 {
565 info_readtype = RT_SCRIPT;
566 info_scripts = true; // has scripts
567 }
568 else if(!strncasecmp(line, "intertext", 9))
569 info_readtype = RT_INTERTEXT;
570 return;
571 }
572
573 // Parse the line. May alter line if necessary.
574 switch(info_readtype)
575 {
576 case RT_LEVELINFO:
577 P_ParseLevelVar(line);
578 break;
579
580 case RT_SCRIPT:
581 P_ParseScriptLine(line);
582 break;
583
584 case RT_INTERTEXT:
585 P_ParseInterText(line);
586 break;
587
588 case RT_OTHER:
589 break;
590 }
591 }
592
593 //-------------------------------------------------------------------------
594 //
595 // P_Load_LevelInfo
596 //
597 // Load the info lump for a level. Call P_ParseInfoCmd for each
598 // line of the lump.
599
600 // Dependent upon level_mapname, level_lumpnum.
P_Load_LevelInfo(void)601 void P_Load_LevelInfo( void )
602 {
603 char *lump;
604 char *endlump_cp;
605 char *readline;
606 char *rlp;
607 int lumpsize;
608
609 // [WDJ] Fix to use char ptrs, and to use normal char append.
610 // Replace operations that could overrun strings.
611 info_readtype = RT_OTHER; // global to I_ParseInfoCmd
612 P_Clear_LevelVars(); // clear vars and init levelscript
613
614 lumpsize = maxscriptsize = W_LumpLength(level_lumpnum);
615 readline = Z_Malloc(lumpsize + 1, PU_IN_USE, 0);
616 rlp = &readline[0];
617
618 if(lumpsize > 0)
619 {
620 fs_src_cp = lump = W_CacheLumpNum(level_lumpnum, PU_IN_USE); // level info
621 endlump_cp = lump + lumpsize; // end of lump
622 while(fs_src_cp < endlump_cp)
623 {
624 register char ch = *fs_src_cp;
625 if(ch == '\n') // end of line
626 {
627 *rlp = '\0';
628 P_ParseInfoCmd(readline); // parse line
629 rlp = &readline[0]; // clear for next line
630 }
631 else
632 {
633 // add to line if valid char
634 if(isprint(ch) || ch == '{' || ch == '}')
635 {
636 *rlp++ = ch; // add char
637 }
638 }
639 fs_src_cp++;
640 }
641
642 // parse last line
643 *rlp = '\0';
644 P_ParseInfoCmd(readline);
645 Z_Free(lump);
646 }
647 Z_Free(readline);
648
649 // These are affected by P_Clear_LevelVars().
650 // Init weapon positions, dependent upon weapons info.
651 P_InitWeapons();
652 // Determine game specific level name.
653 // Dependent upon info level vars and level_mapname.
654 P_FindLevelName();
655
656 //Set the gravity for the level!
657 if( cv_gravity.value != gravity )
658 {
659 if(gravity != FRACUNIT)
660 COM_BufAddText(va("gravity %f\n", ((double)gravity) / 100));
661 else
662 COM_BufAddText(va("gravity %f\n", ((double)gravity) / FRACUNIT));
663 }
664
665 COM_BufExecute( CFG_none ); //Hurdler: flush the command buffer
666 return;
667 }
668
669 //-------------------------------------------------------------------------
670 //
671 // Console Commands
672 //
673
COM_MapInfo_f(void)674 void COM_MapInfo_f(void)
675 {
676 CONS_Printf("Name: %s\n", levelname);
677 CONS_Printf("Author: %s\n", info_creator);
678 }
679
680
P_Register_Info_Commands(void)681 void P_Register_Info_Commands(void)
682 {
683 COM_AddCommand("mapinfo", COM_MapInfo_f, CC_info);
684 }
685
686
687
P_LevelName(void)688 char * P_LevelName(void)
689 {
690 return levelname;
691 }
692
693 // todo : make this use mapinfo lump
694 // Called by WI_Draw_EL "Entering <LevelName>"
695 // Called by IN_Draw_YAH "NOW ENTERING"
P_LevelNameByNum(int episode,int map)696 const char * P_LevelNameByNum( int episode, int map )
697 {
698 register const char * levnam = "New map";
699 switch(gamemode)
700 {
701 case doom_shareware :
702 case doom_registered :
703 case ultdoom_retail : // and UltFreeDoom
704 case chexquest1 :
705 levnam = text[HUSTR_E1M1_NUM + (episode-1)*9+map-1];
706 break;
707 case doom2_commercial : // and FreeDoom, FreeDM
708 switch( gamedesc_id )
709 {
710 case GDESC_tnt :
711 levnam = text[THUSTR_1_NUM + map-1];
712 break;
713 case GDESC_plutonia :
714 levnam = text[PHUSTR_1_NUM + map-1];
715 break;
716 default :
717 levnam = text[HUSTR_1_NUM + map-1];
718 break;
719 }
720 break;
721 case heretic: // and shareware, blasphemer
722 levnam = text[HERETIC_E1M1_NUM + (episode-1)*9+map-1];
723 break;
724 case hexen:
725 break;
726 default:
727 break;
728 }
729 return levnam;
730 }
731