1 /** @file def_main.cpp  Definition subsystem.
2  *
3  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2005-2015 Daniel Swanson <danij@dengine.net>
5  * @authors Copyright © 2006 Jamie Jones <jamie_jones_au@yahoo.com.au>
6  *
7  * @par License
8  * GPL: http://www.gnu.org/licenses/gpl.html
9  *
10  * <small>This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by the
12  * Free Software Foundation; either version 2 of the License, or (at your
13  * option) any later version. This program is distributed in the hope that it
14  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
15  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
16  * Public License for more details. You should have received a copy of the GNU
17  * General Public License along with this program; if not, write to the Free
18  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19  * 02110-1301 USA</small>
20  */
21 
22 #define DENG_NO_API_MACROS_DEFINITIONS
23 
24 #include "def_main.h"
25 
26 #include <cctype>
27 #include <cstring>
28 #include <QMap>
29 #include <QTextStream>
30 #include <de/findfile.h>
31 #include <de/c_wrapper.h>
32 #include <de/App>
33 #include <de/PackageLoader>
34 #include <de/ScriptSystem>
35 #include <de/NativePath>
36 #include <de/RecordValue>
37 #include <doomsday/doomsdayapp.h>
38 #include <doomsday/console/cmd.h>
39 #include <doomsday/defs/decoration.h>
40 #include <doomsday/defs/dedfile.h>
41 #include <doomsday/defs/dedparser.h>
42 #include <doomsday/defs/material.h>
43 #include <doomsday/defs/sky.h>
44 #include <doomsday/defs/state.h>
45 #include <doomsday/filesys/fs_main.h>
46 #include <doomsday/filesys/fs_util.h>
47 #include <doomsday/resource/manifest.h>
48 #include <doomsday/resource/animgroups.h>
49 #include <doomsday/res/Bundles>
50 #include <doomsday/res/DoomsdayPackage>
51 #include <doomsday/res/Textures>
52 #include <doomsday/world/Materials>
53 #include <doomsday/world/MaterialManifest>
54 #include <doomsday/world/xg.h>
55 
56 #include "dd_main.h"
57 #include "dd_def.h"
58 
59 #include "api_def.h"
60 #include "api_sound.h"
61 
62 #include "Generator"
63 #ifdef __CLIENT__
64 #  include "render/rend_particle.h"
65 #endif
66 
67 #include <doomsday/world/detailtexturemateriallayer.h>
68 #include <doomsday/world/shinetexturemateriallayer.h>
69 #include <doomsday/world/texturemateriallayer.h>
70 #ifdef __CLIENT__
71 #  include "resource/lightmaterialdecoration.h"
72 #endif
73 
74 using namespace de;
75 
76 #define LOOPi(n)    for (i = 0; i < (n); ++i)
77 #define LOOPk(n)    for (k = 0; k < (n); ++k)
78 
79 RuntimeDefs runtimeDefs;
80 
81 static bool defsInited;
82 static mobjinfo_t *gettingFor;
83 static Binder *defsBinder;
84 
fileSys()85 static inline FS1 &fileSys()
86 {
87     return App_FileSystem();
88 }
89 
clear()90 void RuntimeDefs::clear()
91 {
92     for (dint i = 0; i < sounds.size(); ++i)
93     {
94         Str_Free(&sounds[i].external);
95     }
96     sounds.clear();
97 
98     mobjInfo.clear();
99     states.clear();
100     texts.clear();
101     stateInfo.clear();
102 }
103 
Function_Defs_GetSoundNum(Context &,const Function::ArgumentValues & args)104 static Value *Function_Defs_GetSoundNum(Context &, const Function::ArgumentValues &args)
105 {
106     return new NumberValue(DED_Definitions()->getSoundNum(args.at(0)->asText()));
107 }
108 
Def_Init()109 void Def_Init()
110 {
111     ::runtimeDefs.clear();
112     DED_Definitions()->clear();
113 
114     auto &defs = *DED_Definitions();
115 
116     // Make the definitions visible in the global namespace.
117     if (!defsBinder)
118     {
119         auto &scr = ScriptSystem::get();
120         scr.addNativeModule("Defs", defs.names);
121 
122         /// TODO: Add a DEDRegister for sounds so this lookup is not needed and can be converted
123         /// to a utility script function.
124         defsBinder = new Binder;
125         defsBinder->init(defs.names)
126             << DE_FUNC(Defs_GetSoundNum, "getSoundNum", "name");
127     }
128 
129     // Constants for definitions.
130     DENG2_ADD_NUMBER_CONSTANT(defs.names, SN_SPAWN);
131     DENG2_ADD_NUMBER_CONSTANT(defs.names, SN_SEE);
132     DENG2_ADD_NUMBER_CONSTANT(defs.names, SN_PAIN);
133     DENG2_ADD_NUMBER_CONSTANT(defs.names, SN_MELEE);
134     DENG2_ADD_NUMBER_CONSTANT(defs.names, SN_MISSILE);
135     DENG2_ADD_NUMBER_CONSTANT(defs.names, SN_CRASH);
136     DENG2_ADD_NUMBER_CONSTANT(defs.names, SN_DEATH);
137     DENG2_ADD_NUMBER_CONSTANT(defs.names, SN_XDEATH);
138     DENG2_ADD_NUMBER_CONSTANT(defs.names, SN_RAISE);
139 
140     DENG2_ADD_NUMBER_CONSTANT(defs.names, SDN_ACTIVE);
141     DENG2_ADD_NUMBER_CONSTANT(defs.names, SDN_ATTACK);
142     DENG2_ADD_NUMBER_CONSTANT(defs.names, SDN_DEATH);
143     DENG2_ADD_NUMBER_CONSTANT(defs.names, SDN_PAIN);
144     DENG2_ADD_NUMBER_CONSTANT(defs.names, SDN_SEE);
145 }
146 
Def_Destroy()147 void Def_Destroy()
148 {
149     delete defsBinder;
150     defsBinder = nullptr;
151 
152     App::app().scriptSystem().removeNativeModule("Defs");
153 
154     DED_Definitions()->clear();
155 
156     // Destroy the databases.
157     ::runtimeDefs.clear();
158     DED_DestroyDefinitions();
159 
160     ::defsInited = false;
161 }
162 
Def_GetState(dint num)163 state_t *Def_GetState(dint num)
164 {
165     if (num >= 0 && num < DED_Definitions()->states.size())
166     {
167         return &::runtimeDefs.states[num];
168     }
169     return nullptr;  // Not found.
170 }
171 
Def_GetSoundInfo(dint soundId,dfloat * freq,dfloat * volume)172 sfxinfo_t *Def_GetSoundInfo(dint soundId, dfloat *freq, dfloat *volume)
173 {
174     if (soundId <= 0 || soundId >= DED_Definitions()->sounds.size())
175         return nullptr;
176 
177     dfloat dummy = 0;
178     if (!freq)   freq   = &dummy;
179     if (!volume) volume = &dummy;
180 
181     // Traverse all links when getting the definition. (But only up to 10, which is
182     // certainly enough and prevents endless recursion.) Update the sound id at the
183     // same time. The links were checked in Def_Read() so there cannot be any bogus
184     // ones.
185     sfxinfo_t *info = &::runtimeDefs.sounds[soundId];
186 
187     for (dint i = 0; info->link && i < 10;
188         info     = info->link,
189         *freq    = (info->linkPitch > 0    ? info->linkPitch  / 128.0f : *freq),
190         *volume += (info->linkVolume != -1 ? info->linkVolume / 127.0f : 0),
191         soundId  = ::runtimeDefs.sounds.indexOf(info),
192        ++i)
193     {}
194 
195     DENG2_ASSERT(soundId < DED_Definitions()->sounds.size());
196 
197     return info;
198 }
199 
Def_SoundIsRepeating(dint soundId)200 bool Def_SoundIsRepeating(dint soundId)
201 {
202     if (sfxinfo_t *info = Def_GetSoundInfo(soundId, nullptr, nullptr))
203     {
204         return (info->flags & SF_REPEAT) != 0;
205     }
206     return false;
207 }
208 
Def_GetCompositeFont(char const * uri)209 ded_compositefont_t *Def_GetCompositeFont(char const *uri)
210 {
211     return DED_Definitions()->getCompositeFont(uri);
212 }
213 
214 /// @todo $revise-texture-animation
tryFindReflection(de::Uri const & uri,bool isCustom)215 static ded_reflection_t *tryFindReflection(de::Uri const &uri, /* bool hasExternal,*/ bool isCustom)
216 {
217     for (dint i = DED_Definitions()->reflections.size() - 1; i >= 0; i--)
218     {
219         ded_reflection_t &def = DED_Definitions()->reflections[i];
220         if (!def.material || *def.material !=  uri) continue;
221 
222         /*
223         if (hasExternal)
224         {
225             if (!(def.flags & REFF_EXTERNAL)) continue;
226         }
227         else */
228         if (!isCustom)
229         {
230             if (def.flags & REFF_NO_IWAD) continue;
231         }
232         else
233         {
234             if (!(def.flags & REFF_PWAD)) continue;
235         }
236 
237         return &def;
238     }
239     return nullptr;  // Not found.
240 }
241 
242 /// @todo $revise-texture-animation
tryFindDetailTexture(de::Uri const & uri,bool isCustom)243 static ded_detailtexture_t *tryFindDetailTexture(de::Uri const &uri, /*bool hasExternal,*/ bool isCustom)
244 {
245     for (dint i = DED_Definitions()->details.size() - 1; i >= 0; i--)
246     {
247         ded_detailtexture_t &def = DED_Definitions()->details[i];
248         for (dint k = 0; k < 2; ++k)
249         {
250             de::Uri const *matUri = (k == 0 ? def.material1 : def.material2);
251             if (!matUri || *matUri != uri) continue;
252 
253             /*if (hasExternal)
254             {
255                 if (!(def.flags & DTLF_EXTERNAL)) continue;
256             }
257             else*/
258             if (!isCustom)
259             {
260                 if (def.flags & DTLF_NO_IWAD) continue;
261             }
262             else
263             {
264                 if (!(def.flags & DTLF_PWAD)) continue;
265             }
266 
267             return &def;
268         }
269     }
270     return nullptr;  // Not found.
271 }
272 
Def_GetGenerator(de::Uri const & uri)273 ded_ptcgen_t *Def_GetGenerator(de::Uri const &uri)
274 {
275     if (uri.isEmpty()) return nullptr;
276 
277     for (dint i = 0; i < DED_Definitions()->ptcGens.size(); ++i)
278     {
279         ded_ptcgen_t *def = &DED_Definitions()->ptcGens[i];
280         if (!def->material) continue;
281 
282         // Is this suitable?
283         if (*def->material == uri)
284             return def;
285 
286 #if 0  /// @todo $revise-texture-animation
287         if (def->flags & PGF_GROUP)
288         {
289             // Generator triggered by all materials in the (animation) group.
290             // A search is necessary only if we know both the used material and
291             // the specified material in this definition are in *a* group.
292             if (Material_IsGroupAnimated(defMat) && Material_IsGroupAnimated(mat) &&
293                &Material_AnimGroup(defMat) == &Material_AnimGroup(mat))
294             {
295                 // Both are in this group! This def will do.
296                 return def;
297             }
298         }
299 #endif
300     }
301 
302     return nullptr;  // None found.
303 }
304 
Def_GetGenerator(uri_s const * uri)305 ded_ptcgen_t *Def_GetGenerator(uri_s const *uri)
306 {
307     if (!uri) return nullptr;
308     return Def_GetGenerator(reinterpret_cast<de::Uri const &>(*uri));
309 }
310 
Def_GetDamageGenerator(int mobjType)311 ded_ptcgen_t *Def_GetDamageGenerator(int mobjType)
312 {
313     // Search for a suitable definition.
314     for (dint i = 0; i < DED_Definitions()->ptcGens.size(); ++i)
315     {
316         ded_ptcgen_t *def = &DED_Definitions()->ptcGens[i];
317 
318         // It must be for this type of mobj.
319         if (def->damageNum == mobjType)
320             return def;
321     }
322     return nullptr;
323 }
324 
325 /**
326  * The following escape sequences are un-escaped:
327  * <pre>
328  *     \\n   Newline
329  *     \\r   Carriage return
330  *     \\t   Tab
331  *     \\\_   Space
332  *     \\s   Space
333  * </pre>
334  */
Def_InitTextDef(ddtext_t * txt,char const * str)335 static void Def_InitTextDef(ddtext_t *txt, char const *str)
336 {
337     DENG2_ASSERT(txt);
338 
339     // Handle null pointers with "".
340     if (!str) str = "";
341 
342     txt->text = (char *) M_Calloc(qstrlen(str) + 1);
343 
344     char const *in = str;
345     char *out = txt->text;
346     for (; *in; out++, in++)
347     {
348         if (*in == '\\')
349         {
350             in++;
351 
352             if (*in == 'n')      *out = '\n'; // Newline.
353             else if (*in == 'r') *out = '\r'; // Carriage return.
354             else if (*in == 't') *out = '\t'; // Tab.
355             else if (*in == '_'
356                  || *in == 's') *out = ' '; // Space.
357             else
358             {
359                 *out = *in;
360             }
361         }
362         else
363         {
364             *out = *in;
365         }
366     }
367 
368     // Adjust buffer to fix exactly.
369     txt->text = (char *) M_Realloc(txt->text, qstrlen(txt->text) + 1);
370 }
371 
372 /**
373  * Prints a count with a 2-space indentation.
374  */
defCountMsg(dint count,String const & label)375 static String defCountMsg(dint count, String const &label)
376 {
377     if (!::verbose && !count)
378         return "";  // Don't print zeros if not verbose.
379 
380     return String(_E(Ta) "  %1 " _E(Tb) "%2\n").arg(count).arg(label);
381 }
382 
383 /**
384  * Read all DD_DEFNS lumps in the primary lump index.
385  */
Def_ReadLumpDefs()386 static void Def_ReadLumpDefs()
387 {
388     LOG_AS("Def_ReadLumpDefs");
389 
390     LumpIndex const &lumpIndex = fileSys().nameIndex();
391     LumpIndex::FoundIndices foundDefns;
392     lumpIndex.findAll("DD_DEFNS.lmp", foundDefns);
393     DENG2_FOR_EACH_CONST(LumpIndex::FoundIndices, i, foundDefns)
394     {
395         if (!DED_ReadLump(DED_Definitions(), *i))
396         {
397             QByteArray path = NativePath(lumpIndex[*i].container().composePath()).pretty().toUtf8();
398             LOG_RES_ERROR("Parse error reading \"%s:DD_DEFNS\": %s") << path.constData() << DED_Error();
399         }
400     }
401 
402     dint const numProcessedLumps = foundDefns.size();
403     if (::verbose && numProcessedLumps > 0)
404     {
405         LOG_RES_NOTE("Processed %i %s")
406                 << numProcessedLumps << (numProcessedLumps != 1 ? "lumps" : "lump");
407     }
408 }
409 
410 /**
411  * Uses gettingFor. Initializes the state-owners information.
412  */
Def_StateForMobj(String const & state)413 dint Def_StateForMobj(String const &state)
414 {
415     dint num = DED_Definitions()->getStateNum(state);
416     if (num < 0) num = 0;
417 
418     // State zero is the NULL state.
419     if (num > 0)
420     {
421         ::runtimeDefs.stateInfo[num].owner = ::gettingFor;
422         // Scan forward at most 'count' states, or until we hit a state with
423         // an owner, or the NULL state.
424         dint st, count = 16;
425         for (st = ::runtimeDefs.states[num].nextState;
426             st > 0 && count-- && !::runtimeDefs.stateInfo[st].owner;
427             st = ::runtimeDefs.states[st].nextState)
428         {
429             ::runtimeDefs.stateInfo[st].owner = ::gettingFor;
430         }
431     }
432 
433     return num;
434 }
435 
readDefinitionFile(String path)436 static void readDefinitionFile(String path)
437 {
438     if (path.isEmpty()) return;
439 
440     LOG_RES_VERBOSE("Reading \"%s\"") << NativePath(path).pretty();
441     Def_ReadProcessDED(DED_Definitions(), path);
442 }
443 
444 #if 0
445 /**
446  * Attempt to prepend the current work path. If @a src is already absolute do nothing.
447  *
448  * @param dst  Absolute path written here.
449  * @param src  Original path.
450  */
451 static void prependWorkPath(ddstring_t *dst, ddstring_t const *src)
452 {
453     DENG2_ASSERT(dst && src);
454 
455     if (!F_IsAbsolute(src))
456     {
457         NativePath prepended = NativePath::workPath() / Str_Text(src);
458 
459         //char *curPath = Dir_CurrentPath();
460         //Str_Prepend(dst, curPath.);
461         //Dir_CleanPathStr(dst);
462         //M_Free(curPath);
463 
464         return;
465     }
466 
467     // Do we need to copy anyway?
468     if (dst != src)
469     {
470         Str_Set(dst, Str_Text(src));
471     }
472 }
473 #endif
474 
475 /**
476  * Returns a URN list (in load order) for all lumps whose name matches the pattern "MAPINFO.lmp".
477  */
allMapInfoUrns()478 static QStringList allMapInfoUrns()
479 {
480     QStringList foundPaths;
481 
482     // The game's main MAPINFO definitions should be processed first.
483     bool ignoreNonCustom = false;
484     try
485     {
486         String mainMapInfo = fileSys().findPath(de::Uri(App_CurrentGame().mainMapInfo()), RLF_MATCH_EXTENSION);
487         if (!mainMapInfo.isEmpty())
488         {
489             foundPaths << mainMapInfo;
490             ignoreNonCustom = true;
491         }
492     }
493     catch (FS1::NotFoundError const &)
494     {}  // Ignore this error.
495 
496     // Process all other lumps named MAPINFO.lmp
497     LumpIndex const &lumpIndex = fileSys().nameIndex();
498     LumpIndex::FoundIndices foundLumps;
499     lumpIndex.findAll("MAPINFO.lmp", foundLumps);
500     for (auto const &lumpNumber : foundLumps)
501     {
502         // Ignore MAPINFO definition data in IWADs?
503         if (ignoreNonCustom)
504         {
505             File1 const &file = lumpIndex[lumpNumber];
506             /// @todo Custom status for contained files is not inherited from the container?
507             if (file.isContained())
508             {
509                 if (!file.container().hasCustom())
510                     continue;
511             }
512             else if (!file.hasCustom())
513                 continue;
514         }
515 
516         foundPaths << String("LumpIndex:%1").arg(lumpNumber);
517     }
518 
519     return foundPaths;
520 }
521 
522 /**
523  * @param mapInfoUrns  MAPINFO definitions to translate, in load order.
524  */
translateMapInfos(QStringList const & mapInfoUrns,String & xlat,String & xlatCustom)525 static void translateMapInfos(QStringList const &mapInfoUrns, String &xlat, String &xlatCustom)
526 {
527     xlat.clear();
528     xlatCustom.clear();
529 
530     String delimitedPaths = mapInfoUrns.join(";");
531     if (delimitedPaths.isEmpty()) return;
532 
533     ddhook_mapinfo_convert_t parm;
534     Str_InitStd(&parm.paths);
535     Str_InitStd(&parm.translated);
536     Str_InitStd(&parm.translatedCustom);
537     try
538     {
539         Str_Set(&parm.paths, delimitedPaths.toUtf8().constData());
540         if (DoomsdayApp::plugins().callAllHooks(HOOK_MAPINFO_CONVERT, 0, &parm))
541         {
542             xlat       = Str_Text(&parm.translated);
543             xlatCustom = Str_Text(&parm.translatedCustom);
544         }
545     }
546     catch (...)
547     {}
548     Str_Free(&parm.translatedCustom);
549     Str_Free(&parm.translated);
550     Str_Free(&parm.paths);
551 }
552 
readAllDefinitions()553 static void readAllDefinitions()
554 {
555     Time begunAt;
556 
557     // Start with engine's own top-level definition file.
558     readDefinitionFile(App::packageLoader().package("net.dengine.base").root()
559                        .locate<File const>("defs/doomsday.ded").path());
560 
561     if (App_GameLoaded())
562     {
563         Game const &game = App_CurrentGame();
564 
565         // Some games use definitions (MAPINFO lumps) that are translated to DED.
566         QStringList mapInfoUrns = allMapInfoUrns();
567         if (!mapInfoUrns.isEmpty())
568         {
569             String xlat, xlatCustom;
570             translateMapInfos(mapInfoUrns, xlat, xlatCustom);
571 
572             if (!xlat.isEmpty())
573             {
574                 LOG_AS("Non-custom translated");
575                 LOGDEV_MAP_VERBOSE("MAPINFO definitions:\n") << xlat;
576 
577                 if (!DED_ReadData(DED_Definitions(), xlat.toUtf8().constData(),
578                                  "[TranslatedMapInfos]", false /*not custom*/))
579                 {
580                     LOG_RES_ERROR("DED parse error: %s") << DED_Error();
581                 }
582             }
583 
584             if (!xlatCustom.isEmpty())
585             {
586                 LOG_AS("Custom translated");
587                 LOGDEV_MAP_VERBOSE("MAPINFO definitions:\n") << xlatCustom;
588 
589                 if (!DED_ReadData(DED_Definitions(), xlatCustom.toUtf8().constData(),
590                                  "[TranslatedMapInfos]", true /*custom*/))
591                 {
592                     LOG_RES_ERROR("DED parse error: %s") << DED_Error();
593                 }
594             }
595         }
596 
597         // Now any startup definition files required by the game.
598         Game::Manifests const &gameResources = game.manifests();
599         dint packageIdx = 0;
600         for (Game::Manifests::const_iterator i = gameResources.find(RC_DEFINITION);
601             i != gameResources.end() && i.key() == RC_DEFINITION; ++i, ++packageIdx)
602         {
603             ResourceManifest &record = **i;
604             /// Try to locate this resource now.
605             String const &path = record.resolvedPath(true/*try to locate*/);
606             if (path.isEmpty())
607             {
608                 const auto names = record.names().join(";");
609                 LOG_RES_ERROR("Failed to locate required game definition \"%s\"") << names;
610             }
611 
612             readDefinitionFile(path);
613         }
614 
615         // Next are definition files in the games' /auto directory.
616         if (!CommandLine_Exists("-noauto"))
617         {
618             FS1::PathList foundPaths;
619             if (fileSys().findAllPaths(de::makeUri("$(App.DefsPath)/$(GamePlugin.Name)/auto/*.ded").resolved(), 0, foundPaths))
620             {
621                 for (FS1::PathListItem const &found : foundPaths)
622                 {
623                     // Ignore directories.
624                     if (found.attrib & A_SUBDIR) continue;
625 
626                     readDefinitionFile(found.path);
627                 }
628             }
629         }
630     }
631 
632     // Definitions from loaded data bundles.
633     for (DataBundle const *bundle : DataBundle::loadedBundles())
634     {
635         if (bundle->format() == DataBundle::Ded)
636         {
637             String const bundleRoot = bundle->rootPath();
638             for (Value const *path : bundle->packageMetadata().geta("dataFiles").elements())
639             {
640                 readDefinitionFile(bundleRoot / path->asText());
641             }
642         }
643     }
644 
645     // Definitions from loaded packages.
646     for (Package *pkg : App::packageLoader().loadedPackagesInOrder())
647     {
648         res::DoomsdayPackage ddPkg(*pkg);
649         if (ddPkg.hasDefinitions())
650         {
651             // Relative to package root.
652             Folder const &defsFolder = pkg->root().locate<Folder const>(ddPkg.defsPath());
653 
654             // Read all the DED files found in this folder, in alphabetical order.
655             // Subfolders are not checked -- the DED files need to manually `Include`
656             // any files from subfolders.
657             defsFolder.forContents([] (String name, File &file)
658             {
659                 if (!name.fileNameExtension().compareWithoutCase(".ded"))
660                 {
661                     readDefinitionFile(file.path());
662                 }
663                 return LoopContinue;
664             });
665         }
666     }
667 
668     // Last are DD_DEFNS definition lumps from loaded add-ons.
669     /// @todo Shouldn't these be processed before definitions on the command line?
670     Def_ReadLumpDefs();
671 
672     LOG_RES_VERBOSE("readAllDefinitions: Completed in %.2f seconds") << begunAt.since();
673 }
674 
defineFlaremap(de::Uri const & resourceUri)675 static void defineFlaremap(de::Uri const &resourceUri)
676 {
677     if (resourceUri.isEmpty()) return;
678 
679     // Reference to none?
680     if (!resourceUri.path().toStringRef().compareWithoutCase("-")) return;
681 
682     // Reference to a "built-in" flaremap?
683     String const &resourcePathStr = resourceUri.path().toStringRef();
684     if (resourcePathStr.length() == 1 &&
685         resourcePathStr.first() >= '0' && resourcePathStr.first() <= '4')
686         return;
687 
688     res::Textures::get().defineTexture("Flaremaps", resourceUri);
689 }
690 
defineLightmap(de::Uri const & resourceUri)691 static void defineLightmap(de::Uri const &resourceUri)
692 {
693     if (resourceUri.isEmpty()) return;
694 
695     // Reference to none?
696     if (!resourceUri.path().toStringRef().compareWithoutCase("-")) return;
697 
698     res::Textures::get().defineTexture("Lightmaps", resourceUri);
699 }
700 
generateMaterialDefForTexture(res::TextureManifest const & manifest)701 static void generateMaterialDefForTexture(res::TextureManifest const &manifest)
702 {
703     LOG_AS("generateMaterialDefForTexture");
704 
705     Record &mat = DED_Definitions()->materials[DED_Definitions()->addMaterial()];
706     mat.set("autoGenerated", true);
707 
708     de::Uri const texUri = manifest.composeUri();
709     mat.set("id", de::Uri(DD_MaterialSchemeNameForTextureScheme(texUri.scheme()), texUri.path()).compose());
710 
711     if (manifest.hasTexture())
712     {
713         res::Texture &tex = manifest.texture();
714         mat.set("dimensions", new ArrayValue(tex.dimensions()));
715         mat.set("flags", dint(tex.isFlagged(res::Texture::NoDraw) ? MATF_NO_DRAW : 0));
716     }
717     else
718     {
719         LOGDEV_RES_MSG("Texture \"%s\" not yet defined, resultant Material will inherit dimensions") << texUri;
720     }
721 
722     // The first layer and stage is implicit.
723     defn::Material matDef(mat);
724     defn::MaterialLayer layerDef(matDef.addLayer());
725 
726     Record &st0 = layerDef.addStage();
727     st0.set("texture", texUri.compose());
728 
729     // Is there an animation for this?
730     res::AnimGroup const *anim = res::AnimGroups::get().animGroupForTexture(manifest);
731     if (anim && anim->frameCount() > 1)
732     {
733         // Determine the start frame.
734         dint startFrame = 0;
735         while (&anim->frame(startFrame).textureManifest() != &manifest)
736         {
737             startFrame++;
738         }
739 
740         // Just animate the first in the sequence?
741         if (startFrame && (anim->flags() & AGF_FIRST_ONLY))
742             return;
743 
744         // Complete configuration of the first stage.
745         res::AnimGroupFrame const &animFrame0 = anim->frame(startFrame);
746         st0.set("tics", dint( animFrame0.tics() + animFrame0.randomTics()) );
747         if (animFrame0.randomTics())
748         {
749             st0.set("variance", animFrame0.randomTics() / st0.getf("tics"));
750         }
751 
752         // Add further stages according to the animation group.
753         startFrame++;
754         for (dint i = 0; i < anim->frameCount() - 1; ++i)
755         {
756             res::AnimGroupFrame const &animFrame      = anim->frame(de::wrap(startFrame + i, 0, anim->frameCount()));
757             res::TextureManifest const &frameManifest = animFrame.textureManifest();
758 
759             Record &st = layerDef.addStage();
760             st.set("texture", frameManifest.composeUrn().compose());
761             st.set("tics", dint( animFrame.tics() + animFrame.randomTics() ));
762             if (animFrame.randomTics())
763             {
764                 st.set("variance", animFrame.randomTics() / st.getf("tics"));
765             }
766         }
767     }
768 }
769 
generateMaterialDefsForAllTexturesInScheme(res::TextureScheme & scheme)770 static void generateMaterialDefsForAllTexturesInScheme(res::TextureScheme &scheme)
771 {
772     PathTreeIterator<res::TextureScheme::Index> iter(scheme.index().leafNodes());
773     while (iter.hasNext()) generateMaterialDefForTexture(iter.next());
774 }
775 
generateMaterialDefsForAllTexturesInScheme(String const & schemeName)776 static inline void generateMaterialDefsForAllTexturesInScheme(String const &schemeName)
777 {
778     generateMaterialDefsForAllTexturesInScheme(res::Textures::get().textureScheme(schemeName));
779 }
780 
generateMaterialDefs()781 static void generateMaterialDefs()
782 {
783     generateMaterialDefsForAllTexturesInScheme("Textures");
784     generateMaterialDefsForAllTexturesInScheme("Flats");
785     generateMaterialDefsForAllTexturesInScheme("Sprites");
786 }
787 
788 #ifdef __CLIENT__
789 
790 /**
791  * Returns @c true iff @a decorDef is compatible with the specified context.
792  */
decorationIsCompatible(Record const & decorDef,de::Uri const & textureUri,bool materialIsCustom)793 static bool decorationIsCompatible(Record const &decorDef, de::Uri const &textureUri,
794                                    bool materialIsCustom)
795 {
796     if (de::makeUri(decorDef.gets("texture")) != textureUri)
797         return false;
798 
799     if (materialIsCustom)
800     {
801         return (decorDef.geti("flags") & DCRF_PWAD) != 0;
802     }
803     return (decorDef.geti("flags") & DCRF_NO_IWAD) == 0;
804 }
805 
806 /**
807  * (Re)Decorate the given @a material according to definition @a def. Any existing
808  * decorations will be cleared in the process.
809  *
810  * @param material  The material being (re)decorated.
811  * @param def       Definition to apply.
812  */
redecorateMaterial(ClientMaterial & material,Record const & def)813 static void redecorateMaterial(ClientMaterial &material, Record const &def)
814 {
815     defn::Material matDef(def);
816 
817     material.clearAllDecorations();
818 
819     // Prefer decorations defined within the material.
820     for (dint i = 0; i < matDef.decorationCount(); ++i)
821     {
822         defn::MaterialDecoration decorDef(matDef.decoration(i));
823 
824         for (dint k = 0; k < decorDef.stageCount(); ++k)
825         {
826             Record const &st = decorDef.stage(k);
827 
828             defineLightmap(de::makeUri(st.gets("lightmapUp")));
829             defineLightmap(de::makeUri(st.gets("lightmapDown")));
830             defineLightmap(de::makeUri(st.gets("lightmapSide")));
831             defineFlaremap(de::makeUri(st.gets("haloTexture")));
832         }
833 
834         material.addDecoration(LightMaterialDecoration::fromDef(decorDef.def()));
835     }
836 
837     if (material.hasDecorations())
838         return;
839 
840     // Perhaps old style linked decoration definitions?
841     if (material.layerCount())
842     {
843         // The animation configuration of layer0 determines decoration animation.
844         auto const &decorationsByTexture = DED_Definitions()->decorations.lookup("texture").elements();
845         auto const &layer0               = material.layer(0).as<world::TextureMaterialLayer>();
846 
847         bool haveDecorations = false;
848         QVector<Record const *> stageDecorations(layer0.stageCount());
849         for (dint i = 0; i < layer0.stageCount(); ++i)
850         {
851             world::TextureMaterialLayer::AnimationStage const &stage = layer0.stage(i);
852             try
853             {
854                 res::TextureManifest &texManifest = res::Textures::get().textureManifest(stage.texture);
855                 de::Uri const texUri = texManifest.composeUri();
856                 for (auto const &pair : decorationsByTexture)
857                 {
858                     Record const &rec = *pair.second->as<RecordValue>().record();
859                     if (decorationIsCompatible(rec, texUri, material.manifest().isCustom()))
860                     {
861                         stageDecorations[i] = &rec;
862                         haveDecorations = true;
863                         break;
864                     }
865                 }
866             }
867             catch (Resources::MissingResourceManifestError const &)
868             {}  // Ignore this error
869         }
870 
871         if (!haveDecorations) return;
872 
873         for (dint i = 0; i < layer0.stageCount(); ++i)
874         {
875             if (!stageDecorations[i]) continue;
876 
877             defn::Decoration mainDef(*stageDecorations[i]);
878             for (dint k = 0; k < mainDef.lightCount(); ++k)
879             {
880                 defn::MaterialDecoration decorDef(mainDef.light(k));
881                 DENG2_ASSERT(decorDef.stageCount() == 1); // sanity check.
882 
883                 std::unique_ptr<LightMaterialDecoration> decor(
884                         new LightMaterialDecoration(Vector2i(decorDef.geta("patternSkip")),
885                                                     Vector2i(decorDef.geta("patternOffset")),
886                                                     false /*don't use interpolation*/));
887 
888                 std::unique_ptr<LightMaterialDecoration::AnimationStage> definedDecorStage(
889                         LightMaterialDecoration::AnimationStage::fromDef(decorDef.stage(0)));
890 
891                 definedDecorStage->tics = layer0.stage(i).tics;
892 
893                 for (dint m = 0; m < i; ++m)
894                 {
895                     LightMaterialDecoration::AnimationStage preStage(*definedDecorStage);
896                     preStage.tics  = layer0.stage(m).tics;
897                     preStage.color = Vector3f();
898                     decor->addStage(preStage);  // makes a copy.
899                 }
900 
901                 decor->addStage(*definedDecorStage);
902 
903                 for (dint m = i + 1; m < layer0.stageCount(); ++m)
904                 {
905                     LightMaterialDecoration::AnimationStage postStage(*definedDecorStage);
906                     postStage.tics  = layer0.stage(m).tics;
907                     postStage.color = Vector3f();
908                     decor->addStage(postStage);
909                 }
910 
911                 material.addDecoration(decor.release());  // takes ownership.
912             }
913         }
914     }
915 }
916 
917 #endif // __CLIENT__
918 
findGroupForMaterialLayerAnimation(de::Uri const & uri)919 static ded_group_t *findGroupForMaterialLayerAnimation(de::Uri const &uri)
920 {
921     if (uri.isEmpty()) return nullptr;
922 
923     // Reverse iteration (later defs override earlier ones).
924     for (dint i = DED_Definitions()->groups.size(); i--> 0; )
925     {
926         ded_group_t &grp = DED_Definitions()->groups[i];
927 
928         // We aren't interested in precache groups.
929         if (grp.flags & AGF_PRECACHE) continue;
930 
931         // Or empty/single-frame groups.
932         if (grp.members.size() < 2) continue;
933 
934         // The referenced material must be a member.
935         if (!grp.tryFindFirstMemberWithMaterial(uri)) continue;
936 
937         // Only consider groups where each frame has a valid duration.
938         dint k;
939         for (k = 0; k < grp.members.size(); ++k)
940         {
941             if (grp.members[k].tics < 0) break;
942         }
943         if (k < grp.members.size()) continue;
944 
945         // Found a suitable Group.
946         return &grp;
947     }
948 
949     return nullptr;  // Not found.
950 }
951 
configureMaterial(world::Material & mat,Record const & definition)952 static void configureMaterial(world::Material &mat, Record const &definition)
953 {
954     defn::Material matDef(definition);
955     de::Uri const materialUri(matDef.gets("id"), RC_NULL);
956 
957     // Reconfigure basic properties.
958     mat.setDimensions(Vector2ui(matDef.geta("dimensions")));
959     mat.markDontDraw((matDef.geti("flags") & MATF_NO_DRAW) != 0);
960     mat.markSkyMasked((matDef.geti("flags") & MATF_SKYMASK) != 0);
961 
962 #ifdef __CLIENT__
963     static_cast<ClientMaterial &>(mat).setAudioEnvironment(S_AudioEnvironmentId(&materialUri));
964 #endif
965 
966     // Reconfigure the layers.
967     mat.clearAllLayers();
968     for (dint i = 0; i < matDef.layerCount(); ++i)
969     {
970         mat.addLayerAt(world::TextureMaterialLayer::fromDef(matDef.layer(i)), mat.layerCount());
971     }
972 
973     if (mat.layerCount() && mat.layer(0).stageCount())
974     {
975         auto &layer0 = mat.layer(0).as<world::TextureMaterialLayer>();
976         world::TextureMaterialLayer::AnimationStage &stage0 = layer0.stage(0);
977 
978         if (!stage0.texture.isEmpty())
979         {
980             // We may need to interpret the layer animation from the now
981             // deprecated Group definitions.
982 
983             if (matDef.getb(QStringLiteral("autoGenerated")) && layer0.stageCount() == 1)
984             {
985                 de::Uri const &textureUri = stage0.texture;
986 
987                 // Possibly; see if there is a compatible definition with
988                 // a member named similarly to the texture for layer #0.
989 
990                 if (ded_group_t const *grp = findGroupForMaterialLayerAnimation(textureUri))
991                 {
992                     // Determine the start frame.
993                     dint startFrame = 0;
994                     while (!grp->members[startFrame].material ||
995                            *grp->members[startFrame].material != textureUri)
996                     {
997                         startFrame++;
998                     }
999 
1000                     // Configure the first stage.
1001                     ded_group_member_t const &gm0 = grp->members[startFrame];
1002                     stage0.tics     = gm0.tics;
1003                     stage0.variance = de::max(gm0.randomTics, 0) / dfloat( gm0.tics );
1004 
1005                     // Add further stages for each frame in the group.
1006                     startFrame++;
1007                     for (dint i = 0; i < grp->members.size() - 1; ++i)
1008                     {
1009                         dint const frame             = de::wrap(startFrame + i, 0, grp->members.size());
1010                         ded_group_member_t const &gm = grp->members[frame];
1011 
1012                         if (gm.material)
1013                         {
1014                             dint const tics       = gm.tics;
1015                             dfloat const variance = de::max(gm.randomTics, 0) / dfloat( gm.tics );
1016 
1017                             layer0.addStage(world::TextureMaterialLayer::AnimationStage(*gm.material, tics, variance));
1018                         }
1019                     }
1020                 }
1021             }
1022 
1023             // Are there Detail definitions we need to produce a layer for?
1024             world::DetailTextureMaterialLayer *dlayer = nullptr;
1025             for (dint i = 0; i < layer0.stageCount(); ++i)
1026             {
1027                 world::TextureMaterialLayer::AnimationStage &stage = layer0.stage(i);
1028                 ded_detailtexture_t const *detailDef =
1029                     tryFindDetailTexture(stage.texture, /*UNKNOWN VALUE,*/ mat.manifest().isCustom());
1030 
1031                 if (!detailDef || !detailDef->stage.texture)
1032                     continue;
1033 
1034                 if (!dlayer)
1035                 {
1036                     // Add a new detail layer.
1037                     mat.addLayerAt(dlayer = world::DetailTextureMaterialLayer::fromDef(*detailDef), 0);
1038                 }
1039                 else
1040                 {
1041                     // Add a new stage.
1042                     try
1043                     {
1044                         res::TextureManifest &texture = res::Textures::get().textureScheme("Details")
1045                                 .findByResourceUri(*detailDef->stage.texture);
1046                         dlayer->addStage(world::DetailTextureMaterialLayer::AnimationStage
1047                                          (texture.composeUri(), stage.tics, stage.variance,
1048                                           detailDef->stage.scale, detailDef->stage.strength,
1049                                           detailDef->stage.maxDistance));
1050 
1051                         if (dlayer->stageCount() == 2)
1052                         {
1053                             // Update the first stage with timing info.
1054                             world::TextureMaterialLayer::AnimationStage const &stage0 = layer0.stage(0);
1055                             world::TextureMaterialLayer::AnimationStage &dstage0      = dlayer->stage(0);
1056 
1057                             dstage0.tics     = stage0.tics;
1058                             dstage0.variance = stage0.variance;
1059                         }
1060                     }
1061                     catch (Resources::MissingResourceManifestError const &)
1062                     {}  // Ignore this error.
1063                 }
1064             }
1065 
1066             // Are there Reflection definition we need to produce a layer for?
1067             world::ShineTextureMaterialLayer *slayer = nullptr;
1068             for (dint i = 0; i < layer0.stageCount(); ++i)
1069             {
1070                 world::TextureMaterialLayer::AnimationStage &stage = layer0.stage(i);
1071                 ded_reflection_t const *shineDef =
1072                     tryFindReflection(stage.texture, /*UNKNOWN VALUE,*/ mat.manifest().isCustom());
1073 
1074                 if (!shineDef || !shineDef->stage.texture)
1075                     continue;
1076 
1077                 if (!slayer)
1078                 {
1079                     // Add a new shine layer.
1080                     mat.addLayerAt(slayer = world::ShineTextureMaterialLayer::fromDef(*shineDef), mat.layerCount());
1081                 }
1082                 else
1083                 {
1084                     // Add a new stage.
1085                     try
1086                     {
1087                         res::TextureManifest &texture = res::Textures::get().textureScheme("Reflections")
1088                                                                .findByResourceUri(*shineDef->stage.texture);
1089 
1090                         if (shineDef->stage.maskTexture)
1091                         {
1092                             try
1093                             {
1094                                 res::TextureManifest *maskTexture = &res::Textures::get().textureScheme("Masks")
1095                                                    .findByResourceUri(*shineDef->stage.maskTexture);
1096 
1097                                 slayer->addStage(world::ShineTextureMaterialLayer::AnimationStage
1098                                                  (texture.composeUri(), stage.tics, stage.variance,
1099                                                   maskTexture->composeUri(), shineDef->stage.blendMode,
1100                                                   shineDef->stage.shininess,
1101                                                   Vector3f(shineDef->stage.minColor),
1102                                                   Vector2f(shineDef->stage.maskWidth, shineDef->stage.maskHeight)));
1103                             }
1104                             catch (Resources::MissingResourceManifestError const &)
1105                             {}  // Ignore this error.
1106                         }
1107 
1108                         if (slayer->stageCount() == 2)
1109                         {
1110                             // Update the first stage with timing info.
1111                             world::TextureMaterialLayer::AnimationStage const &stage0 = layer0.stage(0);
1112                             world::TextureMaterialLayer::AnimationStage &sstage0      = slayer->stage(0);
1113 
1114                             sstage0.tics     = stage0.tics;
1115                             sstage0.variance = stage0.variance;
1116                         }
1117                     }
1118                     catch (Resources::MissingResourceManifestError const &)
1119                     {}  // Ignore this error.
1120                 }
1121             }
1122         }
1123     }
1124 
1125 #ifdef __CLIENT__
1126     redecorateMaterial(static_cast<ClientMaterial &>(mat), definition);
1127 #endif
1128 
1129     // At this point we know the material is usable.
1130     mat.markValid();
1131 }
1132 
interpretMaterialDef(Record const & definition)1133 static void interpretMaterialDef(Record const &definition)
1134 {
1135     LOG_AS("interpretMaterialDef");
1136     defn::Material matDef(definition);
1137     de::Uri const materialUri(matDef.gets("id"), RC_NULL);
1138     try
1139     {
1140         // Create/retrieve a manifest for the would-be material.
1141         world::MaterialManifest *manifest = &world::Materials::get().declareMaterial(materialUri);
1142 
1143         // Update manifest classification:
1144         manifest->setFlags(world::MaterialManifest::AutoGenerated, matDef.getb("autoGenerated")? SetFlags : UnsetFlags);
1145         manifest->setFlags(world::MaterialManifest::Custom, UnsetFlags);
1146         if (matDef.layerCount())
1147         {
1148             defn::MaterialLayer layerDef(matDef.layer(0));
1149             if (layerDef.stageCount() > 0)
1150             {
1151                 de::Uri const textureUri(layerDef.stage(0).gets("texture"), RC_NULL);
1152                 try
1153                 {
1154                     res::TextureManifest &texManifest = res::Textures::get().textureManifest(textureUri);
1155                     if (texManifest.hasTexture() && texManifest.texture().isFlagged(res::Texture::Custom))
1156                     {
1157                         manifest->setFlags(world::MaterialManifest::Custom);
1158                     }
1159                 }
1160                 catch (Resources::MissingResourceManifestError const &er)
1161                 {
1162                     // Log but otherwise ignore this error.
1163                     LOG_RES_MSG("Ignoring unknown texture \"%s\" in Material \"%s\" (layer 0 stage 0): %s")
1164                         << textureUri
1165                         << materialUri
1166                         << er.asText();
1167                 }
1168             }
1169         }
1170 
1171         // (Re)configure the material.
1172         /// @todo Defer until necessary.
1173         configureMaterial(*manifest->derive(), definition);
1174     }
1175     catch (Resources::UnknownSchemeError const &er)
1176     {
1177         LOG_RES_WARNING("Failed to declare material \"%s\": %s")
1178             << materialUri << er.asText();
1179     }
1180     catch (world::MaterialScheme::InvalidPathError const &er)
1181     {
1182         LOG_RES_WARNING("Failed to declare material \"%s\": %s")
1183             << materialUri << er.asText();
1184     }
1185 }
1186 
invalidateAllMaterials()1187 static void invalidateAllMaterials()
1188 {
1189     world::Materials::get().forAllMaterials([] (world::Material &material)
1190     {
1191         material.markValid(false);
1192         return LoopContinue;
1193     });
1194 }
1195 
1196 #ifdef __CLIENT__
clearFontDefinitionLinks()1197 static void clearFontDefinitionLinks()
1198 {
1199     for (AbstractFont *font : ClientResources::get().allFonts())
1200     {
1201         if (CompositeBitmapFont *compFont = maybeAs<CompositeBitmapFont>(font))
1202         {
1203             compFont->setDefinition(nullptr);
1204         }
1205     }
1206 }
1207 #endif // __CLIENT__
1208 
Def_Read()1209 void Def_Read()
1210 {
1211     LOG_AS("Def_Read");
1212 
1213     if (defsInited)
1214     {
1215         // We've already initialized the definitions once.
1216         // Get rid of everything.
1217         FS1::Scheme &scheme = fileSys().scheme(App_ResourceClass("RC_MODEL").defaultScheme());
1218         scheme.reset();
1219 
1220         invalidateAllMaterials();
1221 #ifdef __CLIENT__
1222         clearFontDefinitionLinks();
1223 #endif
1224         defsInited = false;
1225     }
1226 
1227     auto &defs = *DED_Definitions();
1228 
1229     // Now we can clear all existing definitions and re-init.
1230     defs.clear();
1231     runtimeDefs.clear();
1232 
1233     // Generate definitions.
1234     generateMaterialDefs();
1235 
1236     // Read all definitions files and lumps.
1237     LOG_RES_MSG("Parsing definition files...");
1238     readAllDefinitions();
1239 
1240     // Any definition hooks?
1241     DoomsdayApp::plugins().callAllHooks(HOOK_DEFS, 0, &defs);
1242 
1243 #ifdef __CLIENT__
1244     // Composite fonts.
1245     for (dint i = 0; i < defs.compositeFonts.size(); ++i)
1246     {
1247         ClientResources::get().newFontFromDef(defs.compositeFonts[i]);
1248     }
1249 #endif
1250 
1251     // States.
1252     ::runtimeDefs.states.append(defs.states.size());
1253     for (dint i = 0; i < ::runtimeDefs.states.size(); ++i)
1254     {
1255         Record &dst = defs.states[i];
1256 
1257         // Make sure duplicate IDs overwrite the earliest.
1258         dint stateNum = defs.getStateNum(dst.gets("id"));
1259         if (stateNum == -1) continue;
1260 
1261         Record &dstNew = defs.states[stateNum];
1262         state_t *st = &::runtimeDefs.states[stateNum];
1263 
1264         st->sprite    = defs.getSpriteNum(dst.gets("sprite"));
1265         st->flags     = dst.geti("flags");
1266         st->frame     = dst.geti("frame");
1267         st->tics      = dst.geti("tics");
1268         st->action    = P_GetAction(dst.gets("action"));
1269         st->nextState = defs.getStateNum(dst.gets("nextState"));
1270 
1271         if (st->nextState == -1)
1272         {
1273             LOG_WARNING("State \"%s\": next state \"%s\" is not defined") << dst.gets("id")
1274                                                                           << dst.gets("nextState");
1275         }
1276 
1277         auto const &misc = dst.geta("misc");
1278         for (dint k = 0; k < NUM_STATE_MISC; ++k)
1279         {
1280             st->misc[k] = misc[k].asInt();
1281         }
1282 
1283         // Replace the older execute string.
1284         if (&dst != &dstNew)
1285         {
1286             dstNew.set("execute", dst.gets("execute"));
1287         }
1288     }
1289 
1290     ::runtimeDefs.stateInfo.append(defs.states.size());
1291 
1292     // Mobj info.
1293     ::runtimeDefs.mobjInfo.append(defs.things.size());
1294     for (dint i = 0; i < runtimeDefs.mobjInfo.size(); ++i)
1295     {
1296         Record *dmo = &defs.things[i];
1297 
1298         // Make sure duplicate defs overwrite the earliest.
1299         mobjinfo_t *mo = &runtimeDefs.mobjInfo[defs.getMobjNum(dmo->gets("id"))];
1300 
1301         gettingFor       = mo;
1302         mo->doomEdNum    = dmo->geti("doomEdNum");
1303         mo->spawnHealth  = dmo->geti("spawnHealth");
1304         mo->reactionTime = dmo->geti("reactionTime");
1305         mo->painChance   = dmo->geti("painChance");
1306         mo->speed        = dmo->getf("speed");
1307         mo->radius       = dmo->getf("radius");
1308         mo->height       = dmo->getf("height");
1309         mo->mass         = dmo->geti("mass");
1310         mo->damage       = dmo->geti("damage");
1311         mo->flags        = dmo->geta("flags")[0].asInt();
1312         mo->flags2       = dmo->geta("flags")[1].asInt();
1313         mo->flags3       = dmo->geta("flags")[2].asInt();
1314 
1315         auto const &states = dmo->geta("states");
1316         auto const &sounds = dmo->geta("sounds");
1317 
1318         for (dint k = 0; k < STATENAMES_COUNT; ++k)
1319         {
1320             mo->states[k] = Def_StateForMobj(states[k].asText());
1321         }
1322 
1323         mo->seeSound     = defs.getSoundNum(sounds[SDN_SEE].asText());
1324         mo->attackSound  = defs.getSoundNum(sounds[SDN_ATTACK].asText());
1325         mo->painSound    = defs.getSoundNum(sounds[SDN_PAIN].asText());
1326         mo->deathSound   = defs.getSoundNum(sounds[SDN_DEATH].asText());
1327         mo->activeSound  = defs.getSoundNum(sounds[SDN_ACTIVE].asText());
1328 
1329         for (dint k = 0; k < NUM_MOBJ_MISC; ++k)
1330         {
1331             mo->misc[k] = dmo->geta("misc")[k].asInt();
1332         }
1333     }
1334 
1335     // Decorations. (Define textures).
1336     for (dint i = 0; i < defs.decorations.size(); ++i)
1337     {
1338         defn::Decoration decorDef(defs.decorations[i]);
1339         for (dint k = 0; k < decorDef.lightCount(); ++k)
1340         {
1341             Record const &st = defn::MaterialDecoration(decorDef.light(k)).stage(0);
1342             if (Vector3f(st.geta("color")) != Vector3f(0, 0, 0))
1343             {
1344                 defineLightmap(de::makeUri(st["lightmapUp"]));
1345                 defineLightmap(de::makeUri(st["lightmapDown"]));
1346                 defineLightmap(de::makeUri(st["lightmapSide"]));
1347                 defineFlaremap(de::makeUri(st["haloTexture"]));
1348             }
1349         }
1350     }
1351 
1352     // Detail textures (Define textures).
1353     res::Textures::get().textureScheme("Details").clear();
1354     for (dint i = 0; i < defs.details.size(); ++i)
1355     {
1356         ded_detailtexture_t *dtl = &defs.details[i];
1357 
1358         // Ignore definitions which do not specify a material.
1359         if ((!dtl->material1 || dtl->material1->isEmpty()) &&
1360            (!dtl->material2 || dtl->material2->isEmpty())) continue;
1361 
1362         if (!dtl->stage.texture) continue;
1363 
1364         res::Textures::get().defineTexture("Details", *dtl->stage.texture);
1365     }
1366 
1367     // Surface reflections (Define textures).
1368     res::Textures::get().textureScheme("Reflections").clear();
1369     res::Textures::get().textureScheme("Masks").clear();
1370     for (dint i = 0; i < defs.reflections.size(); ++i)
1371     {
1372         ded_reflection_t *ref = &defs.reflections[i];
1373 
1374         // Ignore definitions which do not specify a material.
1375         if (!ref->material || ref->material->isEmpty()) continue;
1376 
1377         if (ref->stage.texture)
1378         {
1379             res::Textures::get().defineTexture("Reflections", *ref->stage.texture);
1380         }
1381         if (ref->stage.maskTexture)
1382         {
1383             res::Textures::get().defineTexture("Masks", *ref->stage.maskTexture,
1384                             Vector2ui(ref->stage.maskWidth, ref->stage.maskHeight));
1385         }
1386     }
1387 
1388     // Materials.
1389     for (dint i = 0; i < defs.materials.size(); ++i)
1390     {
1391         interpretMaterialDef(defs.materials[i]);
1392     }
1393 
1394     // Dynamic lights. Update the sprite numbers.
1395     for (dint i = 0; i < defs.lights.size(); ++i)
1396     {
1397         dint const stateIdx = defs.getStateNum(defs.lights[i].state);
1398         if (stateIdx < 0)
1399         {
1400             // It's probably a bias light definition, then?
1401             if (!defs.lights[i].uniqueMapID[0])
1402             {
1403                 LOG_RES_WARNING("Undefined state '%s' in Light definition") << defs.lights[i].state;
1404             }
1405             continue;
1406         }
1407         ::runtimeDefs.stateInfo[stateIdx].light = &defs.lights[i];
1408     }
1409 
1410     // Sound effects.
1411     ::runtimeDefs.sounds.append(defs.sounds.size());
1412     for (dint i = 0; i < ::runtimeDefs.sounds.size(); ++i)
1413     {
1414         ded_sound_t *snd = &defs.sounds[i];
1415         // Make sure duplicate defs overwrite the earliest.
1416         sfxinfo_t *si    = &::runtimeDefs.sounds[defs.getSoundNum(snd->id)];
1417 
1418         qstrcpy(si->id, snd->id);
1419         qstrcpy(si->lumpName, snd->lumpName);
1420         si->lumpNum     = (qstrlen(snd->lumpName) > 0? fileSys().lumpNumForName(snd->lumpName) : -1);
1421         qstrcpy(si->name, snd->name);
1422 
1423         dint const soundIdx = defs.getSoundNum(snd->link);
1424         si->link        = (soundIdx >= 0 ? &::runtimeDefs.sounds[soundIdx] : 0);
1425 
1426         si->linkPitch   = snd->linkPitch;
1427         si->linkVolume  = snd->linkVolume;
1428         si->priority    = snd->priority;
1429         si->channels    = snd->channels;
1430         si->flags       = snd->flags;
1431         si->group       = snd->group;
1432 
1433         Str_Init(&si->external);
1434         if (snd->ext)
1435         {
1436             Str_Set(&si->external, snd->ext->pathCStr());
1437         }
1438     }
1439 
1440     // Music.
1441     for (int i = defs.musics.size() - 1; i >= 0; --i)
1442     {
1443         const Record &mus = defs.musics[i];
1444 
1445         // Make sure duplicate defs overwrite contents from the earlier ones.
1446         // IDs can't be fully trusted because music definitions are sometimes
1447         // generated by idtech1importer, so they might have IDs that don't
1448         // match the vanilla IDs.
1449 
1450         for (int k = i - 1; k >= 0; --k)
1451         {
1452             Record &earlier = defs.musics[k];
1453 
1454             if (mus.gets("id").compareWithoutCase(earlier.gets("id")) == 0)
1455             {
1456                 earlier.set("lumpName", mus.gets("lumpName"));
1457                 earlier.set("cdTrack", mus.geti("cdTrack"));
1458                 earlier.set("path", mus.gets("path"));
1459             }
1460             else if (mus.gets("lumpName").compareWithoutCase(earlier.gets("lumpName")) == 0)
1461             {
1462                 earlier.set("path", mus.gets("path"));
1463                 earlier.set("cdTrack", mus.geti("cdTrack"));
1464             }
1465         }
1466     }
1467 
1468     // Text.
1469     ::runtimeDefs.texts.append(defs.text.size());
1470     for (dint i = 0; i < defs.text.size(); ++i)
1471     {
1472         Def_InitTextDef(&::runtimeDefs.texts[i], defs.text[i].text);
1473     }
1474     // Handle duplicate strings.
1475     for (dint i = 0; i < ::runtimeDefs.texts.size(); ++i)
1476     {
1477         if (!::runtimeDefs.texts[i].text) continue;
1478 
1479         for (dint k = i + 1; k < ::runtimeDefs.texts.size(); ++k)
1480         {
1481             if (!::runtimeDefs.texts[k].text) continue; // Already done.
1482             if (qstricmp(defs.text[i].id, defs.text[k].id)) continue; // ID mismatch.
1483 
1484             // Update the earlier string.
1485             ::runtimeDefs.texts[i].text = (char *) M_Realloc(::runtimeDefs.texts[i].text, qstrlen(::runtimeDefs.texts[k].text) + 1);
1486             qstrcpy(::runtimeDefs.texts[i].text, ::runtimeDefs.texts[k].text);
1487 
1488             // Free the later string, it isn't used (>NUMTEXT).
1489             M_Free(::runtimeDefs.texts[k].text);
1490             ::runtimeDefs.texts[k].text = nullptr;
1491         }
1492     }
1493 
1494     // Particle generators.
1495     for (dint i = 0; i < defs.ptcGens.size(); ++i)
1496     {
1497         ded_ptcgen_t *pg = &defs.ptcGens[i];
1498         dint st = defs.getStateNum(pg->state);
1499 
1500         if (!qstrcmp(pg->type, "*"))
1501             pg->typeNum = DED_PTCGEN_ANY_MOBJ_TYPE;
1502         else
1503             pg->typeNum = defs.getMobjNum(pg->type);
1504         pg->type2Num  = defs.getMobjNum(pg->type2);
1505         pg->damageNum = defs.getMobjNum(pg->damage);
1506 
1507         // Figure out embedded sound ID numbers.
1508         for (dint k = 0; k < pg->stages.size(); ++k)
1509         {
1510             if (pg->stages[k].sound.name[0])
1511             {
1512                 pg->stages[k].sound.id = defs.getSoundNum(pg->stages[k].sound.name);
1513             }
1514             if (pg->stages[k].hitSound.name[0])
1515             {
1516                 pg->stages[k].hitSound.id = defs.getSoundNum(pg->stages[k].hitSound.name);
1517             }
1518         }
1519 
1520         if (st <= 0)
1521             continue; // Not state triggered, then...
1522 
1523         stateinfo_t *stinfo = &::runtimeDefs.stateInfo[st];
1524 
1525         // Link the definition to the state.
1526         if (pg->flags & world::Generator::StateChain)
1527         {
1528             // Add to the chain.
1529             pg->stateNext = stinfo->ptcGens;
1530             stinfo->ptcGens = pg;
1531         }
1532         else
1533         {
1534             // Make sure the previously built list is unlinked.
1535             while (stinfo->ptcGens)
1536             {
1537                 ded_ptcgen_t *temp = stinfo->ptcGens->stateNext;
1538 
1539                 stinfo->ptcGens->stateNext = nullptr;
1540                 stinfo->ptcGens = temp;
1541             }
1542             stinfo->ptcGens = pg;
1543             pg->stateNext = nullptr;
1544         }
1545     }
1546 
1547     // Map infos.
1548     for (dint i = 0; i < defs.mapInfos.size(); ++i)
1549     {
1550         Record &mi = defs.mapInfos[i];
1551 
1552         // Historically, the map info flags field was used for sky flags, here we copy
1553         // those flags to the embedded sky definition for backward-compatibility.
1554         if (mi.geti("flags") & MIF_DRAW_SPHERE)
1555         {
1556             mi.set("sky.flags", mi.geti("sky.flags") | SIF_DRAW_SPHERE);
1557         }
1558     }
1559 
1560     // Log a summary of the definition database.
1561     LOG_RES_MSG(_E(b) "Definitions:");
1562     String str;
1563     QTextStream os(&str);
1564     os << defCountMsg(defs.episodes.size(),         "episodes");
1565     os << defCountMsg(defs.groups.size(),           "animation groups");
1566     os << defCountMsg(defs.compositeFonts.size(),   "composite fonts");
1567     os << defCountMsg(defs.details.size(),          "detail textures");
1568     os << defCountMsg(defs.finales.size(),          "finales");
1569     os << defCountMsg(defs.lights.size(),           "lights");
1570     os << defCountMsg(defs.lineTypes.size(),        "line types");
1571     os << defCountMsg(defs.mapInfos.size(),         "map infos");
1572 
1573     dint nonAutoGeneratedCount = 0;
1574     for (dint i = 0; i < defs.materials.size(); ++i)
1575     {
1576         if (!defs.materials[i].getb("autoGenerated"))
1577             ++nonAutoGeneratedCount;
1578     }
1579     os << defCountMsg(nonAutoGeneratedCount,        "materials");
1580 
1581     os << defCountMsg(defs.models.size(),           "models");
1582     os << defCountMsg(defs.ptcGens.size(),          "particle generators");
1583     os << defCountMsg(defs.skies.size(),            "skies");
1584     os << defCountMsg(defs.sectorTypes.size(),      "sector types");
1585     os << defCountMsg(defs.musics.size(),           "songs");
1586     os << defCountMsg(::runtimeDefs.sounds.size(),    "sound effects");
1587     os << defCountMsg(defs.sprites.size(),          "sprite names");
1588     os << defCountMsg(::runtimeDefs.states.size(),    "states");
1589     os << defCountMsg(defs.decorations.size(),      "surface decorations");
1590     os << defCountMsg(defs.reflections.size(),      "surface reflections");
1591     os << defCountMsg(::runtimeDefs.texts.size(),     "text strings");
1592     os << defCountMsg(defs.textureEnv.size(),       "texture environments");
1593     os << defCountMsg(::runtimeDefs.mobjInfo.size(),  "things");
1594 
1595     LOG_RES_MSG("%s") << str.rightStrip();
1596 
1597     ::defsInited = true;
1598 }
1599 
initMaterialGroup(ded_group_t & def)1600 static void initMaterialGroup(ded_group_t &def)
1601 {
1602     world::Materials::MaterialManifestGroup *group = nullptr;
1603     for (dint i = 0; i < def.members.size(); ++i)
1604     {
1605         ded_group_member_t *gm = &def.members[i];
1606         if (!gm->material) continue;
1607 
1608         try
1609         {
1610             world::MaterialManifest &manifest = world::Materials::get().materialManifest(*gm->material);
1611 
1612             if (def.flags & AGF_PRECACHE) // A precache group.
1613             {
1614                 // Only create the group once the first material has been found.
1615                 if (!group)
1616                 {
1617                     group = &world::Materials::get().newMaterialGroup();
1618                 }
1619 
1620                 group->insert(&manifest);
1621             }
1622 #if 0 /// @todo $revise-texture-animation
1623             else // An animation group.
1624             {
1625                 // Only create the group once the first material has been found.
1626                 if (animNumber == -1)
1627                 {
1628                     animNumber = resSys().newAnimGroup(def.flags & ~AGF_PRECACHE);
1629                 }
1630 
1631                 resSys().animGroup(animNumber).addFrame(manifest.material(), gm->tics, gm->randomTics);
1632             }
1633 #endif
1634         }
1635         catch (Resources::MissingResourceManifestError const &er)
1636         {
1637             // Log but otherwise ignore this error.
1638             LOG_RES_WARNING("Unknown material \"%s\" in group def %i: %s")
1639                     << *gm->material
1640                     << i << er.asText();
1641         }
1642     }
1643 }
1644 
Def_PostInit()1645 void Def_PostInit()
1646 {
1647 #ifdef __CLIENT__
1648 
1649     // Particle generators: model setup.
1650     for (dint i = 0; i < DED_Definitions()->ptcGens.size(); ++i)
1651     {
1652         ded_ptcgen_t *gen = &DED_Definitions()->ptcGens[i];
1653 
1654         for (dint k = 0; k < gen->stages.size(); ++k)
1655         {
1656             ded_ptcstage_t *st = &gen->stages[k];
1657 
1658             if (st->type < PTC_MODEL || st->type >= PTC_MODEL + MAX_PTC_MODELS)
1659                 continue;
1660 
1661             st->model = -1;
1662             try
1663             {
1664                 FrameModelDef &modef = ClientResources::get().modelDef(String("Particle%1").arg(st->type - PTC_MODEL, 2, 10, QChar('0')));
1665                 if (modef.subModelId(0) == NOMODELID)
1666                 {
1667                     continue;
1668                 }
1669 
1670                 FrameModel &mdl = ClientResources::get().model(modef.subModelId(0));
1671 
1672                 st->model = ClientResources::get().indexOf(&modef);
1673                 st->frame = mdl.frameNumber(st->frameName);
1674                 if (st->frame < 0) st->frame = 0;
1675                 if (st->endFrameName[0])
1676                 {
1677                     st->endFrame = mdl.frameNumber(st->endFrameName);
1678                     if (st->endFrame < 0) st->endFrame = 0;
1679                 }
1680                 else
1681                 {
1682                     st->endFrame = -1;
1683                 }
1684             }
1685             catch (ClientResources::MissingModelDefError const &)
1686             {}  // Ignore this error.
1687         }
1688     }
1689 
1690 #endif // __CLIENT__
1691 
1692     // Lights.
1693     for (dint i = 0; i < DED_Definitions()->lights.size(); ++i)
1694     {
1695         ded_light_t &lightDef = DED_Definitions()->lights[i];
1696 
1697         if (lightDef.up)    defineLightmap(*lightDef.up);
1698         if (lightDef.down)  defineLightmap(*lightDef.down);
1699         if (lightDef.sides) defineLightmap(*lightDef.sides);
1700         if (lightDef.flare) defineFlaremap(*lightDef.flare);
1701     }
1702 
1703     // Material groups (e.g., for precaching).
1704     world::Materials::get().clearAllMaterialGroups();
1705     for (dint i = 0; i < DED_Definitions()->groups.size(); ++i)
1706     {
1707         initMaterialGroup(DED_Definitions()->groups[i]);
1708     }
1709 }
1710 
Def_SameStateSequence(state_t * snew,state_t * sold)1711 bool Def_SameStateSequence(state_t *snew, state_t *sold)
1712 {
1713     if (!snew || !sold) return false;
1714     if (snew == sold) return true;  // Trivial.
1715 
1716     dint const target = ::runtimeDefs.states.indexOf(snew);
1717     dint const start  = ::runtimeDefs.states.indexOf(sold);
1718 
1719     dint count = 0;
1720     for (dint it = sold->nextState; it >= 0 && it != start && count < 16;
1721         it = ::runtimeDefs.states[it].nextState, ++count)
1722     {
1723         if (it == target)
1724             return true;
1725 
1726         if (it == ::runtimeDefs.states[it].nextState)
1727             break;
1728     }
1729     return false;
1730 }
1731 
Def_GetStateName(state_t const * state)1732 String Def_GetStateName(state_t const *state)
1733 {
1734     if (!state) return "(nullptr)";
1735     dint const idx = ::runtimeDefs.states.indexOf(state);
1736     DENG2_ASSERT(idx >= 0);
1737     return DED_Definitions()->states[idx].gets("id");
1738 }
1739 
Friendly(dint num)1740 static inline dint Friendly(dint num)
1741 {
1742     return de::max(0, num);
1743 }
1744 
1745 /**
1746  * Converts a DED line type to the internal format.
1747  * Bit of a nuisance really...
1748  */
Def_CopyLineType(linetype_t * l,ded_linetype_t * def)1749 void Def_CopyLineType(linetype_t *l, ded_linetype_t *def)
1750 {
1751     DENG2_ASSERT(l && def);
1752 
1753     l->id               = def->id;
1754     l->flags            = def->flags[0];
1755     l->flags2           = def->flags[1];
1756     l->flags3           = def->flags[2];
1757     l->lineClass        = def->lineClass;
1758     l->actType          = def->actType;
1759     l->actCount         = def->actCount;
1760     l->actTime          = def->actTime;
1761     l->actTag           = def->actTag;
1762 
1763     for (dint i = 0; i < 10; ++i)
1764     {
1765         if (i == 9)
1766             l->aparm[i] = DED_Definitions()->getMobjNum(def->aparm9);
1767         else
1768             l->aparm[i] = def->aparm[i];
1769     }
1770 
1771     l->tickerStart      = def->tickerStart;
1772     l->tickerEnd        = def->tickerEnd;
1773     l->tickerInterval   = def->tickerInterval;
1774     l->actSound         = Friendly(DED_Definitions()->getSoundNum(def->actSound));
1775     l->deactSound       = Friendly(DED_Definitions()->getSoundNum(def->deactSound));
1776     l->evChain          = def->evChain;
1777     l->actChain         = def->actChain;
1778     l->deactChain       = def->deactChain;
1779     l->actLineType      = def->actLineType;
1780     l->deactLineType    = def->deactLineType;
1781     l->wallSection      = def->wallSection;
1782 
1783     if (def->actMaterial)
1784     {
1785         try
1786         {
1787             l->actMaterial = world::Materials::get().materialManifest(*def->actMaterial).id();
1788         }
1789         catch (Resources::MissingResourceManifestError const &)
1790         {}  // Ignore this error.
1791     }
1792 
1793     if (def->deactMaterial)
1794     {
1795         try
1796         {
1797             l->deactMaterial = world::Materials::get().materialManifest(*def->deactMaterial).id();
1798         }
1799         catch (Resources::MissingResourceManifestError const &)
1800         {}  // Ignore this error.
1801     }
1802 
1803     l->actMsg            = def->actMsg;
1804     l->deactMsg          = def->deactMsg;
1805     l->materialMoveAngle = def->materialMoveAngle;
1806     l->materialMoveSpeed = def->materialMoveSpeed;
1807 
1808     dint i;
1809     LOOPi(20) l->iparm[i] = def->iparm[i];
1810     LOOPi(20) l->fparm[i] = def->fparm[i];
1811     LOOPi(5)  l->sparm[i] = def->sparm[i];
1812 
1813     // Some of the parameters might be strings depending on the line class.
1814     // Find the right mapping table.
1815     for (dint k = 0; k < 20; ++k)
1816     {
1817         dint const a = XG_Class(l->lineClass)->iparm[k].map;
1818         if (a < 0) continue;
1819 
1820         if (a & MAP_SND)
1821         {
1822             l->iparm[k] = Friendly(DED_Definitions()->getSoundNum(def->iparmStr[k]));
1823         }
1824         else if (a & MAP_MATERIAL)
1825         {
1826             if (def->iparmStr[k][0])
1827             {
1828                 if (!qstricmp(def->iparmStr[k], "-1"))
1829                 {
1830                     l->iparm[k] = -1;
1831                 }
1832                 else
1833                 {
1834                     try
1835                     {
1836                         l->iparm[k] = world::Materials::get().materialManifest(de::makeUri(def->iparmStr[k])).id();
1837                     }
1838                     catch (Resources::MissingResourceManifestError const &)
1839                     {}  // Ignore this error.
1840                 }
1841             }
1842         }
1843         else if (a & MAP_MUS)
1844         {
1845             dint temp = Friendly(DED_Definitions()->getMusicNum(def->iparmStr[k]));
1846 
1847             if (temp == 0)
1848             {
1849                 temp = DED_Definitions()->evalFlags(def->iparmStr[k]);
1850                 if (temp)
1851                     l->iparm[k] = temp;
1852             }
1853             else
1854             {
1855                 l->iparm[k] = Friendly(DED_Definitions()->getMusicNum(def->iparmStr[k]));
1856             }
1857         }
1858         else
1859         {
1860             dint temp = DED_Definitions()->evalFlags(def->iparmStr[k]);
1861             if (temp)
1862                 l->iparm[k] = temp;
1863         }
1864     }
1865 }
1866 
1867 /**
1868  * Converts a DED sector type to the internal format.
1869  */
Def_CopySectorType(sectortype_t * s,ded_sectortype_t * def)1870 void Def_CopySectorType(sectortype_t *s, ded_sectortype_t *def)
1871 {
1872     DENG2_ASSERT(s && def);
1873     dint i, k;
1874 
1875     s->id           = def->id;
1876     s->flags        = def->flags;
1877     s->actTag       = def->actTag;
1878     LOOPi(5)
1879     {
1880         s->chain[i]      = def->chain[i];
1881         s->chainFlags[i] = def->chainFlags[i];
1882         s->start[i]      = def->start[i];
1883         s->end[i]        = def->end[i];
1884         LOOPk(2) s->interval[i][k] = def->interval[i][k];
1885         s->count[i]      = def->count[i];
1886     }
1887     s->ambientSound = Friendly(DED_Definitions()->getSoundNum(def->ambientSound));
1888     LOOPi(2)
1889     {
1890         s->soundInterval[i]     = def->soundInterval[i];
1891         s->materialMoveAngle[i] = def->materialMoveAngle[i];
1892         s->materialMoveSpeed[i] = def->materialMoveSpeed[i];
1893     }
1894     s->windAngle    = def->windAngle;
1895     s->windSpeed    = def->windSpeed;
1896     s->verticalWind = def->verticalWind;
1897     s->gravity      = def->gravity;
1898     s->friction     = def->friction;
1899     s->lightFunc    = def->lightFunc;
1900     LOOPi(2) s->lightInterval[i] = def->lightInterval[i];
1901     LOOPi(3)
1902     {
1903         s->colFunc[i] = def->colFunc[i];
1904         LOOPk(2) s->colInterval[i][k] = def->colInterval[i][k];
1905     }
1906     s->floorFunc    = def->floorFunc;
1907     s->floorMul     = def->floorMul;
1908     s->floorOff     = def->floorOff;
1909     LOOPi(2) s->floorInterval[i] = def->floorInterval[i];
1910     s->ceilFunc     = def->ceilFunc;
1911     s->ceilMul      = def->ceilMul;
1912     s->ceilOff      = def->ceilOff;
1913     LOOPi(2) s->ceilInterval[i] = def->ceilInterval[i];
1914 }
1915 
Def_Get(dint type,char const * id,void * out)1916 dint Def_Get(dint type, char const *id, void *out)
1917 {
1918     switch (type)
1919     {
1920     case DD_DEF_ACTION:
1921         if (acfnptr_t action = P_GetAction(id))
1922         {
1923             if (out) *(acfnptr_t *)out = action;
1924             return true;
1925         }
1926         return false;
1927 
1928     case DD_DEF_SOUND_LUMPNAME: {
1929         dint32 i = *((dint32 *) id);
1930         if (i < 0 || i >= ::runtimeDefs.sounds.size())
1931             return false;
1932         qstrcpy((char *)out, ::runtimeDefs.sounds[i].lumpName);
1933         return true; }
1934 
1935     case DD_DEF_LINE_TYPE: {
1936         dint typeId = strtol(id, (char **)nullptr, 10);
1937         for (dint i = DED_Definitions()->lineTypes.size() - 1; i >= 0; i--)
1938         {
1939             if (DED_Definitions()->lineTypes[i].id != typeId) continue;
1940             if (out) Def_CopyLineType((linetype_t *)out, &DED_Definitions()->lineTypes[i]);
1941             return true;
1942         }
1943         return false; }
1944 
1945     case DD_DEF_SECTOR_TYPE: {
1946         dint typeId = strtol(id, (char **)nullptr, 10);
1947         for (dint i = DED_Definitions()->sectorTypes.size() - 1; i >= 0; i--)
1948         {
1949             if (DED_Definitions()->sectorTypes[i].id != typeId) continue;
1950             if (out) Def_CopySectorType((sectortype_t *)out, &DED_Definitions()->sectorTypes[i]);
1951             return true;
1952         }
1953         return false; }
1954 
1955     default: return false;
1956     }
1957 }
1958 
Def_Set(dint type,dint index,dint value,void const * ptr)1959 dint Def_Set(dint type, dint index, dint value, void const *ptr)
1960 {
1961     LOG_AS("Def_Set");
1962 
1963     switch (type)
1964     {
1965     case DD_DEF_SOUND:
1966         if (index < 0 || index >= ::runtimeDefs.sounds.size())
1967         {
1968             DENG2_ASSERT(!"Sound index is invalid");
1969             return false;
1970         }
1971 
1972         switch (value)
1973         {
1974         case DD_LUMP:
1975             S_StopSound(index, 0);
1976             qstrcpy(::runtimeDefs.sounds[index].lumpName, (char const *) ptr);
1977             if (qstrlen(::runtimeDefs.sounds[index].lumpName))
1978             {
1979                 ::runtimeDefs.sounds[index].lumpNum = fileSys().lumpNumForName(::runtimeDefs.sounds[index].lumpName);
1980                 if (::runtimeDefs.sounds[index].lumpNum < 0)
1981                 {
1982                     LOG_RES_WARNING("Unknown sound lump name \"%s\"; sound #%i will be inaudible")
1983                             << ::runtimeDefs.sounds[index].lumpName << index;
1984                 }
1985             }
1986             else
1987             {
1988                 ::runtimeDefs.sounds[index].lumpNum = 0;
1989             }
1990             break;
1991 
1992         default: break;
1993         }
1994         break;
1995 
1996     default: return false;
1997     }
1998 
1999     return true;
2000 }
2001 
2002 /**
2003  * Prints a list of all the registered mobjs to the console.
2004  * @todo Does this belong here?
2005  */
D_CMD(ListMobjs)2006 D_CMD(ListMobjs)
2007 {
2008     DENG2_UNUSED3(src, argc, argv);
2009 
2010     if (DED_Definitions()->things.size() <= 0)
2011     {
2012         LOG_RES_MSG("No mobjtypes defined/loaded");
2013         return true;
2014     }
2015 
2016     LOG_RES_MSG(_E(b) "Registered Mobjs (ID | Name):");
2017     for (dint i = 0; i < DED_Definitions()->things.size(); ++i)
2018     {
2019         auto const &name = DED_Definitions()->things[i].gets("name");
2020         if (!name.isEmpty())
2021             LOG_RES_MSG(" %s | %s") << DED_Definitions()->things[i].gets("id") << name;
2022         else
2023             LOG_RES_MSG(" %s | " _E(l) "(Unnamed)") << DED_Definitions()->things[i].gets("id");
2024     }
2025 
2026     return true;
2027 }
2028 
Def_ConsoleRegister()2029 void Def_ConsoleRegister()
2030 {
2031     C_CMD("listmobjtypes", "", ListMobjs);
2032 }
2033 
2034 DENG_DECLARE_API(Def) =
2035 {
2036     { DE_API_DEFINITIONS },
2037 
2038     Def_Get,
2039     Def_Set
2040 };
2041