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