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