1/**
2 * The Forgotten Server - a free and open-source MMORPG server emulator
3 * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "otpch.h"
21
22#include "iomap.h"
23
24#include "bed.h"
25
26/*
27 OTBM_ROOTV1
28 |
29 |--- OTBM_MAP_DATA
30 | |
31 | |--- OTBM_TILE_AREA
32 | | |--- OTBM_TILE
33 | | |--- OTBM_TILE_SQUARE (not implemented)
34 | | |--- OTBM_TILE_REF (not implemented)
35 | | |--- OTBM_HOUSETILE
36 | |
37 | |--- OTBM_SPAWNS (not implemented)
38 | | |--- OTBM_SPAWN_AREA (not implemented)
39 | | |--- OTBM_MONSTER (not implemented)
40 | |
41 | |--- OTBM_TOWNS
42 | | |--- OTBM_TOWN
43 | |
44 | |--- OTBM_WAYPOINTS
45 | |--- OTBM_WAYPOINT
46 |
47 |--- OTBM_ITEM_DEF (not implemented)
48*/
49
50Tile* IOMap::createTile(Item*& ground, Item* item, uint16_t x, uint16_t y, uint8_t z)
51{
52 if (!ground) {
53 return new StaticTile(x, y, z);
54 }
55
56 Tile* tile;
57 if ((item && item->isBlocking()) || ground->isBlocking()) {
58 tile = new StaticTile(x, y, z);
59 } else {
60 tile = new DynamicTile(x, y, z);
61 }
62
63 tile->internalAddThing(ground);
64 ground->startDecaying();
65 ground = nullptr;
66 return tile;
67}
68
69bool IOMap::loadMap(Map* map, const std::string& fileName)
70{
71 int64_t start = OTSYS_TIME();
72 OTB::Loader loader{fileName, OTB::Identifier{{'O', 'T', 'B', 'M'}}};
73 auto& root = loader.parseTree();
74
75 PropStream propStream;
76 if (!loader.getProps(root, propStream)) {
77 setLastErrorString("Could not read root property.");
78 return false;
79 }
80
81 OTBM_root_header root_header;
82 if (!propStream.read(root_header)) {
83 setLastErrorString("Could not read header.");
84 return false;
85 }
86
87 uint32_t headerVersion = root_header.version;
88 if (headerVersion == 0) {
89 //In otbm version 1 the count variable after splashes/fluidcontainers and stackables
90 //are saved as attributes instead, this solves alot of problems with items
91 //that is changed (stackable/charges/fluidcontainer/splash) during an update.
92 setLastErrorString("This map need to be upgraded by using the latest map editor version to be able to load correctly.");
93 return false;
94 }
95
96 if (headerVersion > 2) {
97 setLastErrorString("Unknown OTBM version detected.");
98 return false;
99 }
100
101 if (root_header.majorVersionItems < 3) {
102 setLastErrorString("This map need to be upgraded by using the latest map editor version to be able to load correctly.");
103 return false;
104 }
105
106 if (root_header.majorVersionItems > Item::items.majorVersion) {
107 setLastErrorString("The map was saved with a different items.otb version, an upgraded items.otb is required.");
108 return false;
109 }
110
111 if (root_header.minorVersionItems < CLIENT_VERSION_810) {
112 setLastErrorString("This map needs to be updated.");
113 return false;
114 }
115
116 if (root_header.minorVersionItems > Item::items.minorVersion) {
117 std::cout << "[Warning - IOMap::loadMap] This map needs an updated items.otb." << std::endl;
118 }
119
120 std::cout << "> Map size: " << root_header.width << "x" << root_header.height << '.' << std::endl;
121 map->width = root_header.width;
122 map->height = root_header.height;
123
124 if (root.children.size() != 1 || root.children[0].type != OTBM_MAP_DATA) {
125 setLastErrorString("Could not read data node.");
126 return false;
127 }
128
129 auto& mapNode = root.children[0];
130 if (!parseMapDataAttributes(loader, mapNode, *map, fileName)) {
131 return false;
132 }
133
134 for (auto& mapDataNode : mapNode.children) {
135 if (mapDataNode.type == OTBM_TILE_AREA) {
136 if (!parseTileArea(loader, mapDataNode, *map)) {
137 return false;
138 }
139 } else if (mapDataNode.type == OTBM_TOWNS) {
140 if (!parseTowns(loader, mapDataNode, *map)) {
141 return false;
142 }
143 } else if (mapDataNode.type == OTBM_WAYPOINTS && headerVersion > 1) {
144 if (!parseWaypoints(loader, mapDataNode, *map)) {
145 return false;
146 }
147 } else {
148 setLastErrorString("Unknown map node.");
149 return false;
150 }
151 }
152
153 std::cout << "> Map loading time: " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl;
154 return true;
155}
156
157bool IOMap::parseMapDataAttributes(OTB::Loader& loader, const OTB::Node& mapNode, Map& map, const std::string& fileName)
158{
159 PropStream propStream;
160 if (!loader.getProps(mapNode, propStream)) {
161 setLastErrorString("Could not read map data attributes.");
162 return false;
163 }
164
165 std::string mapDescription;
166 std::string tmp;
167
168 uint8_t attribute;
169 while (propStream.read<uint8_t>(attribute)) {
170 switch (attribute) {
171 case OTBM_ATTR_DESCRIPTION:
172 if (!propStream.readString(mapDescription)) {
173 setLastErrorString("Invalid description tag.");
174 return false;
175 }
176 break;
177
178 case OTBM_ATTR_EXT_SPAWN_FILE:
179 if (!propStream.readString(tmp)) {
180 setLastErrorString("Invalid spawn tag.");
181 return false;
182 }
183
184 map.spawnfile = fileName.substr(0, fileName.rfind('/') + 1);
185 map.spawnfile += tmp;
186 break;
187
188 case OTBM_ATTR_EXT_HOUSE_FILE:
189 if (!propStream.readString(tmp)) {
190 setLastErrorString("Invalid house tag.");
191 return false;
192 }
193
194 map.housefile = fileName.substr(0, fileName.rfind('/') + 1);
195 map.housefile += tmp;
196 break;
197
198 default:
199 setLastErrorString("Unknown header node.");
200 return false;
201 }
202 }
203 return true;
204}
205
206bool IOMap::parseTileArea(OTB::Loader& loader, const OTB::Node& tileAreaNode, Map& map)
207{
208 PropStream propStream;
209 if (!loader.getProps(tileAreaNode, propStream)) {
210 setLastErrorString("Invalid map node.");
211 return false;
212 }
213
214 OTBM_Destination_coords area_coord;
215 if (!propStream.read(area_coord)) {
216 setLastErrorString("Invalid map node.");
217 return false;
218 }
219
220 uint16_t base_x = area_coord.x;
221 uint16_t base_y = area_coord.y;
222 uint16_t z = area_coord.z;
223
224 for (auto& tileNode : tileAreaNode.children) {
225 if (tileNode.type != OTBM_TILE && tileNode.type != OTBM_HOUSETILE) {
226 setLastErrorString("Unknown tile node.");
227 return false;
228 }
229
230 if (!loader.getProps(tileNode, propStream)) {
231 setLastErrorString("Could not read node data.");
232 return false;
233 }
234
235 OTBM_Tile_coords tile_coord;
236 if (!propStream.read(tile_coord)) {
237 setLastErrorString("Could not read tile position.");
238 return false;
239 }
240
241 uint16_t x = base_x + tile_coord.x;
242 uint16_t y = base_y + tile_coord.y;
243
244 bool isHouseTile = false;
245 House* house = nullptr;
246 Tile* tile = nullptr;
247 Item* ground_item = nullptr;
248 uint32_t tileflags = TILESTATE_NONE;
249
250 if (tileNode.type == OTBM_HOUSETILE) {
251 uint32_t houseId;
252 if (!propStream.read<uint32_t>(houseId)) {
253 std::ostringstream ss;
254 ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Could not read house id.";
255 setLastErrorString(ss.str());
256 return false;
257 }
258
259 house = map.houses.addHouse(houseId);
260 if (!house) {
261 std::ostringstream ss;
262 ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Could not create house id: " << houseId;
263 setLastErrorString(ss.str());
264 return false;
265 }
266
267 tile = new HouseTile(x, y, z, house);
268 house->addTile(static_cast<HouseTile*>(tile));
269 isHouseTile = true;
270 }
271
272 uint8_t attribute;
273 //read tile attributes
274 while (propStream.read<uint8_t>(attribute)) {
275 switch (attribute) {
276 case OTBM_ATTR_TILE_FLAGS: {
277 uint32_t flags;
278 if (!propStream.read<uint32_t>(flags)) {
279 std::ostringstream ss;
280 ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to read tile flags.";
281 setLastErrorString(ss.str());
282 return false;
283 }
284
285 if ((flags & OTBM_TILEFLAG_PROTECTIONZONE) != 0) {
286 tileflags |= TILESTATE_PROTECTIONZONE;
287 } else if ((flags & OTBM_TILEFLAG_NOPVPZONE) != 0) {
288 tileflags |= TILESTATE_NOPVPZONE;
289 } else if ((flags & OTBM_TILEFLAG_PVPZONE) != 0) {
290 tileflags |= TILESTATE_PVPZONE;
291 }
292
293 if ((flags & OTBM_TILEFLAG_NOLOGOUT) != 0) {
294 tileflags |= TILESTATE_NOLOGOUT;
295 }
296 break;
297 }
298
299 case OTBM_ATTR_ITEM: {
300 Item* item = Item::CreateItem(propStream);
301 if (!item) {
302 std::ostringstream ss;
303 ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to create item.";
304 setLastErrorString(ss.str());
305 return false;
306 }
307
308 if (isHouseTile && item->isMoveable()) {
309 std::cout << "[Warning - IOMap::loadMap] Moveable item with ID: " << item->getID() << ", in house: " << house->getId() << ", at position [x: " << x << ", y: " << y << ", z: " << z << "]." << std::endl;
310 delete item;
311 } else {
312 if (item->getItemCount() == 0) {
313 item->setItemCount(1);
314 }
315
316 if (tile) {
317 tile->internalAddThing(item);
318 item->startDecaying();
319 item->setLoadedFromMap(true);
320 } else if (item->isGroundTile()) {
321 delete ground_item;
322 ground_item = item;
323 } else {
324 tile = createTile(ground_item, item, x, y, z);
325 tile->internalAddThing(item);
326 item->startDecaying();
327 item->setLoadedFromMap(true);
328 }
329 }
330 break;
331 }
332
333 default:
334 std::ostringstream ss;
335 ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Unknown tile attribute.";
336 setLastErrorString(ss.str());
337 return false;
338 }
339 }
340
341 for (auto& itemNode : tileNode.children) {
342 if (itemNode.type != OTBM_ITEM) {
343 std::ostringstream ss;
344 ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Unknown node type.";
345 setLastErrorString(ss.str());
346 return false;
347 }
348
349 PropStream stream;
350 if (!loader.getProps(itemNode, stream)) {
351 setLastErrorString("Invalid item node.");
352 return false;
353 }
354
355 Item* item = Item::CreateItem(stream);
356 if (!item) {
357 std::ostringstream ss;
358 ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to create item.";
359 setLastErrorString(ss.str());
360 return false;
361 }
362
363 if (!item->unserializeItemNode(loader, itemNode, stream)) {
364 std::ostringstream ss;
365 ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to load item " << item->getID() << '.';
366 setLastErrorString(ss.str());
367 delete item;
368 return false;
369 }
370
371 if (isHouseTile && item->isMoveable()) {
372 std::cout << "[Warning - IOMap::loadMap] Moveable item with ID: " << item->getID() << ", in house: " << house->getId() << ", at position [x: " << x << ", y: " << y << ", z: " << z << "]." << std::endl;
373 delete item;
374 } else {
375 if (item->getItemCount() == 0) {
376 item->setItemCount(1);
377 }
378
379 if (tile) {
380 tile->internalAddThing(item);
381 item->startDecaying();
382 item->setLoadedFromMap(true);
383 } else if (item->isGroundTile()) {
384 delete ground_item;
385 ground_item = item;
386 } else {
387 tile = createTile(ground_item, item, x, y, z);
388 tile->internalAddThing(item);
389 item->startDecaying();
390 item->setLoadedFromMap(true);
391 }
392 }
393 }
394
395 if (!tile) {
396 tile = createTile(ground_item, nullptr, x, y, z);
397 }
398
399 tile->setFlag(static_cast<tileflags_t>(tileflags));
400
401 map.setTile(x, y, z, tile);
402 }
403 return true;
404}
405
406bool IOMap::parseTowns(OTB::Loader& loader, const OTB::Node& townsNode, Map& map)
407{
408 for (auto& townNode : townsNode.children) {
409 PropStream propStream;
410 if (townNode.type != OTBM_TOWN) {
411 setLastErrorString("Unknown town node.");
412 return false;
413 }
414
415 if (!loader.getProps(townNode, propStream)) {
416 setLastErrorString("Could not read town data.");
417 return false;
418 }
419
420 uint32_t townId;
421 if (!propStream.read<uint32_t>(townId)) {
422 setLastErrorString("Could not read town id.");
423 return false;
424 }
425
426 Town* town = map.towns.getTown(townId);
427 if (!town) {
428 town = new Town(townId);
429 map.towns.addTown(townId, town);
430 }
431
432 std::string townName;
433 if (!propStream.readString(townName)) {
434 setLastErrorString("Could not read town name.");
435 return false;
436 }
437
438 town->setName(townName);
439
440 OTBM_Destination_coords town_coords;
441 if (!propStream.read(town_coords)) {
442 setLastErrorString("Could not read town coordinates.");
443 return false;
444 }
445
446 town->setTemplePos(Position(town_coords.x, town_coords.y, town_coords.z));
447 }
448 return true;
449}
450
451
452bool IOMap::parseWaypoints(OTB::Loader& loader, const OTB::Node& waypointsNode, Map& map)
453{
454 PropStream propStream;
455 for (auto& node : waypointsNode.children) {
456 if (node.type != OTBM_WAYPOINT) {
457 setLastErrorString("Unknown waypoint node.");
458 return false;
459 }
460
461 if (!loader.getProps(node, propStream)) {
462 setLastErrorString("Could not read waypoint data.");
463 return false;
464 }
465
466 std::string name;
467 if (!propStream.readString(name)) {
468 setLastErrorString("Could not read waypoint name.");
469 return false;
470 }
471
472 OTBM_Destination_coords waypoint_coords;
473 if (!propStream.read(waypoint_coords)) {
474 setLastErrorString("Could not read waypoint coordinates.");
475 return false;
476 }
477
478 map.waypoints[name] = Position(waypoint_coords.x, waypoint_coords.y, waypoint_coords.z);
479 }
480 return true;
481}
482
483