1 /** @file ded.cpp Doomsday Engine Definition database.
2 *
3 * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 * @authors Copyright © 2006-2015 Daniel Swanson <danij@dengine.net>
5 *
6 * @par License
7 * GPL: http://www.gnu.org/licenses/gpl.html
8 *
9 * <small>This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by the
11 * Free Software Foundation; either version 2 of the License, or (at your
12 * option) any later version. This program is distributed in the hope that it
13 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15 * Public License for more details. You should have received a copy of the GNU
16 * General Public License along with this program; if not, see:
17 * http://www.gnu.org/licenses</small>
18 */
19
20 #include "doomsday/defs/ded.h"
21
22 #include <cstdio>
23 #include <cstdlib>
24 #include <cstring>
25 #include <de/memory.h>
26 #include <de/strutil.h>
27 #include <de/ArrayValue>
28 #include <de/NumberValue>
29 #include <de/RecordValue>
30
31 #include "doomsday/defs/decoration.h"
32 #include "doomsday/defs/episode.h"
33 #include "doomsday/defs/thing.h"
34 #include "doomsday/defs/state.h"
35 #include "doomsday/defs/finale.h"
36 #include "doomsday/defs/mapinfo.h"
37 #include "doomsday/defs/material.h"
38 #include "doomsday/defs/model.h"
39 #include "doomsday/defs/music.h"
40 #include "doomsday/defs/sky.h"
41
42 using namespace de;
43
particleRadius(int ptcIDX) const44 float ded_ptcstage_t::particleRadius(int ptcIDX) const
45 {
46 if (radiusVariance != 0.f)
47 {
48 static float const rnd[16] = { .875f, .125f, .3125f, .75f, .5f, .375f,
49 .5625f, .0625f, 1, .6875f, .625f, .4375f, .8125f, .1875f,
50 .9375f, .25f
51 };
52 return (rnd[ptcIDX & 0xf] * radiusVariance + (1 - radiusVariance)) * radius;
53 }
54 return radius;
55 }
56
ded_s()57 ded_s::ded_s()
58 : flags (names.addSubrecord("flags"))
59 , episodes (names.addSubrecord("episodes"))
60 , things (names.addSubrecord("things"))
61 , states (names.addSubrecord("states"))
62 , materials (names.addSubrecord("materials"))
63 , models (names.addSubrecord("models"))
64 , skies (names.addSubrecord("skies"))
65 , musics (names.addSubrecord("musics"))
66 , mapInfos (names.addSubrecord("mapInfos"))
67 , finales (names.addSubrecord("finales"))
68 , decorations(names.addSubrecord("decorations"))
69 {
70 decorations.addLookupKey("texture");
71 episodes.addLookupKey(defn::Definition::VAR_ID);
72 things.addLookupKey(defn::Definition::VAR_ID, DEDRegister::OnlyFirst);
73 things.addLookupKey("name");
74 states.addLookupKey(defn::Definition::VAR_ID, DEDRegister::OnlyFirst);
75 finales.addLookupKey(defn::Definition::VAR_ID);
76 finales.addLookupKey("before");
77 finales.addLookupKey("after");
78 flags.addLookupKey(defn::Definition::VAR_ID);
79 mapInfos.addLookupKey(defn::Definition::VAR_ID);
80 materials.addLookupKey(defn::Definition::VAR_ID);
81 models.addLookupKey(defn::Definition::VAR_ID, DEDRegister::OnlyFirst);
82 models.addLookupKey("state");
83 musics.addLookupKey(defn::Definition::VAR_ID, DEDRegister::OnlyFirst);
84 skies.addLookupKey(defn::Definition::VAR_ID);
85
86 clear();
87 }
88
clear()89 void ded_s::clear()
90 {
91 release();
92
93 version = DED_VERSION;
94 modelFlags = 0;
95 modelScale = 0;
96 modelOffset = 0;
97 }
98
addFlag(String const & id,int value)99 int ded_s::addFlag(String const &id, int value)
100 {
101 Record &def = flags.append();
102 def.addText(defn::Definition::VAR_ID, id);
103 def.addNumber("value", value);
104 return def.geti(defn::Definition::VAR_ORDER);
105 }
106
addEpisode()107 int ded_s::addEpisode()
108 {
109 Record &def = episodes.append();
110 defn::Episode(def).resetToDefaults();
111 return def.geti(defn::Definition::VAR_ORDER);
112 }
113
addThing(String const & id)114 int ded_s::addThing(String const &id)
115 {
116 Record &def = things.append();
117 defn::Thing(def).resetToDefaults();
118 def.set(defn::Definition::VAR_ID, id);
119 return def.geti(defn::Definition::VAR_ORDER);
120 }
121
addState(String const & id)122 int ded_s::addState(String const &id)
123 {
124 Record &def = states.append();
125 defn::State(def).resetToDefaults();
126 def.set(defn::Definition::VAR_ID, id);
127 return def.geti(defn::Definition::VAR_ORDER);
128 }
129
addDecoration()130 int ded_s::addDecoration()
131 {
132 Record &def = decorations.append();
133 defn::Decoration(def).resetToDefaults();
134 return def.geti(defn::Definition::VAR_ORDER);
135 }
136
addFinale()137 int ded_s::addFinale()
138 {
139 Record &def = finales.append();
140 defn::Finale(def).resetToDefaults();
141 return def.geti(defn::Definition::VAR_ORDER);
142 }
143
addMapInfo()144 int ded_s::addMapInfo()
145 {
146 Record &def = mapInfos.append();
147 defn::MapInfo(def).resetToDefaults();
148 return def.geti(defn::Definition::VAR_ORDER);
149 }
150
addMaterial()151 int ded_s::addMaterial()
152 {
153 Record &def = materials.append();
154 defn::Material(def).resetToDefaults();
155 return def.geti(defn::Definition::VAR_ORDER);
156 }
157
addModel()158 int ded_s::addModel()
159 {
160 Record &def = models.append();
161 defn::Model(def).resetToDefaults();
162 return def.geti(defn::Definition::VAR_ORDER);
163 }
164
addMusic()165 int ded_s::addMusic()
166 {
167 Record &def = musics.append();
168 defn::Music(def).resetToDefaults();
169 return def.geti(defn::Definition::VAR_ORDER);
170 }
171
addSky()172 int ded_s::addSky()
173 {
174 Record &def = skies.append();
175 defn::Sky(def).resetToDefaults();
176 return def.geti(defn::Definition::VAR_ORDER);
177 }
178
release()179 void ded_s::release()
180 {
181 flags.clear();
182 episodes.clear();
183 things.clear();
184 states.clear();
185 sprites.clear();
186 lights.clear();
187 models.clear();
188 sounds.clear();
189 musics.clear();
190 mapInfos.clear();
191 skies.clear();
192 details.clear();
193 materials.clear();
194 text.clear();
195 textureEnv.clear();
196 compositeFonts.clear();
197 values.clear();
198 decorations.clear();
199 reflections.clear();
200 groups.clear();
201 sectorTypes.clear();
202 lineTypes.clear();
203 ptcGens.clear();
204 finales.clear();
205 }
206
207 /*
208 int DED_AddMobj(ded_t* ded, char const* idstr)
209 {
210 ded_mobj_t *mo = ded->mobjs.append();
211 strcpy(mo->id, idstr);
212 return ded->mobjs.indexOf(mo);
213 }
214 */
215
216 /*
217 int DED_AddState(ded_t* ded, char const* id)
218 {
219 ded_state_t *st = ded->states.append();
220 strcpy(st->id, id);
221 return ded->states.indexOf(st);
222 }
223 */
224
DED_AddSprite(ded_t * ded,char const * name)225 int DED_AddSprite(ded_t* ded, char const* name)
226 {
227 ded_sprid_t *sp = ded->sprites.append();
228 strcpy(sp->id, name);
229 return ded->sprites.indexOf(sp);
230 }
231
DED_AddLight(ded_t * ded,char const * stateid)232 int DED_AddLight(ded_t* ded, char const* stateid)
233 {
234 ded_light_t* light = ded->lights.append();
235 strcpy(light->state, stateid);
236 return ded->lights.indexOf(light);
237 }
238
DED_AddSound(ded_t * ded,char const * id)239 int DED_AddSound(ded_t* ded, char const* id)
240 {
241 ded_sound_t* snd = ded->sounds.append();
242 strcpy(snd->id, id);
243 return ded->sounds.indexOf(snd);
244 }
245
DED_AddText(ded_t * ded,char const * id)246 int DED_AddText(ded_t* ded, char const* id)
247 {
248 ded_text_t* txt = ded->text.append();
249 strcpy(txt->id, id);
250 return ded->text.indexOf(txt);
251 }
252
DED_AddTextureEnv(ded_t * ded,char const * id)253 int DED_AddTextureEnv(ded_t* ded, char const* id)
254 {
255 ded_tenviron_t* env = ded->textureEnv.append();
256 strcpy(env->id, id);
257 return ded->textureEnv.indexOf(env);
258 }
259
DED_AddCompositeFont(ded_t * ded,char const * uri)260 int DED_AddCompositeFont(ded_t* ded, char const *uri)
261 {
262 ded_compositefont_t* cfont = ded->compositeFonts.append();
263 if (uri) cfont->uri = new de::Uri(uri, RC_NULL);
264 return ded->compositeFonts.indexOf(cfont);
265 }
266
DED_AddValue(ded_t * ded,char const * id)267 int DED_AddValue(ded_t* ded, char const* id)
268 {
269 ded_value_t* val = ded->values.append();
270 if (id)
271 {
272 val->id = (char *) M_Malloc(strlen(id) + 1);
273 strcpy(val->id, id);
274 }
275 return ded->values.indexOf(val);
276 }
277
DED_AddDetail(ded_t * ded,char const * lumpname)278 int DED_AddDetail(ded_t *ded, char const *lumpname)
279 {
280 ded_detailtexture_t *dtl = ded->details.append();
281
282 // Default usage is allowed with custom textures and external replacements.
283 dtl->flags = DTLF_PWAD|DTLF_EXTERNAL;
284
285 if (lumpname && lumpname[0])
286 {
287 dtl->stage.texture = new de::Uri(lumpname, RC_NULL);
288 }
289 dtl->stage.scale = 1;
290 dtl->stage.strength = 1;
291
292 return ded->details.indexOf(dtl);
293 }
294
DED_AddPtcGen(ded_t * ded,char const * state)295 int DED_AddPtcGen(ded_t* ded, char const* state)
296 {
297 ded_ptcgen_t* gen = ded->ptcGens.append();
298
299 strcpy(gen->state, state);
300
301 // Default choice (use either submodel zero or one).
302 gen->subModel = -1;
303
304 return ded->ptcGens.indexOf(gen);
305 }
306
DED_AddPtcGenStage(ded_ptcgen_t * gen)307 int DED_AddPtcGenStage(ded_ptcgen_t* gen)
308 {
309 ded_ptcstage_t* stage = gen->stages.append();
310
311 stage->model = -1;
312 stage->sound.volume = 1;
313 stage->hitSound.volume = 1;
314
315 return gen->stages.indexOf(stage);
316 }
317
DED_AddReflection(ded_t * ded)318 int DED_AddReflection(ded_t *ded)
319 {
320 ded_reflection_t *ref = ded->reflections.append();
321
322 // Default usage is allowed with custom textures and external replacements.
323 ref->flags = REFF_PWAD|REFF_EXTERNAL;
324
325 // Init to defaults.
326 ref->stage.shininess = 1.0f;
327 ref->stage.blendMode = BM_ADD;
328 ref->stage.maskWidth = 1.0f;
329 ref->stage.maskHeight = 1.0f;
330
331 return ded->reflections.indexOf(ref);
332 }
333
DED_AddGroup(ded_t * ded)334 int DED_AddGroup(ded_t* ded)
335 {
336 ded_group_t* group = ded->groups.append();
337 return ded->groups.indexOf(group);
338 }
339
DED_AddGroupMember(ded_group_t * grp)340 int DED_AddGroupMember(ded_group_t* grp)
341 {
342 ded_group_member_t* memb = grp->members.append();
343 return grp->members.indexOf(memb);
344 }
345
DED_AddSectorType(ded_t * ded,int id)346 int DED_AddSectorType(ded_t* ded, int id)
347 {
348 ded_sectortype_t* sec = ded->sectorTypes.append();
349 sec->id = id;
350 return ded->sectorTypes.indexOf(sec);
351 }
352
DED_AddLineType(ded_t * ded,int id)353 int DED_AddLineType(ded_t* ded, int id)
354 {
355 ded_linetype_t* li = ded->lineTypes.append();
356 li->id = id;
357 //li->actCount = -1;
358 return ded->lineTypes.indexOf(li);
359 }
360
getMobjNum(String const & id) const361 int ded_s::getMobjNum(String const &id) const
362 {
363 if (Record const *def = things.tryFind(defn::Definition::VAR_ID, id))
364 {
365 return def->geti(defn::Definition::VAR_ORDER);
366 }
367 /*
368 for (i = 0; i < mobjs.size(); ++i)
369 if (!qstricmp(mobjs[i].id, id))
370 return i;*/
371
372 return -1;
373 }
374
getMobjNumForName(const char * name) const375 int ded_s::getMobjNumForName(const char *name) const
376 {
377 if (!name || !name[0])
378 return -1;
379
380 /*
381 for (int i = mobjs.size() - 1; i >= 0; --i)
382 if (!qstricmp(mobjs[i].name, name))
383 return i;*/
384 if (Record const *def = things.tryFind("name", name))
385 {
386 return def->geti(defn::Definition::VAR_ORDER);
387 }
388
389 return -1;
390 }
391
getMobjName(int num) const392 String ded_s::getMobjName(int num) const
393 {
394 if (num < 0) return "(<0)";
395 if (num >= things.size()) return "(>mobjtypes)";
396 return things[num].gets(defn::Definition::VAR_ID);
397 }
398
getStateNum(String const & id) const399 int ded_s::getStateNum(String const &id) const
400 {
401 if (Record const *def = states.tryFind(defn::Definition::VAR_ID, id))
402 {
403 return def->geti(defn::Definition::VAR_ORDER);
404 }
405 return -1;
406 }
407
getStateNum(char const * id) const408 int ded_s::getStateNum(char const *id) const
409 {
410 return getStateNum(String(id));
411 }
412
evalFlags(char const * ptr) const413 dint ded_s::evalFlags(char const *ptr) const
414 {
415 LOG_AS("Defs::evalFlags");
416
417 dint value = 0;
418
419 while (*ptr)
420 {
421 ptr = M_SkipWhite(const_cast<char *>(ptr));
422
423 dint flagNameLength = M_FindWhite(const_cast<char *>(ptr)) - ptr;
424 String flagName(ptr, flagNameLength);
425 ptr += flagNameLength;
426
427 if (Record const *flag = flags.tryFind(defn::Definition::VAR_ID, flagName.toLower()))
428 {
429 value |= flag->geti("value");
430 }
431 else
432 {
433 LOG_RES_WARNING("Flag '%s' is not defined (or used out of context)") << flagName;
434 }
435 }
436 return value;
437 }
438
getEpisodeNum(String const & id) const439 int ded_s::getEpisodeNum(String const &id) const
440 {
441 if (Record const *def = episodes.tryFind(defn::Definition::VAR_ID, id))
442 {
443 return def->geti(defn::Definition::VAR_ORDER);
444 }
445 return -1;
446 }
447
getMapInfoNum(de::Uri const & uri) const448 int ded_s::getMapInfoNum(de::Uri const &uri) const
449 {
450 if (Record const *def = mapInfos.tryFind(defn::Definition::VAR_ID, uri.compose()))
451 {
452 return def->geti(defn::Definition::VAR_ORDER);
453 }
454 return -1; // Not found.
455 }
456
getMaterialNum(de::Uri const & uri) const457 int ded_s::getMaterialNum(de::Uri const &uri) const
458 {
459 if (uri.isEmpty()) return -1; // Not found.
460
461 if (uri.scheme().isEmpty())
462 {
463 // Caller doesn't care which scheme - use a priority search order.
464 de::Uri temp(uri);
465
466 temp.setScheme("Sprites");
467 int idx = getMaterialNum(temp);
468 if (idx >= 0) return idx;
469
470 temp.setScheme("Textures");
471 idx = getMaterialNum(temp);
472 if (idx >= 0) return idx;
473
474 temp.setScheme("Flats");
475 idx = getMaterialNum(temp);
476 /*if (idx >= 0)*/ return idx;
477 }
478
479 if (Record const *def = materials.tryFind(defn::Definition::VAR_ID, uri.compose()))
480 {
481 return def->geti(defn::Definition::VAR_ORDER);
482 }
483 return -1; // Not found.
484 }
485
getModelNum(const char * id) const486 int ded_s::getModelNum(const char *id) const
487 {
488 if (Record const *def = models.tryFind(defn::Definition::VAR_ID, id))
489 {
490 return def->geti(defn::Definition::VAR_ORDER);
491 }
492 return -1;
493
494 /* int idx = -1;
495 if (id && id[0] && !models.empty())
496 {
497 int i = 0;
498 do {
499 if (!qstricmp(models[i].id, id)) idx = i;
500 } while (idx == -1 && ++i < (int)models.size());
501 }
502 return idx;*/
503 }
504
getSkyNum(char const * id) const505 int ded_s::getSkyNum(char const *id) const
506 {
507 if (Record const *def = skies.tryFind(defn::Definition::VAR_ID, id))
508 {
509 return def->geti(defn::Definition::VAR_ORDER);
510 }
511 return -1;
512
513 /*if (!id || !id[0]) return -1;
514
515 for (int i = skies.size() - 1; i >= 0; i--)
516 {
517 if (!qstricmp(skies[i].id, id))
518 return i;
519 }
520 return -1;*/
521 }
522
getSoundNum(String const & id) const523 int ded_s::getSoundNum(String const &id) const
524 {
525 return getSoundNum(id.toUtf8());
526 }
527
getSoundNum(const char * id) const528 int ded_s::getSoundNum(const char *id) const
529 {
530 int idx = -1;
531 if (id && id[0] && sounds.size())
532 {
533 int i = 0;
534 do {
535 if (!qstricmp(sounds[i].id, id)) idx = i;
536 } while (idx == -1 && ++i < sounds.size());
537 }
538 return idx;
539 }
540
getSoundNumForName(const char * name) const541 int ded_s::getSoundNumForName(const char *name) const
542 {
543 if (!name || !name[0])
544 return -1;
545
546 for (int i = 0; i < sounds.size(); ++i)
547 if (!qstricmp(sounds[i].name, name))
548 return i;
549
550 return 0;
551 }
552
getSpriteNum(String const & id) const553 int ded_s::getSpriteNum(String const &id) const
554 {
555 return getSpriteNum(id.toLatin1());
556 }
557
getSpriteNum(char const * id) const558 int ded_s::getSpriteNum(char const *id) const
559 {
560 if (id && id[0])
561 {
562 for (dint i = 0; i < sprites.size(); ++i)
563 {
564 if (!qstricmp(sprites[i].id, id))
565 return i;
566 }
567 }
568 return -1; // Not found.
569 }
570
getMusicNum(char const * id) const571 int ded_s::getMusicNum(char const *id) const
572 {
573 if (Record const *def = musics.tryFind(defn::Definition::VAR_ID, id))
574 {
575 return def->geti(defn::Definition::VAR_ORDER);
576 }
577 return -1;
578
579 /*int idx = -1;
580 if (id && id[0] && musics.size())
581 {
582 int i = 0;
583 do {
584 if (!qstricmp(musics[i].id, id)) idx = i;
585 } while (idx == -1 && ++i < musics.size());
586 }
587 return idx;*/
588 }
589
getValueNum(char const * id) const590 int ded_s::getValueNum(char const *id) const
591 {
592 if (id && id[0])
593 {
594 // Read backwards to allow patching.
595 for (dint i = values.size() - 1; i >= 0; i--)
596 {
597 if (!qstricmp(values[i].id, id))
598 return i;
599 }
600 }
601 return -1; // Not found.
602 }
603
getValueNum(String const & id) const604 int ded_s::getValueNum(String const &id) const
605 {
606 return getValueNum(id.toLatin1());
607 }
608
getValueById(char const * id) const609 ded_value_t *ded_s::getValueById(char const *id) const
610 {
611 if (!id || !id[0]) return nullptr;
612
613 // Read backwards to allow patching.
614 for (dint i = values.size() - 1; i >= 0; i--)
615 {
616 if (!qstricmp(values[i].id, id))
617 return &values[i];
618 }
619 return nullptr;
620 }
getValueById(String const & id) const621 ded_value_t *ded_s::getValueById(String const &id) const
622 {
623 return getValueById(id.toLatin1());
624 }
625
getValueByUri(de::Uri const & uri) const626 ded_value_t *ded_s::getValueByUri(de::Uri const &uri) const
627 {
628 if (!uri.scheme().compareWithoutCase("Values"))
629 {
630 return getValueById(uri.pathCStr());
631 }
632 return nullptr;
633 }
634
findCompositeFontDef(de::Uri const & uri) const635 ded_compositefont_t *ded_s::findCompositeFontDef(de::Uri const &uri) const
636 {
637 for (dint i = compositeFonts.size() - 1; i >= 0; i--)
638 {
639 ded_compositefont_t *def = &compositeFonts[i];
640 if (def->uri && uri == *def->uri)
641 {
642 return def;
643 }
644 }
645 return nullptr;
646 }
647
getCompositeFont(char const * uriCString) const648 ded_compositefont_t *ded_s::getCompositeFont(char const *uriCString) const
649 {
650 ded_compositefont_t *def = nullptr;
651 if (uriCString && uriCString[0])
652 {
653 de::Uri uri(uriCString, RC_NULL);
654
655 if (uri.scheme().isEmpty())
656 {
657 // Caller doesn't care which scheme - use a priority search order.
658 de::Uri temp(uri);
659
660 temp.setScheme("Game");
661 def = findCompositeFontDef(temp);
662 if (!def)
663 {
664 temp.setScheme("System");
665 def = findCompositeFontDef(temp);
666 }
667 }
668
669 if (!def)
670 {
671 def = findCompositeFontDef(uri);
672 }
673 }
674 return def;
675 }
676
findEpisode(String const & mapId) const677 String ded_s::findEpisode(String const &mapId) const
678 {
679 de::Uri mapUri(mapId, RC_NULL);
680 if (mapUri.scheme().isEmpty()) mapUri.setScheme("Maps");
681
682 for (int i = 0; i < episodes.size(); ++i)
683 {
684 defn::Episode episode(episodes[i]);
685 if (episode.tryFindMapGraphNode(mapUri.compose()))
686 {
687 return episode.gets(defn::Definition::VAR_ID);
688 }
689 }
690 return String();
691 }
692
getTextNum(char const * id) const693 int ded_s::getTextNum(char const *id) const
694 {
695 if (id && id[0])
696 {
697 // Search in reverse insertion order to allow patching.
698 for (int i = text.size() - 1; i >= 0; i--)
699 {
700 if (!qstricmp(text[i].id, id)) return i;
701 }
702 }
703 return -1; // Not found.
704 }
705
706 static ded_t *s_defs = nullptr;
707
DED_DestroyDefinitions()708 void DED_DestroyDefinitions()
709 {
710 delete s_defs;
711 s_defs = nullptr;
712 }
713
DED_Definitions()714 ded_t *DED_Definitions()
715 {
716 if (!s_defs)
717 {
718 s_defs = new ded_t;
719 }
720 return s_defs;
721 }
722