1 /** @file importudmf.cpp  Importer plugin for UDMF maps.
2  *
3  * @authors Copyright (c) 2016-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  *
5  * @par License
6  * GPL: http://www.gnu.org/licenses/gpl.html
7  *
8  * <small>This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by the
10  * Free Software Foundation; either version 2 of the License, or (at your
11  * option) any later version. This program is distributed in the hope that it
12  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
14  * Public License for more details. You should have received a copy of the GNU
15  * General Public License along with this program; if not, write to the Free
16  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17  * 02110-1301 USA</small>
18  */
19 
20 #include "importudmf.h"
21 #include "udmfparser.h"
22 
23 #include <doomsday/filesys/lumpindex.h>
24 #include <gamefw/mapspot.h>
25 #include <de/App>
26 #include <de/Log>
27 
28 using namespace de;
29 
30 template <valuetype_t VALUE_TYPE, typename Type>
gmoSetThingProperty(int index,char const * propertyId,Type value)31 void gmoSetThingProperty(int index, char const *propertyId, Type value)
32 {
33     MPE_GameObjProperty("Thing", index, propertyId, VALUE_TYPE, &value);
34 }
35 
36 template <valuetype_t VALUE_TYPE, typename Type>
gmoSetSectorProperty(int index,char const * propertyId,Type value)37 void gmoSetSectorProperty(int index, char const *propertyId, Type value)
38 {
39     MPE_GameObjProperty("XSector", index, propertyId, VALUE_TYPE, &value);
40 }
41 
42 template <valuetype_t VALUE_TYPE, typename Type>
gmoSetLineProperty(int index,char const * propertyId,Type value)43 void gmoSetLineProperty(int index, char const *propertyId, Type value)
44 {
45     MPE_GameObjProperty("XLinedef", index, propertyId, VALUE_TYPE, &value);
46 }
47 
48 /**
49  * This function will be called when Doomsday is asked to load a map that is not
50  * available in its native map format.
51  *
52  * Our job is to read in the map data structures then use the Doomsday map editing
53  * interface to recreate the map in native format.
54  */
importMapHook(int,int,void * context)55 static int importMapHook(int /*hookType*/, int /*parm*/, void *context)
56 {
57     if (Id1MapRecognizer const *recognizer = reinterpret_cast<Id1MapRecognizer *>(context))
58     {
59         if (recognizer->format() == Id1MapRecognizer::UniversalFormat)
60         {
61             LOG_AS("importudmf");
62             try
63             {
64                 // Read the contents of the TEXTMAP lump.
65                 auto *src = recognizer->lumps()[Id1MapRecognizer::UDMFTextmapData];
66                 Block bytes(src->size());
67                 src->read(bytes.data(), false);
68 
69                 // Parse the UDMF source and use the MPE API to create the map elements.
70                 UDMFParser parser;
71 
72                 struct ImportState
73                 {
74                     bool isHexen = false;
75                     bool isDoom64 = false;
76 
77                     int thingCount = 0;
78                     int vertexCount = 0;
79                     int sectorCount = 0;
80 
81                     QList<UDMFParser::Block> linedefs;
82                     QList<UDMFParser::Block> sidedefs;
83                 };
84                 ImportState importState;
85 
86                 parser.setGlobalAssignmentHandler([&importState] (String const &ident, QVariant const &value)
87                 {
88                     if (ident == UDMFLex::NAMESPACE)
89                     {
90                         LOG_MAP_VERBOSE("UDMF namespace: %s") << value.toString();
91                         String const ns = value.toString().toLower();
92                         if (ns == "hexen")
93                         {
94                             importState.isHexen = true;
95                         }
96                         else if (ns == "doom64")
97                         {
98                             importState.isDoom64 = true;
99                         }
100                     }
101                 });
102 
103                 parser.setBlockHandler([&importState] (String const &type, UDMFParser::Block const &block)
104                 {
105                     if (type == UDMFLex::THING)
106                     {
107                         int const index = importState.thingCount++;
108 
109                         // Properties common to all games.
110                         gmoSetThingProperty<DDVT_DOUBLE>(index, "X", block["x"].toDouble());
111                         gmoSetThingProperty<DDVT_DOUBLE>(index, "Y", block["y"].toDouble());
112                         gmoSetThingProperty<DDVT_DOUBLE>(index, "Z", block["z"].toDouble());
113                         gmoSetThingProperty<DDVT_ANGLE>(index, "Angle", angle_t(double(block["angle"].toInt()) / 180.0 * ANGLE_180));
114                         gmoSetThingProperty<DDVT_INT>(index, "DoomEdNum", block["type"].toInt());
115 
116                         // Map spot flags.
117                         {
118                             gfw_mapspot_flags_t gfwFlags = 0;
119 
120                             if (block["ambush"].toBool())      gfwFlags |= GFW_MAPSPOT_DEAF;
121                             if (block["single"].toBool())      gfwFlags |= GFW_MAPSPOT_SINGLE;
122                             if (block["dm"].toBool())          gfwFlags |= GFW_MAPSPOT_DM;
123                             if (block["coop"].toBool())        gfwFlags |= GFW_MAPSPOT_COOP;
124                             if (block["friend"].toBool())      gfwFlags |= GFW_MAPSPOT_MBF_FRIEND;
125                             if (block["dormant"].toBool())     gfwFlags |= GFW_MAPSPOT_DORMANT;
126                             if (block["class1"].toBool())      gfwFlags |= GFW_MAPSPOT_CLASS1;
127                             if (block["class2"].toBool())      gfwFlags |= GFW_MAPSPOT_CLASS2;
128                             if (block["class3"].toBool())      gfwFlags |= GFW_MAPSPOT_CLASS3;
129                             if (block["standing"].toBool())    gfwFlags |= GFW_MAPSPOT_STANDING;
130                             if (block["strifeally"].toBool())  gfwFlags |= GFW_MAPSPOT_STRIFE_ALLY;
131                             if (block["translucent"].toBool()) gfwFlags |= GFW_MAPSPOT_TRANSLUCENT;
132                             if (block["invisible"].toBool())   gfwFlags |= GFW_MAPSPOT_INVISIBLE;
133 
134                             gmoSetThingProperty<DDVT_INT>(index, "Flags",
135                                     gfw_MapSpot_TranslateFlagsToInternal(gfwFlags));
136                         }
137 
138                         // Skill level bits.
139                         {
140                             static String const labels[5] = {
141                                 "skill1", "skill2", "skill3", "skill4", "skill5",
142                             };
143                             int skillModes = 0;
144                             for (int skill = 0; skill < 5; ++skill)
145                             {
146                                 if (block[labels[skill]].toBool())
147                                     skillModes |= 1 << skill;
148                             }
149                             gmoSetThingProperty<DDVT_INT>(index, "SkillModes", skillModes);
150                         }
151 
152                         if (importState.isHexen || importState.isDoom64)
153                         {
154                             gmoSetThingProperty<DDVT_INT>(index, "ID", block["id"].toInt());
155                         }
156                         if (importState.isHexen)
157                         {
158                             gmoSetThingProperty<DDVT_INT>(index, "Special", block["special"].toInt());
159                             gmoSetThingProperty<DDVT_INT>(index, "Arg0", block["arg0"].toInt());
160                             gmoSetThingProperty<DDVT_INT>(index, "Arg1", block["arg1"].toInt());
161                             gmoSetThingProperty<DDVT_INT>(index, "Arg2", block["arg2"].toInt());
162                             gmoSetThingProperty<DDVT_INT>(index, "Arg3", block["arg3"].toInt());
163                             gmoSetThingProperty<DDVT_INT>(index, "Arg4", block["arg4"].toInt());
164                         }
165                     }
166                     else if (type == UDMFLex::VERTEX)
167                     {
168                         int const index = importState.vertexCount++;
169 
170                         MPE_VertexCreate(block["x"].toDouble(), block["y"].toDouble(), index);
171                     }
172                     else if (type == UDMFLex::LINEDEF)
173                     {
174                         importState.linedefs.append(block);
175                     }
176                     else if (type == UDMFLex::SIDEDEF)
177                     {
178                         importState.sidedefs.append(block);
179                     }
180                     else if (type == UDMFLex::SECTOR)
181                     {
182                         const int index = importState.sectorCount++;
183                         const int lightlevel = block.contains("lightlevel")? block["lightlevel"].toInt() : 160;
184                         const struct de_api_sector_hacks_s hacks{{0, 0}, -1};
185 
186                         MPE_SectorCreate(float(lightlevel)/255.f, 1.f, 1.f, 1.f, &hacks, index);
187 
188                         MPE_PlaneCreate(index,
189                                         block["heightfloor"].toDouble(),
190                                         de::Str("Flats:" + block["texturefloor"].toString()),
191                                         0.f, 0.f,
192                                         1.f, 1.f, 1.f,  // color
193                                         1.f,            // opacity
194                                         0, 0, 1.f,      // normal
195                                         -1);            // index in archive
196 
197                         MPE_PlaneCreate(index,
198                                         block["heightceiling"].toDouble(),
199                                         de::Str("Flats:" + block["textureceiling"].toString()),
200                                         0.f, 0.f,
201                                         1.f, 1.f, 1.f,  // color
202                                         1.f,            // opacity
203                                         0, 0, -1.f,     // normal
204                                         -1);            // index in archive
205 
206                         gmoSetSectorProperty<DDVT_INT>(index, "Type", block["special"].toInt());
207                         gmoSetSectorProperty<DDVT_INT>(index, "Tag",  block["id"].toInt());
208                     }
209                 });
210 
211                 parser.parse(String::fromUtf8(bytes));
212 
213                 // Now that all the linedefs and sidedefs are read, let's create them.
214                 for (int index = 0; index < importState.linedefs.size(); ++index)
215                 {
216                     UDMFParser::Block const &linedef = importState.linedefs.at(index);
217 
218                     int sidefront = linedef["sidefront"].toInt();
219                     int sideback  = linedef.contains("sideback")? linedef["sideback"].toInt() : -1;
220 
221                     UDMFParser::Block const &front = importState.sidedefs.at(sidefront);
222                     UDMFParser::Block const *back  =
223                             (sideback >= 0? &importState.sidedefs.at(sideback) : nullptr);
224 
225                     int frontSectorIdx = front["sector"].toInt();
226                     int backSectorIdx  = back? (*back)["sector"].toInt() : -1;
227 
228                     // Line flags.
229                     int ddLineFlags = 0;
230                     short sideFlags = 0;
231                     {
232                         bool const blocking      = linedef["blocking"].toBool();
233                         bool const dontpegtop    = linedef["dontpegtop"].toBool();
234                         bool const dontpegbottom = linedef["dontpegbottom"].toBool();
235                         bool const twosided      = linedef["twosided"].toBool();
236 
237                         if (blocking)      ddLineFlags |= DDLF_BLOCKING;
238                         if (dontpegtop)    ddLineFlags |= DDLF_DONTPEGTOP;
239                         if (dontpegbottom) ddLineFlags |= DDLF_DONTPEGBOTTOM;
240 
241                         if (!twosided && back)
242                         {
243                             sideFlags |= SDF_SUPPRESS_BACK_SECTOR;
244                         }
245                     }
246 
247                     MPE_LineCreate(linedef["v1"].toInt(),
248                                    linedef["v2"].toInt(),
249                                    frontSectorIdx,
250                                    backSectorIdx,
251                                    ddLineFlags,
252                                    index);
253 
254                     auto texName = [] (QVariant tex) -> String {
255                         if (tex.toString().isEmpty()) return String();
256                         return "Textures:" + tex.toString();
257                     };
258 
259                     auto addSide = [&texName, sideFlags](
260                                        int index, const UDMFParser::Block &side, int sideIndex)
261                     {
262                         const int offsetx = side["offsetx"].toInt();
263                         const int offsety = side["offsety"].toInt();
264                         float     opacity = 1.f;
265 
266                         const auto topTex = texName(side["texturetop"]   ).toUtf8();
267                         const auto midTex = texName(side["texturemiddle"]).toUtf8();
268                         const auto botTex = texName(side["texturebottom"]).toUtf8();
269 
270                         struct de_api_side_section_s top = {
271                             topTex,
272                             {float(offsetx), float(offsety)},
273                             {1, 1, 1, 1}
274                         };
275 
276                         struct de_api_side_section_s mid = {
277                             midTex,
278                             {float(offsetx), float(offsety)},
279                             {1, 1, 1, opacity}
280                         };
281 
282                         struct de_api_side_section_s bot = {
283                             botTex,
284                             {float(offsetx), float(offsety)},
285                             {1, 1, 1, 1}
286                         };
287 
288                         MPE_LineAddSide(
289                             index,
290                             0 /* front */,
291                             sideFlags,
292                             &top,
293                             &mid,
294                             &bot,
295                             sideIndex);
296                     };
297 
298                     // Front side.
299                     addSide(index, front, sidefront);
300 
301                     // Back side.
302                     if (back)
303                     {
304                         addSide(index, *back, sideback);
305                     }
306 
307                     // More line flags.
308                     {
309                         short flags = 0;
310 
311                         // TODO: Check all the flags.
312 
313                         gmoSetLineProperty<DDVT_SHORT>(index, "Flags", flags);
314                     }
315 
316                     gmoSetLineProperty<DDVT_INT>(index, "Type", linedef["special"].toInt());
317 
318                     if (!importState.isHexen)
319                     {
320                         gmoSetLineProperty<DDVT_INT>(index, "Tag",
321                                                      linedef.contains("id")?
322                                                          linedef["id"].toInt() : -1);
323                     }
324                     if (importState.isHexen)
325                     {
326                         gmoSetLineProperty<DDVT_INT>(index, "Arg0", linedef["arg0"].toInt());
327                         gmoSetLineProperty<DDVT_INT>(index, "Arg1", linedef["arg1"].toInt());
328                         gmoSetLineProperty<DDVT_INT>(index, "Arg2", linedef["arg2"].toInt());
329                         gmoSetLineProperty<DDVT_INT>(index, "Arg3", linedef["arg3"].toInt());
330                         gmoSetLineProperty<DDVT_INT>(index, "Arg4", linedef["arg4"].toInt());
331                     }
332                 }
333                 LOG_MAP_WARNING("Loading UDMF maps is an experimental feature");
334                 return true;
335             }
336             catch (Error const &er)
337             {
338                 LOG_MAP_ERROR("Error while loading UDMF: ") << er.asText();
339             }
340         }
341     }
342     return false;
343 }
344 
345 /**
346  * This function is called automatically when the plugin is loaded.
347  * We let the engine know what we'd like to do.
348  */
DP_Initialize()349 DENG_ENTRYPOINT void DP_Initialize()
350 {
351     Plug_AddHook(HOOK_MAP_CONVERT, importMapHook);
352 }
353 
354 /**
355  * Declares the type of the plugin so the engine knows how to treat it. Called
356  * automatically when the plugin is loaded.
357  */
deng_LibraryType()358 DENG_ENTRYPOINT char const *deng_LibraryType()
359 {
360     return "deng-plugin/generic";
361 }
362 
363 #if defined (DENG_STATIC_LINK)
364 
staticlib_importudmf_symbol(char const * name)365 DENG_EXTERN_C void *staticlib_importudmf_symbol(char const *name)
366 {
367     DENG_SYMBOL_PTR(name, deng_LibraryType)
368     DENG_SYMBOL_PTR(name, DP_Initialize);
369     qWarning() << name << "not found in importudmf";
370     return nullptr;
371 }
372 
373 #else
374 
375 DENG_DECLARE_API(Map);
376 DENG_DECLARE_API(Material);
377 DENG_DECLARE_API(MPE);
378 
379 DENG_API_EXCHANGE(
380     DENG_GET_API(DE_API_MAP, Map);
381     DENG_GET_API(DE_API_MATERIALS, Material);
382     DENG_GET_API(DE_API_MAP_EDIT, MPE);
383 )
384 
385 #endif
386