libvwad: updated -- vwadwrite: free file buffers on close (otherwise archive creation...
[k8vavoom.git] / source / psim / p_terrain.cpp
blob7345b798bd256a0dec228f8de21fd3ef386fe6f2
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 //**************************************************************************
27 // Terrain types
29 //**************************************************************************
30 #include "../gamedefs.h"
31 #include "p_entity.h"
34 static VCvarB gm_optional_terrain("gm_optional_terrain", true, "Allow liquid sounds for Vanilla Doom?", CVAR_Archive);
37 struct VTerrainType {
38 int Pic;
39 VName TypeName;
40 // it is safe to use pointer here, becasuse it is set after all
41 // terrain data is loaded, and the pointer is immutable
42 VTerrainInfo *Info;
46 // terrain types
47 static TArray<VSplashInfo> SplashInfos;
48 static TArray<VTerrainInfo> TerrainInfos;
49 static TArray<VTerrainType> TerrainTypes;
50 static TArray<VTerrainBootprint *> TerrainBootprints;
52 static TMapNC<VName, int> SplashMap; // key: lowercased name; value: index in `SplashInfos`
53 static TMapNC<VName, int> TerrainMap; // key: lowercased name; value: index in `TerrainInfos`
54 static TMapNC<int, int> TerrainTypeMap; // key: pic number; value: index in `TerrainTypes`
55 static TMapNC<int, VTerrainBootprint *> TerrainBootprintMap; // key: pic number
56 static TMap<VStrCI, VTerrainBootprint *> TerrainBootprintNameMap;
57 static VName DefaultTerrainName;
58 static VStr DefaultTerrainNameStr;
59 static int DefaultTerrainIndex;
61 static bool GlobalDisableOverride = false;
62 static TMap<VStrCI, bool> NoOverrideSplash;
63 static TMap<VStrCI, bool> NoOverrideTerrain;
64 static TMap<VStrCI, bool> NoOverrideBootprint;
67 //==========================================================================
69 // IsSplashNoOverride
71 //==========================================================================
72 static inline bool IsSplashNoOverride (VStr name) noexcept {
73 return NoOverrideSplash.has(name);
77 //==========================================================================
79 // IsTerrainNoOverride
81 //==========================================================================
82 static inline bool IsTerrainNoOverride (VStr name) noexcept {
83 return NoOverrideTerrain.has(name);
87 //==========================================================================
89 // IsBootprintNoOverride
91 //==========================================================================
92 static inline bool IsBootprintNoOverride (VStr name) noexcept {
93 return NoOverrideBootprint.has(name);
97 //==========================================================================
99 // AddSplashNoOverride
101 //==========================================================================
102 static inline void AddSplashNoOverride (VStr name) noexcept {
103 if (!name.isEmpty()) NoOverrideSplash.put(name, true);
107 //==========================================================================
109 // AddTerrainNoOverride
111 //==========================================================================
112 static inline void AddTerrainNoOverride (VStr name) noexcept {
113 if (!name.isEmpty()) NoOverrideTerrain.put(name, true);
117 //==========================================================================
119 // AddBootprintNoOverride
121 //==========================================================================
122 static inline void AddBootprintNoOverride (VStr name) noexcept {
123 if (!name.isEmpty()) NoOverrideBootprint.put(name, true);
127 //==========================================================================
129 // GetSplashInfo
131 //==========================================================================
132 static VSplashInfo *GetSplashInfo (const char *Name) {
133 if (!Name || !Name[0]) return nullptr;
134 VName loname = VName(Name, VName::FindLower);
135 if (loname == NAME_None) return nullptr;
136 auto spp = SplashMap.get(loname);
137 return (spp ? &SplashInfos[*spp] : nullptr);
139 for (int i = 0; i < SplashInfos.length(); ++i) {
140 if (VStr::strEquCI(*SplashInfos[i].Name, *Name)) return &SplashInfos[i];
142 return nullptr;
147 //==========================================================================
149 // GetTerrainInfo
151 //==========================================================================
152 static VTerrainInfo *GetTerrainInfo (const char *Name) {
153 if (!Name || !Name[0]) return &TerrainInfos[DefaultTerrainIndex]; // default one
154 VName loname = VName(Name, VName::FindLower);
155 if (loname == NAME_None) return nullptr;
156 auto spp = TerrainMap.get(loname);
157 return (spp ? &TerrainInfos[*spp] : nullptr);
161 //==========================================================================
163 // FindBootprint
165 //==========================================================================
166 static VTerrainBootprint *FindBootprint (const char *bpname, bool allowCreation, const char *tname=nullptr) {
167 if (!bpname || !bpname[0] || VStr::strEquCI(bpname, "none")) return nullptr;
168 for (VTerrainBootprint *bp : TerrainBootprints) {
169 if (bp->OrigName.strEquCI(bpname)) return bp;
171 if (!allowCreation) {
172 if (tname) Sys_Error("cannot find bootprint definition named '%s' for terrain '%s'", bpname, tname);
173 Sys_Error("cannot find bootprint definition named '%s'", bpname);
175 // create forward definition
176 VTerrainBootprint *bp = new VTerrainBootprint;
177 memset((void *)bp, 0, sizeof(VTerrainBootprint));
178 bp->OrigName = bpname;
179 TerrainBootprints.append(bp);
180 return bp;
184 //==========================================================================
186 // ProcessFlatGlobMask
188 //==========================================================================
189 static void ProcessFlatGlobMask (VStr mask, const char *tername, VStr slocstr) {
190 if (!tername) tername = "";
191 mask = mask.xstrip();
192 if (mask.isEmpty()) return;
193 //GCon->Logf(NAME_Debug, "checking flatglobmask <%s> (terrain: %s)", *mask, (tername ? tername : "<none>"));
194 const int tcount = GTextureManager.GetNumTextures();
195 for (int pic = 1; pic < tcount; ++pic) {
196 VTexture *tx = GTextureManager.getIgnoreAnim(pic);
197 if (!tx) continue;
198 VName tn = tx->Name;
199 if (tn == NAME_None) continue;
200 bool goodtx = false;
201 switch (tx->Type) {
202 case TEXTYPE_Any: //FIXME: dunno
203 case TEXTYPE_WallPatch:
204 case TEXTYPE_Wall:
205 case TEXTYPE_Flat:
206 case TEXTYPE_Overload:
207 goodtx = true;
208 break;
209 default: break;
211 if (!goodtx) {
212 //GCon->Logf(NAME_Debug, " rejected texture '%s'", *tn);
213 continue;
215 if (!VStr::globMatchCI(*tn, *mask)) {
216 //GCon->Logf(NAME_Debug, " skipped texture '%s'", *tn);
217 continue;
219 //GCon->Logf(NAME_Debug, " matched texture '%s'", *tn);
220 // found texture
221 if (!tername[0]) {
222 //WARNING! memory leak on deletion!
223 TerrainTypeMap.del(pic);
224 continue;
226 auto pp = TerrainTypeMap.get(pic);
227 if (pp) {
228 // replace old one
229 if (GlobalDisableOverride && IsTerrainNoOverride(VStr(TerrainTypes[*pp].TypeName))) {
230 GCon->Logf(NAME_Init, "%s: skipped floor '%s' override to '%s' due to 'GlobalDisableOverride'", *slocstr, *tn, tername);
231 } else {
232 TerrainTypes[*pp].TypeName = VName(tername);
234 } else {
235 //!GCon->Logf(NAME_Init, "%s: new terrain floor '%s' of type '%s' (pic=%d)", *sc->GetLoc().toStringNoCol(), *floorname, *sc->String, Pic);
236 VTerrainType &T = TerrainTypes.Alloc();
237 T.Pic = pic;
238 T.TypeName = VName(tername);
239 TerrainTypeMap.put(pic, TerrainTypes.length()-1);
245 //==========================================================================
247 // ParseTerrainSplashClassName
249 //==========================================================================
250 static VClass *ParseTerrainSplashClassName (VScriptParser *sc) {
251 sc->ExpectString();
252 if (sc->String.isEmpty() || sc->String.strEquCI("none")) return nullptr;
253 VClass *cls = VClass::FindClass(*sc->String);
254 if (!cls) {
255 sc->Message(va("splash class '%s' not found", *sc->String));
256 } else if (!cls->IsChildOf(VEntity::StaticClass())) {
257 sc->Message(va("splash class '%s' is not an Entity, ignored", *sc->String));
258 cls = nullptr;
260 return cls;
264 //==========================================================================
266 // ParseTerrainSplashDef
268 // "splash" is already eaten
270 //==========================================================================
271 static void ParseTerrainSplashDef (VScriptParser *sc) {
272 sc->ExpectString();
273 if (sc->String.isEmpty()) sc->String = "none";
275 if (IsSplashNoOverride(sc->String)) {
276 GCon->Logf(NAME_Init, "%s: skipped splash '%s' override due to 'GlobalDisableOverride'", *sc->GetLoc().toStringNoCol(), *sc->String);
277 sc->Expect("{");
278 sc->SkipBracketed(true/*bracketEaten*/);
279 return;
282 VSplashInfo *SInfo = GetSplashInfo(*sc->String);
283 if (!SInfo) {
284 //!GCon->Logf(NAME_Init, "%s: new splash '%s'", *sc->GetLoc().toStringNoCol(), *sc->String);
285 VName nn = VName(*sc->String, VName::AddLower);
286 SInfo = &SplashInfos.Alloc();
287 SInfo->Name = nn;
288 SInfo->OrigName = sc->String;
289 SplashMap.put(nn, SplashInfos.length()-1);
291 SInfo->SmallClass = nullptr;
292 SInfo->SmallClip = 0;
293 SInfo->SmallSound = NAME_None;
294 SInfo->BaseClass = nullptr;
295 SInfo->ChunkClass = nullptr;
296 SInfo->ChunkXVelMul = 0;
297 SInfo->ChunkYVelMul = 0;
298 SInfo->ChunkZVelMul = 0;
299 SInfo->ChunkBaseZVel = 0;
300 SInfo->Sound = NAME_None;
301 SInfo->Flags = 0;
303 if (GlobalDisableOverride) AddSplashNoOverride(SInfo->OrigName);
305 sc->Expect("{");
306 while (!sc->Check("}")) {
307 if (sc->AtEnd()) break;
308 if (sc->Check("smallclass")) {
309 SInfo->SmallClass = ParseTerrainSplashClassName(sc);
310 continue;
312 if (sc->Check("smallclip")) {
313 sc->ExpectFloat();
314 SInfo->SmallClip = sc->Float;
315 continue;
317 if (sc->Check("smallsound")) {
318 sc->ExpectString();
319 SInfo->SmallSound = *sc->String;
320 continue;
322 if (sc->Check("baseclass")) {
323 SInfo->BaseClass = ParseTerrainSplashClassName(sc);
324 continue;
326 if (sc->Check("chunkclass")) {
327 SInfo->ChunkClass = ParseTerrainSplashClassName(sc);
328 continue;
330 if (sc->Check("chunkxvelshift")) {
331 sc->ExpectNumber();
332 SInfo->ChunkXVelMul = sc->Number < 0 ? 0.0f : float((1<<sc->Number)/256);
333 continue;
335 if (sc->Check("chunkyvelshift")) {
336 sc->ExpectNumber();
337 SInfo->ChunkYVelMul = sc->Number < 0 ? 0.0f : float((1<<sc->Number)/256);
338 continue;
340 if (sc->Check("chunkzvelshift")) {
341 sc->ExpectNumber();
342 SInfo->ChunkZVelMul = sc->Number < 0 ? 0.0f : float((1<<sc->Number)/256);
343 continue;
345 if (sc->Check("chunkbasezvel")) {
346 sc->ExpectFloat();
347 SInfo->ChunkBaseZVel = sc->Float;
348 continue;
350 if (sc->Check("sound")) {
351 sc->ExpectString();
352 SInfo->Sound = *sc->String;
353 continue;
355 if (sc->Check("noalert")) {
356 SInfo->Flags |= VSplashInfo::F_NoAlert;
357 continue;
359 sc->Error(va("Unknown splash command (%s)", *sc->String));
364 //==========================================================================
366 // ParseTerrainTerrainDef
368 // "terrain" is already eaten
370 //==========================================================================
371 static void ParseTerrainTerrainDef (VScriptParser *sc, int tkw) {
372 sc->ExpectString();
373 if (sc->String.isEmpty()) sc->String = "none";
374 VStr TerName = sc->String;
376 if (IsTerrainNoOverride(sc->String)) {
377 GCon->Logf(NAME_Init, "%s: skipped terrain '%s' override due to 'GlobalDisableOverride'", *sc->GetLoc().toStringNoCol(), *sc->String);
378 sc->Check("modify");
379 if (sc->Check("{")) sc->SkipBracketed(true/*bracketEaten*/);
380 return;
383 bool modify = false;
384 VTerrainInfo *TInfo;
385 if (tkw == 2) {
386 // default terrain definition, remember new default terrain
387 DefaultTerrainNameStr = TerName;
388 DefaultTerrainName = VName(*TerName, VName::AddLower);
389 // if just a name, do nothing else
390 if (sc->PeekChar() != '{') return;
391 } else {
392 if (sc->Check("modify")) modify = true;
394 //GCon->Logf(NAME_Debug, "terraindef '%s'...", *TerName);
395 // new terrain
396 TInfo = GetTerrainInfo(*TerName);
397 if (!TInfo) {
398 //GCon->Logf(NAME_Debug, "%s: new terrain '%s'", *sc->GetLoc().toStringNoCol(), *TerName);
399 VName nn = VName(*TerName, VName::AddLower);
400 TInfo = &TerrainInfos.Alloc();
401 memset((void *)TInfo, 0, sizeof(VTerrainInfo));
402 TInfo->Name = nn;
403 TInfo->OrigName = TerName;
404 TerrainMap.put(nn, TerrainInfos.length()-1);
405 modify = false; // clear it
408 if (GlobalDisableOverride) AddTerrainNoOverride(TInfo->OrigName);
411 else {
412 GCon->Logf(NAME_Debug, "%s: %s terrain '%s'", *sc->GetLoc().toStringNoCol(), (modify ? "modifying" : "redefining"), *TerName);
415 // clear
416 if (!modify) {
417 TInfo->Splash = NAME_None;
418 TInfo->Flags = 0;
419 TInfo->FootClip = 0.0f;
420 TInfo->DamageTimeMask = 0;
421 TInfo->DamageAmount = 0;
422 TInfo->DamageType = NAME_None;
423 TInfo->Friction = 0.0f;
424 TInfo->MoveFactor = 0.0f;
425 TInfo->WalkingStepVolume = 1.0f;
426 TInfo->RunningStepVolume = 1.0f;
427 TInfo->CrouchingStepVolume = 1.0f;
428 TInfo->WalkingStepTime = 0.0f;
429 TInfo->RunningStepTime = 0.0f;
430 TInfo->CrouchingStepTime = 0.0f;
431 TInfo->LeftStepSound = NAME_None;
432 TInfo->RightStepSound = NAME_None;
433 TInfo->LandVolume = 1.0f;
434 TInfo->LandSound = NAME_None;
435 TInfo->SmallLandVolume = 1.0f;
436 TInfo->SmallLandSound = NAME_None;
437 TInfo->BootPrint = nullptr;
440 sc->Expect("{");
441 while (!sc->Check("}")) {
442 if (sc->AtEnd()) break;
443 //GCon->Logf(NAME_Debug, " terraindef '%s': <%s>", *TInfo->OrigName, *sc->String);
444 if (sc->Check("splash")) {
445 sc->ExpectString();
446 TInfo->Splash = *sc->String;
447 continue;
449 if (sc->Check("liquid")) {
450 TInfo->Flags |= VTerrainInfo::F_Liquid;
451 continue;
453 if (sc->Check("footclip")) {
454 sc->ExpectFloat();
455 TInfo->FootClip = sc->Float;
456 continue;
458 if (sc->Check("damagetimemask")) {
459 sc->ExpectNumber();
460 TInfo->DamageTimeMask = sc->Number;
461 continue;
463 if (sc->Check("damageamount")) {
464 sc->ExpectNumber();
465 TInfo->DamageAmount = sc->Number;
466 continue;
468 if (sc->Check("damagetype")) {
469 sc->ExpectString();
470 TInfo->DamageType = *sc->String;
471 continue;
473 if (sc->Check("damageonland")) {
474 TInfo->Flags |= VTerrainInfo::F_DamageOnLand;
475 continue;
477 if (sc->Check("friction")) {
478 sc->ExpectFloat();
479 int friction, movefactor;
481 // same calculations as in Sector_SetFriction special
482 // a friction of 1.0 is equivalent to ORIG_FRICTION
484 friction = (int)(0x1EB8*(sc->Float*100))/0x80+0xD001;
485 friction = midval(0, friction, 0x10000);
487 if (friction > 0xe800) {
488 // ice
489 movefactor = ((0x10092-friction)*1024)/4352+568;
490 } else {
491 movefactor = ((friction-0xDB34)*(0xA))/0x80;
494 if (movefactor < 32) movefactor = 32;
496 TInfo->Friction = (1.0f-(float)friction/(float)0x10000)*35.0f;
497 TInfo->MoveFactor = float(movefactor)/float(0x10000);
498 continue;
500 if (sc->Check("allowprotection")) {
501 TInfo->Flags |= VTerrainInfo::F_AllowProtection;
502 continue;
504 // k8vavoom extensions
505 if (sc->Check("k8vavoom")) {
506 sc->Expect("{");
507 while (!sc->Check("}")) {
508 if (sc->AtEnd()) break;
509 if (sc->Check("noprotection")) { TInfo->Flags &= ~VTerrainInfo::F_AllowProtection; continue; }
510 if (sc->Check("notliqid")) { TInfo->Flags &= ~VTerrainInfo::F_Liquid; continue; }
511 if (sc->Check("nodamageonland")) { TInfo->Flags &= ~VTerrainInfo::F_DamageOnLand; continue; }
512 if (sc->Check("playeronly")) { TInfo->Flags |= VTerrainInfo::F_PlayerOnly; continue; }
513 if (sc->Check("everybody")) { TInfo->Flags &= ~VTerrainInfo::F_PlayerOnly; continue; }
514 if (sc->Check("optout")) { TInfo->Flags |= VTerrainInfo::F_OptOut; continue; }
515 if (sc->Check("nooptout")) { TInfo->Flags &= ~VTerrainInfo::F_OptOut; continue; }
516 if (sc->Check("bootprint")) {
517 sc->ExpectString();
518 TInfo->BootPrint = FindBootprint(*sc->String, true);
519 continue;
521 if (sc->Check("detectfloorflat")) { // k8vavoom extension
522 sc->ExpectString();
523 ProcessFlatGlobMask(sc->String, *TInfo->Name, sc->GetLoc().toStringNoCol());
524 continue;
526 // footsteps
527 if (sc->Check("walkingstepvolume")) { sc->ExpectFloat(); TInfo->WalkingStepVolume = sc->Float; continue; }
528 if (sc->Check("runningstepvolume")) { sc->ExpectFloat(); TInfo->RunningStepVolume = sc->Float; continue; }
529 if (sc->Check("crouchingstepvolume")) { sc->ExpectFloat(); TInfo->CrouchingStepVolume = sc->Float; continue; }
530 if (sc->Check("allstepvolume")) { sc->ExpectFloat(); TInfo->WalkingStepVolume = TInfo->RunningStepVolume = TInfo->CrouchingStepVolume = sc->Float; continue; }
531 if (sc->Check("walkingsteptime")) { sc->ExpectFloat(); TInfo->WalkingStepTime = sc->Float; continue; }
532 if (sc->Check("runningsteptime")) { sc->ExpectFloat(); TInfo->RunningStepTime = sc->Float; continue; }
533 if (sc->Check("crouchingsteptime")) { sc->ExpectFloat(); TInfo->CrouchingStepTime = sc->Float; continue; }
534 if (sc->Check("allsteptime")) { sc->ExpectFloat(); TInfo->WalkingStepTime = TInfo->RunningStepTime = TInfo->CrouchingStepTime = sc->Float; continue; }
535 if (sc->Check("leftstepsound")) { sc->ExpectString(); TInfo->LeftStepSound = *sc->String; continue; }
536 if (sc->Check("rightstepsound")) { sc->ExpectString(); TInfo->RightStepSound = *sc->String; continue; }
537 if (sc->Check("allstepsound")) { sc->ExpectString(); TInfo->LeftStepSound = TInfo->RightStepSound = *sc->String; continue; }
538 // first step
539 if (sc->Check("landvolume")) { sc->ExpectFloat(); TInfo->LandVolume = sc->Float; continue; }
540 if (sc->Check("smalllandvolume")) { sc->ExpectFloat(); TInfo->SmallLandVolume = sc->Float; continue; }
541 if (sc->Check("alllandvolume")) { sc->ExpectFloat(); TInfo->LandVolume = TInfo->SmallLandVolume = sc->Float; continue; }
542 if (sc->Check("landsound")) { sc->ExpectString(); TInfo->LandSound = *sc->String; continue; }
543 if (sc->Check("smalllandsound")) { sc->ExpectString(); TInfo->SmallLandSound = *sc->String; continue; }
544 if (sc->Check("alllandsound")) { sc->ExpectString(); TInfo->LandSound = TInfo->SmallLandSound = *sc->String; continue; }
545 sc->Error(va("Unknown k8vavoom terrain extension command (%s)", *sc->String));
547 continue;
549 sc->Error(va("Unknown terrain command (%s)", *sc->String));
554 //==========================================================================
556 // ProcessAssignBootprint
558 //==========================================================================
559 static void ProcessAssignBootprint (VTerrainBootprint *bp, VStr mask) {
560 if (!bp) return;
561 if (mask.isEmpty()) return;
562 const int tcount = GTextureManager.GetNumTextures();
563 for (int pic = 1; pic < tcount; ++pic) {
564 VTexture *tx = GTextureManager.getIgnoreAnim(pic);
565 if (!tx) continue;
566 VName tn = tx->Name;
567 if (tn == NAME_None) continue;
568 bool goodtx = false;
569 switch (tx->Type) {
570 case TEXTYPE_Any: //FIXME: dunno
571 case TEXTYPE_WallPatch:
572 case TEXTYPE_Wall:
573 case TEXTYPE_Flat:
574 case TEXTYPE_Overload:
575 goodtx = true;
576 break;
577 default: break;
579 if (!goodtx) continue;
580 if (!VStr::globMatchCI(*tn, *mask)) continue;
581 // found texture
582 TerrainBootprintMap.put(pic, bp);
587 //==========================================================================
589 // ParseTerrainBootPrintDef
591 // "bootprint" is already eaten
593 //==========================================================================
594 static void ParseTerrainBootPrintDef (VScriptParser *sc) {
595 sc->ExpectString();
596 if (sc->String.isEmpty()) sc->String = "none";
597 if (sc->String.strEquCI("none")) sc->Error("invalid bootprint name");
599 if (IsBootprintNoOverride(sc->String)) {
600 GCon->Logf(NAME_Init, "%s: skipped bootprint '%s' override due to 'GlobalDisableOverride'", *sc->GetLoc().toStringNoCol(), *sc->String);
601 sc->Check("modify");
602 if (sc->Check("{")) sc->SkipBracketed(true/*bracketEaten*/);
603 return;
606 VTerrainBootprint *bp = FindBootprint(*sc->String, true);
607 vassert(bp); // invariant
609 constexpr float BOOT_TIME_MIN = 3.8f;
610 constexpr float BOOT_TIME_MAX = 4.2f;
612 bool doclear = true;
613 if (sc->Check("modify")) {
614 doclear = (bp->Name == NAME_None);
615 } else {
616 // remove from bootprint map
617 if (bp->Name != NAME_None) {
618 TArray<int> rmidx;
619 for (auto &&it : TerrainBootprintMap.first()) {
620 if (it.value() == bp) rmidx.append(it.key());
622 for (int ri : rmidx) TerrainBootprintMap.del(ri);
626 bp->Name = VName(*bp->OrigName, VName::AddLower);
627 if (doclear) {
628 bp->TimeMin = BOOT_TIME_MIN;
629 bp->TimeMax = BOOT_TIME_MAX;
630 bp->AlphaMin = bp->AlphaMax = bp->AlphaValue = 1.0f;
631 bp->Translation = 0;
632 bp->ShadeColor = -2;
633 bp->Animator = NAME_None;
634 bp->Flags = 0;
637 if (GlobalDisableOverride) AddBootprintNoOverride(bp->OrigName);
639 sc->Check("{");
640 //bool wasDecalName = false;
641 while (!sc->Check("}")) {
642 if (sc->AtEnd()) sc->Error(va("unfinished bootprint '%s' definition", *bp->OrigName));
644 // decal name
646 if (sc->Check("decal")) {
647 if (wasDecalName) sc->Error("duplicate decal name");
648 wasDecalName = true;
649 sc->ExpectString();
650 bp->DecalName = (sc->String.isEmpty() ? NAME_None : VName(*sc->String));
651 continue;
655 // times
656 if (sc->Check("time")) {
657 if (sc->Check("default")) {
658 bp->TimeMin = BOOT_TIME_MIN;
659 bp->TimeMax = BOOT_TIME_MAX;
660 } else {
661 sc->ExpectFloat();
662 float tmin = sc->Float;
663 sc->ExpectFloat();
664 float tmax = sc->Float;
665 if (tmin > tmax) { const float tt = tmin; tmin = tmax; tmax = tt; }
666 bp->TimeMin = tmin;
667 bp->TimeMax = tmax;
669 continue;
672 // flat name glob
673 if (sc->Check("flat")) {
674 sc->ExpectString();
675 VStr mask = sc->String.xstrip();
676 ProcessAssignBootprint(bp, mask);
677 continue;
680 if (sc->Check("optional")) {
681 bp->setOptional();
682 continue;
685 if (sc->Check("required")) {
686 bp->resetOptional();
687 continue;
690 if (sc->Check("shade")) {
691 if (sc->Check("flatpic")) {
692 bp->ShadeColor = 0xed000000;
693 if (sc->Check("maxout")) {
694 sc->ExpectNumber();
695 bp->ShadeColor |= clampToByte(sc->Number);
696 } else {
697 // mult by 2.0
698 bp->ShadeColor |= 2;
700 } else if (sc->Check("fromdecal")) {
701 bp->ShadeColor = -2;
702 } else {
703 sc->ExpectString();
704 vuint32 ppc = M_ParseColor(*sc->String);
705 bp->ShadeColor = ppc&0xffffff;
707 continue;
710 if (sc->Check("animator")) {
711 sc->ExpectString();
712 if (sc->String.isEmpty()) {
713 bp->Animator = VName("none");
714 } else {
715 bp->Animator = VName(*sc->String);
717 continue;
720 if (sc->Check("translucent")) {
721 if (sc->Check("random")) {
722 // `random min max`
723 sc->ExpectFloat();
724 bp->AlphaMin = clampval(sc->Float, 0.0f, 1.0f);
725 sc->ExpectFloat();
726 bp->AlphaMax = clampval(sc->Float, 0.0f, 1.0f);
727 bp->AlphaValue = FRandomBetween(bp->AlphaMin, bp->AlphaMax);
728 } else {
729 sc->ExpectFloat();
730 bp->AlphaMin = bp->AlphaMax = bp->AlphaValue = clampval(sc->Float, 0.0f, 1.0f);
732 continue;
735 if (sc->Check("solid")) {
736 bp->AlphaMin = bp->AlphaMax = bp->AlphaValue = 1.0f;
737 continue;
740 if (sc->Check("usesourcedecalalpha")) {
741 bp->AlphaMin = bp->AlphaMax = bp->AlphaValue = -1.0f;
742 continue;
745 #if 0
746 // base decal
747 if (sc->Check("basedecal")) {
748 sc->ExpectString();
749 VStr bdbname = sc->String;
750 if (bdbname.isEmpty() || bdbname.strEquCI("none")) {
751 sc->Expect("{");
752 sc->SkipBracketed(true/*bracketEaten*/);
753 continue;
755 if (!wasDecalName) sc->Error("decal name should be defined");
756 if (bp->DecalName == NAME_None || VStr::strEquCI(*bp->DecalName, "none")) {
757 sc->Expect("{");
758 sc->SkipBracketed(true/*bracketEaten*/);
759 continue;
761 if (bdbname.strEquCI(*bp->DecalName)) sc->Error("base decal name should not be equal to decal name");
762 //GCon->Logf(NAME_Debug, "creating decal '%s' from base decal '%s'", *bp->DecalName, *bdbname);
763 VDecalDef::CreateFromBaseDecal(sc, VName(*bdbname), bp->DecalName);
764 continue;
766 #endif
768 sc->Error(va("Unknown bootprint command (%s)", *sc->String));
773 //==========================================================================
775 // CheckTerrainKW
777 // returns:
778 // 0: nope
779 // 1: terrain definition
780 // 2: default terrain definition
782 //==========================================================================
783 static int CheckTerrainKW (VScriptParser *sc) {
784 if (sc->Check("terrain")) return 1;
785 return (sc->Check("defaultterrain") ? 2 : 0);
789 //==========================================================================
791 // ParseTerrainScript
793 //==========================================================================
794 static void ParseTerrainScript (VScriptParser *sc) {
795 GCon->Logf(NAME_Init, "parsing terrain script '%s'", *sc->GetScriptName());
796 bool insideIf = false;
797 int tkw;
798 while (!sc->AtEnd()) {
799 const VTextLocation loc = sc->GetLoc();
801 // splash definition?
802 if (sc->Check("splash")) {
803 ParseTerrainSplashDef(sc);
804 continue;
807 // terrain definition?
808 if ((tkw = CheckTerrainKW(sc)) != 0) {
809 ParseTerrainTerrainDef(sc, tkw);
810 continue;
813 // bootprint definition?
814 if (sc->Check("bootprint")) {
815 ParseTerrainBootPrintDef(sc);
816 continue;
819 // floor detection definition?
820 if (sc->Check("floor")) {
821 const VStr slocstr = sc->GetLoc().toStringNoCol();
822 sc->Check("optional"); // ignore it
823 sc->ExpectName8Warn();
824 VName floorname = sc->Name8;
825 sc->ExpectString();
826 VStr tername = sc->String;
827 if (tername.isEmpty()) tername = "none"; //k8:???
828 //GCon->Logf(NAME_Debug, "::: <%s> -- <%s>", *floorname, *tername);
829 int Pic = GTextureManager.CheckNumForName(floorname, TEXTYPE_Flat, /*false*/true); // allow overloads
830 if (Pic == 0) {
831 GCon->Logf(NAME_Warning, "%s: cannot assign NULL floor texture '%s' for terrain '%s'", *slocstr, *floorname, *tername);
832 continue;
834 if (Pic < 0) {
835 GCon->Logf(NAME_Warning, "%s: ignored unknown floor texture '%s' for terrain '%s'", *slocstr, *floorname, *tername);
836 continue;
838 Pic = GTextureManager.CheckNumForName(floorname, TEXTYPE_Wall, false);
839 if (Pic == 0) { GCon->Logf(NAME_Warning, "%s: cannot assign NULL wall texture '%s' for terrain '%s'", *slocstr, *floorname, *tername); continue; }
840 if (Pic < 0) {
841 Pic = GTextureManager.CheckNumForName(floorname, TEXTYPE_WallPatch, false);
842 if (Pic == 0) { GCon->Logf(NAME_Warning, "%s: cannot assign NULL patch texture '%s' for terrain '%s'", *slocstr, *floorname, *tername); continue; }
843 if (Pic < 0) { GCon->Logf(NAME_Warning, "%s: ignored unknown floor texture '%s' for terrain '%s'", *slocstr, *floorname, *tername); continue; }
844 GCon->Logf(NAME_Init, "%s; assigning patch texture '%s' to terrain '%s'", *slocstr, *floorname, *tername);
845 } else {
846 GCon->Logf(NAME_Init, "%s; assigning wall texture '%s' to terrain '%s'", *slocstr, *floorname, *tername);
849 } else {
850 VTexture *tex = GTextureManager[Pic];
851 if (!tex) {
852 GCon->Logf(NAME_Warning, "%s: ignored unknown floor texture '%s' for terrain '%s'", *slocstr, *floorname, *tername);
853 continue;
855 bool invalid = false;
856 const char *stn = "wtf?";
857 switch (tex->Type) {
858 case TEXTYPE_WallPatch: stn = "patch"; break;
859 case TEXTYPE_Wall: stn = "wall"; break;
860 case TEXTYPE_Flat: stn = nullptr; break;
861 case TEXTYPE_Overload: stn = nullptr/*"overload"*/; break;
862 case TEXTYPE_Sprite: stn = "sprite"; invalid = true; break;
863 case TEXTYPE_SkyMap: stn = "skymap"; invalid = true; break;
864 case TEXTYPE_Skin: stn = "skin"; invalid = true; break;
865 case TEXTYPE_Pic: stn = "pic"; invalid = true; break;
866 case TEXTYPE_Autopage: stn = "autopage"; invalid = true; break;
867 case TEXTYPE_Null: stn = "null"; invalid = true; break;
868 case TEXTYPE_FontChar: stn = "font"; invalid = true; break;
869 default: break;
871 if (invalid) {
872 GCon->Logf(NAME_Warning, "%s: cannot assign %s floor texture '%s' for terrain '%s'", *slocstr, stn, *floorname, *tername);
873 continue;
875 if (stn) {
876 GCon->Logf(NAME_Init, "%s: assigning %s texture '%s' for terrain '%s'", *slocstr, stn, *floorname, *tername);
879 auto pp = TerrainTypeMap.get(Pic);
880 if (pp) {
881 // replace old one
882 if (GlobalDisableOverride && IsTerrainNoOverride(VStr(TerrainTypes[*pp].TypeName))) {
883 GCon->Logf(NAME_Init, "%s: skipped floor '%s' override to '%s' due to 'GlobalDisableOverride'", *sc->GetLoc().toStringNoCol(), *floorname, *tername);
884 } else {
885 TerrainTypes[*pp].TypeName = VName(*tername, VName::AddLower);
887 } else {
888 //!GCon->Logf(NAME_Init, "%s: new terrain floor '%s' of type '%s' (pic=%d)", *sc->GetLoc().toStringNoCol(), *floorname, *tername, Pic);
889 VTerrainType &T = TerrainTypes.Alloc();
890 T.Pic = Pic;
891 T.TypeName = VName(*tername, VName::AddLower);
892 TerrainTypeMap.put(Pic, TerrainTypes.length()-1);
894 continue;
897 // floor detection by globmask definition?
898 if (sc->Check("floorglob")) {
899 GCon->Logf(NAME_Warning, "%s: \"floorglob\" is obsolete, please, use \"assign_terrain\" instead!", *loc.toStringNoCol());
900 sc->ExpectString();
901 VStr mask = sc->String.xstrip();
902 sc->ExpectString();
903 VStr tername = sc->String;
904 ProcessFlatGlobMask(mask, *tername, sc->GetLoc().toStringNoCol());
905 continue;
908 // floor detection by globmask definition? (k8vavoom extension)
909 if (sc->Check("assign_terrain")) {
910 sc->ExpectString();
911 VStr tername = sc->String;
912 sc->Expect("{");
913 while (!sc->Check("}")) {
914 if (sc->AtEnd()) sc->Error(va("unexpected end of file in \"assign_terrain\" (started at %s)", *loc.toStringNoCol()));
915 sc->Expect("flat");
916 sc->ExpectString();
917 VStr mask = sc->String.xstrip();
918 ProcessFlatGlobMask(mask, *tername, sc->GetLoc().toStringNoCol());
920 continue;
923 // floor detection by globmask definition? (k8vavoom extension)
924 if (sc->Check("assign_bootprint")) {
925 sc->ExpectString();
926 VStr tername = sc->String;
927 VTerrainBootprint *bp = FindBootprint(*tername, false);
928 vassert(bp); // invariant
929 sc->Expect("{");
930 while (!sc->Check("}")) {
931 if (sc->AtEnd()) sc->Error(va("unexpected end of file in \"assign_terrain\" (started at %s)", *loc.toStringNoCol()));
932 sc->Expect("flat");
933 sc->ExpectString();
934 VStr mask = sc->String.xstrip();
935 ProcessAssignBootprint(bp, mask);
937 continue;
940 // endif?
941 if (sc->Check("endif")) {
942 if (insideIf) {
943 insideIf = false;
944 } else {
945 GCon->Logf(NAME_Warning, "%s: stray `endif` in terrain script", *loc.toStringNoCol());
947 continue;
950 // ifdefs?
951 if (sc->Check("ifdoom") || sc->Check("ifheretic") ||
952 sc->Check("ifhexen") || sc->Check("ifstrife"))
954 if (insideIf) {
955 sc->Error(va("nested conditionals are not allowed (%s)", *sc->String));
957 //GCon->Logf(NAME_Warning, "%s: k8vavoom doesn't support conditional game commands in terrain script", *loc.toStringNoCol());
958 VStr gmname = VStr(*sc->String+2);
959 if (game_name.asStr().startsWithCI(gmname)) {
960 insideIf = true;
961 GCon->Logf(NAME_Init, "%s: processing conditional section '%s' in terrain script", *loc.toStringNoCol(), *gmname);
962 } else {
963 // skip lines until we hit `endif`
964 GCon->Logf(NAME_Init, "%s: skipping conditional section '%s' in terrain script", *loc.toStringNoCol(), *gmname);
965 while (sc->GetString()) {
966 if (sc->Crossed) {
967 if (sc->String.strEqu("endif")) {
968 //GCon->Logf(NAME_Init, "******************** FOUND ENDIF!");
969 break;
974 continue;
977 // k8vavoom options?
978 if (sc->Check("k8vavoom")) {
979 sc->Expect("{");
980 while (!sc->Check("}")) {
981 if (sc->AtEnd()) sc->Expect("}");
982 if (sc->Check("GlobalDisableOverride")) { GlobalDisableOverride = true; continue; }
983 if (sc->Check("GlobalEnableOverride")) { GlobalDisableOverride = false; continue; }
984 sc->Error(va("Unknown k8vavoom global command (%s)", *sc->String));
986 continue;
989 sc->Error(va("Unknown command (%s)", *sc->String));
991 delete sc;
995 //==========================================================================
997 // P_InitTerrainTypes
999 //==========================================================================
1000 void P_InitTerrainTypes () {
1001 // create default terrain
1002 VTerrainInfo &DefT = TerrainInfos.Alloc();
1003 VName nn = VName("Solid", VName::AddLower);
1004 TerrainMap.put(nn, TerrainInfos.length()-1);
1005 DefT.Name = nn;
1006 DefT.OrigName = "Solid";
1007 DefT.Splash = NAME_None;
1008 DefT.Flags = 0;
1009 DefT.FootClip = 0.0f;
1010 DefT.DamageTimeMask = 0;
1011 DefT.DamageAmount = 0;
1012 DefT.DamageType = NAME_None;
1013 DefT.Friction = 0.0f;
1014 DefT.MoveFactor = 0.0f;
1015 DefT.WalkingStepVolume = 1.0f;
1016 DefT.RunningStepVolume = 1.0f;
1017 DefT.CrouchingStepVolume = 1.0f;
1018 DefT.WalkingStepTime = 0.0f;
1019 DefT.RunningStepTime = 0.0f;
1020 DefT.CrouchingStepTime = 0.0f;
1021 DefT.LeftStepSound = NAME_None;
1022 DefT.RightStepSound = NAME_None;
1023 DefT.LandVolume = 1.0f;
1024 DefT.LandSound = NAME_None;
1025 DefT.SmallLandVolume = 1.0f;
1026 DefT.SmallLandSound = NAME_None;
1027 DefT.BootPrint = nullptr;
1029 DefaultTerrainName = DefT.Name;
1030 DefaultTerrainNameStr = DefT.OrigName;
1031 DefaultTerrainIndex = 0;
1033 for (auto &&it : WadNSNameIterator(NAME_terrain, WADNS_Global)) {
1034 const int Lump = it.lump;
1035 GlobalDisableOverride = false;
1036 ParseTerrainScript(VScriptParser::NewWithLump(Lump));
1038 for (auto &&it : WadNSNameIterator(NAME_vterrain, WADNS_Global)) {
1039 const int Lump = it.lump;
1040 GlobalDisableOverride = false;
1041 ParseTerrainScript(VScriptParser::NewWithLump(Lump));
1043 GCon->Logf(NAME_Init, "got %d terrain definition%s", TerrainInfos.length(), (TerrainInfos.length() != 1 ? "s" : ""));
1045 // fix default terrain name (why not?)
1047 vassert(DefaultTerrainName != NAME_None);
1048 auto spp = TerrainMap.get(DefaultTerrainName);
1049 if (!spp) {
1050 GCon->Logf(NAME_Warning, "unknown default terrain '%s', defaulted to '%s'", *DefaultTerrainNameStr, *TerrainInfos[0].Name);
1051 DefaultTerrainName = TerrainInfos[0].Name;
1052 DefaultTerrainNameStr = TerrainInfos[0].OrigName;
1053 DefaultTerrainIndex = 0;
1054 } else {
1055 GCon->Logf(NAME_Init, "default terrain is '%s' (%d)", *DefaultTerrainNameStr, *spp);
1056 DefaultTerrainIndex = *spp;
1057 vassert(DefaultTerrainIndex >= 0 && DefaultTerrainIndex < TerrainInfos.length());
1061 // setup terrain type pointers
1062 for (auto &&ttinf : TerrainTypes) {
1063 ttinf.Info = GetTerrainInfo(*ttinf.TypeName);
1064 if (!ttinf.Info) {
1065 GCon->Logf(NAME_Warning, "unknown terrain type '%s' for texture '%s'", *ttinf.TypeName, (ttinf.Pic >= 0 ? *GTextureManager[ttinf.Pic]->Name : "<notexture>"));
1066 ttinf.Info = &TerrainInfos[DefaultTerrainIndex]; // default one
1067 } else {
1068 //GCon->Logf(NAME_Warning, "set terrain type '%s' (%s) for texture '%s'", *ttinf.TypeName, *ttinf.Info->Name, (ttinf.Pic >= 0 ? *GTextureManager[ttinf.Pic]->Name : "<notexture>"));
1072 // check bootprints
1073 bool wasErrors = false;
1074 for (auto &&ti : TerrainInfos) {
1075 if (ti.BootPrint && ti.BootPrint->Name == NAME_None) {
1076 wasErrors = true;
1077 GCon->Logf(NAME_Error, "terrain '%s' references unknown bootprint '%s'", *ti.OrigName, *ti.BootPrint->OrigName);
1080 if (wasErrors) Sys_Error("error(s) in terrain definitions");
1082 for (auto &&it : TerrainBootprintMap.first()) {
1083 VTerrainBootprint *bp = it.value();
1084 if (bp->Name == NAME_None) {
1085 wasErrors = true;
1086 GCon->Logf(NAME_Error, "texture '%s' referenced unknown bootprint '%s'", *GTextureManager.GetTextureName(it.key()), *bp->OrigName);
1089 if (wasErrors) Sys_Error("error(s) in bootprint definitions");
1091 // build name map
1092 for (VTerrainBootprint *bp : TerrainBootprints) {
1093 if (bp->Name != NAME_None) {
1094 TerrainBootprintNameMap.put(VStrCI(*bp->Name), bp);
1098 NoOverrideSplash.clear();
1099 NoOverrideTerrain.clear();
1100 NoOverrideBootprint.clear();
1104 //==========================================================================
1106 // SV_TerrainType
1108 //==========================================================================
1109 VTerrainInfo *SV_TerrainType (int pic, bool asPlayer) {
1110 if (pic <= 0) return &TerrainInfos[DefaultTerrainIndex];
1111 auto pp = TerrainTypeMap.get(pic);
1112 VTerrainInfo *ter = (pp ? TerrainTypes[*pp].Info : nullptr);
1113 if (ter && !asPlayer && (ter->Flags&VTerrainInfo::F_PlayerOnly)) ter = nullptr;
1114 if (ter && (ter->Flags&VTerrainInfo::F_OptOut) && !gm_optional_terrain.asBool()) ter = nullptr;
1115 return (ter ? ter : &TerrainInfos[DefaultTerrainIndex]);
1119 //==========================================================================
1121 // SV_TerrainBootprint
1123 //==========================================================================
1124 VTerrainBootprint *SV_TerrainBootprint (int pic) {
1125 if (pic <= 0) return nullptr;
1126 auto pp = TerrainBootprintMap.get(pic);
1127 if (pp) return *pp;
1128 // try terrain
1129 VTerrainInfo *ter = SV_TerrainType(pic, true/*asPlayer*/); // this is used ONLY for player
1130 if (ter) return ter->BootPrint;
1131 return nullptr;
1135 //==========================================================================
1137 // SV_FindBootprintByName
1139 //==========================================================================
1140 VTerrainBootprint *SV_FindBootprintByName (const char *name) {
1141 if (!name || !name[0] || VStr::strEquCI(name, "none")) return nullptr;
1142 auto bpp = TerrainBootprintNameMap.get(name);
1143 return (bpp ? *bpp : nullptr);
1147 //==========================================================================
1149 // SV_GetDefaultTerrain
1151 //==========================================================================
1152 VTerrainInfo *SV_GetDefaultTerrain () {
1153 return &TerrainInfos[DefaultTerrainIndex];
1157 //==========================================================================
1159 // P_FreeTerrainTypes
1161 //==========================================================================
1162 void P_FreeTerrainTypes () {
1163 SplashInfos.Clear();
1164 TerrainInfos.Clear();
1165 TerrainTypes.Clear();
1166 SplashMap.clear();
1167 TerrainMap.clear();
1168 TerrainTypeMap.clear();
1169 for (auto &&bp : TerrainBootprints) { delete bp; bp = nullptr; }
1170 TerrainBootprints.clear();
1171 TerrainBootprintMap.clear();
1172 TerrainBootprintNameMap.clear();
1176 // ////////////////////////////////////////////////////////////////////////// //
1177 IMPLEMENT_FREE_FUNCTION(VObject, GetSplashInfo) {
1178 VName Name;
1179 vobjGetParam(Name);
1180 if (Name == NAME_None) RET_PTR(nullptr); else RET_PTR(GetSplashInfo(*Name));
1183 IMPLEMENT_FREE_FUNCTION(VObject, GetTerrainInfo) {
1184 VName Name;
1185 vobjGetParam(Name);
1186 if (Name == NAME_None) RET_PTR(nullptr); else RET_PTR(GetTerrainInfo(*Name));