1//////////////////////////////////////////////////////////////////////
2// This file is part of Remere's Map Editor
3//////////////////////////////////////////////////////////////////////
4// Remere's Map Editor is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Remere's Map Editor is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//////////////////////////////////////////////////////////////////////
17
18#include "main.h"
19
20#include <wx/wfstream.h>
21#include <wx/tarstrm.h>
22#include <wx/zstream.h>
23#include <wx/mstream.h>
24#include <wx/datstrm.h>
25
26#include "settings.h"
27#include "gui.h" // Loadbar
28
29#include "creatures.h"
30#include "creature.h"
31#include "map.h"
32#include "tile.h"
33#include "item.h"
34#include "complexitem.h"
35#include "town.h"
36#include "wall_brush.h"
37
38#include "iomap_otbm.h"
39
40typedef uint8_t attribute_t;
41typedef uint32_t flags_t;
42
43// H4X
44void reform(Map* map, Tile* tile, Item* item) {
45 /*
46 int aid = item->getActionID();
47 int id = item->getID();
48 int uid = item->getUniqueID();
49
50 if(item->isDoor()) {
51 item->eraseAttribute("aid");
52 item->setAttribute("keyid", aid);
53 }
54
55 if((item->isDoor()) && tile && tile->getHouseID()) {
56 Door* self = static_cast<Door*>(item);
57 House* house = map->houses.getHouse(tile->getHouseID());
58 self->setDoorID(house->getEmptyDoorID());
59 }
60 */
61}
62
63// ============================================================================
64// Item
65
66Item* Item::Create_OTBM(const IOMap& maphandle, BinaryNode* stream) {
67 uint16_t _id;
68 if (!stream->getU16(_id)) {
69 return nullptr;
70 }
71
72 uint8_t _count = 0;
73
74 const ItemType& iType = g_items[_id];
75 if (maphandle.version.otbm == MAP_OTBM_1) {
76 if (iType.stackable || iType.isSplash() || iType.isFluidContainer()) {
77 stream->getU8(_count);
78 }
79 }
80 return Item::Create(_id, _count);
81}
82
83bool Item::readItemAttribute_OTBM(const IOMap& maphandle, OTBM_ItemAttribute attr, BinaryNode* stream) {
84 switch (attr) {
85 case OTBM_ATTR_COUNT: {
86 uint8_t subtype;
87 if (!stream->getU8(subtype)) {
88 return false;
89 }
90 setSubtype(subtype);
91 break;
92 }
93 case OTBM_ATTR_ACTION_ID: {
94 uint16_t aid;
95 if (!stream->getU16(aid)) {
96 return false;
97 }
98 setActionID(aid);
99 break;
100 }
101 case OTBM_ATTR_UNIQUE_ID: {
102 uint16_t uid;
103 if (!stream->getU16(uid)) {
104 return false;
105 }
106 setUniqueID(uid);
107 break;
108 }
109 case OTBM_ATTR_CHARGES: {
110 uint16_t charges;
111 if (!stream->getU16(charges)) {
112 return false;
113 }
114 setSubtype(charges);
115 break;
116 }
117 case OTBM_ATTR_TEXT: {
118 std::string text;
119 if (!stream->getString(text)) {
120 return false;
121 }
122 setText(text);
123 break;
124 }
125 case OTBM_ATTR_DESC: {
126 std::string text;
127 if (!stream->getString(text)) {
128 return false;
129 }
130 setDescription(text);
131 break;
132 }
133 case OTBM_ATTR_RUNE_CHARGES: {
134 uint8_t subtype;
135 if (!stream->getU8(subtype)) {
136 return false;
137 }
138 setSubtype(subtype);
139 break;
140 }
141 case OTBM_ATTR_TIER: {
142 uint8_t tier;
143 if (!stream->getU8(tier)) {
144 return false;
145 }
146 setTier(static_cast<uint16_t>(tier));
147 break;
148 }
149
150 // The following *should* be handled in the derived classes
151 // However, we still need to handle them here since otherwise things
152 // will break horribly
153 case OTBM_ATTR_DEPOT_ID:
154 return stream->skip(2);
155 case OTBM_ATTR_HOUSEDOORID:
156 return stream->skip(1);
157 case OTBM_ATTR_TELE_DEST:
158 return stream->skip(5);
159 case OTBM_ATTR_PODIUMOUTFIT:
160 return stream->skip(15);
161 default:
162 return false;
163 }
164 return true;
165}
166
167bool Item::unserializeAttributes_OTBM(const IOMap& maphandle, BinaryNode* stream) {
168 uint8_t attribute;
169 while (stream->getU8(attribute)) {
170 if (attribute == OTBM_ATTR_ATTRIBUTE_MAP) {
171 if (!ItemAttributes::unserializeAttributeMap(maphandle, stream)) {
172 return false;
173 }
174 } else if (!readItemAttribute_OTBM(maphandle, static_cast<OTBM_ItemAttribute>(attribute), stream)) {
175 return false;
176 }
177 }
178 return true;
179}
180
181bool Item::unserializeItemNode_OTBM(const IOMap& maphandle, BinaryNode* node) {
182 return unserializeAttributes_OTBM(maphandle, node);
183}
184
185void Item::serializeItemAttributes_OTBM(const IOMap& maphandle, NodeFileWriteHandle& stream) const {
186 if (maphandle.version.otbm >= MAP_OTBM_2) {
187 const ItemType& iType = g_items[id];
188 if (iType.stackable || iType.isSplash() || iType.isFluidContainer()) {
189 stream.addU8(OTBM_ATTR_COUNT);
190 stream.addU8(getSubtype());
191 }
192 }
193
194 if (maphandle.version.otbm >= MAP_OTBM_4) {
195 if (attributes && !attributes->empty()) {
196 stream.addU8(OTBM_ATTR_ATTRIBUTE_MAP);
197 serializeAttributeMap(maphandle, stream);
198 }
199 } else {
200 if (g_items.MinorVersion >= CLIENT_VERSION_820 && isCharged()) {
201 stream.addU8(OTBM_ATTR_CHARGES);
202 stream.addU16(getSubtype());
203 }
204
205 uint16_t actionId = getActionID();
206 if (actionId > 0) {
207 stream.addU8(OTBM_ATTR_ACTION_ID);
208 stream.addU16(actionId);
209 }
210
211 uint16_t uniqueId = getUniqueID();
212 if (uniqueId > 0) {
213 stream.addU8(OTBM_ATTR_UNIQUE_ID);
214 stream.addU16(uniqueId);
215 }
216
217 const std::string& text = getText();
218 if (!text.empty()) {
219 stream.addU8(OTBM_ATTR_TEXT);
220 stream.addString(text);
221 }
222
223 const std::string& description = getDescription();
224 if (!description.empty()) {
225 stream.addU8(OTBM_ATTR_DESC);
226 stream.addString(description);
227 }
228
229 uint16_t tier = getTier();
230 if (tier > 0) {
231 stream.addU8(OTBM_ATTR_TIER);
232 stream.addU8(static_cast<uint8_t>(tier));
233 }
234 }
235}
236
237void Item::serializeItemCompact_OTBM(const IOMap& maphandle, NodeFileWriteHandle& stream) const {
238 stream.addU16(id);
239
240 /* This is impossible
241 const ItemType& iType = g_items[id];
242
243 if(iType.stackable || iType.isSplash() || iType.isFluidContainer()){
244 stream.addU8(getSubtype());
245 }
246 */
247}
248
249bool Item::serializeItemNode_OTBM(const IOMap& maphandle, NodeFileWriteHandle& file) const {
250 file.addNode(OTBM_ITEM);
251 file.addU16(id);
252 if (maphandle.version.otbm == MAP_OTBM_1) {
253 const ItemType& iType = g_items[id];
254 if (iType.stackable || iType.isSplash() || iType.isFluidContainer()) {
255 file.addU8(getSubtype());
256 }
257 }
258 serializeItemAttributes_OTBM(maphandle, file);
259 file.endNode();
260 return true;
261}
262
263// ============================================================================
264// Teleport
265
266bool Teleport::readItemAttribute_OTBM(const IOMap& maphandle, OTBM_ItemAttribute attribute, BinaryNode* stream) {
267 if (OTBM_ATTR_TELE_DEST == attribute) {
268 uint16_t x, y;
269 uint8_t z;
270 if (!stream->getU16(x) || !stream->getU16(y) || !stream->getU8(z)) {
271 return false;
272 }
273 destination = Position(x, y, z);
274 return true;
275 } else {
276 return Item::readItemAttribute_OTBM(maphandle, attribute, stream);
277 }
278}
279
280void Teleport::serializeItemAttributes_OTBM(const IOMap& maphandle, NodeFileWriteHandle& stream) const {
281 Item::serializeItemAttributes_OTBM(maphandle, stream);
282
283 stream.addByte(OTBM_ATTR_TELE_DEST);
284 stream.addU16(destination.x);
285 stream.addU16(destination.y);
286 stream.addU8(destination.z);
287}
288
289// ============================================================================
290// Door
291
292bool Door::readItemAttribute_OTBM(const IOMap& maphandle, OTBM_ItemAttribute attribute, BinaryNode* stream) {
293 if (OTBM_ATTR_HOUSEDOORID == attribute) {
294 uint8_t id = 0;
295 if (!stream->getU8(id)) {
296 return false;
297 }
298 doorId = id;
299 return true;
300 } else {
301 return Item::readItemAttribute_OTBM(maphandle, attribute, stream);
302 }
303}
304
305void Door::serializeItemAttributes_OTBM(const IOMap& maphandle, NodeFileWriteHandle& stream) const {
306 Item::serializeItemAttributes_OTBM(maphandle, stream);
307 if (doorId) {
308 stream.addByte(OTBM_ATTR_HOUSEDOORID);
309 stream.addU8(doorId);
310 }
311}
312
313DoorType Door::getDoorType() const {
314 WallBrush* wb = getWallBrush();
315 if (!wb) {
316 return WALL_UNDEFINED;
317 }
318
319 return wb->getDoorTypeFromID(id);
320}
321
322bool Door::isRealDoor() const {
323 const DoorType& dt = getDoorType();
324 // doors with no wallbrush will appear as WALL_UNDEFINED
325 // this is for compatibility
326 return dt == WALL_UNDEFINED || dt == WALL_DOOR_NORMAL || dt == WALL_DOOR_LOCKED || dt == WALL_DOOR_QUEST || dt == WALL_DOOR_MAGIC || dt == WALL_DOOR_NORMAL_ALT;
327}
328
329uint8_t Door::getDoorID() const {
330 return isRealDoor() ? doorId : 0;
331}
332
333void Door::setDoorID(uint8_t id) {
334 doorId = isRealDoor() ? id : 0;
335}
336
337// ============================================================================
338// Depots
339
340bool Depot::readItemAttribute_OTBM(const IOMap& maphandle, OTBM_ItemAttribute attribute, BinaryNode* stream) {
341 if (OTBM_ATTR_DEPOT_ID == attribute) {
342 uint16_t id = 0;
343 if (!stream->getU16(id)) {
344 return false;
345 }
346 depotId = id;
347 return true;
348 } else {
349 return Item::readItemAttribute_OTBM(maphandle, attribute, stream);
350 }
351}
352
353void Depot::serializeItemAttributes_OTBM(const IOMap& maphandle, NodeFileWriteHandle& stream) const {
354 Item::serializeItemAttributes_OTBM(maphandle, stream);
355 if (depotId) {
356 stream.addByte(OTBM_ATTR_DEPOT_ID);
357 stream.addU16(depotId);
358 }
359}
360
361// ============================================================================
362// Container
363
364bool Container::unserializeItemNode_OTBM(const IOMap& maphandle, BinaryNode* node) {
365 if (!Item::unserializeAttributes_OTBM(maphandle, node)) {
366 return false;
367 }
368
369 BinaryNode* child = node->getChild();
370 if (child) {
371 do {
372 uint8_t type;
373 if (!child->getByte(type)) {
374 return false;
375 }
376
377 if (type != OTBM_ITEM) {
378 return false;
379 }
380
381 Item* item = Item::Create_OTBM(maphandle, child);
382 if (!item) {
383 return false;
384 }
385
386 if (!item->unserializeItemNode_OTBM(maphandle, child)) {
387 delete item;
388 return false;
389 }
390
391 contents.push_back(item);
392 } while (child->advance());
393 }
394 return true;
395}
396
397bool Container::serializeItemNode_OTBM(const IOMap& maphandle, NodeFileWriteHandle& file) const {
398 file.addNode(OTBM_ITEM);
399 file.addU16(id);
400 if (maphandle.version.otbm == MAP_OTBM_1) {
401 // In the ludicrous event that an item is a container AND stackable, we have to do this. :p
402 const ItemType& iType = g_items[id];
403 if (iType.stackable || iType.isSplash() || iType.isFluidContainer()) {
404 file.addU8(getSubtype());
405 }
406 }
407
408 serializeItemAttributes_OTBM(maphandle, file);
409 for (Item* item : contents) {
410 item->serializeItemNode_OTBM(maphandle, file);
411 }
412
413 file.endNode();
414 return true;
415}
416
417// ============================================================================
418// Podium
419
420bool Podium::readItemAttribute_OTBM(const IOMap& maphandle, OTBM_ItemAttribute attribute, BinaryNode* stream) {
421 if (OTBM_ATTR_PODIUMOUTFIT == attribute) {
422 uint8_t flags;
423 uint8_t direction;
424
425 uint16_t lookType;
426 uint8_t lookHead;
427 uint8_t lookBody;
428 uint8_t lookLegs;
429 uint8_t lookFeet;
430 uint8_t lookAddon;
431
432 uint16_t lookMount;
433 uint8_t lookMountHead;
434 uint8_t lookMountBody;
435 uint8_t lookMountLegs;
436 uint8_t lookMountFeet;
437
438 if (
439 // podium settings
440 stream->getU8(flags) && stream->getU8(direction) &&
441
442 // outfit
443 stream->getU16(lookType) && stream->getU8(lookHead) && stream->getU8(lookBody) && stream->getU8(lookLegs) && stream->getU8(lookFeet) && stream->getU8(lookAddon) &&
444
445 // mount
446 stream->getU16(lookMount) && stream->getU8(lookMountHead) && stream->getU8(lookMountBody) && stream->getU8(lookMountLegs) && stream->getU8(lookMountFeet)
447 ) { //"if" condition ends here
448
449 setShowOutfit((flags & PODIUM_SHOW_OUTFIT) != 0);
450 setShowMount((flags & PODIUM_SHOW_MOUNT) != 0);
451 setShowPlatform((flags & PODIUM_SHOW_PLATFORM) != 0);
452
453 setDirection(static_cast<Direction>(direction));
454
455 struct Outfit newOutfit = Outfit();
456 newOutfit.lookType = static_cast<int>(lookType);
457 newOutfit.lookHead = static_cast<int>(lookHead);
458 newOutfit.lookBody = static_cast<int>(lookBody);
459 newOutfit.lookLegs = static_cast<int>(lookLegs);
460 newOutfit.lookFeet = static_cast<int>(lookFeet);
461 newOutfit.lookAddon = static_cast<int>(lookAddon);
462 newOutfit.lookMount = static_cast<int>(lookMount);
463 newOutfit.lookMountHead = static_cast<int>(lookMountHead);
464 newOutfit.lookMountBody = static_cast<int>(lookMountBody);
465 newOutfit.lookMountLegs = static_cast<int>(lookMountLegs);
466 newOutfit.lookMountFeet = static_cast<int>(lookMountFeet);
467 setOutfit(newOutfit);
468 return true;
469 }
470 return false;
471 } else {
472 return Item::readItemAttribute_OTBM(maphandle, attribute, stream);
473 }
474}
475
476void Podium::serializeItemAttributes_OTBM(const IOMap& maphandle, NodeFileWriteHandle& stream) const {
477 Item::serializeItemAttributes_OTBM(maphandle, stream);
478
479 uint8_t flags = PODIUM_SHOW_OUTFIT * static_cast<uint8_t>(showOutfit) + PODIUM_SHOW_MOUNT * static_cast<uint8_t>(showMount) + PODIUM_SHOW_PLATFORM * static_cast<uint8_t>(showPlatform);
480
481 stream.addByte(OTBM_ATTR_PODIUMOUTFIT);
482 stream.addU8(flags);
483 stream.addU8(direction);
484
485 stream.addU16(outfit.lookType);
486 stream.addU8(outfit.lookHead);
487 stream.addU8(outfit.lookBody);
488 stream.addU8(outfit.lookLegs);
489 stream.addU8(outfit.lookFeet);
490 stream.addU8(outfit.lookAddon);
491
492 stream.addU16(outfit.lookMount);
493 stream.addU8(outfit.lookMountHead);
494 stream.addU8(outfit.lookMountBody);
495 stream.addU8(outfit.lookMountLegs);
496 stream.addU8(outfit.lookMountFeet);
497}
498
499/*
500 OTBM_ROOTV1
501 |
502 |--- OTBM_MAP_DATA
503 | |
504 | |--- OTBM_TILE_AREA
505 | | |--- OTBM_TILE
506 | | |--- OTBM_TILE_SQUARE (not implemented)
507 | | |--- OTBM_TILE_REF (not implemented)
508 | | |--- OTBM_HOUSETILE
509 | |
510 | |--- OTBM_SPAWNS (not implemented)
511 | | |--- OTBM_SPAWN_AREA (not implemented)
512 | | |--- OTBM_MONSTER (not implemented)
513 | |
514 | |--- OTBM_TOWNS
515 | |--- OTBM_TOWN
516 |
517 |--- OTBM_ITEM_DEF (not implemented)
518*/
519
520bool IOMapOTBM::getVersionInfo(const FileName& filename, MapVersion& out_ver) {
521#ifdef OTGZ_SUPPORT
522 if (filename.GetExt() == "otgz") {
523 // Open the archive
524 std::shared_ptr<struct archive> a(archive_read_new(), archive_read_free);
525 archive_read_support_filter_all(a.get());
526 archive_read_support_format_all(a.get());
527 if (archive_read_open_filename(a.get(), nstr(filename.GetFullPath()).c_str(), 10240) != ARCHIVE_OK) {
528 return false;
529 }
530
531 // Loop over the archive entries until we find the otbm file
532 struct archive_entry* entry;
533 while (archive_read_next_header(a.get(), &entry) == ARCHIVE_OK) {
534 std::string entryName = archive_entry_pathname(entry);
535
536 if (entryName == "world/map.otbm") {
537 // Read the OTBM header into temporary memory
538 uint8_t buffer[8096];
539 memset(buffer, 0, 8096);
540
541 // Read from the archive
542 int read_bytes = archive_read_data(a.get(), buffer, 8096);
543
544 // Check so it at least contains the 4-byte file id
545 if (read_bytes < 4) {
546 return false;
547 }
548
549 // Create a read handle on it
550 std::shared_ptr<NodeFileReadHandle> f(new MemoryNodeFileReadHandle(buffer + 4, read_bytes - 4));
551
552 // Read the version info
553 return getVersionInfo(f.get(), out_ver);
554 }
555 }
556
557 // Didn't find OTBM file, lame
558 return false;
559 }
560#endif
561
562 // Just open a disk-based read handle
563 DiskNodeFileReadHandle f(nstr(filename.GetFullPath()), StringVector(1, "OTBM"));
564 if (!f.isOk()) {
565 return false;
566 }
567 return getVersionInfo(&f, out_ver);
568}
569
570bool IOMapOTBM::getVersionInfo(NodeFileReadHandle* f, MapVersion& out_ver) {
571 BinaryNode* root = f->getRootNode();
572 if (!root) {
573 return false;
574 }
575
576 root->skip(1); // Skip the type byte
577
578 uint16_t u16;
579 uint32_t u32;
580
581 if (!root->getU32(u32)) { // Version
582 return false;
583 }
584 out_ver.otbm = (MapVersionID)u32;
585
586 root->getU16(u16); // map size X
587 root->getU16(u16); // map size Y
588 root->getU32(u32); // OTB major version
589
590 if (!root->getU32(u32)) { // OTB minor version
591 return false;
592 }
593
594 out_ver.client = ClientVersionID(u32);
595 return true;
596}
597
598bool IOMapOTBM::loadMap(Map& map, const FileName& filename) {
599#ifdef OTGZ_SUPPORT
600 if (filename.GetExt() == "otgz") {
601 // Open the archive
602 std::shared_ptr<struct archive> a(archive_read_new(), archive_read_free);
603 archive_read_support_filter_all(a.get());
604 archive_read_support_format_all(a.get());
605 if (archive_read_open_filename(a.get(), nstr(filename.GetFullPath()).c_str(), 10240) != ARCHIVE_OK) {
606 return false;
607 }
608
609 // Memory buffers for the houses & spawns
610 std::shared_ptr<uint8_t> house_buffer;
611 std::shared_ptr<uint8_t> spawn_buffer;
612 size_t house_buffer_size = 0;
613 size_t spawn_buffer_size = 0;
614
615 // See if the otbm file has been loaded
616 bool otbm_loaded = false;
617
618 // Loop over the archive entries until we find the otbm file
619 g_gui.SetLoadDone(0, "Decompressing archive...");
620 struct archive_entry* entry;
621 while (archive_read_next_header(a.get(), &entry) == ARCHIVE_OK) {
622 std::string entryName = archive_entry_pathname(entry);
623
624 if (entryName == "world/map.otbm") {
625 // Read the entire OTBM file into a memory region
626 size_t otbm_size = archive_entry_size(entry);
627 std::shared_ptr<uint8_t> otbm_buffer(new uint8_t[otbm_size], [](uint8_t* p) { delete[] p; });
628
629 // Read from the archive
630 size_t read_bytes = archive_read_data(a.get(), otbm_buffer.get(), otbm_size);
631
632 // Check so it at least contains the 4-byte file id
633 if (read_bytes < 4) {
634 return false;
635 }
636
637 if (read_bytes < otbm_size) {
638 error("Could not read file.");
639 return false;
640 }
641
642 g_gui.SetLoadDone(0, "Loading OTBM map...");
643
644 // Create a read handle on it
645 std::shared_ptr<NodeFileReadHandle> f(
646 new MemoryNodeFileReadHandle(otbm_buffer.get() + 4, otbm_size - 4)
647 );
648
649 // Read the version info
650 if (!loadMap(map, *f.get())) {
651 error("Could not load OTBM file inside archive");
652 return false;
653 }
654
655 otbm_loaded = true;
656 } else if (entryName == "world/houses.xml") {
657 house_buffer_size = archive_entry_size(entry);
658 house_buffer.reset(new uint8_t[house_buffer_size]);
659
660 // Read from the archive
661 size_t read_bytes = archive_read_data(a.get(), house_buffer.get(), house_buffer_size);
662
663 // Check so it at least contains the 4-byte file id
664 if (read_bytes < house_buffer_size) {
665 house_buffer.reset();
666 house_buffer_size = 0;
667 warning("Failed to decompress houses.");
668 }
669 } else if (entryName == "world/spawns.xml") {
670 spawn_buffer_size = archive_entry_size(entry);
671 spawn_buffer.reset(new uint8_t[spawn_buffer_size]);
672
673 // Read from the archive
674 size_t read_bytes = archive_read_data(a.get(), spawn_buffer.get(), spawn_buffer_size);
675
676 // Check so it at least contains the 4-byte file id
677 if (read_bytes < spawn_buffer_size) {
678 spawn_buffer.reset();
679 spawn_buffer_size = 0;
680 warning("Failed to decompress spawns.");
681 }
682 }
683 }
684
685 if (!otbm_loaded) {
686 error("OTBM file not found inside archive.");
687 return false;
688 }
689
690 // Load the houses from the stored buffer
691 if (house_buffer.get() && house_buffer_size > 0) {
692 pugi::xml_document doc;
693 pugi::xml_parse_result result = doc.load_buffer(house_buffer.get(), house_buffer_size);
694 if (result) {
695 if (!loadHouses(map, doc)) {
696 warning("Failed to load houses.");
697 }
698 } else {
699 warning("Failed to load houses due to XML parse error.");
700 }
701 }
702
703 // Load the spawns from the stored buffer
704 if (spawn_buffer.get() && spawn_buffer_size > 0) {
705 pugi::xml_document doc;
706 pugi::xml_parse_result result = doc.load_buffer(spawn_buffer.get(), spawn_buffer_size);
707 if (result) {
708 if (!loadSpawns(map, doc)) {
709 warning("Failed to load spawns.");
710 }
711 } else {
712 warning("Failed to load spawns due to XML parse error.");
713 }
714 }
715
716 return true;
717 }
718#endif
719
720 DiskNodeFileReadHandle f(nstr(filename.GetFullPath()), StringVector(1, "OTBM"));
721 if (!f.isOk()) {
722 error(("Couldn't open file for reading\nThe error reported was: " + wxstr(f.getErrorMessage())).wc_str());
723 return false;
724 }
725
726 if (!loadMap(map, f)) {
727 return false;
728 }
729
730 // Read auxilliary files
731 if (!loadHouses(map, filename)) {
732 warning("Failed to load houses.");
733 map.housefile = nstr(filename.GetName()) + "-house.xml";
734 }
735 if (!loadSpawns(map, filename)) {
736 warning("Failed to load spawns.");
737 map.spawnfile = nstr(filename.GetName()) + "-spawn.xml";
738 }
739 if (!loadWaypoints(map, filename)) {
740 // just assume the map did not have this file before
741 // warning("Failed to load waypoints.");
742 map.waypointfile = nstr(filename.GetName()) + "-waypoint.xml";
743 }
744 return true;
745}
746
747bool IOMapOTBM::loadMap(Map& map, NodeFileReadHandle& f) {
748 BinaryNode* root = f.getRootNode();
749 if (!root) {
750 error("Could not read root node.");
751 return false;
752 }
753 root->skip(1); // Skip the type byte
754
755 uint8_t u8;
756 uint16_t u16;
757 uint32_t u32;
758
759 if (!root->getU32(u32)) {
760 return false;
761 }
762
763 version.otbm = (MapVersionID)u32;
764
765 if (version.otbm > MAP_OTBM_4) {
766 // Failed to read version
767 if (g_gui.PopupDialog("Map error", "The loaded map appears to be a OTBM format that is not supported by the editor."
768 "Do you still want to attempt to load the map?",
769 wxYES | wxNO)
770 == wxID_YES) {
771 warning("Unsupported or damaged map version");
772 } else {
773 error("Unsupported OTBM version, could not load map");
774 return false;
775 }
776 }
777
778 if (!root->getU16(u16)) {
779 return false;
780 }
781
782 map.width = u16;
783 if (!root->getU16(u16)) {
784 return false;
785 }
786
787 map.height = u16;
788
789 if (!root->getU32(u32) || u32 > (unsigned long)g_items.MajorVersion) { // OTB major version
790 if (g_gui.PopupDialog("Map error", "The loaded map appears to be a items.otb format that deviates from the "
791 "items.otb loaded by the editor. Do you still want to attempt to load the map?",
792 wxYES | wxNO)
793 == wxID_YES) {
794 warning("Unsupported or damaged map version");
795 } else {
796 error("Outdated items.otb, could not load map");
797 return false;
798 }
799 }
800
801 if (!root->getU32(u32) || u32 > (unsigned long)g_items.MinorVersion) { // OTB minor version
802 warning("This editor needs an updated items.otb version");
803 }
804 version.client = (ClientVersionID)u32;
805
806 BinaryNode* mapHeaderNode = root->getChild();
807 if (mapHeaderNode == nullptr || !mapHeaderNode->getByte(u8) || u8 != OTBM_MAP_DATA) {
808 error("Could not get root child node. Cannot recover from fatal error!");
809 return false;
810 }
811
812 uint8_t attribute;
813 while (mapHeaderNode->getU8(attribute)) {
814 switch (attribute) {
815 case OTBM_ATTR_DESCRIPTION: {
816 if (!mapHeaderNode->getString(map.description)) {
817 warning("Invalid map description tag");
818 }
819 // std::cout << "Map description: " << mapDescription << std::endl;
820 break;
821 }
822 case OTBM_ATTR_EXT_SPAWN_FILE: {
823 if (!mapHeaderNode->getString(map.spawnfile)) {
824 warning("Invalid map spawnfile tag");
825 }
826 break;
827 }
828 case OTBM_ATTR_EXT_HOUSE_FILE: {
829 if (!mapHeaderNode->getString(map.housefile)) {
830 warning("Invalid map housefile tag");
831 }
832 break;
833 }
834 case OTBM_ATTR_EXT_SPAWN_NPC_FILE: {
835 // compatibility: skip Canary RME NPC spawn file tag
836 std::string stringToSkip;
837 if (!mapHeaderNode->getString(stringToSkip)) {
838 warning("Invalid map housefile tag");
839 }
840 break;
841 }
842 default: {
843 warning("Unknown header node.");
844 break;
845 }
846 }
847 }
848
849 int nodes_loaded = 0;
850
851 for (BinaryNode* mapNode = mapHeaderNode->getChild(); mapNode != nullptr; mapNode = mapNode->advance()) {
852 ++nodes_loaded;
853 if (nodes_loaded % 15 == 0) {
854 g_gui.SetLoadDone(static_cast<int32_t>(100.0 * f.tell() / f.size()));
855 }
856
857 uint8_t node_type;
858 if (!mapNode->getByte(node_type)) {
859 warning("Invalid map node");
860 continue;
861 }
862 if (node_type == OTBM_TILE_AREA) {
863 uint16_t base_x, base_y;
864 uint8_t base_z;
865 if (!mapNode->getU16(base_x) || !mapNode->getU16(base_y) || !mapNode->getU8(base_z)) {
866 warning("Invalid map node, no base coordinate");
867 continue;
868 }
869
870 for (BinaryNode* tileNode = mapNode->getChild(); tileNode != nullptr; tileNode = tileNode->advance()) {
871 Tile* tile = nullptr;
872 uint8_t tile_type;
873 if (!tileNode->getByte(tile_type)) {
874 warning("Invalid tile type");
875 continue;
876 }
877 if (tile_type == OTBM_TILE || tile_type == OTBM_HOUSETILE) {
878 // printf("Start\n");
879 uint8_t x_offset, y_offset;
880 if (!tileNode->getU8(x_offset) || !tileNode->getU8(y_offset)) {
881 warning("Could not read position of tile");
882 continue;
883 }
884 const Position pos(base_x + x_offset, base_y + y_offset, base_z);
885
886 if (map.getTile(pos)) {
887 warning("Duplicate tile at %d:%d:%d, discarding duplicate", pos.x, pos.y, pos.z);
888 continue;
889 }
890
891 tile = map.allocator(map.createTileL(pos));
892 House* house = nullptr;
893 if (tile_type == OTBM_HOUSETILE) {
894 uint32_t house_id;
895 if (!tileNode->getU32(house_id)) {
896 warning("House tile without house data, discarding tile");
897 continue;
898 }
899 if (house_id) {
900 house = map.houses.getHouse(house_id);
901 if (!house) {
902 house = newd House(map);
903 house->setID(house_id);
904 map.houses.addHouse(house);
905 }
906 } else {
907 warning("Invalid house id from tile %d:%d:%d", pos.x, pos.y, pos.z);
908 }
909 }
910
911 // printf("So far so good\n");
912
913 uint8_t attribute;
914 while (tileNode->getU8(attribute)) {
915 switch (attribute) {
916 case OTBM_ATTR_TILE_FLAGS: {
917 uint32_t flags = 0;
918 if (!tileNode->getU32(flags)) {
919 warning("Invalid tile flags of tile on %d:%d:%d", pos.x, pos.y, pos.z);
920 }
921 tile->setMapFlags(flags);
922 if (flags & TILESTATE_ZONE_BRUSH) {
923 uint16_t zoneId = 0;
924 do {
925 if (!tileNode->getU16(zoneId)) {
926 warning("Invalid zone id of tile on %d:%d:%d", pos.x, pos.y, pos.z);
927 }
928
929 if (zoneId != 0) {
930 tile->addZoneId(zoneId);
931 }
932 } while (zoneId != 0);
933 }
934 break;
935 }
936 case OTBM_ATTR_ITEM: {
937 Item* item = Item::Create_OTBM(*this, tileNode);
938 if (item == nullptr) {
939 warning("Invalid item at tile %d:%d:%d", pos.x, pos.y, pos.z);
940 }
941 tile->addItem(item);
942 break;
943 }
944 default: {
945 warning("Unknown tile attribute at %d:%d:%d", pos.x, pos.y, pos.z);
946 break;
947 }
948 }
949 }
950
951 // printf("Didn't die in loop\n");
952
953 for (BinaryNode* itemNode = tileNode->getChild(); itemNode != nullptr; itemNode = itemNode->advance()) {
954 Item* item = nullptr;
955 uint8_t item_type;
956 if (!itemNode->getByte(item_type)) {
957 warning("Unknown item type %d:%d:%d", pos.x, pos.y, pos.z);
958 continue;
959 }
960 if (item_type == OTBM_ITEM) {
961 item = Item::Create_OTBM(*this, itemNode);
962 if (item) {
963 if (!item->unserializeItemNode_OTBM(*this, itemNode)) {
964 warning("Couldn't unserialize item attributes at %d:%d:%d", pos.x, pos.y, pos.z);
965 }
966 // reform(&map, tile, item);
967 tile->addItem(item);
968 }
969 } else {
970 warning("Unknown type of tile child node");
971 }
972 }
973
974 tile->update();
975 if (house) {
976 house->addTile(tile);
977 }
978
979 map.setTile(pos.x, pos.y, pos.z, tile);
980 } else {
981 warning("Unknown type of tile node");
982 }
983 }
984 } else if (node_type == OTBM_TOWNS) {
985 for (BinaryNode* townNode = mapNode->getChild(); townNode != nullptr; townNode = townNode->advance()) {
986 Town* town = nullptr;
987 uint8_t town_type;
988 if (!townNode->getByte(town_type)) {
989 warning("Invalid town type (1)");
990 continue;
991 }
992 if (town_type != OTBM_TOWN) {
993 warning("Invalid town type (2)");
994 continue;
995 }
996 uint32_t town_id;
997 if (!townNode->getU32(town_id)) {
998 warning("Invalid town id");
999 continue;
1000 }
1001
1002 town = map.towns.getTown(town_id);
1003 if (town) {
1004 warning("Duplicate town id %d, discarding duplicate", town_id);
1005 continue;
1006 } else {
1007 town = newd Town(town_id);
1008 if (!map.towns.addTown(town)) {
1009 delete town;
1010 continue;
1011 }
1012 }
1013 std::string town_name;
1014 if (!townNode->getString(town_name)) {
1015 warning("Invalid town name");
1016 continue;
1017 }
1018 town->setName(town_name);
1019 Position pos;
1020 uint16_t x;
1021 uint16_t y;
1022 uint8_t z;
1023 if (!townNode->getU16(x) || !townNode->getU16(y) || !townNode->getU8(z)) {
1024 warning("Invalid town temple position");
1025 continue;
1026 }
1027 pos.x = x;
1028 pos.y = y;
1029 pos.z = z;
1030 town->setTemplePosition(pos);
1031 map.getOrCreateTile(pos)->getLocation()->increaseTownCount();
1032 }
1033 } else if (node_type == OTBM_WAYPOINTS) {
1034 for (BinaryNode* waypointNode = mapNode->getChild(); waypointNode != nullptr; waypointNode = waypointNode->advance()) {
1035 uint8_t waypoint_type;
1036 if (!waypointNode->getByte(waypoint_type)) {
1037 warning("Invalid waypoint type (1)");
1038 continue;
1039 }
1040 if (waypoint_type != OTBM_WAYPOINT) {
1041 warning("Invalid waypoint type (2)");
1042 continue;
1043 }
1044
1045 Waypoint wp;
1046
1047 if (!waypointNode->getString(wp.name)) {
1048 warning("Invalid waypoint name");
1049 continue;
1050 }
1051 uint16_t x;
1052 uint16_t y;
1053 uint8_t z;
1054 if (!waypointNode->getU16(x) || !waypointNode->getU16(y) || !waypointNode->getU8(z)) {
1055 warning("Invalid waypoint position");
1056 continue;
1057 }
1058 wp.pos.x = x;
1059 wp.pos.y = y;
1060 wp.pos.z = z;
1061
1062 map.waypoints.addWaypoint(newd Waypoint(wp));
1063 }
1064 }
1065 }
1066
1067 if (!f.isOk()) {
1068 warning(wxstr(f.getErrorMessage()).wc_str());
1069 }
1070 return true;
1071}
1072
1073bool IOMapOTBM::loadSpawns(Map& map, const FileName& dir) {
1074 std::string fn = (const char*)(dir.GetPath(wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME).mb_str(wxConvUTF8));
1075 fn += map.spawnfile;
1076
1077 FileName filename(wxstr(fn));
1078 if (!filename.FileExists()) {
1079 warnings.push_back("IOMapOTBM::loadSpawns: File not found.");
1080 return false;
1081 }
1082
1083 // has to be declared again as encoding-specific characters break loading there
1084 std::string encoded_path = (const char*)(dir.GetPath(wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME).mb_str(wxConvWhateverWorks));
1085 encoded_path += map.spawnfile;
1086 pugi::xml_document doc;
1087 pugi::xml_parse_result result = doc.load_file(encoded_path.c_str());
1088 if (!result) {
1089 warnings.push_back("IOMapOTBM::loadSpawns: File loading error.");
1090 return false;
1091 }
1092 return loadSpawns(map, doc);
1093}
1094
1095bool IOMapOTBM::loadSpawns(Map& map, pugi::xml_document& doc) {
1096 pugi::xml_node node = doc.child("spawns");
1097 if (!node) {
1098 warnings.push_back("IOMapOTBM::loadSpawns: Invalid rootheader.");
1099 return false;
1100 }
1101
1102 for (pugi::xml_node spawnNode = node.first_child(); spawnNode; spawnNode = spawnNode.next_sibling()) {
1103 if (as_lower_str(spawnNode.name()) != "spawn") {
1104 continue;
1105 }
1106
1107 Position spawnPosition;
1108 spawnPosition.x = spawnNode.attribute("centerx").as_int();
1109 spawnPosition.y = spawnNode.attribute("centery").as_int();
1110 spawnPosition.z = spawnNode.attribute("centerz").as_int();
1111
1112 if (spawnPosition.x == 0 || spawnPosition.y == 0) {
1113 warning("Bad position data on one spawn, discarding...");
1114 continue;
1115 }
1116
1117 int32_t radius = spawnNode.attribute("radius").as_int();
1118 if (radius < 1) {
1119 warning("Couldn't read radius of spawn.. discarding spawn...");
1120 continue;
1121 }
1122
1123 Tile* tile = map.getTile(spawnPosition);
1124 if (tile && tile->spawn) {
1125 warning("Duplicate spawn on position %d:%d:%d\n", tile->getX(), tile->getY(), tile->getZ());
1126 continue;
1127 }
1128
1129 Spawn* spawn = newd Spawn(radius);
1130 if (!tile) {
1131 tile = map.allocator(map.createTileL(spawnPosition));
1132 map.setTile(spawnPosition, tile);
1133 }
1134
1135 tile->spawn = spawn;
1136 map.addSpawn(tile);
1137
1138 for (pugi::xml_node creatureNode = spawnNode.first_child(); creatureNode; creatureNode = creatureNode.next_sibling()) {
1139 const std::string& creatureNodeName = as_lower_str(creatureNode.name());
1140 if (creatureNodeName != "monster" && creatureNodeName != "npc") {
1141 continue;
1142 }
1143
1144 bool isNpc = creatureNodeName == "npc";
1145 const std::string& name = creatureNode.attribute("name").as_string();
1146 if (name.empty()) {
1147 wxString err;
1148 err << "Bad creature position data, discarding creature at spawn " << spawnPosition.x << ":" << spawnPosition.y << ":" << spawnPosition.z << " due missing name.";
1149 warnings.Add(err);
1150 break;
1151 }
1152
1153 int32_t spawntime = creatureNode.attribute("spawntime").as_int();
1154 if (spawntime == 0) {
1155 spawntime = g_settings.getInteger(Config::DEFAULT_SPAWNTIME);
1156 }
1157
1158 Direction direction = NORTH;
1159 int dir = creatureNode.attribute("direction").as_int(-1);
1160 if (dir >= DIRECTION_FIRST && dir <= DIRECTION_LAST) {
1161 direction = (Direction)dir;
1162 }
1163
1164 Position creaturePosition(spawnPosition);
1165
1166 pugi::xml_attribute xAttribute = creatureNode.attribute("x");
1167 pugi::xml_attribute yAttribute = creatureNode.attribute("y");
1168 if (!xAttribute || !yAttribute) {
1169 wxString err;
1170 err << "Bad creature position data, discarding creature \"" << name << "\" at spawn " << creaturePosition.x << ":" << creaturePosition.y << ":" << creaturePosition.z << " due to invalid position.";
1171 warnings.Add(err);
1172 break;
1173 }
1174
1175 creaturePosition.x += xAttribute.as_int();
1176 creaturePosition.y += yAttribute.as_int();
1177
1178 radius = std::max<int32_t>(radius, std::abs(creaturePosition.x - spawnPosition.x));
1179 radius = std::max<int32_t>(radius, std::abs(creaturePosition.y - spawnPosition.y));
1180 radius = std::min<int32_t>(radius, g_settings.getInteger(Config::MAX_SPAWN_RADIUS));
1181
1182 Tile* creatureTile;
1183 if (creaturePosition == spawnPosition) {
1184 creatureTile = tile;
1185 } else {
1186 creatureTile = map.getTile(creaturePosition);
1187 }
1188
1189 if (!creatureTile) {
1190 wxString err;
1191 err << "Discarding creature \"" << name << "\" at " << creaturePosition.x << ":" << creaturePosition.y << ":" << creaturePosition.z << " due to invalid position.";
1192 warnings.Add(err);
1193 break;
1194 }
1195
1196 if (creatureTile->creature) {
1197 wxString err;
1198 err << "Duplicate creature \"" << name << "\" at " << creaturePosition.x << ":" << creaturePosition.y << ":" << creaturePosition.z << " was discarded.";
1199 warnings.Add(err);
1200 break;
1201 }
1202
1203 CreatureType* type = g_creatures[name];
1204 if (!type) {
1205 type = g_creatures.addMissingCreatureType(name, isNpc);
1206 }
1207
1208 Creature* creature = newd Creature(type);
1209 creature->setDirection(direction);
1210 creature->setSpawnTime(spawntime);
1211 creatureTile->creature = creature;
1212
1213 if (creatureTile->getLocation()->getSpawnCount() == 0) {
1214 // No spawn, create a newd one
1215 ASSERT(creatureTile->spawn == nullptr);
1216 Spawn* spawn = newd Spawn(5);
1217 creatureTile->spawn = spawn;
1218 map.addSpawn(creatureTile);
1219 }
1220 }
1221 }
1222 return true;
1223}
1224
1225bool IOMapOTBM::loadHouses(Map& map, const FileName& dir) {
1226 std::string fn = (const char*)(dir.GetPath(wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME).mb_str(wxConvUTF8));
1227 fn += map.housefile;
1228
1229 FileName filename(wxstr(fn));
1230 if (!filename.FileExists()) {
1231 warnings.push_back("IOMapOTBM::loadHouses: File not found.");
1232 return false;
1233 }
1234
1235 // has to be declared again as encoding-specific characters break loading there
1236 std::string encoded_path = (const char*)(dir.GetPath(wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME).mb_str(wxConvWhateverWorks));
1237 encoded_path += map.housefile;
1238 pugi::xml_document doc;
1239 pugi::xml_parse_result result = doc.load_file(encoded_path.c_str());
1240 if (!result) {
1241 warnings.push_back("IOMapOTBM::loadHouses: File loading error.");
1242 return false;
1243 }
1244 return loadHouses(map, doc);
1245}
1246
1247bool IOMapOTBM::loadHouses(Map& map, pugi::xml_document& doc) {
1248 pugi::xml_node node = doc.child("houses");
1249 if (!node) {
1250 warnings.push_back("IOMapOTBM::loadHouses: Invalid rootheader.");
1251 return false;
1252 }
1253
1254 pugi::xml_attribute attribute;
1255 for (pugi::xml_node houseNode = node.first_child(); houseNode; houseNode = houseNode.next_sibling()) {
1256 if (as_lower_str(houseNode.name()) != "house") {
1257 continue;
1258 }
1259
1260 House* house = nullptr;
1261 if ((attribute = houseNode.attribute("houseid"))) {
1262 house = map.houses.getHouse(attribute.as_uint());
1263 if (!house) {
1264 break;
1265 }
1266 }
1267
1268 if ((attribute = houseNode.attribute("name"))) {
1269 house->name = attribute.as_string();
1270 } else {
1271 house->name = "House #" + std::to_string(house->getID());
1272 }
1273
1274 Position exitPosition(
1275 houseNode.attribute("entryx").as_int(),
1276 houseNode.attribute("entryy").as_int(),
1277 houseNode.attribute("entryz").as_int()
1278 );
1279 if (exitPosition.x != 0 && exitPosition.y != 0 && exitPosition.z != 0) {
1280 house->setExit(exitPosition);
1281 }
1282
1283 if ((attribute = houseNode.attribute("rent"))) {
1284 house->rent = attribute.as_int();
1285 }
1286
1287 if ((attribute = houseNode.attribute("guildhall"))) {
1288 house->guildhall = attribute.as_bool();
1289 }
1290
1291 if ((attribute = houseNode.attribute("townid"))) {
1292 house->townid = attribute.as_uint();
1293 } else {
1294 warning("House %d has no town! House was removed.", house->getID());
1295 map.houses.removeHouse(house);
1296 }
1297 }
1298 return true;
1299}
1300
1301bool IOMapOTBM::loadWaypoints(Map& map, const FileName& dir) {
1302 std::string fn = (const char*)(dir.GetPath(wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME).mb_str(wxConvUTF8));
1303 fn += map.waypointfile;
1304 FileName filename(wxstr(fn));
1305 if (!filename.FileExists()) {
1306 return false;
1307 }
1308
1309 pugi::xml_document doc;
1310 pugi::xml_parse_result result = doc.load_file(fn.c_str());
1311 if (!result) {
1312 return false;
1313 }
1314 return loadWaypoints(map, doc);
1315};
1316bool IOMapOTBM::loadWaypoints(Map& map, pugi::xml_document& doc) {
1317 return true;
1318};
1319
1320bool IOMapOTBM::saveMap(Map& map, const FileName& identifier) {
1321#ifdef OTGZ_SUPPORT
1322 if (identifier.GetExt() == "otgz") {
1323 // Create the archive
1324 struct archive* a = archive_write_new();
1325 struct archive_entry* entry = nullptr;
1326 std::ostringstream streamData;
1327
1328 archive_write_set_compression_gzip(a);
1329 archive_write_set_format_pax_restricted(a);
1330 archive_write_open_filename(a, nstr(identifier.GetFullPath()).c_str());
1331
1332 g_gui.SetLoadDone(0, "Saving spawns...");
1333
1334 pugi::xml_document spawnDoc;
1335 if (saveSpawns(map, spawnDoc)) {
1336 // Write the data
1337 spawnDoc.save(streamData, "", pugi::format_raw, pugi::encoding_utf8);
1338 std::string xmlData = streamData.str();
1339
1340 // Write to the arhive
1341 entry = archive_entry_new();
1342 archive_entry_set_pathname(entry, "world/spawns.xml");
1343 archive_entry_set_size(entry, xmlData.size());
1344 archive_entry_set_filetype(entry, AE_IFREG);
1345 archive_entry_set_perm(entry, 0644);
1346
1347 // Write to the archive
1348 archive_write_header(a, entry);
1349 archive_write_data(a, xmlData.data(), xmlData.size());
1350
1351 // Free the entry
1352 archive_entry_free(entry);
1353 streamData.str("");
1354 }
1355
1356 g_gui.SetLoadDone(0, "Saving houses...");
1357
1358 pugi::xml_document houseDoc;
1359 if (saveHouses(map, houseDoc)) {
1360 // Write the data
1361 houseDoc.save(streamData, "", pugi::format_raw, pugi::encoding_utf8);
1362 std::string xmlData = streamData.str();
1363
1364 // Write to the arhive
1365 entry = archive_entry_new();
1366 archive_entry_set_pathname(entry, "world/houses.xml");
1367 archive_entry_set_size(entry, xmlData.size());
1368 archive_entry_set_filetype(entry, AE_IFREG);
1369 archive_entry_set_perm(entry, 0644);
1370
1371 // Write to the archive
1372 archive_write_header(a, entry);
1373 archive_write_data(a, xmlData.data(), xmlData.size());
1374
1375 // Free the entry
1376 archive_entry_free(entry);
1377 streamData.str("");
1378 }
1379 // to do
1380 /*
1381 g_gui.SetLoadDone(0, "Saving waypoints...");
1382
1383 pugi::xml_document wpDoc;
1384 if (saveWaypoints(map, wpDoc)) {
1385 // Write the data
1386 wpDoc.save(streamData, "", pugi::format_raw, pugi::encoding_utf8);
1387 std::string xmlData = streamData.str();
1388
1389 // Write to the arhive
1390 entry = archive_entry_new();
1391 archive_entry_set_pathname(entry, "world/waypoints.xml");
1392 archive_entry_set_size(entry, xmlData.size());
1393 archive_entry_set_filetype(entry, AE_IFREG);
1394 archive_entry_set_perm(entry, 0644);
1395
1396 // Write to the archive
1397 archive_write_header(a, entry);
1398 archive_write_data(a, xmlData.data(), xmlData.size());
1399
1400 // Free the entry
1401 archive_entry_free(entry);
1402 streamData.str("");
1403 }
1404 */
1405 g_gui.SetLoadDone(0, "Saving OTBM map...");
1406
1407 MemoryNodeFileWriteHandle otbmWriter;
1408 saveMap(map, otbmWriter);
1409
1410 g_gui.SetLoadDone(75, "Compressing...");
1411
1412 // Create an archive entry for the otbm file
1413 entry = archive_entry_new();
1414 archive_entry_set_pathname(entry, "world/map.otbm");
1415 archive_entry_set_size(entry, otbmWriter.getSize() + 4); // 4 bytes extra for header
1416 archive_entry_set_filetype(entry, AE_IFREG);
1417 archive_entry_set_perm(entry, 0644);
1418 archive_write_header(a, entry);
1419
1420 // Write the version header
1421 char otbm_identifier[] = "OTBM";
1422 archive_write_data(a, otbm_identifier, 4);
1423
1424 // Write the OTBM data
1425 archive_write_data(a, otbmWriter.getMemory(), otbmWriter.getSize());
1426 archive_entry_free(entry);
1427
1428 // Free / close the archive
1429 archive_write_close(a);
1430 archive_write_free(a);
1431
1432 g_gui.DestroyLoadBar();
1433 return true;
1434 }
1435#endif
1436
1437 DiskNodeFileWriteHandle f(
1438 nstr(identifier.GetFullPath()),
1439 (g_settings.getInteger(Config::SAVE_WITH_OTB_MAGIC_NUMBER) ? "OTBM" : std::string(4, '\0'))
1440 );
1441
1442 if (!f.isOk()) {
1443 error("Can not open file %s for writing", (const char*)identifier.GetFullPath().mb_str(wxConvUTF8));
1444 return false;
1445 }
1446
1447 if (!saveMap(map, f)) {
1448 return false;
1449 }
1450
1451 g_gui.SetLoadDone(99, "Saving spawns...");
1452 saveSpawns(map, identifier);
1453
1454 g_gui.SetLoadDone(99, "Saving houses...");
1455 saveHouses(map, identifier);
1456
1457 // to do
1458 // g_gui.SetLoadDone(99, "Saving waypoints...");
1459 // saveWaypoints(map, identifier);
1460 return true;
1461}
1462
1463bool IOMapOTBM::saveMap(Map& map, NodeFileWriteHandle& f) {
1464 /* STOP!
1465 * Before you even think about modifying this, please reconsider.
1466 * while adding stuff to the binary format may be "cool", you'll
1467 * inevitably make it incompatible with any future releases of
1468 * the map editor, meaning you cannot reuse your map. Before you
1469 * try to modify this, PLEASE consider using an external file
1470 * like spawns.xml or houses.xml, as that will be MUCH easier
1471 * to port to newer versions of the editor than a custom binary
1472 * format.
1473 */
1474
1475 bool waypointsWarning = false;
1476
1477 const IOMapOTBM& self = *this;
1478
1479 FileName tmpName;
1480 MapVersion mapVersion = map.getVersion();
1481
1482 f.addNode(0);
1483 {
1484 f.addU32(mapVersion.otbm); // Version
1485
1486 f.addU16(map.width);
1487 f.addU16(map.height);
1488
1489 f.addU32(g_items.MajorVersion);
1490 f.addU32(g_items.MinorVersion);
1491
1492 f.addNode(OTBM_MAP_DATA);
1493 {
1494 f.addByte(OTBM_ATTR_DESCRIPTION);
1495 // Neither SimOne's nor OpenTibia cares for additional description tags
1496 f.addString("Saved with " + __RME_APPLICATION_NAME__ + " " + __RME_VERSION__);
1497
1498 f.addU8(OTBM_ATTR_DESCRIPTION);
1499 f.addString(map.description);
1500
1501 tmpName.Assign(wxstr(map.spawnfile));
1502 f.addU8(OTBM_ATTR_EXT_SPAWN_FILE);
1503 f.addString(nstr(tmpName.GetFullName()));
1504
1505 tmpName.Assign(wxstr(map.housefile));
1506 f.addU8(OTBM_ATTR_EXT_HOUSE_FILE);
1507 f.addString(nstr(tmpName.GetFullName()));
1508
1509 // Start writing tiles
1510 uint32_t tiles_saved = 0;
1511 bool first = true;
1512
1513 int local_x = -1, local_y = -1, local_z = -1;
1514
1515 MapIterator map_iterator = map.begin();
1516 while (map_iterator != map.end()) {
1517 // Update progressbar
1518 ++tiles_saved;
1519 if (tiles_saved % 8192 == 0) {
1520 g_gui.SetLoadDone(int(tiles_saved / double(map.getTileCount()) * 100.0));
1521 }
1522
1523 // Get tile
1524 Tile* save_tile = (*map_iterator)->get();
1525
1526 // Is it an empty tile that we can skip? (Leftovers...)
1527 if (!save_tile || save_tile->size() == 0) {
1528 ++map_iterator;
1529 continue;
1530 }
1531
1532 const Position& pos = save_tile->getPosition();
1533
1534 // Decide if newd node should be created
1535 if (pos.x < local_x || pos.x >= local_x + 256 || pos.y < local_y || pos.y >= local_y + 256 || pos.z != local_z) {
1536 // End last node
1537 if (!first) {
1538 f.endNode();
1539 }
1540 first = false;
1541
1542 // Start newd node
1543 f.addNode(OTBM_TILE_AREA);
1544 f.addU16(local_x = pos.x & 0xFF00);
1545 f.addU16(local_y = pos.y & 0xFF00);
1546 f.addU8(local_z = pos.z);
1547 }
1548 f.addNode(save_tile->isHouseTile() ? OTBM_HOUSETILE : OTBM_TILE);
1549
1550 f.addU8(save_tile->getX() & 0xFF);
1551 f.addU8(save_tile->getY() & 0xFF);
1552
1553 if (save_tile->isHouseTile()) {
1554 f.addU32(save_tile->getHouseID());
1555 }
1556
1557 if (save_tile->getMapFlags()) {
1558 f.addByte(OTBM_ATTR_TILE_FLAGS);
1559 f.addU32(save_tile->getMapFlags());
1560 if (save_tile->getMapFlags() & TILESTATE_ZONE_BRUSH) {
1561 for (const auto& zoneId : save_tile->getZoneIds()) {
1562 f.addU16(zoneId);
1563 }
1564 f.addU16(0);
1565 }
1566 }
1567
1568 if (save_tile->ground) {
1569 Item* ground = save_tile->ground;
1570 if (ground->isMetaItem()) {
1571 // Do nothing, we don't save metaitems...
1572 } else if (ground->hasBorderEquivalent()) {
1573 bool found = false;
1574 for (Item* item : save_tile->items) {
1575 if (item->getGroundEquivalent() == ground->getID()) {
1576 // Do nothing
1577 // Found equivalent
1578 found = true;
1579 break;
1580 }
1581 }
1582
1583 if (!found) {
1584 ground->serializeItemNode_OTBM(self, f);
1585 }
1586 } else if (ground->isComplex()) {
1587 ground->serializeItemNode_OTBM(self, f);
1588 } else {
1589 f.addByte(OTBM_ATTR_ITEM);
1590 ground->serializeItemCompact_OTBM(self, f);
1591 }
1592 }
1593
1594 for (Item* item : save_tile->items) {
1595 if (!item->isMetaItem()) {
1596 item->serializeItemNode_OTBM(self, f);
1597 }
1598 }
1599
1600 f.endNode();
1601 ++map_iterator;
1602 }
1603
1604 // Only close the last node if one has actually been created
1605 if (!first) {
1606 f.endNode();
1607 }
1608
1609 f.addNode(OTBM_TOWNS);
1610 for (const auto& townEntry : map.towns) {
1611 Town* town = townEntry.second;
1612 const Position& townPosition = town->getTemplePosition();
1613 f.addNode(OTBM_TOWN);
1614 f.addU32(town->getID());
1615 f.addString(town->getName());
1616 f.addU16(townPosition.x);
1617 f.addU16(townPosition.y);
1618 f.addU8(townPosition.z);
1619 f.endNode();
1620 }
1621 f.endNode();
1622
1623 bool supportWaypoints = version.otbm >= MAP_OTBM_3;
1624 if (supportWaypoints || map.waypoints.waypoints.size() > 0) {
1625 if (!supportWaypoints) {
1626 waypointsWarning = true;
1627 }
1628
1629 f.addNode(OTBM_WAYPOINTS);
1630 for (const auto& waypointEntry : map.waypoints) {
1631 Waypoint* waypoint = waypointEntry.second;
1632 f.addNode(OTBM_WAYPOINT);
1633 f.addString(waypoint->name);
1634 f.addU16(waypoint->pos.x);
1635 f.addU16(waypoint->pos.y);
1636 f.addU8(waypoint->pos.z);
1637 f.endNode();
1638 }
1639 f.endNode();
1640 }
1641 }
1642 f.endNode();
1643 }
1644 f.endNode();
1645
1646 if (waypointsWarning) {
1647 g_gui.PopupDialog(g_gui.root, "Warning", "Waypoints were saved, but they are not supported in OTBM 2!\nIf your map fails to load, consider removing all waypoints and saving again.\n\nThis warning can be disabled in file->preferences.", wxOK);
1648 }
1649 return true;
1650}
1651
1652bool IOMapOTBM::saveSpawns(Map& map, const FileName& dir) {
1653 wxString filepath = dir.GetPath(wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME);
1654 filepath += wxString(map.spawnfile.c_str(), wxConvUTF8);
1655
1656 // Create the XML file
1657 pugi::xml_document doc;
1658 if (saveSpawns(map, doc)) {
1659 return doc.save_file(filepath.wc_str(), "\t", pugi::format_default, pugi::encoding_utf8);
1660 }
1661 return false;
1662}
1663
1664bool IOMapOTBM::saveSpawns(Map& map, pugi::xml_document& doc) {
1665 pugi::xml_node decl = doc.prepend_child(pugi::node_declaration);
1666 if (!decl) {
1667 return false;
1668 }
1669
1670 decl.append_attribute("version") = "1.0";
1671
1672 CreatureList creatureList;
1673
1674 pugi::xml_node spawnNodes = doc.append_child("spawns");
1675 for (const auto& spawnPosition : map.spawns) {
1676 Tile* tile = map.getTile(spawnPosition);
1677 if (tile == nullptr) {
1678 continue;
1679 }
1680
1681 Spawn* spawn = tile->spawn;
1682 ASSERT(spawn);
1683
1684 pugi::xml_node spawnNode = spawnNodes.append_child("spawn");
1685
1686 spawnNode.append_attribute("centerx") = spawnPosition.x;
1687 spawnNode.append_attribute("centery") = spawnPosition.y;
1688 spawnNode.append_attribute("centerz") = spawnPosition.z;
1689
1690 int32_t radius = spawn->getSize();
1691 spawnNode.append_attribute("radius") = radius;
1692
1693 for (int32_t y = -radius; y <= radius; ++y) {
1694 for (int32_t x = -radius; x <= radius; ++x) {
1695 Tile* creature_tile = map.getTile(spawnPosition + Position(x, y, 0));
1696 if (creature_tile) {
1697 Creature* creature = creature_tile->creature;
1698 if (creature && !creature->isSaved()) {
1699 pugi::xml_node creatureNode = spawnNode.append_child(creature->isNpc() ? "npc" : "monster");
1700
1701 creatureNode.append_attribute("name") = creature->getName().c_str();
1702 creatureNode.append_attribute("x") = x;
1703 creatureNode.append_attribute("y") = y;
1704 creatureNode.append_attribute("z") = spawnPosition.z;
1705 creatureNode.append_attribute("spawntime") = creature->getSpawnTime();
1706 if (creature->getDirection() != NORTH) {
1707 creatureNode.append_attribute("direction") = creature->getDirection();
1708 }
1709
1710 // Mark as saved
1711 creature->save();
1712 creatureList.push_back(creature);
1713 }
1714 }
1715 }
1716 }
1717 }
1718
1719 for (Creature* creature : creatureList) {
1720 creature->reset();
1721 }
1722 return true;
1723}
1724
1725bool IOMapOTBM::saveHouses(Map& map, const FileName& dir) {
1726 wxString filepath = dir.GetPath(wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME);
1727 filepath += wxString(map.housefile.c_str(), wxConvUTF8);
1728
1729 // Create the XML file
1730 pugi::xml_document doc;
1731 if (saveHouses(map, doc)) {
1732 return doc.save_file(filepath.wc_str(), "\t", pugi::format_default, pugi::encoding_utf8);
1733 }
1734 return false;
1735}
1736
1737bool IOMapOTBM::saveHouses(Map& map, pugi::xml_document& doc) {
1738 pugi::xml_node decl = doc.prepend_child(pugi::node_declaration);
1739 if (!decl) {
1740 return false;
1741 }
1742
1743 decl.append_attribute("version") = "1.0";
1744
1745 pugi::xml_node houseNodes = doc.append_child("houses");
1746 for (const auto& houseEntry : map.houses) {
1747 const House* house = houseEntry.second;
1748 pugi::xml_node houseNode = houseNodes.append_child("house");
1749
1750 houseNode.append_attribute("name") = house->name.c_str();
1751 houseNode.append_attribute("houseid") = house->getID();
1752
1753 const Position& exitPosition = house->getExit();
1754 houseNode.append_attribute("entryx") = exitPosition.x;
1755 houseNode.append_attribute("entryy") = exitPosition.y;
1756 houseNode.append_attribute("entryz") = exitPosition.z;
1757
1758 houseNode.append_attribute("rent") = house->rent;
1759 if (house->guildhall) {
1760 houseNode.append_attribute("guildhall") = true;
1761 }
1762
1763 houseNode.append_attribute("townid") = house->townid;
1764 houseNode.append_attribute("size") = static_cast<int32_t>(house->size());
1765 }
1766 return true;
1767}
1768
1769bool IOMapOTBM::saveWaypoints(Map& map, const FileName& dir) {
1770 wxString filepath = dir.GetPath(wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME);
1771 filepath += wxString(map.waypointfile.c_str(), wxConvUTF8);
1772
1773 // Create the XML file
1774 pugi::xml_document doc;
1775 if (saveWaypoints(map, doc)) {
1776 return doc.save_file(filepath.wc_str(), "\t", pugi::format_default, pugi::encoding_utf8);
1777 }
1778 return false;
1779}
1780
1781bool IOMapOTBM::saveWaypoints(Map& map, pugi::xml_document& doc) {
1782 pugi::xml_node decl = doc.prepend_child(pugi::node_declaration);
1783 if (!decl) {
1784 return false;
1785 }
1786
1787 decl.append_attribute("version") = "1.0";
1788
1789 pugi::xml_node houseNodes = doc.append_child("waypoints");
1790 for (const auto& houseEntry : map.houses) {
1791 const House* house = houseEntry.second;
1792 pugi::xml_node houseNode = houseNodes.append_child("waypoint");
1793
1794 houseNode.append_attribute("name") = house->name.c_str();
1795 houseNode.append_attribute("id") = house->getID();
1796 houseNode.append_attribute("icon") = house->getID();
1797
1798 const Position& exitPosition = house->getExit();
1799 houseNode.append_attribute("x") = exitPosition.x;
1800 houseNode.append_attribute("y") = exitPosition.y;
1801 houseNode.append_attribute("z") = exitPosition.z;
1802
1803 houseNode.append_attribute("townid") = house->townid;
1804 }
1805 return true;
1806}
1807