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