iomap_otmm.cpp

main 975 lines · 26.7 KB Raw
Alex Alex Commit Map Editor 18/10/2026 23:40
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 "iomap_otmm.h"
21#include "item.h"
22#include "complexitem.h"
23#include "iomap_otmm.h"
24#include "filehandle.h"
25#include "map.h"
26#include "gui.h"
27
28// ============================================================================
29// Item
30
31Item* Item::Create_OTMM(const IOMap& maphandle, BinaryNode* stream) {
32 uint16_t _id;
33 if (!stream->getU16(_id)) {
34 return nullptr;
35 }
36
37 return Item::Create(_id);
38}
39
40bool Item::readItemAttribute_OTMM(const IOMap& maphandle, OTMM_ItemAttribute attr, BinaryNode* stream) {
41 switch (attr) {
42 case OTMM_ATTR_SUBTYPE: {
43 uint16_t subtype;
44 if (!stream->getU16(subtype)) {
45 return false;
46 }
47
48 setSubtype(subtype & 0xf);
49 } break;
50 case OTMM_ATTR_ACTION_ID: {
51 uint16_t aid;
52 if (!stream->getU16(aid)) {
53 return false;
54 }
55 setActionID(aid);
56 } break;
57 case OTMM_ATTR_UNIQUE_ID: {
58 uint16_t uid;
59 if (!stream->getU16(uid)) {
60 return false;
61 }
62
63 setUniqueID(uid);
64 } break;
65 case OTMM_ATTR_TEXT: {
66 std::string text;
67 if (!stream->getString(text)) {
68 return false;
69 }
70
71 setText(text);
72 } break;
73 case OTMM_ATTR_DESC: {
74 std::string text;
75 if (!stream->getString(text)) {
76 return false;
77 }
78
79 setDescription(text);
80 } break;
81
82 // If otb structure changes....
83 case OTMM_ATTR_DEPOT_ID: {
84 return stream->skip(2);
85 } break;
86 case OTMM_ATTR_DOOR_ID: {
87 return stream->skip(1);
88 } break;
89 case OTMM_ATTR_TELE_DEST: {
90 return stream->skip(5);
91 } break;
92 default: {
93 return false;
94 } break;
95 }
96
97 return true;
98}
99
100bool Item::unserializeAttributes_OTMM(const IOMap& maphandle, BinaryNode* stream) {
101 uint8_t attribute;
102 while (stream->getU8(attribute)) {
103 if (!readItemAttribute_OTMM(maphandle, OTMM_ItemAttribute(attribute), stream)) {
104 return false;
105 }
106 }
107 return true;
108}
109
110bool Item::unserializeItemNode_OTMM(const IOMap& maphandle, BinaryNode* node) {
111 return unserializeAttributes_OTMM(maphandle, node);
112}
113
114void Item::serializeItemAttributes_OTMM(const IOMap& maphandle, NodeFileWriteHandle& stream) const {
115 if (getSubtype() > 0) {
116 stream.addU8(OTMM_ATTR_SUBTYPE);
117 stream.addU16(getSubtype());
118 }
119
120 if (getActionID()) {
121 stream.addU8(OTMM_ATTR_ACTION_ID);
122 stream.addU16(getActionID());
123 }
124
125 if (getUniqueID()) {
126 stream.addU8(OTMM_ATTR_UNIQUE_ID);
127 stream.addU16(getUniqueID());
128 }
129
130 if (getText().length() > 0) {
131 stream.addU8(OTMM_ATTR_TEXT);
132 stream.addString(getText());
133 }
134}
135
136void Item::serializeItemCompact_OTMM(const IOMap& maphandle, NodeFileWriteHandle& stream) const {
137 stream.addU16(id);
138}
139
140bool Item::serializeItemNode_OTMM(const IOMap& maphandle, NodeFileWriteHandle& f) const {
141 f.addNode(OTMM_ITEM);
142 f.addU16(id);
143 serializeItemAttributes_OTMM(maphandle, f);
144 f.endNode();
145
146 return true;
147}
148
149// ============================================================================
150// Teleport
151
152bool Teleport::readItemAttribute_OTMM(const IOMap& maphandle, OTMM_ItemAttribute attribute, BinaryNode* stream) {
153 if (attribute == OTMM_ATTR_TELE_DEST) {
154 uint16_t x = 0;
155 uint16_t y = 0;
156 uint8_t z = 0;
157 if (!stream->getU16(x) || !stream->getU16(y) || !stream->getU8(z)) {
158 return false;
159 }
160
161 destination = Position(x, y, z);
162 return true;
163 } else {
164 return Item::readItemAttribute_OTMM(maphandle, attribute, stream);
165 }
166}
167
168void Teleport::serializeItemAttributes_OTMM(const IOMap& maphandle, NodeFileWriteHandle& stream) const {
169 Item::serializeItemAttributes_OTMM(maphandle, stream);
170
171 stream.addByte(OTMM_ATTR_TELE_DEST);
172 stream.addU16(destination.x);
173 stream.addU16(destination.y);
174 stream.addU8(destination.z);
175}
176
177// ============================================================================
178// Door
179
180bool Door::readItemAttribute_OTMM(const IOMap& maphandle, OTMM_ItemAttribute attribute, BinaryNode* stream) {
181 if (attribute == OTMM_ATTR_DOOR_ID) {
182 uint8_t id = 0;
183 if (!stream->getU8(id)) {
184 return false;
185 }
186 doorid = id;
187 return true;
188 } else {
189 return Item::readItemAttribute_OTMM(maphandle, attribute, stream);
190 }
191}
192
193void Door::serializeItemAttributes_OTMM(const IOMap& maphandle, NodeFileWriteHandle& stream) const {
194 Item::serializeItemAttributes_OTMM(maphandle, stream);
195 if (doorid) {
196 stream.addByte(OTMM_ATTR_DOOR_ID);
197 stream.addU8(doorid);
198 }
199}
200
201// ============================================================================
202// Depots
203
204bool Depot::readItemAttribute_OTMM(const IOMap& maphandle, OTMM_ItemAttribute attribute, BinaryNode* stream) {
205 if (attribute == OTMM_ATTR_DEPOT_ID) {
206 uint16_t id = 0;
207 if (!stream->getU16(id)) {
208 return false;
209 }
210 depotid = id;
211 return true;
212 } else {
213 return Item::readItemAttribute_OTMM(maphandle, attribute, stream);
214 }
215}
216
217void Depot::serializeItemAttributes_OTMM(const IOMap& maphandle, NodeFileWriteHandle& stream) const {
218 Item::serializeItemAttributes_OTMM(maphandle, stream);
219 if (depotid) {
220 stream.addByte(OTMM_ATTR_DEPOT_ID);
221 stream.addU16(depotid);
222 }
223}
224
225// ============================================================================
226// Container
227
228bool Container::unserializeItemNode_OTMM(const IOMap& maphandle, BinaryNode* node) {
229 bool ret = Item::unserializeAttributes_OTMM(maphandle, node);
230
231 if (ret) {
232 BinaryNode* child = node->getChild();
233 if (child) {
234 do {
235 uint8_t type;
236 if (!child->getByte(type)) {
237 return false;
238 }
239 // load container items
240 if (type == OTMM_ITEM) {
241 Item* item = Item::Create_OTMM(maphandle, child);
242 if (!item) {
243 return false;
244 }
245 if (!item->unserializeItemNode_OTMM(maphandle, child)) {
246 delete item;
247 return false;
248 }
249 contents.push_back(item);
250 } else {
251 // corrupted file data!
252 return false;
253 }
254 } while (child->advance());
255 }
256 return true;
257 }
258 return false;
259}
260
261bool Container::serializeItemNode_OTMM(const IOMap& maphandle, NodeFileWriteHandle& f) const {
262 f.addNode(OTMM_ITEM);
263
264 f.addU16(id);
265 serializeItemAttributes_OTMM(maphandle, f);
266
267 for (ItemVector::const_iterator it = contents.begin(); it != contents.end(); ++it) {
268 (*it)->serializeItemNode_OTMM(maphandle, f);
269 }
270 f.endNode();
271 return true;
272}
273
274IOMapOTMM::IOMapOTMM() {
275}
276
277IOMapOTMM::~IOMapOTMM() {
278}
279
280ClientVersionID IOMapOTMM::getVersionInfo(const FileName& filename) {
281 wxString wpath = filename.GetFullPath();
282 DiskNodeFileReadHandle f((const char*)wpath.mb_str(wxConvUTF8));
283 if (f.isOk() == false) {
284 return CLIENT_VERSION_NONE;
285 }
286
287 BinaryNode* root = f.getRootNode();
288 if (!root) {
289 return CLIENT_VERSION_NONE;
290 }
291 root->skip(1); // Skip the type byte
292
293 uint16_t u16;
294 uint32_t u32;
295
296 if (!root->getU32(u32) || u32 != 1) { // Version
297 return CLIENT_VERSION_NONE;
298 }
299
300 root->getU16(u16);
301 root->getU16(u16);
302 root->getU32(u32);
303
304 if (root->getU32(u32)) { // OTB minor version
305 return ClientVersionID(u32);
306 }
307 return CLIENT_VERSION_NONE;
308}
309
310bool IOMapOTMM::loadMap(Map& map, const FileName& identifier, bool showdialog) {
311 if (showdialog) {
312 g_gui.CreateLoadBar("Loading OTMM map...");
313 }
314 DiskNodeFileReadHandle f(nstr(identifier.GetFullPath()));
315 if (f.isOk() == false) {
316 error("Couldn't open file for reading\nThe error reported was: " + wxstr(f.getErrorMessage()));
317 return false;
318 }
319
320 bool ret = loadMap(map, f, identifier, showdialog);
321
322 if (showdialog) {
323 g_gui.DestroyLoadBar();
324 }
325
326 return ret;
327}
328
329bool IOMapOTMM::loadMap(Map& map, NodeFileReadHandle& f, const FileName& identifier, bool showdialog) {
330 BinaryNode* root = f.getRootNode();
331 if (!root) {
332 error("Could not read root node.");
333 return false;
334 }
335 root->skip(1); // Skip the type byte
336
337 uint8_t u8;
338 uint16_t u16;
339 uint32_t u32;
340
341 if (!root->getU32(u32) || u32 != 1) { // Version
342 error("Unsupported or damaged map version.");
343 return false;
344 }
345
346 if (!root->getU16(u16)) {
347 error("Could not read root header.");
348 return false;
349 }
350 map.width = u16;
351 if (!root->getU16(u16)) {
352 error("Could not read root header.");
353 return false;
354 }
355 map.height = u16;
356
357 if (!root->getU32(u32) || u32 > (unsigned long)g_items.MajorVersion) { // OTB major version
358 if (queryUser("Map error", "The loaded map appears to be a items.otb format that deviates from the items.otb loaded by the editor. Do you still want to attempt to load the map?")) {
359 warning("Unsupported or damaged map version");
360 } else {
361 error("Outdated items.otb, could not load map.");
362 return false;
363 }
364 }
365
366 if (!root->getU32(u32) || u32 > (unsigned long)g_items.MinorVersion) { // OTB minor version
367 warning("The editor needs an updated items.otb version.");
368 }
369
370 BinaryNode* mapHeaderNode = root->getChild();
371 if (mapHeaderNode == nullptr || !mapHeaderNode->getByte(u8) || u8 != OTMM_MAP_DATA) {
372 error("Could not get root child node. Cannot recover from fatal error!");
373 return false;
374 }
375
376 int nodes_loaded = 0;
377
378 BinaryNode* mapNode = mapHeaderNode->getChild();
379 if (mapNode) {
380 do {
381 ++nodes_loaded;
382 if (showdialog && nodes_loaded % 15 == 0) {
383 g_gui.SetLoadDone(int(100.0 * f.tell() / f.size()));
384 }
385 uint8_t node_type;
386 if (!mapNode->getByte(node_type)) {
387 warning("Invalid map node");
388 continue;
389 }
390 switch (node_type) {
391 case OTMM_EDITOR: {
392 } break;
393 case OTMM_DESCRIPTION: {
394 std::string desc;
395 mapNode->getString(desc);
396 map.setMapDescription(desc);
397 } break;
398 case OTMM_TILE_DATA: {
399 BinaryNode* tileNode = mapNode->getChild();
400 if (tileNode) {
401 do {
402 Tile* tile = nullptr;
403 uint8_t tile_type;
404 if (!tileNode->getByte(tile_type)) {
405 warning("Invalid tile type");
406 continue;
407 }
408 if (tile_type != OTMM_TILE && tile_type != OTMM_HOUSETILE) {
409 warning("Unknown type of tile node");
410 continue;
411 }
412
413 uint16_t x_offset, y_offset;
414 uint8_t z_offset;
415 if (!tileNode->getU16(x_offset) || !tileNode->getU16(y_offset) || !tileNode->getU8(z_offset)) {
416 warning("Could not read position of tile");
417 continue;
418 }
419 const Position pos(x_offset, y_offset, z_offset);
420
421 if (map.getTile(pos)) {
422 warning("Duplicate tile at %d:%d:%d, discarding duplicate", pos.x, pos.y, pos.z);
423 continue;
424 }
425
426 tile = map.allocator(pos);
427 House* house = nullptr;
428 if (tile_type == OTMM_HOUSETILE) {
429 uint32_t house_id;
430 if (!tileNode->getU32(house_id)) {
431 warning("House tile without house data, discarding tile");
432 continue;
433 }
434 if (house_id) {
435 house = map.houses.getHouse(house_id);
436 if (!house) {
437 house = newd House(map);
438 house->id = house_id;
439 map.houses.addHouse(house);
440 }
441 } else {
442 warning("Invalid house id from tile %d:%d:%d", pos.x, pos.y, pos.z);
443 }
444 }
445
446 uint16_t ground_id;
447 tileNode->getU16(ground_id);
448 if (ground_id != 0) {
449 tile->addItem(Item::Create(ground_id));
450 }
451
452 uint8_t attribute;
453 while (tileNode->getU8(attribute)) {
454 switch (attribute) {
455 case OTMM_ATTR_TILE_FLAGS: {
456 uint32_t flags = 0;
457 if (!tileNode->getU32(flags)) {
458 warning("Invalid tile flags of tile on %d:%d:%d", pos.x, pos.y, pos.z);
459 }
460 tile->setMapFlags(flags);
461 } break;
462 default: {
463 warning("Unknown tile attribute at %d:%d:%d", pos.x, pos.y, pos.z);
464 } break;
465 }
466 }
467
468 BinaryNode* itemNode = tileNode->getChild();
469 if (itemNode) {
470 do {
471 Item* item = nullptr;
472 uint8_t item_type;
473 if (!itemNode->getByte(item_type)) {
474 warning("Unknown item type %d:%d:%d", pos.x, pos.y, pos.z);
475 continue;
476 }
477 if (item_type == OTMM_ITEM) {
478 item = Item::Create_OTMM(*this, itemNode);
479 if (item) {
480 if (item->unserializeItemNode_OTMM(*this, itemNode) == false) {
481 warning("Couldn't unserialize item attributes at %d:%d:%d", pos.x, pos.y, pos.z);
482 }
483 tile->addItem(item);
484 }
485 } else {
486 warning("Unknown type of tile child node");
487 }
488 } while (itemNode->advance());
489 }
490
491 tile->update();
492 if (house) {
493 house->addTile(tile);
494 }
495 map.setTile(pos, tile);
496 } while (tileNode->advance());
497 }
498 } break;
499 case OTMM_SPAWN_DATA: {
500 BinaryNode* spawnNode = mapNode->getChild();
501 if (spawnNode) {
502 do {
503 uint8_t spawn_type;
504 if (!spawnNode->getByte(spawn_type)) {
505 warning("Could not read spawn type.");
506 continue;
507 }
508 if (spawn_type != OTMM_SPAWN_AREA) {
509 warning("Invalid spawn type.");
510 continue;
511 }
512
513 // Read position
514 uint16_t spawn_x, spawn_y;
515 uint8_t spawn_z;
516 uint32_t radius;
517 if (!spawnNode->getU16(spawn_x) || !spawnNode->getU16(spawn_y) || !spawnNode->getU8(spawn_z)) {
518 warning("Could not read spawn position.");
519 continue;
520 }
521 const Position spawnpos(spawn_x, spawn_y, spawn_z);
522
523 // Read radius
524 if (!spawnNode->getU32(radius)) {
525 warning("Could not read spawn radius.");
526 continue;
527 }
528 // Adjust radius
529 radius = min(radius, uint32_t(g_settings.getInteger(Config::MAX_SPAWN_RADIUS)));
530
531 // Create and assign spawn
532 Tile* spawn_tile = map.getTile(spawnpos);
533 if (spawn_tile && spawn_tile->spawn) {
534 warning("Duplicate spawn on position %d:%d:%d\n", spawn_tile->getX(), spawn_tile->getY(), spawn_tile->getZ());
535 continue;
536 }
537
538 Spawn* spawn = newd Spawn(radius);
539 if (!spawn_tile) {
540 spawn_tile = map.allocator(spawnpos);
541 map.setTile(spawnpos, spawn_tile);
542 }
543 spawn_tile->spawn = spawn;
544 map.addSpawn(spawn_tile);
545
546 // Read any creatures associated with the spawn
547 BinaryNode* creatureNode = spawnNode->getChild();
548 if (creatureNode) {
549 do {
550 uint8_t creature_type;
551 if (!creatureNode->getByte(creature_type)) {
552 warning("Could not read type of creature node.");
553 continue;
554 }
555 bool isNPC;
556 std::string name;
557 uint32_t spawntime = 0; // Only applicable for monsters
558
559 if (creature_type == OTMM_NPC) {
560 isNPC = true;
561 if (!creatureNode->getString(name)) {
562 warning("Could not read name of NPC.");
563 return false;
564 }
565 } else if (creature_type == OTMM_MONSTER) {
566 isNPC = false;
567 if (!creatureNode->getString(name)) {
568 warning("Could not read name of monster.");
569 return false;
570 }
571 if (!creatureNode->getU32(spawntime)) {
572 warning("Could not read spawn time of monster.");
573 return false;
574 }
575 } else {
576 warning("Unknown creature node type (0x%.2x).", creature_type);
577 return false;
578 }
579
580 // Read creature position
581 uint16_t creature_x, creature_y;
582 uint8_t creature_z;
583 if (!creatureNode->getU16(creature_x) || !creatureNode->getU16(creature_y) || !creatureNode->getU8(creature_z)) {
584 warning("Could not read creature position.");
585 continue;
586 }
587 const Position creaturepos(creature_x, creature_y, creature_z);
588
589 // Check radius
590 if (uint32_t(abs(creaturepos.x - spawnpos.x)) > radius || uint32_t(abs(creaturepos.y - spawnpos.y)) > radius) {
591 // Outside of the spawn...
592 }
593
594 // Create creature and put on map
595 Tile* creature_tile;
596 if (creaturepos == spawnpos) {
597 creature_tile = spawn_tile;
598 } else {
599 creature_tile = map.getTile(creaturepos);
600 }
601 if (!creature_tile) {
602 warning("Discarding creature \"%s\" at %d:%d:%d due to invalid position", name.c_str(), creaturepos.x, creaturepos.y, creaturepos.z);
603 break;
604 }
605 if (creature_tile->creature) {
606 warning("Duplicate creature \"%s\" at %d:%d:%d, discarding", name.c_str(), creaturepos.x, creaturepos.y, creaturepos.z);
607 break;
608 }
609 CreatureType* type = g_creatures[name];
610 if (!type) {
611 type = g_creatures.addMissingCreatureType(name, isNPC);
612 }
613 Creature* creature = newd Creature(type);
614 creature->setSpawnTime(spawntime);
615 creature_tile->creature = creature;
616 if (creature_tile->spawn_count == 0) {
617 // No spawn, create a newd one (this happends if the radius of the spawn has been decreased due to g_settings)
618 ASSERT(creature_tile->spawn == nullptr);
619 Spawn* spawn = newd Spawn(5);
620 creature_tile->spawn = spawn;
621 map.addSpawn(creature_tile);
622 }
623 } while (creatureNode->advance());
624 }
625 } while (spawnNode->advance());
626 }
627 } break;
628 case OTMM_TOWN_DATA: {
629 BinaryNode* townNode = mapNode->getChild();
630 if (townNode) {
631 do {
632 uint8_t town_type;
633 if (!townNode->getByte(town_type)) {
634 warning("Could not read town type");
635 continue;
636 }
637 if (town_type != OTMM_TOWN) {
638 warning("Unknown town type");
639 continue;
640 }
641 uint32_t town_id;
642 if (!townNode->getU32(town_id)) {
643 warning("Invalid town id");
644 continue;
645 }
646
647 Town* town = map.towns.getTown(town_id);
648 if (town) {
649 warning("Duplicate town id %d, discarding duplicate", town_id);
650 continue;
651 } else {
652 town = newd Town(town_id);
653 if (!map.towns.addTown(town)) {
654 delete town;
655 continue;
656 }
657 }
658 std::string town_name;
659 if (!townNode->getString(town_name)) {
660 warning("Invalid town name");
661 continue;
662 }
663 town->setName(town_name);
664 Position pos;
665 uint16_t x;
666 uint16_t y;
667 uint8_t z;
668 if (!townNode->getU16(x) || !townNode->getU16(y) || !townNode->getU8(z)) {
669 warning("Invalid town temple position");
670 continue;
671 }
672 pos.x = x;
673 pos.y = y;
674 pos.z = z;
675 town->setTemplePosition(pos);
676 } while (townNode->advance());
677 }
678 } break;
679 case OTMM_HOUSE_DATA: {
680 BinaryNode* houseNode = mapNode->getChild();
681 if (houseNode) {
682 do {
683 uint8_t house_type;
684 if (!houseNode->getByte(house_type)) {
685 warning("Could not read house type");
686 continue;
687 }
688 if (house_type != OTMM_HOUSE) {
689 warning("Unknown house type.");
690 continue;
691 }
692 uint32_t house_id;
693 if (!houseNode->getU32(house_id)) {
694 warning("Could not read house id.");
695 continue;
696 }
697
698 House* house = map.houses.getHouse(house_id);
699 if (!house) {
700 continue;
701 }
702
703 std::string house_name;
704 if (!houseNode->getString(house_name)) {
705 warning("Could not read house name.");
706 continue;
707 }
708
709 uint32_t town_id;
710 if (!houseNode->getU32(town_id)) {
711 warning("Could not read house town id.");
712 continue;
713 }
714
715 uint32_t rent;
716 if (!houseNode->getU32(rent)) {
717 warning("Could not read house rent.");
718 continue;
719 }
720
721 house->name = house_name;
722 house->townid = town_id;
723 house->rent = rent;
724
725 uint16_t x;
726 uint16_t y;
727 uint8_t z;
728 if (!houseNode->getU16(x) || !houseNode->getU16(y) || !houseNode->getU8(z)) {
729 warning("Invalid town temple position");
730 continue;
731 }
732 house->setExit(Position(x, y, z));
733 } while (houseNode->advance());
734 }
735 } break;
736 }
737 } while (mapNode->advance());
738 }
739 return true;
740}
741
742bool IOMapOTMM::saveMap(Map& map, const FileName& identifier, bool showdialog) {
743 DiskNodeFileWriteHandle f(std::string(identifier.GetFullPath().mb_str(wxConvUTF8)));
744
745 if (f.isOk() == false) {
746 error("Can not open file %s for writing", (const char*)identifier.GetFullPath().mb_str(wxConvUTF8));
747 return false;
748 }
749
750 if (showdialog) {
751 g_gui.CreateLoadBar("Saving OTMM map...");
752 }
753 bool ret = saveMap(map, f, identifier, showdialog);
754 if (showdialog) {
755 g_gui.DestroyLoadBar();
756 }
757
758 return ret;
759}
760
761bool IOMapOTMM::saveMap(Map& map, NodeFileWriteHandle& f, const FileName& identifier, bool showdialog) {
762 /* STOP!
763 * Before you even think about modifying this, please reconsider.
764 * while adding stuff to the binary format may be "cool", you'll
765 * inevitably make it incompatible with any future releases of
766 * the map editor, meaning you cannot reuse your map. Before you
767 * try to modify this, PLEASE consider using an external file
768 * like spawns.xml or houses.xml, as that will be MUCH easier
769 * to port to newer versions of the editor than a custom binary
770 * format.
771 */
772
773 f.addNode(0);
774 {
775 f.addU32(1); // Version
776 f.addU16(map.getWidth());
777 f.addU16(map.getHeight());
778 f.addU32(g_items.MajorVersion);
779 f.addU32(g_items.MinorVersion);
780
781 f.addNode(OTMM_MAP_DATA);
782 {
783 f.addNode(OTMM_EDITOR);
784 {
785 f.addString("Saved with Remere's Map Editor " + __RME_VERSION__);
786 }
787 f.endNode(); // OTMM_DESCRIPTION
788 f.addNode(OTMM_DESCRIPTION);
789 {
790 f.addString(map.getMapDescription());
791 }
792 f.endNode(); // OTMM_DESCRIPTION
793
794 // Start writing tiles
795 uint tiles_saved = 0;
796
797 MapIterator map_iterator = map.begin();
798
799 f.addNode(OTMM_TILE_DATA);
800 {
801 while (map_iterator != map.end()) {
802 // Update progressbar
803 ++tiles_saved;
804 if (showdialog && tiles_saved % 8192 == 0) {
805 g_gui.SetLoadDone(int(tiles_saved / double(map.getTileCount()) * 100.0));
806 }
807
808 // Get tile
809 const Tile* save_tile = *map_iterator;
810 const Position& pos = save_tile->getPosition();
811
812 // Is it an empty tile that we can skip? (Leftovers...)
813 if (save_tile->size() == 0) {
814 ++map_iterator;
815 continue;
816 }
817
818 f.addNode(save_tile->isHouseTile() ? OTMM_HOUSETILE : OTMM_TILE);
819 {
820 f.addU16(save_tile->getX());
821 f.addU16(save_tile->getY());
822 f.addU8(save_tile->getZ() & 0xf);
823
824 if (save_tile->isHouseTile()) {
825 f.addU32(save_tile->getHouseID());
826 }
827 if (save_tile->getMapFlags()) {
828 f.addByte(OTMM_ATTR_TILE_FLAGS);
829 f.addU32(save_tile->getMapFlags());
830 }
831
832 if (save_tile->ground) {
833 const Item* ground = save_tile->ground;
834 if (ground->isMetaItem()) {
835 // Do nothing, we don't save metaitems...
836 f.addU16(0);
837 } else if (ground->hasBorderEquivalent()) {
838 bool found = false;
839 for (ItemVector::const_iterator it = save_tile->items.begin(); it != save_tile->items.end(); ++it) {
840 if ((*it)->getGroundEquivalent() == ground->getID()) {
841 // Do nothing
842 // Found equivalent
843 found = true;
844 break;
845 }
846 }
847 if (found == false) {
848 f.addU16(ground->getID());
849 } else {
850 f.addU16(0);
851 }
852 } else if (ground->isComplex() || ground->hasSubtype()) {
853 f.addU16(0);
854 ground->serializeItemNode_OTMM(*this, f);
855 } else {
856 f.addU16(ground->getID());
857 }
858 } else {
859 f.addU16(0);
860 }
861
862 for (ItemVector::const_iterator it = save_tile->items.begin(); it != save_tile->items.end(); ++it) {
863 if (!(*it)->isMetaItem()) {
864 (*it)->serializeItemNode_OTMM(*this, f);
865 }
866 }
867 }
868 f.endNode();
869
870 ++map_iterator;
871 }
872 }
873 f.endNode(); // OTMM_TILE_DATA
874
875 f.addNode(OTMM_SPAWN_DATA);
876 {
877 Spawns& spawns = map.spawns;
878 CreatureList creature_list;
879 for (SpawnPositionList::const_iterator piter = spawns.begin(); piter != spawns.end(); ++piter) {
880 const Tile* tile = map.getTile(*piter);
881 ASSERT(tile);
882 const Spawn* spawn = tile->spawn;
883 ASSERT(spawn);
884
885 f.addNode(OTMM_SPAWN_AREA);
886 {
887 f.addU16(tile->getX());
888 f.addU16(tile->getY());
889 f.addU8(tile->getZ() & 0xf);
890 f.addU32(spawn->getSize());
891 for (int y = -tile->spawn->getSize(); y <= tile->spawn->getSize(); ++y) {
892 for (int x = -tile->spawn->getSize(); x <= tile->spawn->getSize(); ++x) {
893 Tile* creature_tile = map.getTile(*piter + Position(x, y, 0));
894 if (creature_tile) {
895 Creature* c = creature_tile->creature;
896 if (c && c->isSaved() == false) {
897 if (c->isNpc()) {
898 f.addNode(OTMM_NPC);
899 {
900 f.addString(c->getName());
901 f.addU16(creature_tile->getX());
902 f.addU16(creature_tile->getY());
903 f.addU8(creature_tile->getZ() & 0xf);
904 }
905 f.endNode(); // OTMM_NPC
906 } else {
907 f.addNode(OTMM_MONSTER);
908 {
909 f.addString(c->getName());
910 f.addU32(c->getSpawnTime());
911 f.addU16(creature_tile->getX());
912 f.addU16(creature_tile->getY());
913 f.addU8(creature_tile->getZ() & 0xf);
914 }
915 f.endNode(); // OTMM_MONSTER
916 }
917
918 // Flag as saved
919 c->save();
920 creature_list.push_back(c);
921 }
922 }
923 }
924 }
925 }
926 f.endNode(); // OTMM_SPAWN_AREA
927 }
928 for (CreatureList::iterator iter = creature_list.begin(); iter != creature_list.end(); ++iter) {
929 (*iter)->reset();
930 }
931 }
932 f.endNode(); // OTMM_SPAWN_DATA
933
934 f.addNode(OTMM_TOWN_DATA);
935 {
936 for (TownMap::const_iterator it = map.towns.begin(); it != map.towns.end(); ++it) {
937 const Town* town = it->second;
938 f.addNode(OTMM_TOWN);
939 {
940 f.addU32(town->getID());
941 f.addString(town->getName());
942 f.addU16(town->getTemplePosition().x);
943 f.addU16(town->getTemplePosition().y);
944 f.addU8(town->getTemplePosition().z & 0xf);
945 }
946 f.endNode(); // OTMM_TOWN
947 }
948 }
949 f.endNode(); // OTMM_TOWN_DATA
950
951 f.addNode(OTMM_HOUSE_DATA);
952 {
953 for (HouseMap::const_iterator it = map.houses.begin(); it != map.houses.end(); ++it) {
954 const House* house = it->second;
955 f.addNode(OTMM_HOUSE);
956 {
957 f.addU32(house->id);
958 f.addString(house->name);
959 f.addU16(house->townid);
960 f.addU16(house->rent);
961 f.addU16(house->getExit().x);
962 f.addU16(house->getExit().y);
963 f.addU8(house->getExit().z & 0xf);
964 }
965 f.endNode(); // OTMM_HOUSE
966 }
967 }
968 f.endNode(); // OTMM_HOUSE_DATA
969 }
970 f.endNode(); // OTMM_MAP_DATA
971 }
972 f.endNode(); // root node
973 return true;
974}
975
Top