1 //**************************************************************************
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
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.
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.
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/>.
25 //**************************************************************************
29 //**************************************************************************
30 #include "../gamedefs.h"
34 static VCvarB
gm_optional_terrain("gm_optional_terrain", true, "Allow liquid sounds for Vanilla Doom?", CVAR_Archive
);
40 // it is safe to use pointer here, becasuse it is set after all
41 // terrain data is loaded, and the pointer is immutable
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 //==========================================================================
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 //==========================================================================
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];
147 //==========================================================================
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 //==========================================================================
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
);
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
);
199 if (tn
== NAME_None
) continue;
202 case TEXTYPE_Any
: //FIXME: dunno
203 case TEXTYPE_WallPatch
:
206 case TEXTYPE_Overload
:
212 //GCon->Logf(NAME_Debug, " rejected texture '%s'", *tn);
215 if (!VStr::globMatchCI(*tn
, *mask
)) {
216 //GCon->Logf(NAME_Debug, " skipped texture '%s'", *tn);
219 //GCon->Logf(NAME_Debug, " matched texture '%s'", *tn);
222 //WARNING! memory leak on deletion!
223 TerrainTypeMap
.del(pic
);
226 auto pp
= TerrainTypeMap
.get(pic
);
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
);
232 TerrainTypes
[*pp
].TypeName
= VName(tername
);
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();
238 T
.TypeName
= VName(tername
);
239 TerrainTypeMap
.put(pic
, TerrainTypes
.length()-1);
245 //==========================================================================
247 // ParseTerrainSplashClassName
249 //==========================================================================
250 static VClass
*ParseTerrainSplashClassName (VScriptParser
*sc
) {
252 if (sc
->String
.isEmpty() || sc
->String
.strEquCI("none")) return nullptr;
253 VClass
*cls
= VClass::FindClass(*sc
->String
);
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
));
264 //==========================================================================
266 // ParseTerrainSplashDef
268 // "splash" is already eaten
270 //==========================================================================
271 static void ParseTerrainSplashDef (VScriptParser
*sc
) {
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
);
278 sc
->SkipBracketed(true/*bracketEaten*/);
282 VSplashInfo
*SInfo
= GetSplashInfo(*sc
->String
);
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();
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
;
303 if (GlobalDisableOverride
) AddSplashNoOverride(SInfo
->OrigName
);
306 while (!sc
->Check("}")) {
307 if (sc
->AtEnd()) break;
308 if (sc
->Check("smallclass")) {
309 SInfo
->SmallClass
= ParseTerrainSplashClassName(sc
);
312 if (sc
->Check("smallclip")) {
314 SInfo
->SmallClip
= sc
->Float
;
317 if (sc
->Check("smallsound")) {
319 SInfo
->SmallSound
= *sc
->String
;
322 if (sc
->Check("baseclass")) {
323 SInfo
->BaseClass
= ParseTerrainSplashClassName(sc
);
326 if (sc
->Check("chunkclass")) {
327 SInfo
->ChunkClass
= ParseTerrainSplashClassName(sc
);
330 if (sc
->Check("chunkxvelshift")) {
332 SInfo
->ChunkXVelMul
= sc
->Number
< 0 ? 0.0f
: float((1<<sc
->Number
)/256);
335 if (sc
->Check("chunkyvelshift")) {
337 SInfo
->ChunkYVelMul
= sc
->Number
< 0 ? 0.0f
: float((1<<sc
->Number
)/256);
340 if (sc
->Check("chunkzvelshift")) {
342 SInfo
->ChunkZVelMul
= sc
->Number
< 0 ? 0.0f
: float((1<<sc
->Number
)/256);
345 if (sc
->Check("chunkbasezvel")) {
347 SInfo
->ChunkBaseZVel
= sc
->Float
;
350 if (sc
->Check("sound")) {
352 SInfo
->Sound
= *sc
->String
;
355 if (sc
->Check("noalert")) {
356 SInfo
->Flags
|= VSplashInfo::F_NoAlert
;
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
) {
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
);
379 if (sc
->Check("{")) sc
->SkipBracketed(true/*bracketEaten*/);
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;
392 if (sc
->Check("modify")) modify
= true;
394 //GCon->Logf(NAME_Debug, "terraindef '%s'...", *TerName);
396 TInfo
= GetTerrainInfo(*TerName
);
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
));
403 TInfo
->OrigName
= TerName
;
404 TerrainMap
.put(nn
, TerrainInfos
.length()-1);
405 modify
= false; // clear it
408 if (GlobalDisableOverride
) AddTerrainNoOverride(TInfo
->OrigName
);
412 GCon->Logf(NAME_Debug, "%s: %s terrain '%s'", *sc->GetLoc().toStringNoCol(), (modify ? "modifying" : "redefining"), *TerName);
417 TInfo
->Splash
= NAME_None
;
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;
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")) {
446 TInfo
->Splash
= *sc
->String
;
449 if (sc
->Check("liquid")) {
450 TInfo
->Flags
|= VTerrainInfo::F_Liquid
;
453 if (sc
->Check("footclip")) {
455 TInfo
->FootClip
= sc
->Float
;
458 if (sc
->Check("damagetimemask")) {
460 TInfo
->DamageTimeMask
= sc
->Number
;
463 if (sc
->Check("damageamount")) {
465 TInfo
->DamageAmount
= sc
->Number
;
468 if (sc
->Check("damagetype")) {
470 TInfo
->DamageType
= *sc
->String
;
473 if (sc
->Check("damageonland")) {
474 TInfo
->Flags
|= VTerrainInfo::F_DamageOnLand
;
477 if (sc
->Check("friction")) {
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) {
489 movefactor
= ((0x10092-friction
)*1024)/4352+568;
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);
500 if (sc
->Check("allowprotection")) {
501 TInfo
->Flags
|= VTerrainInfo::F_AllowProtection
;
504 // k8vavoom extensions
505 if (sc
->Check("k8vavoom")) {
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")) {
518 TInfo
->BootPrint
= FindBootprint(*sc
->String
, true);
521 if (sc
->Check("detectfloorflat")) { // k8vavoom extension
523 ProcessFlatGlobMask(sc
->String
, *TInfo
->Name
, sc
->GetLoc().toStringNoCol());
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; }
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
));
549 sc
->Error(va("Unknown terrain command (%s)", *sc
->String
));
554 //==========================================================================
556 // ProcessAssignBootprint
558 //==========================================================================
559 static void ProcessAssignBootprint (VTerrainBootprint
*bp
, VStr mask
) {
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
);
567 if (tn
== NAME_None
) continue;
570 case TEXTYPE_Any
: //FIXME: dunno
571 case TEXTYPE_WallPatch
:
574 case TEXTYPE_Overload
:
579 if (!goodtx
) continue;
580 if (!VStr::globMatchCI(*tn
, *mask
)) continue;
582 TerrainBootprintMap
.put(pic
, bp
);
587 //==========================================================================
589 // ParseTerrainBootPrintDef
591 // "bootprint" is already eaten
593 //==========================================================================
594 static void ParseTerrainBootPrintDef (VScriptParser
*sc
) {
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
);
602 if (sc
->Check("{")) sc
->SkipBracketed(true/*bracketEaten*/);
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
;
613 if (sc
->Check("modify")) {
614 doclear
= (bp
->Name
== NAME_None
);
616 // remove from bootprint map
617 if (bp
->Name
!= NAME_None
) {
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
);
628 bp
->TimeMin
= BOOT_TIME_MIN
;
629 bp
->TimeMax
= BOOT_TIME_MAX
;
630 bp
->AlphaMin
= bp
->AlphaMax
= bp
->AlphaValue
= 1.0f
;
633 bp
->Animator
= NAME_None
;
637 if (GlobalDisableOverride
) AddBootprintNoOverride(bp
->OrigName
);
640 //bool wasDecalName = false;
641 while (!sc
->Check("}")) {
642 if (sc
->AtEnd()) sc
->Error(va("unfinished bootprint '%s' definition", *bp
->OrigName
));
646 if (sc->Check("decal")) {
647 if (wasDecalName) sc->Error("duplicate decal name");
650 bp->DecalName = (sc->String.isEmpty() ? NAME_None : VName(*sc->String));
656 if (sc
->Check("time")) {
657 if (sc
->Check("default")) {
658 bp
->TimeMin
= BOOT_TIME_MIN
;
659 bp
->TimeMax
= BOOT_TIME_MAX
;
662 float tmin
= sc
->Float
;
664 float tmax
= sc
->Float
;
665 if (tmin
> tmax
) { const float tt
= tmin
; tmin
= tmax
; tmax
= tt
; }
673 if (sc
->Check("flat")) {
675 VStr mask
= sc
->String
.xstrip();
676 ProcessAssignBootprint(bp
, mask
);
680 if (sc
->Check("optional")) {
685 if (sc
->Check("required")) {
690 if (sc
->Check("shade")) {
691 if (sc
->Check("flatpic")) {
692 bp
->ShadeColor
= 0xed000000;
693 if (sc
->Check("maxout")) {
695 bp
->ShadeColor
|= clampToByte(sc
->Number
);
700 } else if (sc
->Check("fromdecal")) {
704 vuint32 ppc
= M_ParseColor(*sc
->String
);
705 bp
->ShadeColor
= ppc
&0xffffff;
710 if (sc
->Check("animator")) {
712 if (sc
->String
.isEmpty()) {
713 bp
->Animator
= VName("none");
715 bp
->Animator
= VName(*sc
->String
);
720 if (sc
->Check("translucent")) {
721 if (sc
->Check("random")) {
724 bp
->AlphaMin
= clampval(sc
->Float
, 0.0f
, 1.0f
);
726 bp
->AlphaMax
= clampval(sc
->Float
, 0.0f
, 1.0f
);
727 bp
->AlphaValue
= FRandomBetween(bp
->AlphaMin
, bp
->AlphaMax
);
730 bp
->AlphaMin
= bp
->AlphaMax
= bp
->AlphaValue
= clampval(sc
->Float
, 0.0f
, 1.0f
);
735 if (sc
->Check("solid")) {
736 bp
->AlphaMin
= bp
->AlphaMax
= bp
->AlphaValue
= 1.0f
;
740 if (sc
->Check("usesourcedecalalpha")) {
741 bp
->AlphaMin
= bp
->AlphaMax
= bp
->AlphaValue
= -1.0f
;
747 if (sc
->Check("basedecal")) {
749 VStr bdbname
= sc
->String
;
750 if (bdbname
.isEmpty() || bdbname
.strEquCI("none")) {
752 sc
->SkipBracketed(true/*bracketEaten*/);
755 if (!wasDecalName
) sc
->Error("decal name should be defined");
756 if (bp
->DecalName
== NAME_None
|| VStr::strEquCI(*bp
->DecalName
, "none")) {
758 sc
->SkipBracketed(true/*bracketEaten*/);
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
);
768 sc
->Error(va("Unknown bootprint command (%s)", *sc
->String
));
773 //==========================================================================
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;
798 while (!sc
->AtEnd()) {
799 const VTextLocation loc
= sc
->GetLoc();
801 // splash definition?
802 if (sc
->Check("splash")) {
803 ParseTerrainSplashDef(sc
);
807 // terrain definition?
808 if ((tkw
= CheckTerrainKW(sc
)) != 0) {
809 ParseTerrainTerrainDef(sc
, tkw
);
813 // bootprint definition?
814 if (sc
->Check("bootprint")) {
815 ParseTerrainBootPrintDef(sc
);
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
;
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
831 GCon
->Logf(NAME_Warning
, "%s: cannot assign NULL floor texture '%s' for terrain '%s'", *slocstr
, *floorname
, *tername
);
835 GCon
->Logf(NAME_Warning
, "%s: ignored unknown floor texture '%s' for terrain '%s'", *slocstr
, *floorname
, *tername
);
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; }
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);
846 GCon->Logf(NAME_Init, "%s; assigning wall texture '%s' to terrain '%s'", *slocstr, *floorname, *tername);
850 VTexture
*tex
= GTextureManager
[Pic
];
852 GCon
->Logf(NAME_Warning
, "%s: ignored unknown floor texture '%s' for terrain '%s'", *slocstr
, *floorname
, *tername
);
855 bool invalid
= false;
856 const char *stn
= "wtf?";
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;
872 GCon
->Logf(NAME_Warning
, "%s: cannot assign %s floor texture '%s' for terrain '%s'", *slocstr
, stn
, *floorname
, *tername
);
876 GCon
->Logf(NAME_Init
, "%s: assigning %s texture '%s' for terrain '%s'", *slocstr
, stn
, *floorname
, *tername
);
879 auto pp
= TerrainTypeMap
.get(Pic
);
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
);
885 TerrainTypes
[*pp
].TypeName
= VName(*tername
, VName::AddLower
);
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();
891 T
.TypeName
= VName(*tername
, VName::AddLower
);
892 TerrainTypeMap
.put(Pic
, TerrainTypes
.length()-1);
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());
901 VStr mask
= sc
->String
.xstrip();
903 VStr tername
= sc
->String
;
904 ProcessFlatGlobMask(mask
, *tername
, sc
->GetLoc().toStringNoCol());
908 // floor detection by globmask definition? (k8vavoom extension)
909 if (sc
->Check("assign_terrain")) {
911 VStr tername
= sc
->String
;
913 while (!sc
->Check("}")) {
914 if (sc
->AtEnd()) sc
->Error(va("unexpected end of file in \"assign_terrain\" (started at %s)", *loc
.toStringNoCol()));
917 VStr mask
= sc
->String
.xstrip();
918 ProcessFlatGlobMask(mask
, *tername
, sc
->GetLoc().toStringNoCol());
923 // floor detection by globmask definition? (k8vavoom extension)
924 if (sc
->Check("assign_bootprint")) {
926 VStr tername
= sc
->String
;
927 VTerrainBootprint
*bp
= FindBootprint(*tername
, false);
928 vassert(bp
); // invariant
930 while (!sc
->Check("}")) {
931 if (sc
->AtEnd()) sc
->Error(va("unexpected end of file in \"assign_terrain\" (started at %s)", *loc
.toStringNoCol()));
934 VStr mask
= sc
->String
.xstrip();
935 ProcessAssignBootprint(bp
, mask
);
941 if (sc
->Check("endif")) {
945 GCon
->Logf(NAME_Warning
, "%s: stray `endif` in terrain script", *loc
.toStringNoCol());
951 if (sc
->Check("ifdoom") || sc
->Check("ifheretic") ||
952 sc
->Check("ifhexen") || sc
->Check("ifstrife"))
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
)) {
961 GCon
->Logf(NAME_Init
, "%s: processing conditional section '%s' in terrain script", *loc
.toStringNoCol(), *gmname
);
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()) {
967 if (sc
->String
.strEqu("endif")) {
968 //GCon->Logf(NAME_Init, "******************** FOUND ENDIF!");
978 if (sc
->Check("k8vavoom")) {
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
));
989 sc
->Error(va("Unknown command (%s)", *sc
->String
));
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);
1006 DefT
.OrigName
= "Solid";
1007 DefT
.Splash
= NAME_None
;
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
);
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;
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
);
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
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>"));
1073 bool wasErrors
= false;
1074 for (auto &&ti
: TerrainInfos
) {
1075 if (ti
.BootPrint
&& ti
.BootPrint
->Name
== NAME_None
) {
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
) {
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");
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 //==========================================================================
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
);
1129 VTerrainInfo
*ter
= SV_TerrainType(pic
, true/*asPlayer*/); // this is used ONLY for player
1130 if (ter
) return ter
->BootPrint
;
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();
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
) {
1180 if (Name
== NAME_None
) RET_PTR(nullptr); else RET_PTR(GetSplashInfo(*Name
));
1183 IMPLEMENT_FREE_FUNCTION(VObject
, GetTerrainInfo
) {
1186 if (Name
== NAME_None
) RET_PTR(nullptr); else RET_PTR(GetTerrainInfo(*Name
));