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