engine: reject mbf21 and shit24 wads. there is no way to know if it is safe to ignore...
[k8vavoom.git] / source / mapinfo.cpp
blobff61e1650237570ea20225c433c826be53d028e0
1 //**************************************************************************
2 //**
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
9 //**
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
12 //**
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
16 //**
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
21 //**
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
24 //**
25 //**************************************************************************
26 #include "gamedefs.h"
27 #include "mapinfo.h"
28 #include "psim/p_levelinfo.h"
29 #include "server/server.h"
30 #include "server/sv_local.h"
31 #include "decorate/vc_decorate.h"
32 #include "language.h"
33 #include "filesys/files.h"
34 #include "render/r_public.h" /* R_HasNamedSkybox */
37 static int cli_NoMapinfoPlrClasses = 0;
38 static int cli_MapperIsIdiot = 0;
39 int cli_NoZMapinfo = 0; // need to be extern for files.cpp
41 /*static*/ bool cliRegister_mapinfo_args =
42 VParsedArgs::RegisterFlagSet("-nomapinfoplayerclasses", "ignore player classes from MAPINFO", &cli_NoMapinfoPlrClasses) &&
43 VParsedArgs::RegisterFlagSet("-nozmapinfo", "do not load ZMAPINFO", &cli_NoZMapinfo) &&
44 VParsedArgs::RegisterFlagSet("-mapper-is-idiot", "!", &cli_MapperIsIdiot);
47 static VCvarB gm_default_pain_limit("gm_default_pain_limit", true, "Limit Pain Elemental skull spawning by default?", CVAR_Archive);
50 // ////////////////////////////////////////////////////////////////////////// //
51 // switches to C mode
52 struct SCParseModeSaver {
53 VScriptParser *sc;
54 bool oldCMode;
55 bool oldEscape;
57 SCParseModeSaver (VScriptParser *asc) : sc(asc) {
58 oldCMode = sc->IsCMode();
59 oldEscape = sc->IsEscape();
60 sc->SetCMode(true);
61 sc->SetEscape(true);
64 ~SCParseModeSaver () {
65 sc->SetCMode(oldCMode);
66 sc->SetEscape(oldEscape);
69 //SCParseModeSaver (const SCParseModeSaver &);
70 //void operator = (const SCParseModeSaver &) const;
71 VV_DISABLE_COPY(SCParseModeSaver)
75 struct FMapSongInfo {
76 VName MapName;
77 VName SongName;
80 struct ParTimeInfo {
81 VName MapName;
82 int par;
86 struct SpawnEdFixup {
87 VStr ClassName;
88 int num;
89 int flags;
90 int special;
91 int args[5];
95 // ////////////////////////////////////////////////////////////////////////// //
96 struct MapInfoCommand {
97 const char *cmd;
98 void (*handler) (VScriptParser *sc, bool newFormat, VMapInfo *info, bool &HexenMode);
99 MapInfoCommand *next;
103 static MapInfoCommand *mclist = nullptr;
104 static TMap<VStr, MapInfoCommand *> mcmap; // key is lowercase name
105 static bool hasCustomDamageFactors = false;
107 enum {
108 WSK_WAS_SKY1 = 1u<<0,
109 WSK_WAS_SKY2 = 1u<<1,
111 static unsigned wasSky1Sky2 = 0u; // bit0: was sky1; bit1: was sky2
114 #define MAPINFOCMD(name_) \
115 class MapInfoCommandImpl##name_ { \
116 public: \
117 /*static*/ MapInfoCommand mci; \
118 MapInfoCommandImpl##name_ (const char *aname) { \
119 mci.cmd = aname; \
120 mci.handler = &Handler; \
121 mci.next = nullptr; \
122 if (!mclist) { \
123 mclist = &mci; \
124 } else { \
125 MapInfoCommand *last = mclist; \
126 while (last->next) last = last->next; \
127 last->next = &mci; \
130 static void Handler (VScriptParser *sc, bool newFormat, VMapInfo *info, bool &HexenMode); \
131 }; \
132 /*MapInfoCommand MapInfoCommandImpl##name_ mci;*/ \
133 MapInfoCommandImpl##name_ mpiprzintrnlz_mici_##name_(#name_); \
134 void MapInfoCommandImpl##name_::Handler (VScriptParser *sc, bool newFormat, VMapInfo *info, bool &HexenMode)
137 //==========================================================================
139 // checkEndBracket
141 //==========================================================================
142 static bool checkEndBracket (VScriptParser *sc) {
143 if (sc->Check("}")) return true;
144 if (sc->AtEnd()) {
145 sc->Message("Some mapper cannot into proper mapinfo (missing '}')");
146 return true;
148 return false;
152 //==========================================================================
154 // ExpectBool
156 //==========================================================================
157 static bool ExpectBool (const char *optname, VScriptParser *sc) {
158 if (sc->Check("true") || sc->Check("on") || sc->Check("tan")) return true;
159 if (sc->CheckNumber()) return !!sc->Number;
160 sc->Error(va("boolean value expected for option '%s'", optname));
161 return false;
165 // ////////////////////////////////////////////////////////////////////////// //
166 VName P_TranslateMap (int map);
167 // `sc->SourceLump` must be set
168 static void ParseMapInfo (VScriptParser *sc);
169 // `sc->SourceLump` must be set
170 static void ParseUMapinfo (VScriptParser *sc);
173 // ////////////////////////////////////////////////////////////////////////// //
174 static char miWarningBuf[16384];
176 VVA_OKUNUSED __attribute__((format(printf, 2, 3))) static void miWarning (VScriptParser *sc, const char *fmt, ...) {
177 va_list argptr;
178 static char miWarningBuf[16384];
179 va_start(argptr, fmt);
180 vsnprintf(miWarningBuf, sizeof(miWarningBuf), fmt, argptr);
181 va_end(argptr);
182 if (sc) {
183 GCon->Logf(NAME_Warning, "MAPINFO:%s: %s", *sc->GetLoc().toStringNoCol(), miWarningBuf);
184 } else {
185 GCon->Logf(NAME_Warning, "MAPINFO: %s", miWarningBuf);
190 VVA_OKUNUSED __attribute__((format(printf, 2, 3))) static void miWarning (const VTextLocation &loc, const char *fmt, ...) {
191 va_list argptr;
192 va_start(argptr, fmt);
193 vsnprintf(miWarningBuf, sizeof(miWarningBuf), fmt, argptr);
194 va_end(argptr);
195 GCon->Logf(NAME_Warning, "MAPINFO:%s: %s", *loc.toStringNoCol(), miWarningBuf);
199 // ////////////////////////////////////////////////////////////////////////// //
200 static VMapInfo DefaultMap;
201 static TArray<VMapInfo> MapInfo;
202 static TArray<FMapSongInfo> MapSongList;
203 static VClusterDef DefaultClusterDef;
204 static TArray<VClusterDef> ClusterDefs;
205 static TArray<VEpisodeDef> EpisodeDefs;
206 static TArray<VSkillDef> SkillDefs;
208 static bool mapinfoParsed = false;
209 static TArray<ParTimeInfo> partimes; // not a hashmap, so i can use `ICmp`
211 static TArray<VName> MapInfoPlayerClasses;
213 static TMap<int, SpawnEdFixup> SpawnNumFixups; // keyed by num
214 static TMap<int, SpawnEdFixup> DoomEdNumFixups; // keyed by num
217 //==========================================================================
219 // VMapInfo::GetName
221 //==========================================================================
222 VStr VMapInfo::GetName () const {
223 return (Flags&VLevelInfo::LIF_LookupName ? GLanguage[*Name] : Name);
227 //==========================================================================
229 // VMapInfo::dump
231 //==========================================================================
232 void VMapInfo::dump (const char *msg) const {
233 if (msg && msg[0]) GCon->Logf(NAME_Debug, "==== mapinfo: %s ===", msg); else GCon->Log(NAME_Debug, "==== mapinfo ===");
234 GCon->Logf(NAME_Debug, " LumpName: \"%s\"", *VStr(LumpName).quote());
235 GCon->Logf(NAME_Debug, " Name: \"%s\"", *Name.quote());
236 GCon->Logf(NAME_Debug, " LevelNum: %d", LevelNum);
237 GCon->Logf(NAME_Debug, " Cluster: %d", Cluster);
238 GCon->Logf(NAME_Debug, " WarpTrans: %d", WarpTrans);
239 GCon->Logf(NAME_Debug, " NextMap: \"%s\"", *VStr(NextMap).quote());
240 GCon->Logf(NAME_Debug, " SecretMap: \"%s\"", *VStr(SecretMap).quote());
241 GCon->Logf(NAME_Debug, " SongLump: \"%s\"", *VStr(SongLump).quote());
242 GCon->Logf(NAME_Debug, " Sky1Texture: %d", Sky1Texture);
243 GCon->Logf(NAME_Debug, " Sky2Texture: %d", Sky2Texture);
244 GCon->Logf(NAME_Debug, " Sky1ScrollDelta: %g", Sky1ScrollDelta);
245 GCon->Logf(NAME_Debug, " Sky2ScrollDelta: %g", Sky2ScrollDelta);
246 GCon->Logf(NAME_Debug, " SkyBox: \"%s\"", *VStr(SkyBox).quote());
247 GCon->Logf(NAME_Debug, " FadeTable: \"%s\"", *VStr(FadeTable).quote());
248 GCon->Logf(NAME_Debug, " Fade: 0x%08x", Fade);
249 GCon->Logf(NAME_Debug, " OutsideFog: 0x%08x", OutsideFog);
250 GCon->Logf(NAME_Debug, " Gravity: %g", Gravity);
251 GCon->Logf(NAME_Debug, " AirControl: %g", AirControl);
252 GCon->Logf(NAME_Debug, " Flags: 0x%08x", Flags);
253 GCon->Logf(NAME_Debug, " Flags2: 0x%08x", Flags2);
254 GCon->Logf(NAME_Debug, " EnterTitlePatch: \"%s\"", *VStr(EnterTitlePatch).quote());
255 GCon->Logf(NAME_Debug, " ExitTitlePatch: \"%s\"", *VStr(ExitTitlePatch).quote());
256 GCon->Logf(NAME_Debug, " ParTime: %d", ParTime);
257 GCon->Logf(NAME_Debug, " SuckTime: %d", SuckTime);
258 GCon->Logf(NAME_Debug, " HorizWallShade: %d", HorizWallShade);
259 GCon->Logf(NAME_Debug, " VertWallShade: %d", VertWallShade);
260 GCon->Logf(NAME_Debug, " Infighting: %d", Infighting);
261 GCon->Logf(NAME_Debug, " RedirectType: \"%s\"", *VStr(RedirectType).quote());
262 GCon->Logf(NAME_Debug, " RedirectMap: \"%s\"", *VStr(RedirectMap).quote());
263 GCon->Logf(NAME_Debug, " ExitPic: \"%s\"", *VStr(ExitPic).quote());
264 GCon->Logf(NAME_Debug, " EnterPic: \"%s\"", *VStr(EnterPic).quote());
265 GCon->Logf(NAME_Debug, " InterMusic: \"%s\"", *VStr(InterMusic).quote());
266 GCon->Logf(NAME_Debug, " ExitText: \"%s\"", *VStr(ExitText).quote());
267 GCon->Logf(NAME_Debug, " SecretExitText: \"%s\"", *VStr(SecretExitText).quote());
268 GCon->Logf(NAME_Debug, " InterBackdrop: \"%s\"", *VStr(InterBackdrop).quote());
269 for (auto &&sac : SpecialActions) {
270 GCon->Log(NAME_Debug, " --- special action ---");
271 GCon->Logf(NAME_Debug, " TypeName: \"%s\"", *VStr(sac.TypeName).quote());
272 GCon->Logf(NAME_Debug, " Special: %d (%d,%d,%d,%d,%d)", sac.Special, sac.Args[0], sac.Args[1], sac.Args[2], sac.Args[3], sac.Args[4]);
277 //==========================================================================
279 // P_SetupMapinfoPlayerClasses
281 //==========================================================================
282 void P_SetupMapinfoPlayerClasses () {
283 VClass *PPClass = VClass::FindClass("PlayerPawn");
284 if (!PPClass) {
285 GCon->Logf(NAME_Warning, "Can't find PlayerPawn class");
286 return;
288 if (!flForcePlayerClass.isEmpty()) {
289 VClass *Class = VClass::FindClassNoCase(*flForcePlayerClass);
290 if (!Class) {
291 GCon->Logf(NAME_Warning, "Mode-ForcePlayerClass: No such class `%s`", *flForcePlayerClass);
292 } else if (!Class->IsChildOf(PPClass)) {
293 GCon->Logf(NAME_Warning, "Mode-ForcePlayerClass: '%s' is not a player pawn class", *flForcePlayerClass);
294 } else {
295 GGameInfo->PlayerClasses.Clear();
296 GGameInfo->PlayerClasses.Append(Class);
297 GCon->Logf(NAME_Init, "mode-forced player class '%s'", *flForcePlayerClass);
298 return;
301 if (cli_NoMapinfoPlrClasses > 0) return;
302 if (MapInfoPlayerClasses.length() == 0) return;
303 GCon->Logf(NAME_Init, "setting up %d player class%s from mapinfo", MapInfoPlayerClasses.length(), (MapInfoPlayerClasses.length() != 1 ? "es" : ""));
304 GGameInfo->PlayerClasses.Clear();
305 for (int f = 0; f < MapInfoPlayerClasses.length(); ++f) {
306 VClass *Class = VClass::FindClassNoCase(*MapInfoPlayerClasses[f]);
307 if (!Class) {
308 GCon->Logf(NAME_Warning, "No player class '%s'", *MapInfoPlayerClasses[f]);
309 continue;
311 if (!Class->IsChildOf(PPClass)) {
312 GCon->Logf(NAME_Warning, "'%s' is not a player pawn class", *MapInfoPlayerClasses[f]);
313 continue;
315 GGameInfo->PlayerClasses.Append(Class);
317 if (GGameInfo->PlayerClasses.length() == 0) Sys_Error("no valid classes found in MAPINFO playerclass replacement");
321 //==========================================================================
323 // appendNumFixup
325 //==========================================================================
326 static void appendNumFixup (TMap<int, SpawnEdFixup> &arr, VStr className, int num, int flags=0, int special=0, int arg1=0, int arg2=0, int arg3=0, int arg4=0, int arg5=0) {
327 SpawnEdFixup *fxp = arr.get(num);
328 if (fxp) {
329 fxp->ClassName = className;
330 fxp->flags = flags;
331 fxp->special = special;
332 fxp->args[0] = arg1;
333 fxp->args[1] = arg2;
334 fxp->args[2] = arg3;
335 fxp->args[3] = arg4;
336 fxp->args[4] = arg5;
337 return;
339 SpawnEdFixup fx;
340 fx.num = num;
341 fx.flags = flags;
342 fx.special = special;
343 fx.args[0] = arg1;
344 fx.args[1] = arg2;
345 fx.args[2] = arg3;
346 fx.args[3] = arg4;
347 fx.args[4] = arg5;
348 fx.ClassName = className;
349 arr.put(num, fx);
353 //==========================================================================
355 // processNumFixups
357 //==========================================================================
358 static void processNumFixups (const char *errname, bool ismobj, TMap<int, SpawnEdFixup> &fixups) {
359 //GCon->Logf(NAME_Debug, "fixing '%s' (%d)", errname, fixups.count());
360 #if 0
361 int f = 0;
362 while (f < list.length()) {
363 mobjinfo_t &nfo = list[f];
364 SpawnEdFixup *fxp = fixups.get(nfo.DoomEdNum);
365 if (fxp) {
366 SpawnEdFixup fix = *fxp;
367 VStr cname = fxp->ClassName;
368 //GCon->Logf(NAME_Debug, " MAPINFO: class '%s' for %s got doomed num %d (got %d)", *cname, errname, fxp->num, nfo.DoomEdNum);
369 fixups.del(nfo.DoomEdNum);
371 if (cname.length() == 0 || cname.ICmp("none") == 0) {
372 // remove it
373 list.removeAt(f);
374 continue;
377 // set it
378 VClass *cls;
379 if (cname.length() == 0 || cname.ICmp("none") == 0) {
380 cls = nullptr;
381 } else {
382 cls = VClass::FindClassNoCase(*cname);
383 if (!cls) GCon->Logf(NAME_Warning, "MAPINFO: class '%s' for %s %d not found", *cname, errname, nfo.DoomEdNum);
385 nfo.Class = cls;
386 nfo.GameFilter = GAME_Any;
387 nfo.flags = fix.flags;
388 nfo.special = fix.special;
389 nfo.args[0] = fix.args[0];
390 nfo.args[1] = fix.args[1];
391 nfo.args[2] = fix.args[2];
392 nfo.args[3] = fix.args[3];
393 nfo.args[4] = fix.args[4];
395 ++f;
397 #endif
398 //GCon->Logf(NAME_Debug, " appending '%s' (%d)", errname, fixups.count());
399 // append new
400 for (auto it = fixups.first(); it; ++it) {
401 SpawnEdFixup *fxp = &it.getValue();
402 if (fxp->num <= 0) continue;
403 VStr cname = fxp->ClassName;
404 //GCon->Logf(NAME_Debug, " MAPINFO0: class '%s' for %s got doomed num %d", *cname, errname, fxp->num);
405 // set it
406 VClass *cls;
407 if (cname.length() == 0 || cname.ICmp("none") == 0) {
408 cls = nullptr;
409 } else {
410 cls = VClass::FindClassNoCase(*cname);
411 if (!cls) GCon->Logf(NAME_Warning, "MAPINFO: class '%s' for %s %d not found", *cname, errname, fxp->num);
413 //GCon->Logf(NAME_Debug, " MAPINFO1: class '%s' for %s got doomed num %d", *cname, errname, fxp->num);
414 if (!cls) {
415 if (ismobj) {
416 VClass::RemoveMObjId(fxp->num, GGameInfo->GameFilterFlag);
417 } else {
418 VClass::RemoveScriptId(fxp->num, GGameInfo->GameFilterFlag);
420 } else {
421 mobjinfo_t *nfo = (ismobj ? VClass::AllocMObjId(fxp->num, GGameInfo->GameFilterFlag, cls) : VClass::AllocScriptId(fxp->num, GGameInfo->GameFilterFlag, cls));
422 if (nfo) {
423 nfo->Class = cls;
424 nfo->flags = fxp->flags;
425 nfo->special = fxp->special;
426 nfo->args[0] = fxp->args[0];
427 nfo->args[1] = fxp->args[1];
428 nfo->args[2] = fxp->args[2];
429 nfo->args[3] = fxp->args[3];
430 nfo->args[4] = fxp->args[4];
434 fixups.clear();
438 //==========================================================================
440 // findSavedPar
442 // returns -1 if not found
444 //==========================================================================
445 static int findSavedPar (VName map) {
446 if (map == NAME_None) return -1;
447 for (int f = partimes.length()-1; f >= 0; --f) {
448 if (VStr::ICmp(*partimes[f].MapName, *map) == 0) return partimes[f].par;
450 return -1;
454 //==========================================================================
456 // loadSkyTexture
458 //==========================================================================
459 static int loadSkyTexture (VScriptParser *sc, VName name, bool silent=false) {
460 static TMapNC<VName, int> forceList;
462 if (name == NAME_None) return GTextureManager.DefaultTexture;
464 VName loname = VName(*name, VName::AddLower);
465 auto tidp = forceList.get(loname);
466 if (tidp) return *tidp;
468 //int Tex = GTextureManager.NumForName(sc->Name8, TEXTYPE_Wall, false);
469 //info->Sky1Texture = GTextureManager.NumForName(sc->Name8, TEXTYPE_Wall, false);
470 int Tex = GTextureManager.CheckNumForName(name, TEXTYPE_SkyMap, true);
471 if (Tex > 0) return Tex;
473 Tex = GTextureManager.CheckNumForName(name, TEXTYPE_Wall, true);
474 if (Tex >= 0) {
475 forceList.put(loname, Tex);
476 return Tex;
479 Tex = GTextureManager.CheckNumForName(name, TEXTYPE_WallPatch, false);
480 if (Tex < 0) {
481 int LumpNum = W_CheckNumForTextureFileName(*name);
482 if (LumpNum >= 0) {
483 Tex = GTextureManager.FindTextureByLumpNum(LumpNum);
484 if (Tex < 0) {
485 VTexture *T = VTexture::CreateTexture(TEXTYPE_WallPatch, LumpNum);
486 if (!T) T = VTexture::CreateTexture(TEXTYPE_Any, LumpNum);
487 if (T) {
488 Tex = GTextureManager.AddTexture(T);
489 T->Name = NAME_None;
492 } else {
493 LumpNum = W_CheckNumForName(name, WADNS_Patches);
494 if (LumpNum < 0) LumpNum = W_CheckNumForTextureFileName(*name);
495 if (LumpNum >= 0) {
496 Tex = GTextureManager.AddTexture(VTexture::CreateTexture(TEXTYPE_WallPatch, LumpNum));
497 } else {
498 // DooM:Complete has some patches in "sprites/"
499 LumpNum = W_CheckNumForName(name, WADNS_Sprites);
500 if (LumpNum >= 0) Tex = GTextureManager.AddTexture(VTexture::CreateTexture(TEXTYPE_Any, LumpNum));
505 if (Tex < 0) Tex = GTextureManager.CheckNumForName(name, TEXTYPE_Any, false);
507 if (Tex < 0) Tex = GTextureManager.AddPatch(name, TEXTYPE_WallPatch, true);
509 if (Tex < 0) {
510 if (silent) return -1;
511 miWarning(sc, "sky '%s' not found; replaced with 'sky1'", *name);
512 Tex = GTextureManager.CheckNumForName("sky1", TEXTYPE_SkyMap, true);
513 if (Tex < 0) Tex = GTextureManager.CheckNumForName("sky1", TEXTYPE_Wall, true);
514 forceList.put(loname, Tex);
515 return Tex;
516 //return GTextureManager.DefaultTexture;
519 forceList.put(loname, Tex);
520 miWarning(sc, "force-loaded sky '%s'", *name);
521 return Tex;
525 //==========================================================================
527 // LoadMapInfoLump
529 //==========================================================================
530 static void LoadMapInfoLump (int Lump, bool doFixups=true) {
531 GCon->Logf(NAME_Init, "mapinfo file: '%s'", *W_FullLumpName(Lump));
532 VScriptParser *sc = VScriptParser::NewWithLump(Lump);
533 if (!sc) Sys_Error("cannot load lump #%d: '%s'", Lump, *W_FullLumpName(Lump));
534 ParseMapInfo(sc);
535 if (doFixups) {
536 processNumFixups("DoomEdNum", true, DoomEdNumFixups);
537 processNumFixups("SpawnNum", false, SpawnNumFixups);
542 //==========================================================================
544 // LoadAllMapInfoLumpsInFile
546 // do this scanning fuckery, because some idiotic tools
547 // loves duplicate lumps
549 //==========================================================================
550 static void LoadAllMapInfoLumpsInFile (int miLump, int zmiLump, int vmiLump) {
551 if (miLump < 0 && zmiLump < 0) return;
552 VName milname;
553 if (vmiLump >= 0) {
554 milname = VName("vmapinfo", VName::Add);
555 zmiLump = vmiLump;
556 } else if (zmiLump >= 0) {
557 milname = VName("zmapinfo", VName::Add);
558 } else {
559 vassert(miLump >= 0);
560 zmiLump = miLump;
561 milname = NAME_mapinfo;
563 vassert(zmiLump >= 0);
564 int currFile = W_LumpFile(zmiLump);
565 bool wasLoaded = false;
566 for (; zmiLump >= 0; zmiLump = W_IterateNS(zmiLump, WADNS_Global)) {
567 if (W_LumpFile(zmiLump) != currFile) break;
568 if (W_LumpName(zmiLump) == milname) {
569 wasLoaded = true;
570 LoadMapInfoLump(zmiLump, false); // no fixups yet
573 // do fixups if somethig was loaded
574 if (wasLoaded) {
575 processNumFixups("DoomEdNum", true, DoomEdNumFixups);
576 processNumFixups("SpawnNum", false, SpawnNumFixups);
581 //==========================================================================
583 // LoadUmapinfoLump
585 //==========================================================================
586 static void LoadUmapinfoLump (int lump) {
587 if (lump < 0) return;
588 GCon->Logf(NAME_Init, "umapinfo file: '%s'", *W_FullLumpName(lump));
589 VScriptParser *sc = VScriptParser::NewWithLump(lump);
590 if (!sc) Sys_Error("cannot load lump #%d: '%s'", lump, *W_FullLumpName(lump));
591 ParseUMapinfo(sc);
595 //==========================================================================
597 // InitMapInfo
599 //==========================================================================
600 void InitMapInfo () {
601 // use "zmapinfo" if it is present?
602 bool zmapinfoAllowed = (cli_NoZMapinfo >= 0);
603 if (!zmapinfoAllowed) GCon->Logf(NAME_Init, "zmapinfo parsing disabled by user");
605 int lastMapinfoFile = -1; // haven't seen yet
606 int lastMapinfoLump = -1; // haven't seen yet
607 int lastVMapinfoLump = -1; // haven't seen yet
608 int lastZMapinfoLump = -1; // haven't seen yet
609 int lastUmapinfoLump = -1; // haven't seen yet
610 bool doSkipFile = false;
611 VName nameVMI = VName("vmapinfo", VName::Add);
612 VName nameZMI = VName("zmapinfo", VName::Add);
613 VName nameUMI = VName("umapinfo", VName::Add);
615 TArray<int> fileKeyconfLump;
617 for (int Lump = W_IterateNS(-1, WADNS_Global); Lump >= 0; Lump = W_IterateNS(Lump, WADNS_Global)) {
618 int currFile = W_LumpFile(Lump);
619 if (doSkipFile) {
620 if (currFile == lastMapinfoFile) continue;
621 doSkipFile = false; // just in case
623 // if we hit another file, load last seen [z]mapinfo lump
624 if (currFile != lastMapinfoFile) {
625 //GCon->Logf(NAME_Debug, "*** new archive at '%s' ***", *W_FullLumpName(Lump));
626 LoadAllMapInfoLumpsInFile(lastMapinfoLump, lastZMapinfoLump, lastVMapinfoLump);
627 if (lastMapinfoLump < 0 && lastZMapinfoLump < 0 && lastVMapinfoLump < 0 && lastUmapinfoLump >= 0) {
628 LoadUmapinfoLump(lastUmapinfoLump);
630 // reset/update remembered lump indices
631 lastMapinfoFile = currFile;
632 lastMapinfoLump = lastVMapinfoLump = lastZMapinfoLump = lastUmapinfoLump = -1; // haven't seen yet
633 // load keyconfs from previous files
634 for (auto &&klmp : fileKeyconfLump) VCommand::LoadKeyconfLump(klmp);
635 fileKeyconfLump.resetNoDtor();
636 // skip zip files
637 doSkipFile = !W_IsWadPK3File(currFile);
638 if (doSkipFile) continue;
640 // remember keyconf
641 if (W_LumpName(Lump) == NAME_keyconf) fileKeyconfLump.append(Lump);
642 // remember last seen [z]mapinfo lump
643 if (lastMapinfoLump < 0 && W_LumpName(Lump) == NAME_mapinfo) lastMapinfoLump = Lump;
644 if (zmapinfoAllowed && lastZMapinfoLump < 0 && W_LumpName(Lump) == nameZMI) lastZMapinfoLump = Lump;
645 if (lastVMapinfoLump < 0 && W_LumpName(Lump) == nameVMI) lastVMapinfoLump = Lump;
646 if (lastUmapinfoLump < 0 && W_LumpName(Lump) == nameUMI) lastUmapinfoLump = Lump;
649 // load last seen mapinfos
650 LoadAllMapInfoLumpsInFile(lastMapinfoLump, lastZMapinfoLump, lastVMapinfoLump);
651 if (lastMapinfoLump < 0 && lastZMapinfoLump < 0 && lastVMapinfoLump < 0 && lastUmapinfoLump >= 0) {
652 LoadUmapinfoLump(lastUmapinfoLump);
655 mapinfoParsed = true;
657 // load latest keyconfs
658 for (auto &&klmp : fileKeyconfLump) VCommand::LoadKeyconfLump(klmp);
659 fileKeyconfLump.resetNoDtor();
661 for (int i = 0; i < MapInfo.length(); ++i) {
662 if (VStr(MapInfo[i].NextMap).StartsWith("&wt@")) {
663 MapInfo[i].NextMap = P_TranslateMap(VStr::atoi(*MapInfo[i].NextMap+4));
665 if (VStr(MapInfo[i].SecretMap).StartsWith("&wt@")) {
666 MapInfo[i].SecretMap = P_TranslateMap(VStr::atoi(*MapInfo[i].SecretMap+4));
670 for (int i = 0; i < EpisodeDefs.length(); ++i) {
671 if (VStr(EpisodeDefs[i].Name).StartsWith("&wt@")) {
672 EpisodeDefs[i].Name = P_TranslateMap(VStr::atoi(*EpisodeDefs[i].Name+4));
674 if (VStr(EpisodeDefs[i].TeaserName).StartsWith("&wt@")) {
675 EpisodeDefs[i].TeaserName = P_TranslateMap(VStr::atoi(*EpisodeDefs[i].TeaserName+4));
679 // set up default map info returned for maps that have not defined in MAPINFO
680 memset((void *)&DefaultMap, 0, sizeof(DefaultMap));
681 DefaultMap.Name = "Unnamed";
682 DefaultMap.Sky1Texture = loadSkyTexture(nullptr, "sky1"); //GTextureManager.CheckNumForName("sky1", TEXTYPE_Wall, true, true);
683 DefaultMap.Sky2Texture = DefaultMap.Sky1Texture;
684 DefaultMap.FadeTable = NAME_colormap;
685 DefaultMap.HorizWallShade = -8;
686 DefaultMap.VertWallShade = 8;
687 //GCon->Logf(NAME_Debug, "*** DEFAULT MAP: Sky1Texture=%d", DefaultMap.Sky1Texture);
689 // we don't need it anymore
690 mcmap.clear();
692 if (hasCustomDamageFactors) SV_ReplaceCustomDamageFactors();
696 //==========================================================================
698 // SetMapDefaults
700 //==========================================================================
701 static void SetMapDefaults (VMapInfo &Info) {
702 Info.LumpName = NAME_None;
703 Info.Name = VStr();
704 Info.LevelNum = 0;
705 Info.Cluster = 0;
706 Info.WarpTrans = 0;
707 Info.NextMap = NAME_None;
708 Info.SecretMap = NAME_None;
709 Info.SongLump = NAME_None;
710 //Info.Sky1Texture = GTextureManager.DefaultTexture;
711 //Info.Sky2Texture = GTextureManager.DefaultTexture;
712 Info.Sky1Texture = loadSkyTexture(nullptr, "sky1"); //GTextureManager.CheckNumForName("sky1", TEXTYPE_Wall, true, true);
713 Info.Sky2Texture = Info.Sky1Texture;
714 //Info.Sky2Texture = GTextureManager.DefaultTexture;
715 Info.Sky1ScrollDelta = 0;
716 Info.Sky2ScrollDelta = 0;
717 Info.SkyBox = NAME_None;
718 Info.FadeTable = NAME_colormap;
719 Info.Fade = 0;
720 Info.OutsideFog = 0;
721 Info.Gravity = 0;
722 Info.AirControl = 0;
723 Info.Flags = 0;
724 Info.Flags2 = (gm_default_pain_limit ? VLevelInfo::LIF2_CompatLimitPain : 0);
725 Info.EnterTitlePatch = NAME_None;
726 Info.ExitTitlePatch = NAME_None;
727 Info.ParTime = 0;
728 Info.SuckTime = 0;
729 Info.HorizWallShade = -8;
730 Info.VertWallShade = 8;
731 Info.Infighting = 0;
732 Info.SpecialActions.Clear();
733 Info.RedirectType = NAME_None;
734 Info.RedirectMap = NAME_None;
735 Info.ExitPic = NAME_None;
736 Info.EnterPic = NAME_None;
737 Info.InterMusic = NAME_None;
738 Info.ExitText = VStr::EmptyString;
739 Info.SecretExitText = VStr::EmptyString;
740 Info.InterBackdrop = NAME_None;
742 if (GGameInfo->Flags&VGameInfo::GIF_DefaultLaxMonsterActivation) {
743 Info.Flags2 |= VLevelInfo::LIF2_LaxMonsterActivation;
748 //==========================================================================
750 // ParseNextMapName
752 //==========================================================================
753 static VName ParseNextMapName (VScriptParser *sc, bool HexenMode) {
754 if (sc->CheckNumber()) {
755 if (HexenMode) return va("&wt@%02d", sc->Number);
756 return va("map%02d", sc->Number);
758 if (sc->Check("endbunny")) return "EndGameBunny";
759 if (sc->Check("endcast")) return "EndGameCast";
760 if (sc->Check("enddemon")) return "EndGameDemon";
761 if (sc->Check("endchess")) return "EndGameChess";
762 if (sc->Check("endunderwater")) return "EndGameUnderwater";
763 if (sc->Check("endbuystrife")) return "EndGameBuyStrife";
764 if (sc->Check("endpic")) {
765 sc->Check(",");
766 sc->ExpectName8();
767 return va("EndGameCustomPic%s", *sc->Name8);
769 sc->ExpectString();
770 if (sc->String.ToLower().StartsWith("endgame")) {
771 switch (sc->String[7]) {
772 case '1': return "EndGamePic1";
773 case '2': return "EndGamePic2";
774 case '3': return "EndGameBunny";
775 case 'c': case 'C': return "EndGameCast";
776 case 'w': case 'W': return "EndGameUnderwater";
777 case 's': case 'S': return "EndGameStrife";
778 default: return "EndGamePic3";
781 return VName(*sc->String, VName::AddLower8);
785 //==========================================================================
787 // DoCompatFlag
789 //==========================================================================
790 static void DoCompatFlag (VScriptParser *sc, VMapInfo *info, int Flag, bool newFormat) {
791 int Set = 1;
792 if (newFormat) {
793 sc->Check("=");
794 if (sc->CheckNumber()) Set = sc->Number;
795 } else if (sc->CheckNumber()) {
796 Set = sc->Number;
798 if (Flag) {
799 if (Set) {
800 info->Flags2 |= Flag;
801 } else {
802 info->Flags2 &= ~Flag;
808 //==========================================================================
810 // skipUnimplementedCommand
812 //==========================================================================
813 static void skipUnimplementedCommand (VScriptParser *sc, bool wantArg) {
814 VStr cmd = sc->String;
815 if (sc->Check("=")) {
816 miWarning(sc, "Unimplemented command '%s'", *cmd);
817 sc->ExpectString();
818 while (sc->Check(",")) {
819 if (sc->Check("}")) { sc->UnGet(); break; }
820 if (sc->AtEnd()) break;
821 sc->ExpectString();
823 } else if (wantArg) {
824 miWarning(sc, "Unimplemented old command '%s'", *cmd);
825 sc->ExpectString();
826 } else {
827 miWarning(sc, "Unimplemented flag '%s'", *cmd);
832 // ////////////////////////////////////////////////////////////////////////// //
833 MAPINFOCMD(levelnum) {
834 (void)HexenMode;
835 if (newFormat) sc->Expect("=");
836 sc->ExpectNumber();
837 info->LevelNum = sc->Number;
841 // ////////////////////////////////////////////////////////////////////////// //
842 MAPINFOCMD(author) {
843 (void)HexenMode;
844 (void)info;
845 if (newFormat) sc->Expect("=");
846 sc->ExpectString();
850 // ////////////////////////////////////////////////////////////////////////// //
851 MAPINFOCMD(cluster) {
852 (void)HexenMode;
853 if (newFormat) sc->Expect("=");
854 sc->ExpectNumber();
855 info->Cluster = sc->Number;
856 if (P_GetClusterDef(info->Cluster) == &DefaultClusterDef) {
857 // add empty cluster def if it doesn't exist yet
858 VClusterDef &C = ClusterDefs.Alloc();
859 C.Cluster = info->Cluster;
860 C.Flags = 0;
861 C.EnterText = VStr();
862 C.ExitText = VStr();
863 C.Flat = NAME_None;
864 C.Music = NAME_None;
865 if (HexenMode) C.Flags |= CLUSTERF_Hub;
869 // ////////////////////////////////////////////////////////////////////////// //
870 MAPINFOCMD(warptrans) {
871 (void)HexenMode;
872 if (newFormat) sc->Expect("=");
873 sc->ExpectNumber();
874 info->WarpTrans = sc->Number;
877 // ////////////////////////////////////////////////////////////////////////// //
878 MAPINFOCMD(next) {
879 (void)HexenMode;
880 if (newFormat) sc->Expect("=");
881 info->NextMap = ParseNextMapName(sc, HexenMode);
882 // hack for "complete"
883 if (sc->Check("{")) {
884 info->NextMap = "endgamec";
885 sc->SkipBracketed(true); // bracket eaten
886 } else if (newFormat && sc->Check(",")) {
887 sc->ExpectString();
888 // check for more commas?
892 // ////////////////////////////////////////////////////////////////////////// //
893 MAPINFOCMD(secret) {
894 (void)HexenMode;
895 if (newFormat) sc->Expect("=");
896 info->SecretMap = ParseNextMapName(sc, HexenMode);
899 MAPINFOCMD(secretnext) {
900 (void)HexenMode;
901 if (newFormat) sc->Expect("=");
902 info->SecretMap = ParseNextMapName(sc, HexenMode);
905 // ////////////////////////////////////////////////////////////////////////// //
906 MAPINFOCMD(sky1) {
907 (void)HexenMode;
908 wasSky1Sky2 |= WSK_WAS_SKY1;
909 const VTextLocation loc = sc->GetLoc();
910 if (newFormat) sc->Expect("=");
911 sc->ExpectName();
912 //info->Sky1Texture = GTextureManager.NumForName(sc->Name, TEXTYPE_Wall, false);
913 VName skbname = R_HasNamedSkybox(sc->String);
914 if (skbname != NAME_None) {
915 //k8: ok, this may be done to support sourceports that cannot into skyboxes
916 miWarning(loc, "sky1 '%s' is actually a skybox (this is mostly harmless)", *sc->String);
917 info->SkyBox = skbname;
918 info->Sky1Texture = GTextureManager.DefaultTexture;
919 info->Sky2Texture = GTextureManager.DefaultTexture;
920 info->Sky1ScrollDelta = 0;
921 info->Sky2ScrollDelta = 0;
922 //GCon->Logf(NAME_Init, "using gz skybox '%s'", *skbname);
923 if (!sc->IsAtEol()) {
924 sc->Check(",");
925 sc->ExpectFloatWithSign();
926 if (HexenMode) sc->Float /= 256.0f;
927 if (sc->Float != 0) miWarning(loc, "ignoring sky scroll for skybox '%s' (this is mostly harmless)", *skbname);
929 } else {
930 info->SkyBox = NAME_None;
931 info->Sky1Texture = loadSkyTexture(sc, sc->Name);
932 info->Sky1ScrollDelta = 0;
933 if (newFormat) {
934 if (!sc->IsAtEol()) {
935 sc->Check(",");
936 sc->ExpectFloatWithSign();
937 if (HexenMode) sc->Float /= 256.0f;
938 info->Sky1ScrollDelta = sc->Float*35.0f;
940 } else {
941 if (!sc->IsAtEol()) {
942 sc->Check(",");
943 sc->ExpectFloatWithSign();
944 if (HexenMode) sc->Float /= 256.0f;
945 info->Sky1ScrollDelta = sc->Float*35.0f;
951 // ////////////////////////////////////////////////////////////////////////// //
952 MAPINFOCMD(sky2) {
953 (void)HexenMode;
954 wasSky1Sky2 |= WSK_WAS_SKY2;
955 if (newFormat) sc->Expect("=");
956 sc->ExpectName();
957 //info->Sky2Texture = GTextureManager.NumForName(sc->Name8, TEXTYPE_Wall, false);
958 //info->SkyBox = NAME_None; //k8:required or not???
959 info->Sky2Texture = loadSkyTexture(sc, sc->Name);
960 info->Sky2ScrollDelta = 0;
961 if (newFormat) {
962 if (!sc->IsAtEol()) {
963 sc->Check(",");
964 sc->ExpectFloatWithSign();
965 if (HexenMode) sc->Float /= 256.0f;
966 info->Sky1ScrollDelta = sc->Float*35.0f;
968 } else {
969 if (!sc->IsAtEol()) {
970 sc->Check(",");
971 sc->ExpectFloatWithSign();
972 if (HexenMode) sc->Float /= 256.0f;
973 info->Sky2ScrollDelta = sc->Float*35.0f;
978 // ////////////////////////////////////////////////////////////////////////// //
979 MAPINFOCMD(skybox) {
980 (void)HexenMode;
981 const VTextLocation loc = sc->GetLoc();
982 if (newFormat) sc->Expect("=");
983 sc->ExpectString();
984 info->Sky1ScrollDelta = 0;
985 info->Sky2ScrollDelta = 0;
986 VName skbname = R_HasNamedSkybox(sc->String);
987 if (skbname != NAME_None) {
988 info->SkyBox = skbname;
989 info->Sky1Texture = GTextureManager.DefaultTexture;
990 info->Sky2Texture = GTextureManager.DefaultTexture;
991 } else {
992 if (cli_MapperIsIdiot > 0) {
993 miWarning(loc, "skybox '%s' not found (mapper is idiot)!", *sc->String);
994 } else {
995 sc->Error(va("skybox '%s' not found (this mapinfo is broken)", *sc->String));
997 info->SkyBox = NAME_None;
998 info->Sky1Texture = loadSkyTexture(sc, VName(*sc->String, VName::AddLower8));
999 info->Sky2Texture = info->Sky1Texture;
1003 // ////////////////////////////////////////////////////////////////////////// //
1004 MAPINFOCMD(skyrotate) {
1005 (void)HexenMode;
1006 (void)info;
1007 miWarning(sc, "\"skyrotate\" command is not supported yet");
1008 if (newFormat) sc->Expect("=");
1009 sc->ExpectFloatWithSign();
1010 if (sc->Check(",")) sc->ExpectFloatWithSign();
1011 if (sc->Check(",")) sc->ExpectFloatWithSign();
1014 // ////////////////////////////////////////////////////////////////////////// //
1015 MAPINFOCMD(doublesky) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_DoubleSky; }
1016 MAPINFOCMD(lightning) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_Lightning; }
1017 MAPINFOCMD(forcenoskystretch) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_ForceNoSkyStretch; }
1018 MAPINFOCMD(skystretch) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags &= ~VLevelInfo::LIF_ForceNoSkyStretch; }
1019 MAPINFOCMD(map07special) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_Map07Special; }
1020 MAPINFOCMD(baronspecial) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_BaronSpecial; }
1021 MAPINFOCMD(cyberdemonspecial) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_CyberDemonSpecial; }
1022 MAPINFOCMD(spidermastermindspecial) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_SpiderMastermindSpecial; }
1023 MAPINFOCMD(minotaurspecial) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_MinotaurSpecial; }
1024 MAPINFOCMD(dsparilspecial) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_DSparilSpecial; }
1025 MAPINFOCMD(ironlichspecial) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_IronLichSpecial; }
1026 MAPINFOCMD(specialaction_exitlevel) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags &= ~(VLevelInfo::LIF_SpecialActionOpenDoor|VLevelInfo::LIF_SpecialActionLowerFloor); }
1027 MAPINFOCMD(specialaction_opendoor) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags &= ~VLevelInfo::LIF_SpecialActionLowerFloor; info->Flags |= VLevelInfo::LIF_SpecialActionOpenDoor; }
1028 MAPINFOCMD(specialaction_lowerfloor) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_SpecialActionLowerFloor; info->Flags &= ~VLevelInfo::LIF_SpecialActionOpenDoor; }
1029 MAPINFOCMD(specialaction_killmonsters) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_SpecialActionKillMonsters; }
1030 MAPINFOCMD(specialaction_blazedoor) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; } //FIXME
1031 MAPINFOCMD(intermission) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags &= ~VLevelInfo::LIF_NoIntermission; }
1032 MAPINFOCMD(nointermission) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_NoIntermission; }
1033 MAPINFOCMD(nosoundclipping) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; /* ignored */ }
1034 MAPINFOCMD(noinventorybar) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; /* ignored */ }
1035 MAPINFOCMD(allowmonstertelefrags) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_AllowMonsterTelefrags; }
1036 MAPINFOCMD(noallies) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_NoAllies; }
1037 MAPINFOCMD(fallingdamage) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags &= ~(VLevelInfo::LIF_OldFallingDamage|VLevelInfo::LIF_StrifeFallingDamage); info->Flags |= VLevelInfo::LIF_FallingDamage; }
1038 MAPINFOCMD(oldfallingdamage) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags &= ~(VLevelInfo::LIF_FallingDamage|VLevelInfo::LIF_StrifeFallingDamage); info->Flags |= VLevelInfo::LIF_OldFallingDamage; }
1039 MAPINFOCMD(forcefallingdamage) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags &= ~(VLevelInfo::LIF_FallingDamage|VLevelInfo::LIF_StrifeFallingDamage); info->Flags |= VLevelInfo::LIF_OldFallingDamage; }
1040 MAPINFOCMD(strifefallingdamage) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags &= ~(VLevelInfo::LIF_OldFallingDamage|VLevelInfo::LIF_FallingDamage); info->Flags |= VLevelInfo::LIF_StrifeFallingDamage; }
1041 MAPINFOCMD(nofallingdamage) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags &= ~(VLevelInfo::LIF_OldFallingDamage|VLevelInfo::LIF_StrifeFallingDamage|VLevelInfo::LIF_FallingDamage); }
1042 MAPINFOCMD(monsterfallingdamage) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_MonsterFallingDamage; }
1043 MAPINFOCMD(nomonsterfallingdamage) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags &= ~VLevelInfo::LIF_MonsterFallingDamage; }
1044 MAPINFOCMD(deathslideshow) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_DeathSlideShow; }
1045 MAPINFOCMD(allowfreelook) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags &= ~VLevelInfo::LIF_NoFreelook; }
1046 MAPINFOCMD(nofreelook) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_NoFreelook; }
1047 MAPINFOCMD(allowjump) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags &= ~VLevelInfo::LIF_NoJump; }
1048 MAPINFOCMD(allowcrouch) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags2 &= ~VLevelInfo::LIF2_NoCrouch; }
1049 MAPINFOCMD(nojump) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_NoJump; }
1050 MAPINFOCMD(nocrouch) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags2 |= VLevelInfo::LIF2_NoCrouch; }
1051 MAPINFOCMD(resethealth) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags2 |= VLevelInfo::LIF2_ResetHealth; }
1052 MAPINFOCMD(resetinventory) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags2 |= VLevelInfo::LIF2_ResetInventory; }
1053 MAPINFOCMD(resetitems) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags2 |= VLevelInfo::LIF2_ResetItems; }
1054 MAPINFOCMD(noautosequences) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_NoAutoSndSeq; }
1055 MAPINFOCMD(activateowndeathspecials) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_ActivateOwnSpecial; }
1056 MAPINFOCMD(killeractivatesdeathspecials) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags &= ~VLevelInfo::LIF_ActivateOwnSpecial; }
1057 MAPINFOCMD(missilesactivateimpactlines) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_MissilesActivateImpact; }
1058 MAPINFOCMD(missileshootersactivetimpactlines) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags &= ~VLevelInfo::LIF_MissilesActivateImpact; }
1059 MAPINFOCMD(filterstarts) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_FilterStarts; }
1060 MAPINFOCMD(infiniteflightpowerup) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_InfiniteFlightPowerup; }
1061 MAPINFOCMD(noinfiniteflightpowerup) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags &= ~VLevelInfo::LIF_InfiniteFlightPowerup; }
1062 MAPINFOCMD(clipmidtextures) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_ClipMidTex; /*sc->Message("ClipMidTextures is ignored");*/ }
1063 MAPINFOCMD(wrapmidtextures) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_WrapMidTex; sc->Message("WrapMidTextures is ignored"); }
1064 MAPINFOCMD(keepfullinventory) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags |= VLevelInfo::LIF_KeepFullInventory; }
1065 MAPINFOCMD(additive_scrollers) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; DoCompatFlag(sc, info, VLevelInfo::LIF2_CompatBoomScroll, newFormat); }
1066 MAPINFOCMD(checkswitchrange) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags2 |= VLevelInfo::LIF2_CheckSwitchRange; info->Flags2 &= ~VLevelInfo::LIF2_NoCheckSwitchRange; }
1067 MAPINFOCMD(nocheckswitchrange) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Flags2 &= ~VLevelInfo::LIF2_CheckSwitchRange; info->Flags2 |= VLevelInfo::LIF2_NoCheckSwitchRange; }
1068 MAPINFOCMD(compat_shorttex) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; DoCompatFlag(sc, info, VLevelInfo::LIF2_CompatShortTex, newFormat); }
1069 MAPINFOCMD(compat_stairs) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; DoCompatFlag(sc, info, VLevelInfo::LIF2_CompatStairs, newFormat); }
1070 MAPINFOCMD(compat_limitpain) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; DoCompatFlag(sc, info, VLevelInfo::LIF2_CompatLimitPain, newFormat); }
1071 MAPINFOCMD(compat_nopassover) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; DoCompatFlag(sc, info, VLevelInfo::LIF2_CompatNoPassOver, newFormat); }
1072 MAPINFOCMD(compat_notossdrops) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; DoCompatFlag(sc, info, VLevelInfo::LIF2_CompatNoTossDrops, newFormat); }
1073 MAPINFOCMD(compat_useblocking) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; DoCompatFlag(sc, info, VLevelInfo::LIF2_CompatUseBlocking, newFormat); }
1074 MAPINFOCMD(compat_nodoorlight) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; DoCompatFlag(sc, info, VLevelInfo::LIF2_CompatNoDoorLight, newFormat); }
1075 MAPINFOCMD(compat_ravenscroll) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; DoCompatFlag(sc, info, VLevelInfo::LIF2_CompatRavenScroll, newFormat); }
1076 MAPINFOCMD(compat_soundtarget) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; DoCompatFlag(sc, info, VLevelInfo::LIF2_CompatSoundTarget, newFormat); }
1077 MAPINFOCMD(compat_dehhealth) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; DoCompatFlag(sc, info, VLevelInfo::LIF2_CompatDehHealth, newFormat); }
1078 MAPINFOCMD(compat_trace) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; DoCompatFlag(sc, info, VLevelInfo::LIF2_CompatTrace, newFormat); }
1079 MAPINFOCMD(compat_dropoff) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; DoCompatFlag(sc, info, VLevelInfo::LIF2_CompatDropOff, newFormat); }
1080 MAPINFOCMD(compat_boomscroll) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; DoCompatFlag(sc, info, VLevelInfo::LIF2_CompatBoomScroll, newFormat); }
1081 MAPINFOCMD(compat_invisibility) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; DoCompatFlag(sc, info, VLevelInfo::LIF2_CompatInvisibility, newFormat); }
1082 MAPINFOCMD(compat_sectorsounds) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; DoCompatFlag(sc, info, 0, newFormat); }
1083 MAPINFOCMD(compat_crossdropoff) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; DoCompatFlag(sc, info, 0, newFormat); }
1085 // ////////////////////////////////////////////////////////////////////////// //
1086 MAPINFOCMD(noinfighting) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Infighting = -1; }
1087 MAPINFOCMD(normalinfighting) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Infighting = 0; }
1088 MAPINFOCMD(totalinfighting) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->Infighting = 1; }
1090 // ////////////////////////////////////////////////////////////////////////// //
1091 MAPINFOCMD(fadetable) {
1092 (void)HexenMode;
1093 if (newFormat) sc->Expect("=");
1094 sc->ExpectName8();
1095 info->FadeTable = sc->Name8;
1098 // ////////////////////////////////////////////////////////////////////////// //
1099 MAPINFOCMD(fade) {
1100 (void)HexenMode;
1101 if (newFormat) sc->Expect("=");
1102 sc->ExpectString();
1103 info->Fade = M_ParseColor(*sc->String);
1106 // ////////////////////////////////////////////////////////////////////////// //
1107 MAPINFOCMD(outsidefog) {
1108 (void)HexenMode;
1109 if (newFormat) sc->Expect("=");
1110 sc->ExpectString();
1111 info->OutsideFog = M_ParseColor(*sc->String);
1114 // ////////////////////////////////////////////////////////////////////////// //
1115 MAPINFOCMD(music) {
1116 (void)HexenMode;
1117 if (newFormat) sc->Expect("=");
1118 //sc->ExpectName8();
1119 //info->SongLump = sc->Name8;
1120 sc->ExpectName();
1121 info->SongLump = sc->Name;
1122 const char *nn = *sc->Name;
1123 if (nn[0] == '$') {
1124 ++nn;
1125 if (nn[0] && GLanguage.HasTranslation(nn)) {
1126 info->SongLump = VName(*GLanguage[nn], VName::AddLower);
1131 // ////////////////////////////////////////////////////////////////////////// //
1132 MAPINFOCMD(cdtrack) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; if (newFormat) sc->Expect("="); sc->ExpectNumber(); /*info->CDTrack = sc->Number;*/ }
1133 MAPINFOCMD(cd_start_track) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; if (newFormat) sc->Expect("="); sc->ExpectNumber(); /*cd_NonLevelTracks[CD_STARTTRACK] = sc->Number;*/ }
1134 MAPINFOCMD(cd_end1_track) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; if (newFormat) sc->Expect("="); sc->ExpectNumber(); /*cd_NonLevelTracks[CD_END1TRACK] = sc->Number;*/ }
1135 MAPINFOCMD(cd_end2_track) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; if (newFormat) sc->Expect("="); sc->ExpectNumber(); /*cd_NonLevelTracks[CD_END2TRACK] = sc->Number;*/ }
1136 MAPINFOCMD(cd_end3_track) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; if (newFormat) sc->Expect("="); sc->ExpectNumber(); /*cd_NonLevelTracks[CD_END3TRACK] = sc->Number;*/ }
1137 MAPINFOCMD(cd_intermission_track) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; if (newFormat) sc->Expect("="); sc->ExpectNumber(); /*cd_NonLevelTracks[CD_INTERTRACK] = sc->Number;*/ }
1138 MAPINFOCMD(cd_title_track) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; if (newFormat) sc->Expect("="); sc->ExpectNumber(); /*cd_NonLevelTracks[CD_TITLETRACK] = sc->Number;*/ }
1139 MAPINFOCMD(cdid) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; skipUnimplementedCommand(sc, true); }
1141 // ////////////////////////////////////////////////////////////////////////// //
1142 MAPINFOCMD(gravity) {
1143 (void)HexenMode; (void)info; (void)newFormat; (void)sc;
1144 if (newFormat) sc->Expect("=");
1145 sc->ExpectFloat();
1146 if (sc->Float <= 0.0 || sc->Float > 100000.0f) {
1147 sc->Messagef("ignored invalid gravity value %g", sc->Float);
1148 } else {
1149 info->Gravity = sc->Float;
1153 // ////////////////////////////////////////////////////////////////////////// //
1154 MAPINFOCMD(aircontrol) {
1155 (void)HexenMode; (void)info; (void)newFormat; (void)sc;
1156 if (newFormat) sc->Expect("=");
1157 sc->ExpectFloat();
1158 if (sc->Float < 0.0 || sc->Float > 10.0f) {
1159 sc->Messagef("ignored invalid air control value %g", sc->Float);
1160 } else {
1161 info->AirControl = sc->Float;
1165 // ////////////////////////////////////////////////////////////////////////// //
1166 MAPINFOCMD(titlepatch) {
1167 (void)HexenMode; (void)info; (void)newFormat; (void)sc;
1168 //FIXME: quoted string is a textual level name
1169 if (newFormat) sc->Expect("=");
1170 sc->ExpectName8Def(NAME_None);
1171 info->EnterTitlePatch = info->ExitTitlePatch = sc->Name8;
1174 // ////////////////////////////////////////////////////////////////////////// //
1175 MAPINFOCMD(par) {
1176 (void)HexenMode; (void)info; (void)newFormat; (void)sc;
1177 if (newFormat) sc->Expect("=");
1178 sc->ExpectNumber();
1179 info->ParTime = sc->Number;
1182 // ////////////////////////////////////////////////////////////////////////// //
1183 MAPINFOCMD(sucktime) {
1184 (void)HexenMode; (void)info; (void)newFormat; (void)sc;
1185 if (newFormat) sc->Expect("=");
1186 sc->ExpectNumber();
1187 info->SuckTime = sc->Number;
1190 // ////////////////////////////////////////////////////////////////////////// //
1191 MAPINFOCMD(vertwallshade) {
1192 (void)HexenMode; (void)info; (void)newFormat; (void)sc;
1193 if (newFormat) sc->Expect("=");
1194 sc->ExpectNumber();
1195 info->VertWallShade = midval(-128, sc->Number, 127);
1198 // ////////////////////////////////////////////////////////////////////////// //
1199 MAPINFOCMD(horizwallshade) {
1200 (void)HexenMode; (void)info; (void)newFormat; (void)sc;
1201 if (newFormat) sc->Expect("=");
1202 sc->ExpectNumber();
1203 info->HorizWallShade = midval(-128, sc->Number, 127);
1206 // ////////////////////////////////////////////////////////////////////////// //
1207 MAPINFOCMD(specialaction) {
1208 (void)HexenMode; (void)info; (void)newFormat; (void)sc;
1209 if (newFormat) sc->Expect("=");
1210 VMapSpecialAction &A = info->SpecialActions.Alloc();
1211 //sc->SetCMode(true);
1212 sc->ExpectString();
1213 A.TypeName = *sc->String.ToLower();
1214 sc->Expect(",");
1215 sc->ExpectString();
1216 A.Special = FindScriptLineSpecialByName(sc->String);
1217 if (!A.Special) miWarning(sc, "Unknown action special '%s'", *sc->String);
1218 memset(A.Args, 0, sizeof(A.Args));
1219 for (int i = 0; i < 5 && sc->Check(","); ++i) {
1220 sc->ExpectNumber();
1221 A.Args[i] = sc->Number;
1225 // ////////////////////////////////////////////////////////////////////////// //
1226 MAPINFOCMD(redirect) {
1227 (void)HexenMode; (void)info; (void)newFormat; (void)sc;
1228 if (newFormat) sc->Expect("=");
1229 sc->ExpectString();
1230 info->RedirectType = *sc->String.ToLower();
1231 info->RedirectMap = ParseNextMapName(sc, HexenMode);
1234 // ////////////////////////////////////////////////////////////////////////// //
1235 MAPINFOCMD(strictmonsteractivation) {
1236 (void)HexenMode; (void)info; (void)newFormat; (void)sc;
1237 info->Flags2 &= ~VLevelInfo::LIF2_LaxMonsterActivation;
1238 info->Flags2 |= VLevelInfo::LIF2_HaveMonsterActivation;
1241 // ////////////////////////////////////////////////////////////////////////// //
1242 MAPINFOCMD(laxmonsteractivation) {
1243 (void)HexenMode; (void)info; (void)newFormat; (void)sc;
1244 info->Flags2 |= VLevelInfo::LIF2_LaxMonsterActivation;
1245 info->Flags2 |= VLevelInfo::LIF2_HaveMonsterActivation;
1248 // ////////////////////////////////////////////////////////////////////////// //
1249 MAPINFOCMD(interpic) {
1250 (void)HexenMode; (void)info; (void)newFormat; (void)sc;
1251 if (newFormat) sc->Expect("=");
1252 //sc->ExpectName8();
1253 sc->ExpectString();
1254 info->ExitPic = *sc->String.ToLower();
1257 // ////////////////////////////////////////////////////////////////////////// //
1258 MAPINFOCMD(enterpic) {
1259 (void)HexenMode; (void)info; (void)newFormat; (void)sc;
1260 if (newFormat) sc->Expect("=");
1261 //sc->ExpectName8();
1262 sc->ExpectString();
1263 info->EnterPic = *sc->String.ToLower();
1266 // ////////////////////////////////////////////////////////////////////////// //
1267 MAPINFOCMD(exitpic) {
1268 (void)HexenMode; (void)info; (void)newFormat; (void)sc;
1269 if (newFormat) sc->Expect("=");
1270 //sc->ExpectName8();
1271 sc->ExpectString();
1272 info->ExitPic = *sc->String.ToLower();
1275 // ////////////////////////////////////////////////////////////////////////// //
1276 MAPINFOCMD(intermusic) {
1277 (void)HexenMode; (void)info; (void)newFormat; (void)sc;
1278 if (newFormat) sc->Expect("=");
1279 sc->ExpectString();
1280 info->InterMusic = *sc->String.ToLower();
1283 // ////////////////////////////////////////////////////////////////////////// //
1284 MAPINFOCMD(background) {
1285 (void)HexenMode; (void)info; (void)newFormat; (void)sc;
1286 sc->Message("'background' mapinfo command is not supported");
1287 if (newFormat) sc->Expect("=");
1288 //sc->ExpectName8();
1289 sc->ExpectString();
1292 // ////////////////////////////////////////////////////////////////////////// //
1293 MAPINFOCMD(airsupply) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; skipUnimplementedCommand(sc, true); }
1294 MAPINFOCMD(sndseq) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; skipUnimplementedCommand(sc, true); }
1295 MAPINFOCMD(sndinfo) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; skipUnimplementedCommand(sc, true); }
1296 MAPINFOCMD(soundinfo) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; skipUnimplementedCommand(sc, true); }
1297 MAPINFOCMD(bordertexture) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; skipUnimplementedCommand(sc, true); }
1298 MAPINFOCMD(f1) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; skipUnimplementedCommand(sc, true); }
1299 MAPINFOCMD(teamdamage) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; skipUnimplementedCommand(sc, true); }
1300 MAPINFOCMD(fogdensity) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; skipUnimplementedCommand(sc, true); }
1301 MAPINFOCMD(outsidefogdensity) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; skipUnimplementedCommand(sc, true); }
1302 MAPINFOCMD(skyfog) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; skipUnimplementedCommand(sc, true); }
1303 MAPINFOCMD(translator) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; sc->MessageErr(va("*** map '%s' contains translator lump, it may not work!", *info->LumpName)); skipUnimplementedCommand(sc, true); }
1304 MAPINFOCMD(lightmode) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; skipUnimplementedCommand(sc, true); }
1305 MAPINFOCMD(smoothlighting) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->FakeContrast = 1; }
1306 MAPINFOCMD(evenlighting) { (void)HexenMode; (void)info; (void)newFormat; (void)sc; info->FakeContrast = 2; }
1309 //==========================================================================
1311 // FixSkyTexturesHack
1313 // another zdoom hack: check for "sky_maplump" sky texture
1315 //==========================================================================
1316 static void FixOneSkyTextureHack (VScriptParser *sc, VMapInfo *info, int skynum, vint32 &tx) {
1317 if (tx < 1) return;
1319 //GCon->Logf(NAME_Debug, "map '%s': sky1 '%s'", *info->LumpName, *GTextureManager.GetTextureName(tx));
1320 VName skn = VName(*(VStr(*GTextureManager.GetTextureName(tx))+"_"+(*info->LumpName)), VName::AddLower);
1321 if (VStr::length(*skn) <= 8) {
1322 //GCon->Logf(NAME_Debug, "map '%s': trying sky1 '%s'", *info->LumpName, *skn);
1323 int tt = loadSkyTexture(sc, skn, true);
1324 if (tt > 0) {
1325 GCon->Logf(NAME_Debug, "map '%s': sky%d '%s' replaced with '%s'", *info->LumpName, skynum, *GTextureManager.GetTextureName(tx), *GTextureManager.GetTextureName(tt));
1326 tx = tt;
1327 return;
1331 skn = VName(*(VStr("sky_")+(*info->LumpName)), VName::AddLower);
1332 if (VStr::length(*skn) <= 8) {
1333 //GCon->Logf(NAME_Debug, "map '%s': trying sky1 '%s'", *info->LumpName, *skn);
1334 int tt = loadSkyTexture(sc, skn, true);
1335 if (tt > 0) {
1336 GCon->Logf(NAME_Debug, "map '%s': sky%d '%s' replaced with '%s'", *info->LumpName, skynum, *GTextureManager.GetTextureName(tx), *GTextureManager.GetTextureName(tt));
1337 tx = tt;
1338 return;
1344 //==========================================================================
1346 // FixSkyTexturesHack
1348 // another zdoom hack: check for "sky_maplump" sky texture
1350 //==========================================================================
1351 static void FixSkyTexturesHack (VScriptParser *sc, VMapInfo *info) {
1352 FixOneSkyTextureHack(sc, info, 1, info->Sky1Texture);
1353 //FixOneSkyTextureHack(sc, info, 2, info->Sky2Texture);
1355 // if we have "lightning", but no "sky2", make "sky2" equal to "sky1" (otherwise the sky may flicker)
1356 // actually, always do this, because why not?
1357 if (/*(info->Flags&VLevelInfo::LIF_Lightning) != 0 &&*/ (wasSky1Sky2&WSK_WAS_SKY2) == 0) {
1358 // has lighting, but no second sky; force second sky to be the same as the first one
1359 // (but only if the first one is not a skybox)
1360 if (info->SkyBox == NAME_None) {
1361 info->Sky2Texture = info->Sky1Texture;
1362 info->Sky2ScrollDelta = info->Sky1ScrollDelta;
1368 //==========================================================================
1370 // ParseMapCommon
1372 //==========================================================================
1373 static void ParseMapCommon (VScriptParser *sc, VMapInfo *info, bool &HexenMode, const VMapInfo &Default) {
1374 // build command map, if it is not built yet
1375 if (mcmap.length() == 0 && mclist) {
1376 for (MapInfoCommand *mcp = mclist; mcp; mcp = mcp->next) {
1377 VStr cn = VStr(mcp->cmd).toLowerCase().xstrip();
1378 if (cn.isEmpty()) Sys_Error("internal engine error: unnamed mapinfo command handler!");
1379 if (mcmap.put(cn, mcp)) Sys_Error("internal engine error: duplicate mapinfo command handler for '%s'!", mcp->cmd);
1383 // if we have "lightning", but no "sky2", make "sky2" equal to "sky1" (otherwise the sky may flicker)
1384 wasSky1Sky2 = 0u; // clear "was skyN" flag
1386 const bool newFormat = sc->Check("{");
1387 // clear some more info for the new format (GZDoom compatibility)
1388 if (newFormat) {
1389 info->Cluster = Default.Cluster;
1390 info->NextMap = Default.NextMap;
1391 info->SecretMap = Default.SecretMap;
1392 info->Sky1Texture = Default.Sky1Texture;
1393 info->Sky2Texture = Default.Sky2Texture;
1394 info->Sky1ScrollDelta = Default.Sky1ScrollDelta;
1395 info->Sky2ScrollDelta = Default.Sky2ScrollDelta;
1396 info->SkyBox = Default.SkyBox;
1397 info->ExitPic = Default.ExitPic;
1398 info->EnterPic = Default.EnterPic;
1399 info->InterMusic = Default.InterMusic;
1402 //if (newFormat) sc->SetCMode(true);
1403 // process optional tokens
1404 for (;;) {
1405 //sc->GetString(); sc->UnGet(); GCon->Logf(NAME_Debug, "%s: %s", *sc->GetLoc().toStringNoCol(), *sc->String);
1406 if (!sc->GetString()) break;
1407 auto mpp = mcmap.get(sc->String.toLowerCase());
1408 if (mpp) {
1409 (*(*mpp)->handler)(sc, newFormat, info, HexenMode);
1410 } else {
1411 //GCon->Logf(NAME_Debug, "%s: NOT FOUND cmd='%s' (new=%d)", *sc->GetLoc().toStringNoCol(), *sc->String, (int)newFormat);
1412 sc->UnGet();
1413 if (!newFormat) break;
1414 if (sc->Check("}")) break;
1415 if (!sc->GetString()) break;
1416 //sc->Message(va("*** unknown mapinfo command '%s', skipping", *sc->String));
1417 skipUnimplementedCommand(sc, false); // don't force args, but skip them
1418 if (sc->Check("}")) break;
1419 continue;
1422 if (sc->CheckStartsWith("compat_")) {
1423 GCon->Logf(NAME_Warning, "%s: mapdef '%s' is not supported yet", *sc->GetLoc().toStringNoCol(), *sc->String);
1424 sc->Check("=");
1425 sc->CheckNumber();
1428 // these are stubs for now
1429 //} else if (sc->Check("noinventorybar")) { skipUnimplementedCommand(sc, false);
1430 //} else if (sc->Check("allowcrouch")) { skipUnimplementedCommand(sc, false);
1431 //} else if (sc->Check("pausemusicinmenus")) { skipUnimplementedCommand(sc, false);
1432 //} else if (sc->Check("allowrespawn")) { skipUnimplementedCommand(sc, false);
1433 //} else if (sc->Check("teamplayon")) { skipUnimplementedCommand(sc, false);
1434 //} else if (sc->Check("teamplayoff")) { skipUnimplementedCommand(sc, false);
1435 //} else if (sc->Check("checkswitchrange")) { skipUnimplementedCommand(sc, false);
1436 //} else if (sc->Check("nocheckswitchrange")) { skipUnimplementedCommand(sc, false);
1437 //} else if (sc->Check("unfreezesingleplayerconversations")) { skipUnimplementedCommand(sc, false);
1438 //} else if (sc->Check("smoothlighting")) { skipUnimplementedCommand(sc, false);
1439 //} else if (sc->Check("Grinding_PolyObj")) { skipUnimplementedCommand(sc, false);
1440 //} else if (sc->Check("UsePlayerStartZ")) { skipUnimplementedCommand(sc, false);
1441 //} else if (sc->Check("spawnwithweaponraised")) { skipUnimplementedCommand(sc, false);
1442 //} else if (sc->Check("noautosavehint")) { skipUnimplementedCommand(sc, false);
1443 //} else if (sc->Check("PrecacheTextures")) { skipUnimplementedCommand(sc, false);
1444 //} else if (sc->Check("PrecacheSounds")) { skipUnimplementedCommand(sc, false);
1445 //} else if (sc->Check("PrecacheClasses")) { skipUnimplementedCommand(sc, false);
1446 //} else if (sc->Check("intermissionmusic")) { skipUnimplementedCommand(sc, false);
1448 //if (newFormat) sc->SetCMode(false);
1450 FixSkyTexturesHack(sc, info);
1452 // second sky defaults to first sky
1453 if (info->Sky2Texture == GTextureManager.DefaultTexture) info->Sky2Texture = info->Sky1Texture;
1454 if (info->Flags&VLevelInfo::LIF_DoubleSky) GTextureManager.SetFrontSkyLayer(info->Sky1Texture);
1458 //==========================================================================
1460 // ParseNameOrLookup
1462 //==========================================================================
1463 static void ParseNameOrLookup (VScriptParser *sc, vuint32 lookupFlag, VStr *name, vuint32 *flags, bool newStyle) {
1464 if (sc->Check("lookup")) {
1465 if (sc->GetString()) {
1466 if (sc->QuotedString || sc->String != ",") sc->UnGet();
1468 //if (newStyle) sc->Check(",");
1469 *flags |= lookupFlag;
1470 sc->ExpectString();
1471 if (sc->String.length() > 1 && sc->String[0] == '$') {
1472 *name = VStr(*sc->String+1).ToLower();
1473 } else {
1474 *name = sc->String.ToLower();
1476 } else {
1477 sc->ExpectString();
1478 if (sc->String.Length() > 1 && sc->String[0] == '$') {
1479 *flags |= lookupFlag;
1480 *name = VStr(*sc->String+1).ToLower();
1481 } else {
1482 *flags &= ~lookupFlag;
1483 *name = sc->String;
1484 if (lookupFlag == VLevelInfo::LIF_LookupName) return;
1485 if (newStyle) {
1486 while (!sc->AtEnd()) {
1487 if (!sc->Check(",")) break;
1488 if (sc->AtEnd()) break;
1489 sc->ExpectString();
1490 while (!sc->QuotedString) {
1491 if (sc->String == "}") { sc->UnGet(); break; } // stray comma
1492 if (sc->String != ",") { sc->UnGet(); sc->Error("comma expected"); break; }
1493 if (sc->AtEnd()) break;
1494 sc->ExpectString();
1496 *name += "\n";
1497 *name += sc->String;
1499 } else {
1500 while (!sc->AtEnd()) {
1501 sc->ExpectString();
1502 if (sc->Crossed) { sc->UnGet(); break; }
1503 while (!sc->QuotedString) {
1504 if (sc->String != ",") { sc->UnGet(); sc->Error("comma expected"); break; }
1505 if (sc->AtEnd()) break;
1506 sc->ExpectString();
1508 *name += "\n";
1509 *name += sc->String;
1512 //GCon->Logf(NAME_Debug, "COLLECTED: <%s>", **name);
1518 //==========================================================================
1520 // ParseNameOrLookup
1522 //==========================================================================
1523 static void ParseNameOrLookup (VScriptParser *sc, vint32 lookupFlag, VStr *name, vint32 *flags, bool newStyle) {
1524 vuint32 lf = (vuint32)lookupFlag;
1525 vuint32 flg = (vuint32)*flags;
1526 ParseNameOrLookup(sc, lf, name, &flg, newStyle);
1527 *flags = (vint32)flg;
1531 //==========================================================================
1533 // ParseUStringKey
1535 //==========================================================================
1536 static VStr ParseUStringKey (VScriptParser *sc) {
1537 sc->Expect("=");
1538 sc->ExpectString();
1539 return sc->String.xstrip();
1543 //==========================================================================
1545 // ParseUBoolKey
1547 //==========================================================================
1548 static bool ParseUBoolKey (VScriptParser *sc) {
1549 sc->Expect("=");
1550 sc->ExpectString();
1551 VStr ss = sc->String.xstrip();
1552 if (ss.strEquCI("false") || ss.strEquCI("0")) return false;
1553 if (ss.strEquCI("true") || ss.strEquCI("1")) return true;
1554 sc->Error("boolean value expected");
1555 return false;
1559 //==========================================================================
1561 // ParseMapUMapinfo
1563 //==========================================================================
1564 static void ParseMapUMapinfo (VScriptParser *sc, VMapInfo *info) {
1565 // if we have "lightning", but no "sky2", make "sky2" equal to "sky1" (otherwise the sky may flicker)
1566 wasSky1Sky2 = 0u; // clear "was skyN" flag
1567 bool wasEndGame = false;
1568 bool wasClearBossAction = false;
1569 VStr endType;
1570 VStr episodeName;
1572 sc->Expect("{");
1573 for (;;) {
1574 const VTextLocation loc = sc->GetLoc();
1576 if (sc->Check("}")) break;
1577 if (sc->AtEnd()) break;
1579 if (sc->Check("levelname")) {
1580 VStr ss = ParseUStringKey(sc);
1581 if (ss.length()) info->Name = ss;
1582 continue;
1584 if (sc->Check("levelpic")) {
1585 VStr ss = ParseUStringKey(sc);
1586 info->EnterTitlePatch = info->ExitTitlePatch = VName(*ss, VName::AddLower);
1587 continue;
1589 if (sc->Check("exitpic")) {
1590 VStr ss = ParseUStringKey(sc);
1591 info->ExitPic = VName(*ss, VName::AddLower);
1592 continue;
1594 if (sc->Check("enterpic")) {
1595 VStr ss = ParseUStringKey(sc);
1596 info->EnterPic = VName(*ss, VName::AddLower);
1597 continue;
1599 if (sc->Check("next")) {
1600 VStr ss = ParseUStringKey(sc);
1601 if (ss.length()) info->NextMap = VName(*ss, VName::AddLower);
1602 continue;
1604 if (sc->Check("nextsecret")) {
1605 VStr ss = ParseUStringKey(sc);
1606 if (ss.length()) info->SecretMap = VName(*ss, VName::AddLower);
1607 continue;
1609 if (sc->Check("skytexture")) {
1610 VStr ss = ParseUStringKey(sc);
1611 if (ss.length()) {
1612 wasSky1Sky2 |= WSK_WAS_SKY1;
1613 VName skbname = R_HasNamedSkybox(sc->String);
1614 if (skbname != NAME_None) {
1615 //k8: ok, this may be done to support sourceports that cannot into skyboxes
1616 miWarning(loc, "sky1 '%s' is actually a skybox (this is mostly harmless)", *sc->String);
1617 info->SkyBox = skbname;
1618 info->Sky1Texture = GTextureManager.DefaultTexture;
1619 info->Sky2Texture = GTextureManager.DefaultTexture;
1620 info->Sky1ScrollDelta = 0;
1621 info->Sky2ScrollDelta = 0;
1622 } else {
1623 info->SkyBox = NAME_None;
1624 info->Sky1Texture = loadSkyTexture(sc, VName(*ss, VName::AddLower));
1625 info->Sky1ScrollDelta = 0;
1628 continue;
1630 if (sc->Check("music")) {
1631 VStr ss = ParseUStringKey(sc);
1632 if (ss.length()) info->SongLump = VName(*ss, VName::AddLower);
1633 continue;
1635 if (sc->Check("partime")) {
1636 sc->Expect("=");
1637 sc->ExpectNumber();
1638 info->ParTime = sc->Number;
1639 continue;
1641 if (sc->Check("endgame")) {
1642 wasEndGame = ParseUBoolKey(sc);
1643 continue;
1645 if (sc->Check("endpic")) {
1646 VStr ss = ParseUStringKey(sc);
1647 if (ss.length()) {
1648 endType = VStr(va("EndGameCustomPic%s", *ss));
1649 } else {
1650 endType = "EndGamePic3"; // arbitrary decision, credits
1652 continue;
1654 if (sc->Check("endbunny")) {
1655 if (ParseUBoolKey(sc)) endType = "EndGameBunny";
1656 continue;
1658 if (sc->Check("endcast")) {
1659 if (ParseUBoolKey(sc)) endType = "EndGameCast";
1660 continue;
1662 if (sc->Check("bossaction")) {
1663 // clear all special map action flags
1664 info->Flags &= ~(
1665 VLevelInfo::LIF_Map07Special|
1666 VLevelInfo::LIF_BaronSpecial|
1667 VLevelInfo::LIF_CyberDemonSpecial|
1668 VLevelInfo::LIF_SpiderMastermindSpecial|
1669 VLevelInfo::LIF_MinotaurSpecial|
1670 VLevelInfo::LIF_DSparilSpecial|
1671 VLevelInfo::LIF_IronLichSpecial|
1672 VLevelInfo::LIF_SpecialActionOpenDoor|
1673 VLevelInfo::LIF_SpecialActionLowerFloor|
1674 VLevelInfo::LIF_SpecialActionKillMonsters);
1675 // can't we fuckin' have a complete specs for ANYTHING, for fuck's sake?!
1676 VStr className = ParseUStringKey(sc);
1677 if (className.strEquCI("clear")) {
1678 info->SpecialActions.clear();
1679 wasClearBossAction = true;
1680 } else {
1681 //bossaction = thingtype, linespecial, tag
1682 sc->Expect(",");
1683 sc->ExpectNumber();
1684 int special = sc->Number;
1685 //if (special < 0) sc->Error(va("invalid bossaction special %d", special));
1686 sc->Expect(",");
1687 sc->ExpectNumber();
1688 int tag = sc->Number;
1689 // allow no 0-tag specials here, unless a level exit
1690 if (className.length() && special > 0 && (tag != 0 || special == 11 || special == 51 || special == 52 || special == 124)) {
1691 // add special action
1692 if (info->SpecialActions.length() == 0) {
1693 VMapSpecialAction &aa = info->SpecialActions.Alloc();
1694 aa.TypeName = VName("UMapInfoActions");
1695 aa.Special = 666999; // this means nothing
1697 VMapSpecialAction &A = info->SpecialActions.Alloc();
1698 A.TypeName = VName(*className);
1699 A.Special = -special; // it should be translated
1700 A.Args[0] = tag;
1701 A.Args[1] = A.Args[2] = A.Args[3] = A.Args[4] = 0;
1702 wasClearBossAction = false;
1703 } else {
1704 miWarning(sc, "Invalid bossaction special %d (tag %d)", special, tag);
1707 continue;
1710 if (sc->Check("episode")) {
1711 VStr ss = ParseUStringKey(sc);
1712 if (ss.strEquCI("clear")) {
1713 EpisodeDefs.Clear();
1714 ClusterDefs.Clear(); // clear clusterdefs too
1715 } else {
1716 VStr pic = ss;
1717 VStr epname;
1718 bool checkReplace = true;
1719 if (sc->Check(",")) {
1720 sc->ExpectString();
1721 epname = sc->String.xstrip();
1722 if (sc->Check(",")) sc->ExpectString(); // ignore key
1723 } else {
1724 epname = "Unnamed episode";
1725 checkReplace = false;
1728 VEpisodeDef *EDef = nullptr;
1729 // check for replaced episode
1730 if (checkReplace) {
1731 for (int i = 0; i < EpisodeDefs.length(); ++i) {
1732 if (sc->Name == EpisodeDefs[i].Name) {
1733 EDef = &EpisodeDefs[i];
1734 break;
1738 if (!EDef) EDef = &EpisodeDefs.Alloc();
1740 // set defaults
1741 EDef->Name = info->LumpName;
1742 EDef->TeaserName = NAME_None;
1743 EDef->Text = epname;
1744 EDef->PicName = VName(*ss, VName::AddLower);
1745 EDef->Flags = 0;
1746 EDef->Key = VStr();
1747 EDef->MapinfoSourceLump = info->MapinfoSourceLump;
1749 continue;
1752 if (sc->Check("nointermission")) {
1753 if (ParseUBoolKey(sc)) info->Flags |= VLevelInfo::LIF_NoIntermission; else info->Flags &= ~VLevelInfo::LIF_NoIntermission;
1754 continue;
1757 // special hack for intertexts in `VBasePlayer::DoClientIntermission()` allows to use per-map texts
1758 if (sc->Check("intertext")) {
1759 VStr exitText = ParseUStringKey(sc);
1760 while (sc->Check(",")) {
1761 exitText += "\n";
1762 sc->ExpectString();
1763 exitText += sc->String.xstrip();
1765 exitText = exitText.xstrip();
1766 if (exitText.strEquCI("clear")) exitText = VStr(" ");
1767 info->ExitText = exitText;
1768 continue;
1770 if (sc->Check("intertextsecret")) {
1771 VStr exitText = ParseUStringKey(sc);
1772 while (sc->Check(",")) {
1773 exitText += "\n";
1774 sc->ExpectString();
1775 exitText += sc->String.xstrip();
1777 exitText = exitText.xstrip();
1778 if (exitText.strEquCI("clear")) exitText = VStr(" ");
1779 info->SecretExitText = exitText;
1780 continue;
1782 if (sc->Check("interbackdrop")) {
1783 VStr ss = ParseUStringKey(sc);
1784 info->InterBackdrop = VName(*ss, VName::AddLower);
1785 continue;
1787 if (sc->Check("intermusic")) {
1788 VStr ss = ParseUStringKey(sc);
1789 info->InterMusic = VName(*ss, VName::AddLower);
1790 continue;
1793 //TODO:
1794 // label = "name"
1795 // Specifies the string to prepend to the levelname on the automap.
1796 // If not specified the mapname will be used by default followed by
1797 // a colon and a space character (e.g. "E1M1: ").
1798 // label = clear
1799 // Only print the levelname on the automap.
1801 // i'll ignore that for now, it is better than crashing.
1802 if (sc->Check("label")) {
1803 miWarning(sc, "ignored UMAPINFO `label`, because it is not supported yet (this is harmless)");
1804 (void)ParseUStringKey(sc);
1805 continue;
1808 if (sc->Check("author")) {
1809 miWarning(sc, "ignored UMAPINFO `author`, because it is not supported yet (this is harmless)");
1810 (void)ParseUStringKey(sc);
1811 continue;
1814 if (sc->Check("enteranim")) {
1815 miWarning(sc, "ignored UMAPINFO `enteranim`, because it is not supported yet (this is harmless)");
1816 (void)ParseUStringKey(sc);
1817 continue;
1820 if (sc->Check("exitanim")) {
1821 miWarning(sc, "ignored UMAPINFO `exitanim`, because it is not supported yet (this is harmless)");
1822 (void)ParseUStringKey(sc);
1823 continue;
1826 sc->Error(va("Unknown UMAPINFO map key '%s'", *sc->String));
1829 if (wasEndGame || endType.length()) {
1830 if (endType.length() == 0) endType = "EndGamePic3"; // arbitrary decision, credits
1831 info->NextMap = VName(*endType);
1834 if (info->SecretMap == NAME_None) info->SecretMap = info->NextMap;
1836 if (wasClearBossAction) {
1837 vassert(info->SpecialActions.length() == 0);
1838 // special action
1839 VMapSpecialAction &A = info->SpecialActions.Alloc();
1840 A.TypeName = VName("UMapInfoDoNothing");
1841 A.Special = 666999; // this means nothing
1844 FixSkyTexturesHack(sc, info);
1846 // second sky defaults to first sky
1847 if (info->Sky2Texture == GTextureManager.DefaultTexture) info->Sky2Texture = info->Sky1Texture;
1848 if (info->Flags&VLevelInfo::LIF_DoubleSky) GTextureManager.SetFrontSkyLayer(info->Sky1Texture);
1852 //==========================================================================
1854 // ParseMap
1856 //==========================================================================
1857 static void ParseMap (VScriptParser *sc, bool &HexenMode, const VMapInfo &Default, bool umapinfo=false) {
1858 VMapInfo *info = nullptr;
1859 vuint32 savedFlags = 0;
1861 VName MapLumpName;
1862 if (!umapinfo && sc->CheckNumber()) {
1863 // map number, for Hexen compatibility
1864 HexenMode = true;
1865 if (sc->Number < 1 || sc->Number > 99) sc->Error("Map number out or range");
1866 MapLumpName = va("map%02d", sc->Number);
1867 } else {
1868 // map name
1869 sc->ExpectString();
1870 VStr nn = sc->String.xstrip();
1871 if (nn.length() == 0) sc->Error("empty map name");
1872 MapLumpName = VName(*sc->String, VName::AddLower);
1875 // check for replaced map info
1876 bool replacement = false;
1877 for (int i = 0; i < MapInfo.length(); ++i) {
1878 if (MapLumpName == MapInfo[i].LumpName) {
1879 info = &MapInfo[i];
1880 //GCon->Logf(NAME_Init, "replaced map '%s' (Sky1Texture=%d; default=%d)", *info->LumpName, info->Sky1Texture, Default.Sky1Texture);
1881 savedFlags = info->Flags;
1882 replacement = true;
1883 break;
1886 if (!info) info = &MapInfo.Alloc();
1888 // Copy defaults to current map definition
1889 info->LumpName = MapLumpName;
1890 if (!replacement) {
1891 info->LevelNum = Default.LevelNum;
1892 info->Cluster = Default.Cluster;
1894 info->WarpTrans = Default.WarpTrans;
1895 if (!replacement) {
1896 info->NextMap = Default.NextMap;
1897 info->SecretMap = Default.SecretMap;
1898 info->SongLump = Default.SongLump;
1900 if (!replacement) {
1901 info->Sky1Texture = Default.Sky1Texture;
1902 info->Sky2Texture = Default.Sky2Texture;
1903 info->Sky1ScrollDelta = Default.Sky1ScrollDelta;
1904 info->Sky2ScrollDelta = Default.Sky2ScrollDelta;
1905 info->SkyBox = Default.SkyBox;
1907 info->FadeTable = Default.FadeTable;
1908 info->Fade = Default.Fade;
1909 info->OutsideFog = Default.OutsideFog;
1910 info->Gravity = Default.Gravity;
1911 info->AirControl = Default.AirControl;
1912 info->Flags = Default.Flags;
1913 info->Flags2 = Default.Flags2;
1914 info->EnterTitlePatch = Default.EnterTitlePatch;
1915 info->ExitTitlePatch = Default.ExitTitlePatch;
1916 info->ParTime = Default.ParTime;
1917 info->SuckTime = Default.SuckTime;
1918 info->HorizWallShade = Default.HorizWallShade;
1919 info->VertWallShade = Default.VertWallShade;
1920 info->Infighting = Default.Infighting;
1921 info->SpecialActions = Default.SpecialActions;
1922 info->RedirectType = Default.RedirectType;
1923 info->RedirectMap = Default.RedirectMap;
1924 info->ExitText = Default.ExitText;
1925 info->SecretExitText = Default.SecretExitText;
1926 info->InterBackdrop = Default.InterBackdrop;
1927 if (!replacement) {
1928 info->ExitPic = Default.ExitPic;
1929 info->EnterPic = Default.EnterPic;
1930 info->InterMusic = Default.InterMusic;
1933 // copy "no intermission" flag from default map
1934 if (Default.Flags&VLevelInfo::LIF_NoIntermission) info->Flags |= VLevelInfo::LIF_NoIntermission;
1936 if (HexenMode) {
1937 info->Flags |= VLevelInfo::LIF_NoIntermission|
1938 VLevelInfo::LIF_FallingDamage|
1939 VLevelInfo::LIF_MonsterFallingDamage|
1940 VLevelInfo::LIF_NoAutoSndSeq|
1941 VLevelInfo::LIF_ActivateOwnSpecial|
1942 VLevelInfo::LIF_MissilesActivateImpact|
1943 VLevelInfo::LIF_InfiniteFlightPowerup;
1946 // set saved par time
1947 int par = findSavedPar(MapLumpName);
1948 if (par > 0) {
1949 //GCon->Logf(NAME_Init, "found dehacked par time for map '%s' (%d)", *MapLumpName, par);
1950 info->ParTime = par;
1953 if (!umapinfo) {
1954 // map name must follow the number
1955 ParseNameOrLookup(sc, VLevelInfo::LIF_LookupName, &info->Name, &info->Flags, false);
1958 // set song lump name from SNDINFO script
1959 for (int i = 0; i < MapSongList.length(); ++i) {
1960 if (MapSongList[i].MapName == info->LumpName) {
1961 info->SongLump = MapSongList[i].SongName;
1965 // set default levelnum for this map
1966 const char *mn = *MapLumpName;
1967 if (mn[0] == 'm' && mn[1] == 'a' && mn[2] == 'p' && mn[5] == 0) {
1968 int num = VStr::atoi(mn+3);
1969 if (num >= 1 && num <= 99) info->LevelNum = num;
1970 } else if (mn[0] == 'e' && mn[1] >= '0' && mn[1] <= '9' &&
1971 mn[2] == 'm' && mn[3] >= '0' && mn[3] <= '9')
1973 info->LevelNum = (mn[1]-'1')*10+(mn[3]-'0');
1976 info->MapinfoSourceLump = sc->SourceLump;
1978 if (!umapinfo) {
1979 ParseMapCommon(sc, info, HexenMode, Default);
1980 } else {
1981 // copy special actions, they should be explicitly cleared
1982 info->Flags = savedFlags&(
1983 VLevelInfo::LIF_Map07Special|
1984 VLevelInfo::LIF_BaronSpecial|
1985 VLevelInfo::LIF_CyberDemonSpecial|
1986 VLevelInfo::LIF_SpiderMastermindSpecial|
1987 VLevelInfo::LIF_MinotaurSpecial|
1988 VLevelInfo::LIF_DSparilSpecial|
1989 VLevelInfo::LIF_IronLichSpecial|
1990 VLevelInfo::LIF_SpecialActionOpenDoor|
1991 VLevelInfo::LIF_SpecialActionLowerFloor|
1992 VLevelInfo::LIF_SpecialActionKillMonsters);
1993 ParseMapUMapinfo(sc, info);
1996 info->MapinfoSourceLump = sc->SourceLump;
1998 // avoid duplicate levelnums, later one takes precedance
1999 if (info->LevelNum) {
2000 for (int i = 0; i < MapInfo.length(); ++i) {
2001 if (MapInfo[i].LevelNum == info->LevelNum && &MapInfo[i] != info) {
2002 if (W_IsUserWadLump(MapInfo[i].MapinfoSourceLump)) {
2003 if (MapInfo[i].MapinfoSourceLump != info->MapinfoSourceLump) {
2004 GCon->Logf(NAME_Warning, "duplicate levelnum %d for maps '%s' and '%s' ('%s' zeroed)", info->LevelNum, *MapInfo[i].LumpName, *info->LumpName, *MapInfo[i].LumpName);
2005 GCon->Logf(NAME_Warning, " first map is defined in '%s'", *W_FullLumpName(MapInfo[i].MapinfoSourceLump));
2006 GCon->Logf(NAME_Warning, " second map is defined in '%s'", *W_FullLumpName(info->MapinfoSourceLump));
2007 } else {
2008 GCon->Logf(NAME_Warning, "duplicate levelnum %d for maps '%s' and '%s' ('%s' zeroed)", info->LevelNum, *MapInfo[i].LumpName, *info->LumpName, *info->LumpName);
2009 GCon->Logf(NAME_Warning, " both maps are defined in '%s'", *W_FullLumpName(info->MapinfoSourceLump));
2010 // this means that latter episode is fucked -- so fuck it for real
2011 info->LevelNum = 0;
2012 break;
2015 MapInfo[i].LevelNum = 0;
2022 //==========================================================================
2024 // ParseClusterDef
2026 //==========================================================================
2027 static void ParseClusterDef (VScriptParser *sc) {
2028 VClusterDef *CDef = nullptr;
2029 sc->ExpectNumber();
2031 // check for replaced cluster def
2032 for (int i = 0; i < ClusterDefs.length(); ++i) {
2033 if (sc->Number == ClusterDefs[i].Cluster) {
2034 CDef = &ClusterDefs[i];
2035 break;
2038 if (!CDef) CDef = &ClusterDefs.Alloc();
2040 // set defaults
2041 CDef->Cluster = sc->Number;
2042 CDef->Flags = 0;
2043 CDef->EnterText = VStr();
2044 CDef->ExitText = VStr();
2045 CDef->Flat = NAME_None;
2046 CDef->Music = NAME_None;
2048 //GCon->Logf(NAME_Debug, "=== NEW CLUSTER %d ===", CDef->Cluster);
2049 bool newFormat = sc->Check("{");
2050 //if (newFormat) sc->SetCMode(true);
2051 while (!sc->AtEnd()) {
2052 //if (sc->GetString()) { GCon->Logf(NAME_Debug, ":%s: CLUSTER(%d): <%s>", *sc->GetLoc().toStringNoCol(), (newFormat ? 1 : 0), *sc->String); sc->UnGet(); }
2053 if (sc->Check("hub")) {
2054 CDef->Flags |= CLUSTERF_Hub;
2055 } else if (sc->Check("entertext")) {
2056 if (newFormat) sc->Expect("=");
2057 ParseNameOrLookup(sc, CLUSTERF_LookupEnterText, &CDef->EnterText, &CDef->Flags, newFormat);
2058 //GCon->Logf(NAME_Debug, "::: <%s>", *CDef->EnterText);
2059 } else if (sc->Check("entertextislump")) {
2060 CDef->Flags |= CLUSTERF_EnterTextIsLump;
2061 } else if (sc->Check("exittext")) {
2062 if (newFormat) sc->Expect("=");
2063 ParseNameOrLookup(sc, CLUSTERF_LookupExitText, &CDef->ExitText, &CDef->Flags, newFormat);
2064 } else if (sc->Check("exittextislump")) {
2065 CDef->Flags |= CLUSTERF_ExitTextIsLump;
2066 } else if (sc->Check("flat")) {
2067 if (newFormat) sc->Expect("=");
2068 sc->ExpectName8();
2069 CDef->Flat = sc->Name8;
2070 CDef->Flags &= ~CLUSTERF_FinalePic;
2071 } else if (sc->Check("pic")) {
2072 if (newFormat) sc->Expect("=");
2073 //sc->ExpectName8();
2074 sc->ExpectName();
2075 CDef->Flat = sc->Name;
2076 CDef->Flags |= CLUSTERF_FinalePic;
2077 } else if (sc->Check("music")) {
2078 if (newFormat) sc->Expect("=");
2079 //sc->ExpectName8();
2080 sc->ExpectName();
2081 CDef->Music = sc->Name;
2082 } else if (sc->Check("cdtrack")) {
2083 if (newFormat) sc->Expect("=");
2084 sc->ExpectNumber();
2085 //CDef->CDTrack = sc->Number;
2086 } else if (sc->Check("cdid")) {
2087 if (newFormat) sc->Expect("=");
2088 sc->ExpectNumber();
2089 //CDef->CDId = sc->Number;
2090 } else if (sc->Check("name")) {
2091 const VTextLocation loc = sc->GetLoc();
2092 if (newFormat) sc->Expect("=");
2093 if (sc->Check("lookup")) {
2094 if (newFormat) sc->Expect(",");
2096 sc->ExpectString();
2097 miWarning(loc, "Unimplemented cluster command 'name'");
2098 } else {
2099 if (newFormat) {
2100 if (!sc->Check("}")) {
2101 const VTextLocation loc = sc->GetLoc();
2102 sc->ExpectString();
2103 VStr cmd = sc->String;
2104 //fprintf(stderr, "!!!!!!\n");
2105 if (sc->Check("=")) {
2106 //fprintf(stderr, "******\n");
2107 while (!sc->AtEnd()) {
2108 sc->ExpectString();
2109 if (!sc->Check(",")) break;
2112 miWarning(loc, "unknown clusterdef command '%s'", *cmd);
2113 } else {
2114 break;
2115 //sc->Error(va("'}' expected in clusterdef, but got \"%s\"", *sc->String));
2117 } else {
2118 break;
2122 //if (newFormat) sc->SetCMode(false);
2124 // make sure text lump names are in lower case
2125 if (CDef->Flags&CLUSTERF_EnterTextIsLump) CDef->EnterText = CDef->EnterText.ToLower();
2126 if (CDef->Flags&CLUSTERF_ExitTextIsLump) CDef->ExitText = CDef->ExitText.ToLower();
2130 //==========================================================================
2132 // ParseEpisodeDef
2134 //==========================================================================
2135 static void ParseEpisodeDef (VScriptParser *sc) {
2136 VEpisodeDef *EDef = nullptr;
2137 int EIdx = 0;
2138 sc->ExpectName();
2140 // check for replaced episode
2141 for (int i = 0; i < EpisodeDefs.length(); ++i) {
2142 if (sc->Name == EpisodeDefs[i].Name) {
2143 EDef = &EpisodeDefs[i];
2144 EIdx = i;
2145 break;
2148 if (!EDef) {
2149 EDef = &EpisodeDefs.Alloc();
2150 EIdx = EpisodeDefs.length()-1;
2153 // check for removal of an episode
2154 if (sc->Check("remove")) {
2155 EpisodeDefs.RemoveIndex(EIdx);
2156 return;
2159 // set defaults
2160 EDef->Name = sc->Name;
2161 EDef->TeaserName = NAME_None;
2162 EDef->Text = VStr();
2163 EDef->PicName = NAME_None;
2164 EDef->Flags = 0;
2165 EDef->Key = VStr();
2166 EDef->MapinfoSourceLump = sc->SourceLump;
2168 if (sc->Check("teaser")) {
2169 sc->ExpectName();
2170 EDef->TeaserName = sc->Name;
2173 bool newFormat = sc->Check("{");
2174 //if (newFormat) sc->SetCMode(true);
2175 while (!sc->AtEnd()) {
2176 if (sc->Check("name")) {
2177 if (newFormat) sc->Expect("=");
2178 ParseNameOrLookup(sc, EPISODEF_LookupText, &EDef->Text, &EDef->Flags, newFormat);
2179 } else if (sc->Check("picname")) {
2180 if (newFormat) sc->Expect("=");
2181 sc->ExpectName();
2182 EDef->PicName = sc->Name;
2183 } else if (sc->Check("key")) {
2184 if (newFormat) sc->Expect("=");
2185 sc->ExpectString();
2186 EDef->Key = sc->String.ToLower();
2187 } else if (sc->Check("noskillmenu")) {
2188 EDef->Flags |= EPISODEF_NoSkillMenu;
2189 } else if (sc->Check("optional")) {
2190 EDef->Flags |= EPISODEF_Optional;
2191 } else {
2192 if (newFormat && !sc->AtEnd()) sc->Expect("}");
2193 break;
2196 //if (newFormat) sc->SetCMode(false);
2200 //==========================================================================
2202 // ParseSkillDefOld
2204 //==========================================================================
2205 static void ParseSkillDefOld (VScriptParser *sc, VSkillDef *sdef) {
2206 while (!sc->AtEnd()) {
2207 if (sc->Check("AmmoFactor")) {
2208 sc->ExpectFloat();
2209 sdef->AmmoFactor = sc->Float;
2210 } else if (sc->Check("DropAmmoFactor")) {
2211 sc->ExpectFloat();
2212 GCon->Logf(NAME_Warning, "MAPINFO:%s: skill setting 'DropAmmoFactor' is not implemented yet.", *sc->GetLoc().toStringNoCol());
2213 } else if (sc->Check("DoubleAmmoFactor")) {
2214 sc->ExpectFloat();
2215 sdef->DoubleAmmoFactor = sc->Float;
2216 } else if (sc->Check("DamageFactor")) {
2217 sc->ExpectFloat();
2218 sdef->DamageFactor = sc->Float;
2219 } else if (sc->Check("FastMonsters")) {
2220 sdef->Flags |= SKILLF_FastMonsters;
2221 } else if (sc->Check("DisableCheats")) {
2222 //k8: no, really?
2223 //sdef->Flags |= SKILLF_DisableCheats;
2224 } else if (sc->Check("EasyBossBrain")) {
2225 sdef->Flags |= SKILLF_EasyBossBrain;
2226 } else if (sc->Check("AutoUseHealth")) {
2227 sdef->Flags |= SKILLF_AutoUseHealth;
2228 } else if (sc->Check("RespawnTime")) {
2229 sc->ExpectFloat();
2230 sdef->RespawnTime = sc->Float;
2231 } else if (sc->Check("RespawnLimit")) {
2232 sc->ExpectNumber();
2233 sdef->RespawnLimit = sc->Number;
2234 } else if (sc->Check("NoPain")) {
2235 GCon->Logf(NAME_Warning, "MAPINFO:%s: skill param 'NoPain' is not implemented yet.", *sc->GetLoc().toStringNoCol());
2236 } else if (sc->Check("Aggressiveness")) {
2237 sc->ExpectFloatWithSign();
2238 if (sc->Float < 0) GCon->Logf(NAME_Warning, "%s:MAPINFO: \"Aggressiveness\" should be positive", *sc->GetLoc().toStringNoCol());
2239 sdef->Aggressiveness = 1.0f-midval(0.0f, sc->Float, 1.0f);
2240 } else if (sc->Check("SpawnFilter")) {
2241 if (sc->CheckNumber()) {
2242 if (sc->Number > 0 && sc->Number < 31) sdef->SpawnFilter = 1<<(sc->Number-1);
2243 } else {
2244 if (sc->Check("Baby")) sdef->SpawnFilter = 0x01;
2245 else if (sc->Check("Easy")) sdef->SpawnFilter = 0x02;
2246 else if (sc->Check("Normal")) sdef->SpawnFilter = 0x04;
2247 else if (sc->Check("Hard")) sdef->SpawnFilter = 0x08;
2248 else if (sc->Check("Nightmare")) sdef->SpawnFilter = 0x10;
2249 else sc->ExpectString();
2251 } else if (sc->Check("ACSReturn")) {
2252 sc->ExpectNumber();
2253 sdef->AcsReturn = sc->Number;
2254 } else if (sc->Check("Name")) {
2255 sc->ExpectString();
2256 sdef->MenuName = sc->String;
2257 sdef->Flags &= ~SKILLF_MenuNameIsPic;
2258 } else if (sc->Check("PlayerClassName")) {
2259 VSkillPlayerClassName &CN = sdef->PlayerClassNames.Alloc();
2260 sc->ExpectString();
2261 CN.ClassName = sc->String;
2262 sc->ExpectString();
2263 CN.MenuName = sc->String;
2264 } else if (sc->Check("PicName")) {
2265 sc->ExpectString();
2266 sdef->MenuName = sc->String.ToLower();
2267 sdef->Flags |= SKILLF_MenuNameIsPic;
2268 } else if (sc->Check("MustConfirm")) {
2269 sdef->Flags |= SKILLF_MustConfirm;
2270 if (sc->CheckQuotedString()) sdef->ConfirmationText = sc->String;
2271 } else if (sc->Check("Key")) {
2272 sc->ExpectString();
2273 sdef->Key = sc->String;
2274 } else if (sc->Check("TextColor")) {
2275 sc->ExpectString();
2276 sdef->TextColor = sc->String;
2277 } else {
2278 break;
2282 if (sdef->SpawnFilter == 0) {
2283 GCon->Logf(NAME_Warning, "MAPINFO:%s: skill param 'SpawnFilter' is not set for skill '%s'; assume UV.", *sc->GetLoc().toStringNoCol(), *sdef->MenuName);
2284 sdef->SpawnFilter = 8; // UV
2289 //==========================================================================
2291 // ParseSkillDef
2293 //==========================================================================
2294 static void ParseSkillDef (VScriptParser *sc) {
2295 VSkillDef *sdef = nullptr;
2296 sc->ExpectString();
2298 // check for replaced skill
2299 for (int i = 0; i < SkillDefs.length(); ++i) {
2300 if (sc->String.ICmp(SkillDefs[i].Name) == 0) {
2301 sdef = &SkillDefs[i];
2302 //GCon->Logf(NAME_Debug, "SKILLDEF:%s: replaced skill #%d '%s' (%s)", *sc->GetLoc().toStringNoCol(), i, *sc->String, *SkillDefs[i].Name);
2303 break;
2306 if (!sdef) {
2307 sdef = &SkillDefs.Alloc();
2308 sdef->Name = sc->String;
2309 //GCon->Logf(NAME_Debug, "SKILLDEF:%s: new skill #%d '%s'", *sc->GetLoc().toStringNoCol(), SkillDefs.length(), *sc->String);
2312 // set defaults
2313 sdef->AmmoFactor = 1.0f;
2314 sdef->DoubleAmmoFactor = 2.0f;
2315 sdef->DamageFactor = 1.0f;
2316 sdef->RespawnTime = 0.0f;
2317 sdef->RespawnLimit = 0;
2318 sdef->Aggressiveness = 1.0f;
2319 sdef->SpawnFilter = 0;
2320 sdef->MonsterHealth = 1.0f;
2321 sdef->HealthFactor = 1.0f;
2322 sdef->AcsReturn = SkillDefs.length()-1;
2323 sdef->MenuName.Clean();
2324 sdef->PlayerClassNames.Clear();
2325 sdef->ConfirmationText.Clean();
2326 sdef->Key.Clean();
2327 sdef->TextColor.Clean();
2328 sdef->Flags = 0;
2329 // if skill definition contains replacements, clear the old ones
2330 // k8: i am not sure if i should keep old replacements here, but why not?
2331 bool sdefClearReplacements = true;
2333 VClass *eexCls = VClass::FindClassNoCase("Actor"); // we'll need it later
2335 if (!sc->Check("{")) { ParseSkillDefOld(sc, sdef); return; }
2336 SCParseModeSaver msave(sc);
2338 while (!checkEndBracket(sc)) {
2339 if (sc->Check("AmmoFactor")) {
2340 sc->Expect("=");
2341 sc->ExpectFloat();
2342 sdef->AmmoFactor = sc->Float;
2343 } else if (sc->Check("DoubleAmmoFactor")) {
2344 sc->Expect("=");
2345 sc->ExpectFloat();
2346 sdef->DoubleAmmoFactor = sc->Float;
2347 } else if (sc->Check("DropAmmoFactor")) {
2348 sc->Expect("=");
2349 sc->ExpectFloat();
2350 GCon->Logf(NAME_Warning, "MAPINFO:%s: skill setting 'DropAmmoFactor' is not implemented yet.", *sc->GetLoc().toStringNoCol());
2351 } else if (sc->Check("DamageFactor")) {
2352 sc->Expect("=");
2353 sc->ExpectFloat();
2354 sdef->DamageFactor = sc->Float;
2355 } else if (sc->Check("RespawnTime")) {
2356 sc->Expect("=");
2357 sc->ExpectFloat();
2358 sdef->RespawnTime = sc->Float;
2359 } else if (sc->Check("RespawnLimit")) {
2360 sc->Expect("=");
2361 sc->ExpectNumber();
2362 sdef->RespawnLimit = sc->Number;
2363 } else if (sc->Check("Aggressiveness")) {
2364 sc->Expect("=");
2365 sc->ExpectFloatWithSign();
2366 if (sc->Float < 0) GCon->Logf(NAME_Warning, "%s:MAPINFO: \"Aggressiveness\" should be positive", *sc->GetLoc().toStringNoCol());
2367 sdef->Aggressiveness = 1.0f-midval(0.0f, sc->Float, 1.0f);
2368 } else if (sc->Check("SpawnFilter")) {
2369 sc->Expect("=");
2370 if (sc->CheckNumber()) {
2371 if (sc->Number > 0 && sc->Number < 31) {
2372 sdef->SpawnFilter = 1<<(sc->Number-1);
2373 } else {
2374 GCon->Logf(NAME_Warning, "MAPINFO:%s: invalid spawnfilter value %d", *sc->GetLoc().toStringNoCol(), sc->Number);
2376 } else {
2377 if (sc->Check("Baby")) sdef->SpawnFilter = 1;
2378 else if (sc->Check("Easy")) sdef->SpawnFilter = 2;
2379 else if (sc->Check("Normal")) sdef->SpawnFilter = 4;
2380 else if (sc->Check("Hard")) sdef->SpawnFilter = 8;
2381 else if (sc->Check("Nightmare")) sdef->SpawnFilter = 16;
2382 else { sc->ExpectString(); GCon->Logf(NAME_Warning, "MAPINFO:%s: unknown spawnfilter '%s'", *sc->GetLoc().toStringNoCol(), *sc->String); }
2384 } else if (sc->Check("ACSReturn")) {
2385 sc->Expect("=");
2386 sc->ExpectNumber();
2387 sdef->AcsReturn = sc->Number;
2388 } else if (sc->Check("Key")) {
2389 sc->Expect("=");
2390 sc->ExpectString();
2391 sdef->Key = sc->String;
2392 } else if (sc->Check("MustConfirm")) {
2393 sdef->Flags |= SKILLF_MustConfirm;
2394 if (sc->Check("=")) {
2395 sc->ExpectString();
2396 sdef->ConfirmationText = sc->String;
2398 } else if (sc->Check("Name")) {
2399 sc->Expect("=");
2400 sc->ExpectString();
2401 sdef->MenuName = sc->String;
2402 sdef->Flags &= ~SKILLF_MenuNameIsPic;
2403 } else if (sc->Check("PlayerClassName")) {
2404 sc->Expect("=");
2405 VSkillPlayerClassName &CN = sdef->PlayerClassNames.Alloc();
2406 sc->ExpectString();
2407 CN.ClassName = sc->String;
2408 sc->Expect(",");
2409 sc->ExpectString();
2410 CN.MenuName = sc->String;
2411 } else if (sc->Check("PicName")) {
2412 sc->Expect("=");
2413 sc->ExpectString();
2414 sdef->MenuName = sc->String.ToLower();
2415 sdef->Flags |= SKILLF_MenuNameIsPic;
2416 } else if (sc->Check("TextColor")) {
2417 sc->Expect("=");
2418 sc->ExpectString();
2419 sdef->TextColor = sc->String;
2420 } else if (sc->Check("EasyBossBrain")) {
2421 sdef->Flags |= SKILLF_EasyBossBrain;
2422 } else if (sc->Check("EasyKey")) {
2423 GCon->Logf(NAME_Warning, "MAPINFO:%s: skill param 'EasyKey' is not implemented yet.", *sc->GetLoc().toStringNoCol());
2424 } else if (sc->Check("FastMonsters")) {
2425 sdef->Flags |= SKILLF_FastMonsters;
2426 } else if (sc->Check("SlowMonsters")) {
2427 sdef->Flags |= SKILLF_SlowMonsters;
2428 } else if (sc->Check("SpawnMulti")) {
2429 sdef->Flags |= SKILLF_SpawnMulti;
2430 } else if (sc->Check("DisableCheats")) {
2431 //k8: no, really?
2432 //sdef->Flags |= SKILLF_DisableCheats;
2433 } else if (sc->Check("AutoUseHealth")) {
2434 sdef->Flags |= SKILLF_AutoUseHealth;
2435 } else if (sc->Check("ReplaceActor")) {
2436 //GCon->Logf(NAME_Warning, "MAPINFO:%s: skill param 'ReplaceActor' is not implemented yet.", *sc->GetLoc().toStringNoCol());
2437 sc->Expect("=");
2438 sc->ExpectString();
2439 VStr oldCN = sc->String;
2440 sc->Expect(",");
2441 sc->ExpectString();
2442 VStr newCN = sc->String;
2443 if (sdefClearReplacements) { sdef->Replacements.clear(); sdefClearReplacements = false; }
2444 if (eexCls && !oldCN.isEmpty()) {
2445 VClass *oldCls = VClass::FindClassNoCase(*oldCN);
2446 if (!oldCls) oldCls = VClass::FindClassNoCase(*oldCN.xstrip());
2447 if (!oldCls || !oldCls->IsChildOf(eexCls)) {
2448 GCon->Logf(NAME_Warning, "MAPINFO:%s: source class `%s` in 'ReplaceActor' is invalid.", *sc->GetLoc().toStringNoCol(), *oldCN);
2449 } else {
2450 // source is ok, check destination
2451 VClass *newCls = nullptr;
2452 bool newIsValid = true;
2453 if (!newCN.isEmpty() && newCN.xstrip().isEmpty()) newCN.clear(); // some morons are using a space as "nothing"
2454 if (!newCN.isEmpty() && !newCN.xstrip().strEquCI("none") && !newCN.xstrip().strEquCI("null")) {
2455 newCls = VClass::FindClassNoCase(*newCN);
2456 if (!newCls) newCls = VClass::FindClassNoCase(*newCN.xstrip());
2457 if (!newCls || !newCls->IsChildOf(eexCls)) {
2458 GCon->Logf(NAME_Warning, "MAPINFO:%s: destination class `%s` for source class `%s` in 'ReplaceActor' is invalid.", *sc->GetLoc().toStringNoCol(), *newCN, *oldCN);
2459 newIsValid = false;
2462 if (newIsValid) {
2463 VSkillMonsterReplacement &rp = sdef->Replacements.alloc();
2464 rp.oldClass = oldCls;
2465 rp.newClass = newCls;
2469 } else if (sc->Check("MonsterHealth")) {
2470 //GCon->Logf(NAME_Warning, "MAPINFO:%s: skill param 'MonsterHealth' is not implemented yet.", *sc->GetLoc().toStringNoCol());
2471 sc->Expect("=");
2472 sc->ExpectFloat();
2473 if (sc->Float < 0) GCon->Logf(NAME_Warning, "%s:MAPINFO: \"MonsterHealth\" should be positive", *sc->GetLoc().toStringNoCol());
2474 sdef->MonsterHealth = sc->Float;
2475 } else if (sc->Check("FriendlyHealth")) {
2476 GCon->Logf(NAME_Warning, "MAPINFO:%s: skill param 'FriendlyHealth' is not implemented yet.", *sc->GetLoc().toStringNoCol());
2477 sc->Expect("=");
2478 sc->ExpectFloat();
2479 } else if (sc->Check("NoPain")) {
2480 GCon->Logf(NAME_Warning, "MAPINFO:%s: skill param 'NoPain' is not implemented yet.", *sc->GetLoc().toStringNoCol());
2481 } else if (sc->Check("DefaultSkill")) {
2482 GCon->Logf(NAME_Warning, "MAPINFO:%s: skill param 'DefaultSkill' is not implemented yet.", *sc->GetLoc().toStringNoCol());
2483 } else if (sc->Check("ArmorFactor")) {
2484 GCon->Logf(NAME_Warning, "MAPINFO:%s: skill param 'ArmorFactor' is not implemented yet.", *sc->GetLoc().toStringNoCol());
2485 sc->Expect("=");
2486 sc->ExpectFloat();
2487 } else if (sc->Check("NoInfighting")) {
2488 GCon->Logf(NAME_Warning, "MAPINFO:%s: skill param 'NoInfighting' is not implemented yet.", *sc->GetLoc().toStringNoCol());
2489 } else if (sc->Check("TotalInfighting")) {
2490 GCon->Logf(NAME_Warning, "MAPINFO:%s: skill param 'TotalInfighting' is not implemented yet.", *sc->GetLoc().toStringNoCol());
2491 } else if (sc->Check("HealthFactor")) {
2492 //GCon->Logf(NAME_Warning, "MAPINFO:%s: skill param 'HealthFactor' is not implemented yet.", *sc->GetLoc().toStringNoCol());
2493 sc->Expect("=");
2494 sc->ExpectFloat();
2495 if (sc->Float < 0) GCon->Logf(NAME_Warning, "%s:MAPINFO: \"HealthFactor\" should be positive", *sc->GetLoc().toStringNoCol());
2496 sdef->HealthFactor = sc->Float;
2497 } else if (sc->Check("KickbackFactor")) {
2498 GCon->Logf(NAME_Warning, "MAPINFO:%s: skill param 'KickbackFactor' is not implemented yet.", *sc->GetLoc().toStringNoCol());
2499 sc->Expect("=");
2500 sc->ExpectFloat();
2501 } else if (sc->Check("NoMenu")) {
2502 GCon->Logf(NAME_Warning, "MAPINFO:%s: skill param 'NoMenu' is not implemented yet.", *sc->GetLoc().toStringNoCol());
2503 } else if (sc->Check("PlayerRespawn")) {
2504 GCon->Logf(NAME_Warning, "MAPINFO:%s: skill param 'PlayerRespawn' is not implemented yet.", *sc->GetLoc().toStringNoCol());
2505 } else if (sc->Check("ReplaceActor")) {
2506 GCon->Logf(NAME_Warning, "MAPINFO:%s: skill param 'ReplaceActor' is not implemented yet.", *sc->GetLoc().toStringNoCol());
2507 sc->Expect("=");
2508 while (!sc->AtEnd()) {
2509 sc->ExpectString();
2510 if (!sc->Check(",")) break;
2512 } else {
2513 sc->ExpectString();
2514 sc->Error(va("unknown MAPINFO skill command '%s'", *sc->String));
2515 break;
2519 if (sdef->SpawnFilter == 0) {
2520 GCon->Logf(NAME_Warning, "MAPINFO:%s: skill param 'SpawnFilter' is not set for skill '%s'; assume UV.", *sc->GetLoc().toStringNoCol(), *sdef->MenuName);
2521 sdef->SpawnFilter = 8; // UV
2526 //==========================================================================
2528 // ParseGameInfo
2530 //==========================================================================
2531 static void ParseGameInfo (VScriptParser *sc) {
2532 //auto cmode = sc->IsCMode();
2533 //sc->SetCMode(true);
2534 sc->Expect("{");
2535 //sc->SkipBracketed(true);
2536 for (;;) {
2537 if (sc->AtEnd()) { sc->Message("'}' not found"); break; }
2538 if (sc->Check("}")) break;
2539 if (sc->Check("PlayerClasses")) {
2540 MapInfoPlayerClasses.clear();
2541 sc->Expect("=");
2542 while (!sc->AtEnd()) {
2543 sc->ExpectString();
2544 if (sc->String.length()) {
2545 if (!FL_IsIgnoredPlayerClass(sc->String)) {
2546 MapInfoPlayerClasses.append(VName(*sc->String));
2547 } else {
2548 GCon->Logf(NAME_Init, "mapinfo player class '%s' ignored due to mod detector/mode orders", *sc->String);
2551 if (!sc->Check(",")) break;
2553 } else if (sc->Check("weaponslot")) {
2554 sc->Expect("=");
2555 sc->ExpectNumber();
2556 int slot = sc->Number;
2557 if (slot < 0 || slot > 10) sc->Message(va("ignoring gameinfo weaponslot %d", slot));
2558 TArray<VStr> clist;
2559 clist.append("CmdSetSlot"); // arg0
2560 clist.append(VStr(slot)); // arg1 is number
2561 while (sc->Check(",")) {
2562 if (!sc->GetString()) sc->Error("unexpected gameinfo end in mapinfo");
2563 if (!sc->String.isEmpty()) clist.append(sc->String);
2565 // we only have so many slots
2566 if (slot >= 0 && slot <= 10) {
2567 GGameInfo->eventCmdSetSlot(&clist, false); // as gameinfo
2569 } else if (sc->Check("ForceKillScripts")) {
2570 sc->Expect("=");
2571 bool bval = ExpectBool("ForceKillScripts", sc);
2572 //mapInfoGameInfoInitial.bForceKillScripts = bval;
2573 if (bval) GGameInfo->Flags |= VGameInfo::GIF_ForceKillScripts; else GGameInfo->Flags &= ~VGameInfo::GIF_ForceKillScripts;
2574 } else if (sc->Check("SkyFlatName")) {
2575 sc->Expect("=");
2576 sc->ExpectString();
2577 if (sc->String.length() == 0 || sc->String.length() > 8) {
2578 miWarning(sc, "invalid sky flat name: '%s'", *sc->String);
2579 } else {
2580 GTextureManager.SetSkyFlatName(sc->String);
2581 SV_UpdateSkyFlat();
2583 } else {
2584 sc->ExpectString();
2585 if (!sc->String.strEquCI("CanIgnoreZScript")) {
2586 sc->Message(va("skipped gameinfo command '%s'", *sc->String));
2588 sc->Expect("=");
2589 sc->ExpectString();
2590 while (sc->Check(",")) {
2591 if (!sc->GetString()) sc->Error("unexpected gameinfo end in mapinfo");
2595 //sc->SetCMode(cmode);
2599 //==========================================================================
2601 // ParseDamageType
2603 //==========================================================================
2604 static void ParseDamageType (VScriptParser *sc) {
2605 sc->ExpectString(); // name
2606 if (sc->String.strEquCI("Normal")) sc->String = "None";
2607 VStr dfname = sc->String;
2608 sc->Expect("{");
2610 float factor = 1.0f;
2611 bool noarmor = false;
2612 bool replace = false;
2613 while (!checkEndBracket(sc)) {
2614 if (sc->Check("NoArmor")) {
2615 noarmor = true;
2616 continue;
2618 if (sc->Check("ReplaceFactor")) {
2619 replace = true;
2620 continue;
2622 if (sc->Check("Factor")) {
2623 sc->Expect("=");
2624 sc->ExpectFloat();
2625 factor = sc->Float;
2626 continue;
2628 //FIXME: implement this!
2629 if (sc->Check("Obituary")) {
2630 sc->Expect("=");
2631 sc->ExpectString();
2632 continue;
2634 sc->Error(va("unknown DamageType field '%s'", *sc->String));
2637 // add or replace damage type
2638 VDamageFactor *fc = nullptr;
2639 for (auto &&df : CustomDamageFactors) {
2640 if (dfname.strEquCI(*df.DamageType)) {
2641 fc = &df;
2642 break;
2646 if (!fc) {
2647 fc = &CustomDamageFactors.alloc();
2648 memset((void *)fc, 0, sizeof(VDamageFactor));
2649 fc->DamageType = VName(*dfname);
2652 fc->Factor = factor;
2653 if (replace) fc->Flags |= VDamageFactor::DF_REPLACE;
2654 if (noarmor) fc->Flags |= VDamageFactor::DF_NOARMOR;
2656 hasCustomDamageFactors = true;
2660 //==========================================================================
2662 // ParseMapInfo
2664 // `sc->SourceLump` must be set
2666 //==========================================================================
2667 static void ParseMapInfo (VScriptParser *sc) {
2668 const unsigned int MaxStack = 64;
2669 bool HexenMode = false;
2670 VScriptParser *scstack[MaxStack];
2671 unsigned int scsp = 0;
2672 bool error = false;
2674 // set up default map info
2675 VMapInfo Default;
2676 SetMapDefaults(Default);
2678 for (;;) {
2679 while (!sc->AtEnd()) {
2680 if (sc->Check("map")) {
2681 ParseMap(sc, HexenMode, Default);
2682 } else if (sc->Check("defaultmap")) {
2683 SetMapDefaults(Default);
2684 ParseMapCommon(sc, &Default, HexenMode, Default);
2685 } else if (sc->Check("adddefaultmap")) {
2686 ParseMapCommon(sc, &Default, HexenMode, Default);
2687 } else if (sc->Check("clusterdef")) {
2688 ParseClusterDef(sc);
2689 } else if (sc->Check("cluster")) {
2690 ParseClusterDef(sc);
2691 } else if (sc->Check("episode")) {
2692 ParseEpisodeDef(sc);
2693 } else if (sc->Check("clearepisodes")) {
2694 // clear episodes and clusterdefs
2695 EpisodeDefs.Clear();
2696 ClusterDefs.Clear();
2697 } else if (sc->Check("skill")) {
2698 ParseSkillDef(sc);
2699 } else if (sc->Check("clearskills")) {
2700 SkillDefs.Clear();
2701 } else if (sc->Check("include")) {
2702 sc->ExpectString();
2703 //int lmp = W_CheckNumForFileName(sc->String);
2704 int lmp = VScriptParser::FindIncludeLump(sc->SourceLump, sc->String);
2705 if (lmp >= 0) {
2706 if (scsp >= MaxStack) {
2707 sc->Error("mapinfo include nesting too deep");
2708 error = true;
2709 break;
2711 GCon->Logf(NAME_Init, "Including '%s'...", *sc->String);
2712 scstack[scsp++] = sc;
2713 sc = VScriptParser::NewWithLump(lmp);
2714 } else {
2715 sc->Error(va("mapinfo include '%s' not found", *sc->String));
2716 error = true;
2717 break;
2719 } else if (sc->Check("gameinfo")) {
2720 ParseGameInfo(sc);
2721 } else if (sc->Check("damagetype")) {
2722 ParseDamageType(sc);
2724 sc->Message("Unimplemented MAPINFO command `DamageType`");
2725 if (!sc->Check("{")) {
2726 sc->ExpectString();
2727 sc->SkipBracketed();
2728 } else {
2729 sc->SkipBracketed(true); // bracket eaten
2732 } else if (sc->Check("GameSkyFlatName")) {
2733 if (sc->Check("=")) {} // just in case
2734 sc->ExpectString();
2735 if (sc->String.length() == 0 || sc->String.length() > 8) {
2736 miWarning(sc, "invalid sky flat name: '%s'", *sc->String);
2737 } else {
2738 GTextureManager.SetSkyFlatName(sc->String);
2739 SV_UpdateSkyFlat();
2741 } else if (sc->Check("intermission")) {
2742 sc->Message("Unimplemented MAPINFO command `Intermission`");
2743 if (!sc->Check("{")) {
2744 sc->ExpectString();
2745 sc->SkipBracketed();
2746 } else {
2747 sc->SkipBracketed(true); // bracket eaten
2750 } else if (sc->Check("gamedefaults")) {
2751 GCon->Logf(NAME_Warning, "Unimplemented MAPINFO section gamedefaults");
2752 sc->SkipBracketed();
2753 } else if (sc->Check("automap")) {
2754 GCon->Logf(NAME_Warning, "Unimplemented MAPINFO command Automap");
2755 sc->SkipBracketed();
2756 } else if (sc->Check("automap_overlay")) {
2757 GCon->Logf(NAME_Warning, "Unimplemented MAPINFO command automap_overlay");
2758 sc->SkipBracketed();
2760 } else if (sc->Check("DoomEdNums")) {
2761 //GCon->Logf(NAME_Debug, "*** <%s> ***", *sc->String);
2762 //auto cmode = sc->IsCMode();
2763 //sc->SetCMode(true);
2764 sc->Expect("{");
2765 for (;;) {
2766 if (checkEndBracket(sc)) break;
2767 sc->ExpectNumber();
2768 int num = sc->Number;
2769 sc->Expect("=");
2770 sc->ExpectString();
2771 VStr clsname = sc->String;
2772 int flags = 0;
2773 int special = 0;
2774 int args[5] = {0, 0, 0, 0, 0};
2775 if (sc->Check(",")) {
2776 const VTextLocation loc = sc->GetLoc();
2777 bool doit = true;
2778 if (sc->Check("noskillflags")) { flags |= mobjinfo_t::FlagNoSkill; doit = sc->Check(","); }
2779 if (doit) {
2780 flags |= mobjinfo_t::FlagSpecial;
2781 sc->ExpectString();
2782 VStr spcname = sc->String;
2783 // no name?
2784 int argn = 0;
2785 int arg0 = 0;
2786 if (VStr::convertInt(*spcname, &arg0)) {
2787 spcname = VStr();
2788 special = -1;
2789 args[argn++] = arg0;
2791 while (sc->Check(",")) {
2792 sc->ExpectNumber(true); // with sign, why not
2793 if (argn < 5) args[argn] = sc->Number;
2794 ++argn;
2796 if (argn > 5) GCon->Logf(NAME_Warning, "MAPINFO:%s: too many arguments (%d) to special '%s'", *loc.toStringNoCol(), argn, *spcname);
2797 // find special number
2798 if (special == 0) special = FindScriptLineSpecialByName(spcname);
2799 if (!special) {
2800 flags &= ~mobjinfo_t::FlagSpecial;
2801 GCon->Logf(NAME_Warning, "MAPINFO:%s: special '%s' not found", *loc.toStringNoCol(), *spcname);
2803 if (special == -1) special = 0; // special special ;-)
2806 //GCon->Logf(NAME_Debug, "MAPINFO: DOOMED: '%s', %d (%d)", *clsname, num, flags);
2807 appendNumFixup(DoomEdNumFixups, clsname, num, flags, special, args[0], args[1], args[2], args[3], args[4]);
2809 //sc->SetCMode(cmode);
2810 } else if (sc->Check("SpawnNums")) {
2811 //auto cmode = sc->IsCMode();
2812 //sc->SetCMode(true);
2813 sc->Expect("{");
2814 for (;;) {
2815 if (checkEndBracket(sc)) break;
2816 sc->ExpectNumber();
2817 int num = sc->Number;
2818 sc->Expect("=");
2819 sc->ExpectString();
2820 appendNumFixup(SpawnNumFixups, VStr(sc->String), num);
2822 //sc->SetCMode(cmode);
2823 } else if (sc->Check("author")) {
2824 sc->ExpectString();
2825 } else {
2826 VStr cmdname = sc->String;
2827 sc->ExpectString();
2828 if (sc->Check("{")) {
2829 GCon->Logf(NAME_Warning, "Unimplemented MAPINFO command \"%s\"", *cmdname.quote());
2830 sc->SkipBracketed(true); // bracket eaten
2831 } else if (!sc->String.strEquCI("}")) { // some mappers cannot into mapinfo
2832 sc->Error(va("Invalid command '%s'", *sc->String));
2833 error = true;
2834 break;
2835 } else {
2836 sc->Message(va("Some mapper cannot into proper mapinfo (stray \"%s\")", *sc->String.quote()));
2840 if (error) {
2841 while (scsp > 0) { delete sc; sc = scstack[--scsp]; }
2842 break;
2844 if (scsp == 0) break;
2845 GCon->Logf(NAME_Init, "Finished included '%s'", *sc->GetLoc().GetSourceFile());
2846 delete sc;
2847 sc = scstack[--scsp];
2849 delete sc;
2853 //==========================================================================
2855 // ParseUMapinfo
2857 // `sc->SourceLump` must be set
2859 //==========================================================================
2860 static void ParseUMapinfo (VScriptParser *sc) {
2861 // set up default map info
2862 VMapInfo Default;
2863 SetMapDefaults(Default);
2864 bool HexenMode = false;
2866 while (!sc->AtEnd()) {
2867 if (sc->Check("map")) {
2868 ParseMap(sc, HexenMode, Default, true);
2869 } else {
2870 sc->Error(va("Unknown UMAPINFO key '%s'", *sc->String));
2874 delete sc;
2878 //==========================================================================
2880 // QualifyMap
2882 //==========================================================================
2883 static int QualifyMap (int map) {
2884 return (map < 0 || map >= MapInfo.length() ? 0 : map);
2888 //==========================================================================
2890 // P_GetMapInfo
2892 //==========================================================================
2893 const VMapInfo &P_GetMapInfo (VName map) {
2894 for (int i = 0; i < MapInfo.length(); ++i) {
2895 if (map == MapInfo[i].LumpName) return MapInfo[i];
2897 return DefaultMap;
2901 //==========================================================================
2903 // P_GetMapName
2905 //==========================================================================
2906 VStr P_GetMapName (int map) {
2907 return MapInfo[QualifyMap(map)].GetName();
2911 //==========================================================================
2913 // P_GetMapInfoIndexByLevelNum
2915 // Returns map info index given a level number
2917 //==========================================================================
2918 int P_GetMapIndexByLevelNum (int map) {
2919 for (int i = 0; i < MapInfo.length(); ++i) {
2920 if (MapInfo[i].LevelNum == map) return i;
2922 // not found
2923 return 0;
2927 //==========================================================================
2929 // P_GetMapLumpName
2931 //==========================================================================
2932 VName P_GetMapLumpName (int map) {
2933 return MapInfo[QualifyMap(map)].LumpName;
2937 //==========================================================================
2939 // P_TranslateMap
2941 // Returns the map lump name given a warp map number.
2943 //==========================================================================
2944 VName P_TranslateMap (int map) {
2945 for (int i = MapInfo.length()-1; i >= 0; --i) {
2946 if (MapInfo[i].WarpTrans == map) return MapInfo[i].LumpName;
2948 // not found
2949 return (MapInfo.length() > 0 ? MapInfo[0].LumpName : NAME_None);
2953 //==========================================================================
2955 // P_TranslateMapEx
2957 // Returns the map lump name given a warp map number.
2959 //==========================================================================
2960 VName P_TranslateMapEx (int map) {
2961 for (int i = MapInfo.length()-1; i >= 0; --i) {
2962 if (MapInfo[i].WarpTrans == map) return MapInfo[i].LumpName;
2964 // not found
2965 return NAME_None;
2969 //==========================================================================
2971 // P_GetMapLumpNameByLevelNum
2973 // Returns the map lump name given a level number.
2975 //==========================================================================
2976 VName P_GetMapLumpNameByLevelNum (int map) {
2977 for (int i = 0; i < MapInfo.length(); ++i) {
2978 if (MapInfo[i].LevelNum == map) return MapInfo[i].LumpName;
2980 // not found, use map##
2981 return va("map%02d", map);
2985 //==========================================================================
2987 // P_PutMapSongLump
2989 //==========================================================================
2990 void P_PutMapSongLump (int map, VName lumpName) {
2991 FMapSongInfo &ms = MapSongList.Alloc();
2992 ms.MapName = va("map%02d", map);
2993 ms.SongName = lumpName;
2997 //==========================================================================
2999 // P_GetClusterDef
3001 //==========================================================================
3002 const VClusterDef *P_GetClusterDef (int Cluster) {
3003 for (int i = 0; i < ClusterDefs.length(); ++i) {
3004 if (Cluster == ClusterDefs[i].Cluster) return &ClusterDefs[i];
3006 return &DefaultClusterDef;
3010 //==========================================================================
3012 // P_GetNumEpisodes
3014 //==========================================================================
3015 int P_GetNumEpisodes () {
3016 return EpisodeDefs.length();
3020 //==========================================================================
3022 // P_GetNumMaps
3024 //==========================================================================
3025 int P_GetNumMaps () {
3026 return MapInfo.length();
3030 //==========================================================================
3032 // P_GetMapInfo
3034 //==========================================================================
3035 VMapInfo *P_GetMapInfoPtr (int mapidx) {
3036 return (mapidx >= 0 && mapidx < MapInfo.length() ? &MapInfo[mapidx] : nullptr);
3040 //==========================================================================
3042 // P_GetEpisodeDef
3044 //==========================================================================
3045 VEpisodeDef *P_GetEpisodeDef (int Index) {
3046 return &EpisodeDefs[Index];
3050 //==========================================================================
3052 // P_GetNumSkills
3054 //==========================================================================
3055 int P_GetNumSkills () {
3056 return SkillDefs.length();
3060 //==========================================================================
3062 // P_GetSkillDef
3064 //==========================================================================
3065 const VSkillDef *P_GetSkillDef (int Index) {
3066 return &SkillDefs[Index];
3070 //==========================================================================
3072 // P_GetMusicLumpNames
3074 //==========================================================================
3075 void P_GetMusicLumpNames (TArray<FReplacedString> &List) {
3076 for (int i = 0; i < MapInfo.length(); ++i) {
3077 const char *MName = *MapInfo[i].SongLump;
3078 if (MName[0] == 'd' && MName[1] == '_') {
3079 FReplacedString &R = List.Alloc();
3080 R.Index = i;
3081 R.Replaced = false;
3082 R.Old = MName+2;
3088 //==========================================================================
3090 // P_ReplaceMusicLumpNames
3092 //==========================================================================
3093 void P_ReplaceMusicLumpNames (TArray<FReplacedString> &List) {
3094 for (int i = 0; i < List.length(); ++i) {
3095 if (List[i].Replaced) {
3096 MapInfo[List[i].Index].SongLump = VName(*(VStr("d_")+List[i].New), VName::AddLower8);
3102 //==========================================================================
3104 // P_SetParTime
3106 //==========================================================================
3107 void P_SetParTime (VName Map, int Par) {
3108 if (Map == NAME_None || Par < 0) return;
3109 if (mapinfoParsed) {
3110 for (int i = 0; i < MapInfo.length(); ++i) {
3111 if (MapInfo[i].LumpName == NAME_None) continue;
3112 if (VStr::ICmp(*MapInfo[i].LumpName, *Map) == 0) {
3113 MapInfo[i].ParTime = Par;
3114 return;
3117 GCon->Logf(NAME_Warning, "No such map '%s' for par time", *Map);
3118 } else {
3119 ParTimeInfo &pi = partimes.alloc();
3120 pi.MapName = Map;
3121 pi.par = Par;
3126 //==========================================================================
3128 // IsMapPresent
3130 //==========================================================================
3131 bool IsMapPresent (VName MapName) {
3132 if (W_CheckNumForName(MapName) >= 0) return true;
3133 VStr FileName = va("maps/%s.wad", *MapName);
3134 if (FL_FileExists(FileName)) return true;
3135 return false;
3139 //==========================================================================
3141 // COMMAND MapList
3143 //==========================================================================
3144 COMMAND(MapList) {
3145 for (int i = 0; i < MapInfo.length(); ++i) {
3146 if (IsMapPresent(MapInfo[i].LumpName)) {
3147 GCon->Log(VStr(MapInfo[i].LumpName)+" - "+(MapInfo[i].Flags&VLevelInfo::LIF_LookupName ? GLanguage[*MapInfo[i].Name] : MapInfo[i].Name));
3153 //==========================================================================
3155 // ShutdownMapInfo
3157 //==========================================================================
3158 void ShutdownMapInfo () {
3159 DefaultMap.Name.Clean();
3160 MapInfo.Clear();
3161 MapSongList.Clear();
3162 ClusterDefs.Clear();
3163 EpisodeDefs.Clear();
3164 SkillDefs.Clear();