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
19#include "main.h"
20
21#include "editor.h"
22#include "materials.h"
23#include "map.h"
24#include "complexitem.h"
25#include "settings.h"
26#include "gui.h"
27#include "map_display.h"
28#include "brush.h"
29#include "ground_brush.h"
30#include "wall_brush.h"
31#include "table_brush.h"
32#include "carpet_brush.h"
33#include "waypoint_brush.h"
34#include "house_exit_brush.h"
35#include "doodad_brush.h"
36#include "creature_brush.h"
37#include "spawn_brush.h"
38
39#include "live_server.h"
40#include "live_client.h"
41#include "live_action.h"
42#include "minimap_window.h"
43#include "borderize_window.h"
44
45Editor::Editor(CopyBuffer& copybuffer) :
46 live_server(nullptr),
47 live_client(nullptr),
48
49 actionQueue(newd ActionQueue(*this)),
50 selection(*this),
51 copybuffer(copybuffer),
52 replace_brush(nullptr) {
53 wxString error;
54 wxArrayString warnings;
55 bool ok = true;
56
57 ClientVersionID defaultVersion = ClientVersionID(g_settings.getInteger(Config::DEFAULT_CLIENT_VERSION));
58 if (defaultVersion == CLIENT_VERSION_NONE) {
59 defaultVersion = ClientVersion::getLatestVersion()->getID();
60 }
61
62 if (g_gui.GetCurrentVersionID() != defaultVersion) {
63 if (g_gui.CloseAllEditors()) {
64 ok = g_gui.LoadVersion(defaultVersion, error, warnings);
65 g_gui.PopupDialog("Error", error, wxOK);
66 g_gui.ListDialog("Warnings", warnings);
67 } else {
68 throw std::runtime_error("All maps of different versions were not closed.");
69 }
70 }
71
72 if (!ok) {
73 throw std::runtime_error("Couldn't load client version");
74 }
75
76 MapVersion version;
77 version.otbm = g_gui.GetCurrentVersion().getPrefferedMapVersionID();
78 version.client = g_gui.GetCurrentVersionID();
79 map.convert(version);
80
81 map.height = 2048;
82 map.width = 2048;
83
84 static int unnamed_counter = 0;
85
86 std::string sname = "Untitled-" + i2s(++unnamed_counter);
87 map.name = sname + ".otbm";
88 map.spawnfile = sname + "-spawn.xml";
89 map.housefile = sname + "-house.xml";
90 map.waypointfile = sname + "-waypoint.xml";
91 map.description = "No map description available.";
92 map.unnamed = true;
93
94 map.doChange();
95}
96
97Editor::Editor(CopyBuffer& copybuffer, const FileName& fn) :
98 live_server(nullptr),
99 live_client(nullptr),
100 actionQueue(newd ActionQueue(*this)),
101 selection(*this),
102 copybuffer(copybuffer),
103 replace_brush(nullptr) {
104 MapVersion ver;
105 if (!IOMapOTBM::getVersionInfo(fn, ver)) {
106 // g_gui.PopupDialog("Error", "Could not open file \"" + fn.GetFullPath() + "\".", wxOK);
107 throw std::runtime_error("Could not open file \"" + nstr(fn.GetFullPath()) + "\".\nThis is not a valid OTBM file or it does not exist.");
108 }
109
110 /*
111 if(ver < CLIENT_VERSION_760) {
112 long b = g_gui.PopupDialog("Error", "Unsupported Client Version (pre 7.6), do you want to try to load the map anyways?", wxYES | wxNO);
113 if(b == wxID_NO) {
114 valid_state = false;
115 return;
116 }
117 }
118 */
119
120 bool success = true;
121 if (g_gui.GetCurrentVersionID() != ver.client) {
122 wxString error;
123 wxArrayString warnings;
124 if (g_gui.CloseAllEditors()) {
125 success = g_gui.LoadVersion(ver.client, error, warnings);
126 if (!success) {
127 g_gui.PopupDialog("Error", error, wxOK);
128 } else {
129 g_gui.ListDialog("Warnings", warnings);
130 }
131 } else {
132 throw std::runtime_error("All maps of different versions were not closed.");
133 }
134 }
135
136 if (success) {
137 ScopedLoadingBar LoadingBar("Loading OTBM map...");
138 success = map.open(nstr(fn.GetFullPath()));
139 /* TODO
140 if(success && ver.client == CLIENT_VERSION_854_BAD) {
141 int ok = g_gui.PopupDialog("Incorrect OTB", "This map has been saved with an incorrect OTB version, do you want to convert it to the new OTB version?\n\nIf you are not sure, click Yes.", wxYES | wxNO);
142
143 if(ok == wxID_YES){
144 ver.client = CLIENT_VERSION_854;
145 map.convert(ver);
146 }
147 }
148 */
149 }
150}
151
152Editor::Editor(CopyBuffer& copybuffer, LiveClient* client) :
153 live_server(nullptr),
154 live_client(client),
155 actionQueue(newd NetworkedActionQueue(*this)),
156 selection(*this),
157 copybuffer(copybuffer),
158 replace_brush(nullptr) {
159 ;
160}
161
162Editor::~Editor() {
163 if (IsLive()) {
164 CloseLiveServer();
165 }
166
167 UnnamedRenderingLock();
168 selection.clear();
169 delete actionQueue;
170}
171
172void Editor::addBatch(BatchAction* action, int stacking_delay) {
173 actionQueue->addBatch(action, stacking_delay);
174 g_gui.UpdateMenus();
175}
176
177void Editor::addAction(Action* action, int stacking_delay) {
178 actionQueue->addAction(action, stacking_delay);
179 g_gui.UpdateMenus();
180}
181
182void Editor::saveMap(FileName filename, bool showdialog) {
183 std::string savefile = filename.GetFullPath().mb_str(wxConvUTF8).data();
184 bool save_as = false;
185 bool save_otgz = false;
186
187 if (savefile.empty()) {
188 savefile = map.filename;
189
190 FileName c1(wxstr(savefile));
191 FileName c2(wxstr(map.filename));
192 save_as = c1 != c2;
193 }
194
195 // If not named yet, propagate the file name to the auxilliary files
196 if (map.unnamed) {
197 FileName _name(filename);
198 _name.SetExt("xml");
199
200 _name.SetName(filename.GetName() + "-spawn");
201 map.spawnfile = nstr(_name.GetFullName());
202 _name.SetName(filename.GetName() + "-house");
203 map.housefile = nstr(_name.GetFullName());
204 _name.SetName(filename.GetName() + "-waypoint");
205 map.waypointfile = nstr(_name.GetFullName());
206
207 map.unnamed = false;
208 }
209
210 // File object to convert between local paths etc.
211 FileName converter;
212 converter.Assign(wxstr(savefile));
213 std::string map_path = nstr(converter.GetPath(wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME));
214
215 // Make temporary backups
216 // converter.Assign(wxstr(savefile));
217 std::string backup_otbm, backup_house, backup_spawn, backup_waypoint;
218
219 if (converter.GetExt() == "otgz") {
220 save_otgz = true;
221 if (converter.FileExists()) {
222 backup_otbm = map_path + nstr(converter.GetName()) + ".otgz~";
223 std::remove(backup_otbm.c_str());
224 std::rename(savefile.c_str(), backup_otbm.c_str());
225 }
226 } else {
227 if (converter.FileExists()) {
228 backup_otbm = map_path + nstr(converter.GetName()) + ".otbm~";
229 std::remove(backup_otbm.c_str());
230 std::rename(savefile.c_str(), backup_otbm.c_str());
231 }
232
233 converter.SetFullName(wxstr(map.housefile));
234 if (converter.FileExists()) {
235 backup_house = map_path + nstr(converter.GetName()) + ".xml~";
236 std::remove(backup_house.c_str());
237 std::rename((map_path + map.housefile).c_str(), backup_house.c_str());
238 }
239
240 converter.SetFullName(wxstr(map.spawnfile));
241 if (converter.FileExists()) {
242 backup_spawn = map_path + nstr(converter.GetName()) + ".xml~";
243 std::remove(backup_spawn.c_str());
244 std::rename((map_path + map.spawnfile).c_str(), backup_spawn.c_str());
245 }
246
247 converter.SetFullName(wxstr(map.waypointfile));
248 if (converter.FileExists()) {
249 backup_waypoint = map_path + nstr(converter.GetName()) + ".xml~";
250 std::remove(backup_waypoint.c_str());
251 std::rename((map_path + map.waypointfile).c_str(), backup_waypoint.c_str());
252 }
253 }
254
255 // Save the map
256 {
257 std::string n = nstr(g_gui.GetLocalDataDirectory()) + ".saving.txt";
258 std::ofstream f(n.c_str(), std::ios::trunc | std::ios::out);
259 f << backup_otbm << std::endl
260 << backup_house << std::endl
261 << backup_spawn << std::endl;
262 }
263
264 {
265
266 // Set up the Map paths
267 wxFileName fn = wxstr(savefile);
268 map.filename = fn.GetFullPath().mb_str(wxConvUTF8);
269 map.name = fn.GetFullName().mb_str(wxConvUTF8);
270
271 if (showdialog) {
272 g_gui.CreateLoadBar("Saving OTBM map...");
273 }
274
275 // Perform the actual save
276 IOMapOTBM mapsaver(map.getVersion());
277 bool success = mapsaver.saveMap(map, fn);
278
279 if (showdialog) {
280 g_gui.DestroyLoadBar();
281 }
282
283 // Check for errors...
284 if (!success) {
285 // Rename the temporary backup files back to their previous names
286 if (!backup_otbm.empty()) {
287 converter.SetFullName(wxstr(savefile));
288 std::string otbm_filename = map_path + nstr(converter.GetName());
289 std::rename(backup_otbm.c_str(), std::string(otbm_filename + (save_otgz ? ".otgz" : ".otbm")).c_str());
290 }
291
292 if (!backup_house.empty()) {
293 converter.SetFullName(wxstr(map.housefile));
294 std::string house_filename = map_path + nstr(converter.GetName());
295 std::rename(backup_house.c_str(), std::string(house_filename + ".xml").c_str());
296 }
297
298 if (!backup_spawn.empty()) {
299 converter.SetFullName(wxstr(map.spawnfile));
300 std::string spawn_filename = map_path + nstr(converter.GetName());
301 std::rename(backup_spawn.c_str(), std::string(spawn_filename + ".xml").c_str());
302 }
303
304 if (!backup_waypoint.empty()) {
305 converter.SetFullName(wxstr(map.waypointfile));
306 std::string waypoint_filename = map_path + nstr(converter.GetName());
307 std::rename(backup_waypoint.c_str(), std::string(waypoint_filename + ".xml").c_str());
308 }
309
310 // Display the error
311 g_gui.PopupDialog("Error", "Could not save, unable to open target for writing.", wxOK);
312 }
313
314 // Remove temporary save runfile
315 {
316 std::string n = nstr(g_gui.GetLocalDataDirectory()) + ".saving.txt";
317 std::remove(n.c_str());
318 }
319
320 // If failure, don't run the rest of the function
321 if (!success) {
322 return;
323 }
324 }
325
326 // Move to permanent backup
327 if (!save_as && g_settings.getInteger(Config::ALWAYS_MAKE_BACKUP)) {
328 // Move temporary backups to their proper files
329 time_t t = time(nullptr);
330 tm* current_time = localtime(&t);
331 ASSERT(current_time);
332
333 std::ostringstream date;
334 date << (1900 + current_time->tm_year);
335 if (current_time->tm_mon < 9) {
336 date << "-"
337 << "0" << current_time->tm_mon + 1;
338 } else {
339 date << "-" << current_time->tm_mon + 1;
340 }
341 date << "-" << current_time->tm_mday;
342 date << "-" << current_time->tm_hour;
343 date << "-" << current_time->tm_min;
344 date << "-" << current_time->tm_sec;
345
346 if (!backup_otbm.empty()) {
347 converter.SetFullName(wxstr(savefile));
348 std::string otbm_filename = map_path + nstr(converter.GetName());
349 std::rename(backup_otbm.c_str(), std::string(otbm_filename + "." + date.str() + (save_otgz ? ".otgz" : ".otbm")).c_str());
350 }
351
352 if (!backup_house.empty()) {
353 converter.SetFullName(wxstr(map.housefile));
354 std::string house_filename = map_path + nstr(converter.GetName());
355 std::rename(backup_house.c_str(), std::string(house_filename + "." + date.str() + ".xml").c_str());
356 }
357
358 if (!backup_spawn.empty()) {
359 converter.SetFullName(wxstr(map.spawnfile));
360 std::string spawn_filename = map_path + nstr(converter.GetName());
361 std::rename(backup_spawn.c_str(), std::string(spawn_filename + "." + date.str() + ".xml").c_str());
362 }
363
364 if (!backup_waypoint.empty()) {
365 converter.SetFullName(wxstr(map.spawnfile));
366 std::string waypoint_filename = map_path + nstr(converter.GetName());
367 std::rename(backup_waypoint.c_str(), std::string(waypoint_filename + "." + date.str() + ".xml").c_str());
368 }
369 } else {
370 // Delete the temporary files
371 std::remove(backup_otbm.c_str());
372 std::remove(backup_house.c_str());
373 std::remove(backup_spawn.c_str());
374 }
375
376 map.clearChanges();
377}
378
379bool Editor::importMiniMap(FileName filename, int import, int import_x_offset, int import_y_offset, int import_z_offset) {
380 return false;
381}
382
383bool Editor::exportMiniMap(FileName filename, int floor /*= GROUND_LAYER*/, bool displaydialog) {
384 return map.exportMinimap(filename, floor, displaydialog);
385}
386
387bool Editor::exportSelectionAsMiniMap(FileName directory, wxString fileName) {
388 if (!directory.Exists() || !directory.IsDirWritable()) {
389 return false;
390 }
391
392 int min_x = MAP_MAX_WIDTH + 1, min_y = MAP_MAX_HEIGHT + 1, min_z = MAP_MAX_LAYER + 1;
393 int max_x = 0, max_y = 0, max_z = 0;
394
395 const TileSet& tiles = selection.getTiles();
396 for (Tile* tile : tiles) {
397 if (tile->empty()) {
398 continue;
399 }
400
401 Position pos = tile->getPosition();
402
403 if (pos.x < min_x) {
404 min_x = pos.x;
405 }
406 if (pos.x > max_x) {
407 max_x = pos.x;
408 }
409
410 if (pos.y < min_y) {
411 min_y = pos.y;
412 }
413 if (pos.y > max_y) {
414 max_y = pos.y;
415 }
416
417 if (pos.z < min_z) {
418 min_z = pos.z;
419 }
420 if (pos.z > max_z) {
421 max_z = pos.z;
422 }
423 }
424
425 int numtiles = (max_x - min_x) * (max_y - min_y);
426 int minimap_width = max_x - min_x + 1;
427 int minimap_height = max_y - min_y + 1;
428
429 if (numtiles == 0) {
430 return false;
431 }
432
433 if (minimap_width > 2048 || minimap_height > 2048) {
434 g_gui.PopupDialog("Error", "Minimap size greater than 2048px.", wxOK);
435 return false;
436 }
437
438 int tiles_iterated = 0;
439
440 for (int z = min_z; z <= max_z; z++) {
441 uint8_t* pixels = newd uint8_t[minimap_width * minimap_height * 3]; // 3 bytes per pixel
442 memset(pixels, 0, minimap_width * minimap_height * 3);
443
444 for (Tile* tile : tiles) {
445 if (tile->getZ() != z) {
446 continue;
447 }
448
449 ++tiles_iterated;
450 if (tiles_iterated % 8192 == 0) {
451 g_gui.SetLoadDone(int(tiles_iterated / double(tiles.size()) * 90.0));
452 }
453
454 if (tile->empty()) {
455 continue;
456 }
457
458 uint8_t color = 0;
459
460 for (Item* item : tile->items) {
461 if (item->getMiniMapColor() != 0) {
462 color = item->getMiniMapColor();
463 break;
464 }
465 }
466
467 if (color == 0 && tile->hasGround()) {
468 color = tile->ground->getMiniMapColor();
469 }
470
471 uint32_t index = ((tile->getY() - min_y) * minimap_width + (tile->getX() - min_x)) * 3;
472
473 pixels[index] = (uint8_t)(int(color / 36) % 6 * 51); // red
474 pixels[index + 1] = (uint8_t)(int(color / 6) % 6 * 51); // green
475 pixels[index + 2] = (uint8_t)(color % 6 * 51); // blue
476 }
477
478 FileName file(fileName + "_" + i2ws(z) + ".png");
479 file.Normalize(wxPATH_NORM_ALL, directory.GetFullPath());
480 wxImage* image = newd wxImage(minimap_width, minimap_height, pixels, true);
481 image->SaveFile(file.GetFullPath(), wxBITMAP_TYPE_PNG);
482 image->Destroy();
483 delete[] pixels;
484 }
485
486 return true;
487}
488
489bool Editor::importMap(FileName filename, int import_x_offset, int import_y_offset, ImportType house_import_type, ImportType spawn_import_type) {
490 selection.clear();
491 actionQueue->clear();
492
493 Map imported_map;
494 bool loaded = imported_map.open(nstr(filename.GetFullPath()));
495
496 if (!loaded) {
497 g_gui.PopupDialog("Error", "Error loading map!\n" + imported_map.getError(), wxOK | wxICON_INFORMATION);
498 return false;
499 }
500 g_gui.ListDialog("Warning", imported_map.getWarnings());
501
502 Position offset(import_x_offset, import_y_offset, 0);
503
504 bool resizemap = false;
505 bool resize_asked = false;
506 int newsize_x = map.getWidth(), newsize_y = map.getHeight();
507 int discarded_tiles = 0;
508
509 g_gui.CreateLoadBar("Merging maps...");
510
511 std::map<uint32_t, uint32_t> town_id_map;
512 std::map<uint32_t, uint32_t> house_id_map;
513
514 if (house_import_type != IMPORT_DONT) {
515 for (TownMap::iterator tit = imported_map.towns.begin(); tit != imported_map.towns.end();) {
516 Town* imported_town = tit->second;
517 Town* current_town = map.towns.getTown(imported_town->getID());
518
519 Position oldexit = imported_town->getTemplePosition();
520 Position newexit = oldexit + offset;
521 if (newexit.isValid()) {
522 imported_town->setTemplePosition(newexit);
523 map.getOrCreateTile(newexit)->getLocation()->increaseTownCount();
524 }
525
526 switch (house_import_type) {
527 case IMPORT_MERGE: {
528 town_id_map[imported_town->getID()] = imported_town->getID();
529 if (current_town) {
530 ++tit;
531 continue;
532 }
533 break;
534 }
535 case IMPORT_SMART_MERGE: {
536 if (current_town) {
537 // Compare and insert/merge depending on parameters
538 if (current_town->getName() == imported_town->getName() && current_town->getID() == imported_town->getID()) {
539 // Just add to map
540 town_id_map[imported_town->getID()] = current_town->getID();
541 ++tit;
542 continue;
543 } else {
544 // Conflict! Find a newd id and replace old
545 uint32_t new_id = map.towns.getEmptyID();
546 imported_town->setID(new_id);
547 town_id_map[imported_town->getID()] = new_id;
548 }
549 } else {
550 town_id_map[imported_town->getID()] = imported_town->getID();
551 }
552 break;
553 }
554 case IMPORT_INSERT: {
555 // Find a newd id and replace old
556 uint32_t new_id = map.towns.getEmptyID();
557 imported_town->setID(new_id);
558 town_id_map[imported_town->getID()] = new_id;
559 break;
560 }
561 case IMPORT_DONT: {
562 ++tit;
563 continue; // Should never happend..?
564 break; // Continue or break ?
565 }
566 }
567
568 map.towns.addTown(imported_town);
569
570#ifdef __VISUALC__ // C++0x compliance to some degree :)
571 tit = imported_map.towns.erase(tit);
572#else // Bulky, slow way
573 TownMap::iterator tmp_iter = tit;
574 ++tmp_iter;
575 uint32_t next_key = 0;
576 if (tmp_iter != imported_map.towns.end()) {
577 next_key = tmp_iter->first;
578 }
579 imported_map.towns.erase(tit);
580 if (next_key != 0) {
581 tit = imported_map.towns.find(next_key);
582 } else {
583 tit = imported_map.towns.end();
584 }
585#endif
586 }
587
588 for (HouseMap::iterator hit = imported_map.houses.begin(); hit != imported_map.houses.end();) {
589 House* imported_house = hit->second;
590 House* current_house = map.houses.getHouse(imported_house->getID());
591 imported_house->townid = town_id_map[imported_house->townid];
592
593 Position oldexit = imported_house->getExit();
594 imported_house->setExit(nullptr, Position()); // Reset it
595
596 switch (house_import_type) {
597 case IMPORT_MERGE: {
598 house_id_map[imported_house->getID()] = imported_house->getID();
599 if (current_house) {
600 ++hit;
601 Position newexit = oldexit + offset;
602 if (newexit.isValid()) {
603 current_house->setExit(&map, newexit);
604 }
605 continue;
606 }
607 break;
608 }
609 case IMPORT_SMART_MERGE: {
610 if (current_house) {
611 // Compare and insert/merge depending on parameters
612 if (current_house->name == imported_house->name && current_house->townid == imported_house->townid) {
613 // Just add to map
614 house_id_map[imported_house->getID()] = current_house->getID();
615 ++hit;
616 Position newexit = oldexit + offset;
617 if (newexit.isValid()) {
618 imported_house->setExit(&map, newexit);
619 }
620 continue;
621 } else {
622 // Conflict! Find a newd id and replace old
623 uint32_t new_id = map.houses.getEmptyID();
624 house_id_map[imported_house->getID()] = new_id;
625 imported_house->setID(new_id);
626 }
627 } else {
628 house_id_map[imported_house->getID()] = imported_house->getID();
629 }
630 break;
631 }
632 case IMPORT_INSERT: {
633 // Find a newd id and replace old
634 uint32_t new_id = map.houses.getEmptyID();
635 house_id_map[imported_house->getID()] = new_id;
636 imported_house->setID(new_id);
637 break;
638 }
639 case IMPORT_DONT: {
640 ++hit;
641 Position newexit = oldexit + offset;
642 if (newexit.isValid()) {
643 imported_house->setExit(&map, newexit);
644 }
645 continue; // Should never happend..?
646 break;
647 }
648 }
649
650 Position newexit = oldexit + offset;
651 if (newexit.isValid()) {
652 imported_house->setExit(&map, newexit);
653 }
654 map.houses.addHouse(imported_house);
655
656#ifdef __VISUALC__ // C++0x compliance to some degree :)
657 hit = imported_map.houses.erase(hit);
658#else // Bulky, slow way
659 HouseMap::iterator tmp_iter = hit;
660 ++tmp_iter;
661 uint32_t next_key = 0;
662 if (tmp_iter != imported_map.houses.end()) {
663 next_key = tmp_iter->first;
664 }
665 imported_map.houses.erase(hit);
666 if (next_key != 0) {
667 hit = imported_map.houses.find(next_key);
668 } else {
669 hit = imported_map.houses.end();
670 }
671#endif
672 }
673 }
674
675 std::map<Position, Spawn*> spawn_map;
676 if (spawn_import_type != IMPORT_DONT) {
677 for (SpawnPositionList::iterator siter = imported_map.spawns.begin(); siter != imported_map.spawns.end();) {
678 Position old_spawn_pos = *siter;
679 Position new_spawn_pos = *siter + offset;
680 switch (spawn_import_type) {
681 case IMPORT_SMART_MERGE:
682 case IMPORT_INSERT:
683 case IMPORT_MERGE: {
684 Tile* imported_tile = imported_map.getTile(old_spawn_pos);
685 if (imported_tile) {
686 ASSERT(imported_tile->spawn);
687 spawn_map[new_spawn_pos] = imported_tile->spawn;
688
689 SpawnPositionList::iterator next = siter;
690 bool cont = true;
691 Position next_spawn;
692
693 ++next;
694 if (next == imported_map.spawns.end()) {
695 cont = false;
696 } else {
697 next_spawn = *next;
698 }
699 imported_map.spawns.erase(siter);
700 if (cont) {
701 siter = imported_map.spawns.find(next_spawn);
702 } else {
703 siter = imported_map.spawns.end();
704 }
705 }
706 break;
707 }
708 case IMPORT_DONT: {
709 ++siter;
710 break;
711 }
712 }
713 }
714 }
715
716 // Plain merge of waypoints, very simple! :)
717 for (WaypointMap::iterator iter = imported_map.waypoints.begin(); iter != imported_map.waypoints.end(); ++iter) {
718 iter->second->pos += offset;
719 }
720
721 map.waypoints.waypoints.insert(imported_map.waypoints.begin(), imported_map.waypoints.end());
722 imported_map.waypoints.waypoints.clear();
723
724 uint64_t tiles_merged = 0;
725 uint64_t tiles_to_import = imported_map.tilecount;
726 for (MapIterator mit = imported_map.begin(); mit != imported_map.end(); ++mit) {
727 if (tiles_merged % 8092 == 0) {
728 g_gui.SetLoadDone(int(100.0 * tiles_merged / tiles_to_import));
729 }
730 ++tiles_merged;
731
732 Tile* import_tile = (*mit)->get();
733 Position new_pos = import_tile->getPosition() + offset;
734 if (!new_pos.isValid()) {
735 ++discarded_tiles;
736 continue;
737 }
738
739 if (!resizemap && (new_pos.x > map.getWidth() || new_pos.y > map.getHeight())) {
740 if (resize_asked) {
741 ++discarded_tiles;
742 continue;
743 } else {
744 resize_asked = true;
745 int ret = g_gui.PopupDialog("Collision", "The imported tiles are outside the current map scope. Do you want to resize the map? (Else additional tiles will be removed)", wxYES | wxNO);
746
747 if (ret == wxID_YES) {
748 // ...
749 resizemap = true;
750 } else {
751 ++discarded_tiles;
752 continue;
753 }
754 }
755 }
756
757 if (new_pos.x > newsize_x) {
758 newsize_x = new_pos.x;
759 }
760 if (new_pos.y > newsize_y) {
761 newsize_y = new_pos.y;
762 }
763
764 imported_map.setTile(import_tile->getPosition(), nullptr);
765 TileLocation* location = map.createTileL(new_pos);
766
767 // Check if we should update any houses
768 int new_houseid = house_id_map[import_tile->getHouseID()];
769 House* house = map.houses.getHouse(new_houseid);
770 if (import_tile->isHouseTile() && house_import_type != IMPORT_DONT && house) {
771 // We need to notify houses of the tile moving
772 house->removeTile(import_tile);
773 import_tile->setLocation(location);
774 house->addTile(import_tile);
775 } else {
776 import_tile->setLocation(location);
777 }
778
779 if (offset != Position(0, 0, 0)) {
780 for (ItemVector::iterator iter = import_tile->items.begin(); iter != import_tile->items.end(); ++iter) {
781 Item* item = *iter;
782 if (Teleport* teleport = dynamic_cast<Teleport*>(item)) {
783 teleport->setDestination(teleport->getDestination() + offset);
784 }
785 }
786 }
787
788 Tile* old_tile = map.getTile(new_pos);
789 if (old_tile) {
790 map.removeSpawn(old_tile);
791 }
792 import_tile->spawn = nullptr;
793
794 map.setTile(new_pos, import_tile, true);
795 }
796
797 for (std::map<Position, Spawn*>::iterator spawn_iter = spawn_map.begin(); spawn_iter != spawn_map.end(); ++spawn_iter) {
798 Position pos = spawn_iter->first;
799 TileLocation* location = map.createTileL(pos);
800 Tile* tile = location->get();
801 if (!tile) {
802 tile = map.allocator(location);
803 map.setTile(pos, tile);
804 } else if (tile->spawn) {
805 map.removeSpawnInternal(tile);
806 delete tile->spawn;
807 }
808 tile->spawn = spawn_iter->second;
809
810 map.addSpawn(tile);
811 }
812
813 g_gui.DestroyLoadBar();
814
815 map.setWidth(newsize_x);
816 map.setHeight(newsize_y);
817 g_gui.PopupDialog("Success", "Map imported successfully, " + i2ws(discarded_tiles) + " tiles were discarded as invalid.", wxOK);
818
819 g_gui.RefreshPalettes();
820 g_gui.FitViewToMap();
821
822 return true;
823}
824
825void Editor::borderizeSelection() {
826 if (selection.size() == 0) {
827 g_gui.SetStatusText("No items selected. Can't borderize.");
828 return;
829 }
830
831 BorderizeWindow* window = newd BorderizeWindow(g_gui.root, *this);
832 window->Start();
833 window->Destroy();
834}
835
836void Editor::borderizeMap(bool showdialog) {
837 if (!showdialog) {
838 // Old immediate processing for automated calls
839 uint64_t tiles_done = 0;
840 for (TileLocation* tileLocation : map) {
841 Tile* tile = tileLocation->get();
842 ASSERT(tile);
843 tile->borderize(&map);
844 ++tiles_done;
845 }
846 return;
847 }
848
849 BorderizeWindow* window = newd BorderizeWindow(g_gui.root, *this);
850 window->Start();
851 window->Destroy();
852}
853
854void Editor::randomizeSelection() {
855 if (selection.size() == 0) {
856 g_gui.SetStatusText("No items selected. Can't randomize.");
857 }
858
859 Action* action = actionQueue->createAction(ACTION_RANDOMIZE);
860 for (Tile* tile : selection) {
861 Tile* newTile = tile->deepCopy(map);
862 GroundBrush* groundBrush = newTile->getGroundBrush();
863 if (groundBrush && groundBrush->isReRandomizable()) {
864 groundBrush->draw(&map, newTile, nullptr);
865
866 Item* oldGround = tile->ground;
867 Item* newGround = newTile->ground;
868 if (oldGround && newGround) {
869 newGround->setActionID(oldGround->getActionID());
870 newGround->setUniqueID(oldGround->getUniqueID());
871 }
872
873 newTile->select();
874 action->addChange(newd Change(newTile));
875 }
876 }
877 addAction(action);
878}
879
880void Editor::randomizeMap(bool showdialog) {
881 if (showdialog) {
882 g_gui.CreateLoadBar("Randomizing map...");
883 }
884
885 uint64_t tiles_done = 0;
886 for (TileLocation* tileLocation : map) {
887 if (showdialog && tiles_done % 4096 == 0) {
888 g_gui.SetLoadDone(static_cast<int32_t>(tiles_done / double(map.tilecount) * 100.0));
889 }
890
891 Tile* tile = tileLocation->get();
892 ASSERT(tile);
893
894 GroundBrush* groundBrush = tile->getGroundBrush();
895 if (groundBrush) {
896 Item* oldGround = tile->ground;
897
898 uint16_t actionId, uniqueId;
899 if (oldGround) {
900 actionId = oldGround->getActionID();
901 uniqueId = oldGround->getUniqueID();
902 } else {
903 actionId = 0;
904 uniqueId = 0;
905 }
906 groundBrush->draw(&map, tile, nullptr);
907
908 Item* newGround = tile->ground;
909 if (newGround) {
910 newGround->setActionID(actionId);
911 newGround->setUniqueID(uniqueId);
912 }
913 tile->update();
914 }
915 ++tiles_done;
916 }
917
918 if (showdialog) {
919 g_gui.DestroyLoadBar();
920 }
921}
922
923void Editor::clearInvalidHouseTiles(bool showdialog) {
924 if (showdialog) {
925 g_gui.CreateLoadBar("Clearing invalid house tiles...");
926 }
927
928 Houses& houses = map.houses;
929
930 HouseMap::iterator iter = houses.begin();
931 while (iter != houses.end()) {
932 House* h = iter->second;
933 if (map.towns.getTown(h->townid) == nullptr) {
934#ifdef __VISUALC__ // C++0x compliance to some degree :)
935 iter = houses.erase(iter);
936#else // Bulky, slow way
937 HouseMap::iterator tmp_iter = iter;
938 ++tmp_iter;
939 uint32_t next_key = 0;
940 if (tmp_iter != houses.end()) {
941 next_key = tmp_iter->first;
942 }
943 houses.erase(iter);
944 if (next_key != 0) {
945 iter = houses.find(next_key);
946 } else {
947 iter = houses.end();
948 }
949#endif
950 } else {
951 ++iter;
952 }
953 }
954
955 uint64_t tiles_done = 0;
956 for (MapIterator map_iter = map.begin(); map_iter != map.end(); ++map_iter) {
957 if (showdialog && tiles_done % 4096 == 0) {
958 g_gui.SetLoadDone(int(tiles_done / double(map.tilecount) * 100.0));
959 }
960
961 Tile* tile = (*map_iter)->get();
962 ASSERT(tile);
963 if (tile->isHouseTile()) {
964 if (houses.getHouse(tile->getHouseID()) == nullptr) {
965 tile->setHouse(nullptr);
966 }
967 }
968 ++tiles_done;
969 }
970
971 if (showdialog) {
972 g_gui.DestroyLoadBar();
973 }
974}
975
976void Editor::clearModifiedTileState(bool showdialog) {
977 if (showdialog) {
978 g_gui.CreateLoadBar("Clearing modified state from all tiles...");
979 }
980
981 uint64_t tiles_done = 0;
982 for (MapIterator map_iter = map.begin(); map_iter != map.end(); ++map_iter) {
983 if (showdialog && tiles_done % 4096 == 0) {
984 g_gui.SetLoadDone(int(tiles_done / double(map.tilecount) * 100.0));
985 }
986
987 Tile* tile = (*map_iter)->get();
988 ASSERT(tile);
989 tile->unmodify();
990 ++tiles_done;
991 }
992
993 if (showdialog) {
994 g_gui.DestroyLoadBar();
995 }
996}
997
998void Editor::moveSelection(Position offset) {
999 BatchAction* batchAction = actionQueue->createBatch(ACTION_MOVE); // Our saved action batch, for undo!
1000 Action* action;
1001
1002 // Remove tiles from the map
1003 action = actionQueue->createAction(batchAction); // Our action!
1004 bool doborders = false;
1005 TileSet tmp_storage;
1006
1007 // Update the tiles with the newd positions
1008 for (TileSet::iterator it = selection.begin(); it != selection.end(); ++it) {
1009 // First we get the old tile and it's position
1010 Tile* tile = (*it);
1011 // const Position pos = tile->getPosition();
1012
1013 // Create the duplicate source tile, which will replace the old one later
1014 Tile* old_src_tile = tile;
1015 Tile* new_src_tile;
1016
1017 new_src_tile = old_src_tile->deepCopy(map);
1018
1019 Tile* tmp_storage_tile = map.allocator(tile->getLocation());
1020
1021 // Get all the selected items from the NEW source tile and iterate through them
1022 // This transfers ownership to the temporary tile
1023 ItemVector tile_selection = new_src_tile->popSelectedItems();
1024 for (ItemVector::iterator iit = tile_selection.begin(); iit != tile_selection.end(); iit++) {
1025 // Add the copied item to the newd destination tile,
1026 Item* item = (*iit);
1027 tmp_storage_tile->addItem(item);
1028 }
1029 // Move spawns
1030 if (new_src_tile->spawn && new_src_tile->spawn->isSelected()) {
1031 tmp_storage_tile->spawn = new_src_tile->spawn;
1032 new_src_tile->spawn = nullptr;
1033 }
1034 // Move creatures
1035 if (new_src_tile->creature && new_src_tile->creature->isSelected()) {
1036 tmp_storage_tile->creature = new_src_tile->creature;
1037 new_src_tile->creature = nullptr;
1038 }
1039
1040 // Move house data & tile status if ground is transferred
1041 if (tmp_storage_tile->ground) {
1042 tmp_storage_tile->house_id = new_src_tile->house_id;
1043 new_src_tile->house_id = 0;
1044 tmp_storage_tile->setZoneIds(new_src_tile);
1045 new_src_tile->setMapFlags(TILESTATE_NONE);
1046 new_src_tile->clearZoneId();
1047 doborders = true;
1048 }
1049
1050 tmp_storage.insert(tmp_storage_tile);
1051 // Add the tile copy to the action
1052 action->addChange(newd Change(new_src_tile));
1053 }
1054 // Commit changes to map
1055 batchAction->addAndCommitAction(action);
1056
1057 // Remove old borders (and create some newd?)
1058 if (g_settings.getInteger(Config::USE_AUTOMAGIC) && g_settings.getInteger(Config::BORDERIZE_DRAG) && selection.size() < size_t(g_settings.getInteger(Config::BORDERIZE_DRAG_THRESHOLD))) {
1059 action = actionQueue->createAction(batchAction);
1060 TileList borderize_tiles;
1061 // Go through all modified (selected) tiles (might be slow)
1062 for (TileSet::iterator it = tmp_storage.begin(); it != tmp_storage.end(); ++it) {
1063 Position pos = (*it)->getPosition();
1064 // Go through all neighbours
1065 Tile* t;
1066 t = map.getTile(pos.x, pos.y, pos.z);
1067 if (t && !t->isSelected()) {
1068 borderize_tiles.push_back(t);
1069 }
1070 t = map.getTile(pos.x - 1, pos.y - 1, pos.z);
1071 if (t && !t->isSelected()) {
1072 borderize_tiles.push_back(t);
1073 }
1074 t = map.getTile(pos.x, pos.y - 1, pos.z);
1075 if (t && !t->isSelected()) {
1076 borderize_tiles.push_back(t);
1077 }
1078 t = map.getTile(pos.x + 1, pos.y - 1, pos.z);
1079 if (t && !t->isSelected()) {
1080 borderize_tiles.push_back(t);
1081 }
1082 t = map.getTile(pos.x - 1, pos.y, pos.z);
1083 if (t && !t->isSelected()) {
1084 borderize_tiles.push_back(t);
1085 }
1086 t = map.getTile(pos.x + 1, pos.y, pos.z);
1087 if (t && !t->isSelected()) {
1088 borderize_tiles.push_back(t);
1089 }
1090 t = map.getTile(pos.x - 1, pos.y + 1, pos.z);
1091 if (t && !t->isSelected()) {
1092 borderize_tiles.push_back(t);
1093 }
1094 t = map.getTile(pos.x, pos.y + 1, pos.z);
1095 if (t && !t->isSelected()) {
1096 borderize_tiles.push_back(t);
1097 }
1098 t = map.getTile(pos.x + 1, pos.y + 1, pos.z);
1099 if (t && !t->isSelected()) {
1100 borderize_tiles.push_back(t);
1101 }
1102 }
1103 // Remove duplicates
1104 borderize_tiles.sort();
1105 borderize_tiles.unique();
1106 // Do le borders!
1107 for (TileList::iterator it = borderize_tiles.begin(); it != borderize_tiles.end(); ++it) {
1108 Tile* tile = *it;
1109 Tile* new_tile = (*it)->deepCopy(map);
1110 if (doborders) {
1111 new_tile->borderize(&map);
1112 }
1113 new_tile->wallize(&map);
1114 new_tile->tableize(&map);
1115 new_tile->carpetize(&map);
1116 if (tile->ground && tile->ground->isSelected()) {
1117 new_tile->selectGround();
1118 }
1119 action->addChange(newd Change(new_tile));
1120 }
1121 // Commit changes to map
1122 batchAction->addAndCommitAction(action);
1123 }
1124
1125 // New action for adding the destination tiles
1126 action = actionQueue->createAction(batchAction);
1127 for (TileSet::iterator it = tmp_storage.begin(); it != tmp_storage.end(); ++it) {
1128 Tile* tile = (*it);
1129 const Position old_pos = tile->getPosition();
1130 Position new_pos;
1131
1132 new_pos = old_pos - offset;
1133
1134 if (new_pos.z < 0 && new_pos.z > MAP_MAX_LAYER) {
1135 delete tile;
1136 continue;
1137 }
1138 // Create the duplicate dest tile, which will replace the old one later
1139 TileLocation* location = map.createTileL(new_pos);
1140 Tile* old_dest_tile = location->get();
1141 Tile* new_dest_tile = nullptr;
1142
1143 if (g_settings.getInteger(Config::MERGE_MOVE) || !tile->ground) {
1144 // Move items
1145 if (old_dest_tile) {
1146 new_dest_tile = old_dest_tile->deepCopy(map);
1147 } else {
1148 new_dest_tile = map.allocator(location);
1149 }
1150 new_dest_tile->merge(tile);
1151 delete tile;
1152 } else {
1153 // Replace tile instead of just merge
1154 tile->setLocation(location);
1155 new_dest_tile = tile;
1156 }
1157
1158 action->addChange(newd Change(new_dest_tile));
1159 }
1160
1161 // Commit changes to the map
1162 batchAction->addAndCommitAction(action);
1163
1164 // Create borders
1165 if (g_settings.getInteger(Config::USE_AUTOMAGIC) && g_settings.getInteger(Config::BORDERIZE_DRAG) && selection.size() < size_t(g_settings.getInteger(Config::BORDERIZE_DRAG_THRESHOLD))) {
1166 action = actionQueue->createAction(batchAction);
1167 TileList borderize_tiles;
1168 // Go through all modified (selected) tiles (might be slow)
1169 for (TileSet::iterator it = selection.begin(); it != selection.end(); it++) {
1170 bool add_me = false; // If this tile is touched
1171 Position pos = (*it)->getPosition();
1172 // Go through all neighbours
1173 Tile* t;
1174 t = map.getTile(pos.x - 1, pos.y - 1, pos.z);
1175 if (t && !t->isSelected()) {
1176 borderize_tiles.push_back(t);
1177 add_me = true;
1178 }
1179 t = map.getTile(pos.x - 1, pos.y - 1, pos.z);
1180 if (t && !t->isSelected()) {
1181 borderize_tiles.push_back(t);
1182 add_me = true;
1183 }
1184 t = map.getTile(pos.x, pos.y - 1, pos.z);
1185 if (t && !t->isSelected()) {
1186 borderize_tiles.push_back(t);
1187 add_me = true;
1188 }
1189 t = map.getTile(pos.x + 1, pos.y - 1, pos.z);
1190 if (t && !t->isSelected()) {
1191 borderize_tiles.push_back(t);
1192 add_me = true;
1193 }
1194 t = map.getTile(pos.x - 1, pos.y, pos.z);
1195 if (t && !t->isSelected()) {
1196 borderize_tiles.push_back(t);
1197 add_me = true;
1198 }
1199 t = map.getTile(pos.x + 1, pos.y, pos.z);
1200 if (t && !t->isSelected()) {
1201 borderize_tiles.push_back(t);
1202 add_me = true;
1203 }
1204 t = map.getTile(pos.x - 1, pos.y + 1, pos.z);
1205 if (t && !t->isSelected()) {
1206 borderize_tiles.push_back(t);
1207 add_me = true;
1208 }
1209 t = map.getTile(pos.x, pos.y + 1, pos.z);
1210 if (t && !t->isSelected()) {
1211 borderize_tiles.push_back(t);
1212 add_me = true;
1213 }
1214 t = map.getTile(pos.x + 1, pos.y + 1, pos.z);
1215 if (t && !t->isSelected()) {
1216 borderize_tiles.push_back(t);
1217 add_me = true;
1218 }
1219 if (add_me) {
1220 borderize_tiles.push_back(*it);
1221 }
1222 }
1223 // Remove duplicates
1224 borderize_tiles.sort();
1225 borderize_tiles.unique();
1226 // Do le borders!
1227 for (TileList::iterator it = borderize_tiles.begin(); it != borderize_tiles.end(); it++) {
1228 Tile* tile = *it;
1229 if (tile->ground) {
1230 if (tile->ground->getGroundBrush()) {
1231 Tile* new_tile = tile->deepCopy(map);
1232
1233 if (doborders) {
1234 new_tile->borderize(&map);
1235 }
1236
1237 new_tile->wallize(&map);
1238 new_tile->tableize(&map);
1239 new_tile->carpetize(&map);
1240 if (tile->ground->isSelected()) {
1241 new_tile->selectGround();
1242 }
1243
1244 action->addChange(newd Change(new_tile));
1245 }
1246 }
1247 }
1248 // Commit changes to map
1249 batchAction->addAndCommitAction(action);
1250 }
1251
1252 // Store the action for undo
1253 addBatch(batchAction);
1254 selection.updateSelectionCount();
1255}
1256
1257void Editor::destroySelection() {
1258 if (selection.size() == 0) {
1259 g_gui.SetStatusText("No selected items to delete.");
1260 } else {
1261 int tile_count = 0;
1262 int item_count = 0;
1263 PositionList tilestoborder;
1264
1265 BatchAction* batch = actionQueue->createBatch(ACTION_DELETE_TILES);
1266 Action* action = actionQueue->createAction(batch);
1267
1268 for (TileSet::iterator it = selection.begin(); it != selection.end(); ++it) {
1269 tile_count++;
1270
1271 Tile* tile = *it;
1272 Tile* newtile = tile->deepCopy(map);
1273
1274 ItemVector tile_selection = newtile->popSelectedItems();
1275 for (ItemVector::iterator iit = tile_selection.begin(); iit != tile_selection.end(); ++iit) {
1276 ++item_count;
1277 // Delete the items from the tile
1278 delete *iit;
1279 }
1280
1281 if (newtile->creature && newtile->creature->isSelected()) {
1282 delete newtile->creature;
1283 newtile->creature = nullptr;
1284 }
1285
1286 if (newtile->spawn && newtile->spawn->isSelected()) {
1287 delete newtile->spawn;
1288 newtile->spawn = nullptr;
1289 }
1290
1291 // Only gather border tiles if both automagic and borderize on delete are enabled
1292 if (g_settings.getInteger(Config::USE_AUTOMAGIC) && g_settings.getInteger(Config::BORDERIZE_DELETE)) {
1293 for (int y = -1; y <= 1; y++) {
1294 for (int x = -1; x <= 1; x++) {
1295 tilestoborder.push_back(
1296 Position(tile->getPosition().x + x, tile->getPosition().y + y, tile->getPosition().z)
1297 );
1298 }
1299 }
1300 }
1301 action->addChange(newd Change(newtile));
1302 }
1303
1304 batch->addAndCommitAction(action);
1305
1306 // Only do automagic if both automagic and borderize on delete are enabled
1307 if (g_settings.getInteger(Config::USE_AUTOMAGIC) && g_settings.getInteger(Config::BORDERIZE_DELETE)) {
1308 // Remove duplicates
1309 tilestoborder.sort();
1310 tilestoborder.unique();
1311
1312 action = actionQueue->createAction(batch);
1313 for (PositionList::iterator it = tilestoborder.begin(); it != tilestoborder.end(); ++it) {
1314 TileLocation* location = map.createTileL(*it);
1315 Tile* tile = location->get();
1316
1317 if (tile) {
1318 Tile* new_tile = tile->deepCopy(map);
1319 new_tile->borderize(&map);
1320 new_tile->wallize(&map);
1321 new_tile->tableize(&map);
1322 new_tile->carpetize(&map);
1323 action->addChange(newd Change(new_tile));
1324 } else {
1325 Tile* new_tile = map.allocator(location);
1326 new_tile->borderize(&map);
1327 if (new_tile->size()) {
1328 action->addChange(newd Change(new_tile));
1329 } else {
1330 delete new_tile;
1331 }
1332 }
1333 }
1334
1335 batch->addAndCommitAction(action);
1336 }
1337
1338 addBatch(batch);
1339 wxString ss;
1340 ss << "Deleted " << tile_count << " tile" << (tile_count > 1 ? "s" : "") << " (" << item_count << " item" << (item_count > 1 ? "s" : "") << ")";
1341 g_gui.SetStatusText(ss);
1342 }
1343}
1344
1345// Macro to avoid useless code repetition
1346void doSurroundingBorders(DoodadBrush* doodad_brush, PositionList& tilestoborder, Tile* buffer_tile, Tile* new_tile) {
1347 if (doodad_brush->doNewBorders() && g_settings.getInteger(Config::USE_AUTOMAGIC)) {
1348 tilestoborder.push_back(Position(new_tile->getPosition().x, new_tile->getPosition().y, new_tile->getPosition().z));
1349 if (buffer_tile->hasGround()) {
1350 for (int y = -1; y <= 1; y++) {
1351 for (int x = -1; x <= 1; x++) {
1352 tilestoborder.push_back(Position(new_tile->getPosition().x + x, new_tile->getPosition().y + y, new_tile->getPosition().z));
1353 }
1354 }
1355 } else if (buffer_tile->hasWall()) {
1356 tilestoborder.push_back(Position(new_tile->getPosition().x, new_tile->getPosition().y - 1, new_tile->getPosition().z));
1357 tilestoborder.push_back(Position(new_tile->getPosition().x - 1, new_tile->getPosition().y, new_tile->getPosition().z));
1358 tilestoborder.push_back(Position(new_tile->getPosition().x + 1, new_tile->getPosition().y, new_tile->getPosition().z));
1359 tilestoborder.push_back(Position(new_tile->getPosition().x, new_tile->getPosition().y + 1, new_tile->getPosition().z));
1360 }
1361 }
1362}
1363
1364void removeDuplicateWalls(Tile* buffer, Tile* tile) {
1365 for (ItemVector::const_iterator iter = buffer->items.begin(); iter != buffer->items.end(); ++iter) {
1366 if ((*iter)->getWallBrush()) {
1367 tile->cleanWalls((*iter)->getWallBrush());
1368 }
1369 }
1370}
1371
1372void Editor::drawInternal(Position offset, bool alt, bool dodraw) {
1373 Brush* brush = g_gui.GetCurrentBrush();
1374 if (!brush) {
1375 return;
1376 }
1377
1378 if (brush->isDoodad()) {
1379 BatchAction* batch = actionQueue->createBatch(ACTION_DRAW);
1380 Action* action = actionQueue->createAction(batch);
1381 BaseMap* buffer_map = g_gui.doodad_buffer_map.get();
1382
1383 Position delta_pos = offset - Position(0x8000, 0x8000, 0x8);
1384 PositionList tilestoborder;
1385
1386 for (MapIterator it = buffer_map->begin(); it != buffer_map->end(); ++it) {
1387 Tile* buffer_tile = (*it)->get();
1388 Position pos = buffer_tile->getPosition() + delta_pos;
1389 if (!pos.isValid()) {
1390 continue;
1391 }
1392
1393 TileLocation* location = map.createTileL(pos);
1394 Tile* tile = location->get();
1395 DoodadBrush* doodad_brush = brush->asDoodad();
1396
1397 if (doodad_brush->placeOnBlocking() || alt) {
1398 if (tile) {
1399 bool place = true;
1400 if (!doodad_brush->placeOnDuplicate() && !alt) {
1401 for (ItemVector::const_iterator iter = tile->items.begin(); iter != tile->items.end(); ++iter) {
1402 if (doodad_brush->ownsItem(*iter)) {
1403 place = false;
1404 break;
1405 }
1406 }
1407 }
1408 if (place) {
1409 Tile* new_tile = tile->deepCopy(map);
1410 removeDuplicateWalls(buffer_tile, new_tile);
1411 doSurroundingBorders(doodad_brush, tilestoborder, buffer_tile, new_tile);
1412 new_tile->merge(buffer_tile);
1413 action->addChange(newd Change(new_tile));
1414 }
1415 } else {
1416 Tile* new_tile = map.allocator(location);
1417 removeDuplicateWalls(buffer_tile, new_tile);
1418 doSurroundingBorders(doodad_brush, tilestoborder, buffer_tile, new_tile);
1419 new_tile->merge(buffer_tile);
1420 action->addChange(newd Change(new_tile));
1421 }
1422 } else {
1423 if (tile && !tile->isBlocking()) {
1424 bool place = true;
1425 if (!doodad_brush->placeOnDuplicate() && !alt) {
1426 for (ItemVector::const_iterator iter = tile->items.begin(); iter != tile->items.end(); ++iter) {
1427 if (doodad_brush->ownsItem(*iter)) {
1428 place = false;
1429 break;
1430 }
1431 }
1432 }
1433 if (place) {
1434 Tile* new_tile = tile->deepCopy(map);
1435 removeDuplicateWalls(buffer_tile, new_tile);
1436 doSurroundingBorders(doodad_brush, tilestoborder, buffer_tile, new_tile);
1437 new_tile->merge(buffer_tile);
1438 action->addChange(newd Change(new_tile));
1439 }
1440 }
1441 }
1442 }
1443 batch->addAndCommitAction(action);
1444
1445 if (tilestoborder.size() > 0) {
1446 Action* action = actionQueue->createAction(batch);
1447
1448 // Remove duplicates
1449 tilestoborder.sort();
1450 tilestoborder.unique();
1451
1452 for (PositionList::const_iterator it = tilestoborder.begin(); it != tilestoborder.end(); ++it) {
1453 Tile* tile = map.getTile(*it);
1454 if (tile) {
1455 Tile* new_tile = tile->deepCopy(map);
1456 new_tile->borderize(&map);
1457 new_tile->wallize(&map);
1458 action->addChange(newd Change(new_tile));
1459 }
1460 }
1461 batch->addAndCommitAction(action);
1462 }
1463 addBatch(batch, 2);
1464 } else if (brush->isHouseExit()) {
1465 HouseExitBrush* house_exit_brush = brush->asHouseExit();
1466 if (!house_exit_brush->canDraw(&map, offset)) {
1467 return;
1468 }
1469
1470 House* house = map.houses.getHouse(house_exit_brush->getHouseID());
1471 if (!house) {
1472 return;
1473 }
1474
1475 BatchAction* batch = actionQueue->createBatch(ACTION_DRAW);
1476 Action* action = actionQueue->createAction(batch);
1477 action->addChange(Change::Create(house, offset));
1478 batch->addAndCommitAction(action);
1479 addBatch(batch, 2);
1480 } else if (brush->isWaypoint()) {
1481 WaypointBrush* waypoint_brush = brush->asWaypoint();
1482 if (!waypoint_brush->canDraw(&map, offset)) {
1483 return;
1484 }
1485
1486 Waypoint* waypoint = map.waypoints.getWaypoint(waypoint_brush->getWaypoint());
1487 if (!waypoint || waypoint->pos == offset) {
1488 return;
1489 }
1490
1491 BatchAction* batch = actionQueue->createBatch(ACTION_DRAW);
1492 Action* action = actionQueue->createAction(batch);
1493 action->addChange(Change::Create(waypoint, offset));
1494 batch->addAndCommitAction(action);
1495 addBatch(batch, 2);
1496 } else if (brush->isWall()) {
1497 BatchAction* batch = actionQueue->createBatch(ACTION_DRAW);
1498 Action* action = actionQueue->createAction(batch);
1499 // This will only occur with a size 0, when clicking on a tile (not drawing)
1500 Tile* tile = map.getTile(offset);
1501 Tile* new_tile = nullptr;
1502 if (tile) {
1503 new_tile = tile->deepCopy(map);
1504 } else {
1505 new_tile = map.allocator(map.createTileL(offset));
1506 }
1507
1508 if (dodraw) {
1509 bool b = true;
1510 brush->asWall()->draw(&map, new_tile, &b);
1511 } else {
1512 brush->asWall()->undraw(&map, new_tile);
1513 }
1514 action->addChange(newd Change(new_tile));
1515 batch->addAndCommitAction(action);
1516 addBatch(batch, 2);
1517 } else if (brush->isSpawn() || brush->isCreature()) {
1518 BatchAction* batch = actionQueue->createBatch(ACTION_DRAW);
1519 Action* action = actionQueue->createAction(batch);
1520
1521 Tile* tile = map.getTile(offset);
1522 Tile* new_tile = nullptr;
1523 if (tile) {
1524 new_tile = tile->deepCopy(map);
1525 } else {
1526 new_tile = map.allocator(map.createTileL(offset));
1527 }
1528 int param;
1529 if (!brush->isCreature()) {
1530 param = g_gui.GetBrushSize();
1531 }
1532 if (dodraw) {
1533 brush->draw(&map, new_tile, ¶m);
1534 } else {
1535 brush->undraw(&map, new_tile);
1536 }
1537 action->addChange(newd Change(new_tile));
1538 batch->addAndCommitAction(action);
1539 addBatch(batch, 2);
1540 }
1541
1542 // After drawing is complete, update minimap
1543 if (g_gui.minimap) {
1544 // For doodad brushes, collect modified positions
1545 PositionVector drawnPositions;
1546
1547 // Add all positions that were modified
1548 if (brush->isDoodad()) {
1549 BaseMap* buffer_map = g_gui.doodad_buffer_map.get();
1550 Position delta_pos = offset - Position(0x8000, 0x8000, 0x8);
1551
1552 for (MapIterator it = buffer_map->begin(); it != buffer_map->end(); ++it) {
1553
1554 Position pos = (*it)->getPosition() + delta_pos;
1555 if (pos.isValid()) {
1556 drawnPositions.push_back(pos);
1557 }
1558 }
1559 }
1560
1561 // Update minimap with modified positions
1562 if (!drawnPositions.empty()) {
1563 g_gui.minimap->UpdateDrawnTiles(drawnPositions);
1564 }
1565 }
1566}
1567
1568void Editor::drawInternal(const PositionVector& tilestodraw, bool alt, bool dodraw) {
1569 Brush* brush = g_gui.GetCurrentBrush();
1570 if (!brush) {
1571 return;
1572 }
1573
1574#ifdef __DEBUG__
1575 if (brush->isGround() || brush->isWall()) {
1576 // Wrong function, end call
1577 return;
1578 }
1579#endif
1580
1581 Action* action = actionQueue->createAction(ACTION_DRAW);
1582
1583 if (brush->isOptionalBorder()) {
1584 // We actually need to do borders, but on the same tiles we draw to
1585 for (PositionVector::const_iterator it = tilestodraw.begin(); it != tilestodraw.end(); ++it) {
1586 TileLocation* location = map.createTileL(*it);
1587 Tile* tile = location->get();
1588 if (tile) {
1589 if (dodraw) {
1590 Tile* new_tile = tile->deepCopy(map);
1591 brush->draw(&map, new_tile);
1592 new_tile->borderize(&map);
1593 action->addChange(newd Change(new_tile));
1594 } else if (!dodraw && tile->hasOptionalBorder()) {
1595 Tile* new_tile = tile->deepCopy(map);
1596 brush->undraw(&map, new_tile);
1597 new_tile->borderize(&map);
1598 action->addChange(newd Change(new_tile));
1599 }
1600 } else if (dodraw) {
1601 Tile* new_tile = map.allocator(location);
1602 brush->draw(&map, new_tile);
1603 new_tile->borderize(&map);
1604 if (new_tile->size() == 0) {
1605 delete new_tile;
1606 continue;
1607 }
1608 action->addChange(newd Change(new_tile));
1609 }
1610 }
1611 } else {
1612
1613 for (PositionVector::const_iterator it = tilestodraw.begin(); it != tilestodraw.end(); ++it) {
1614 TileLocation* location = map.createTileL(*it);
1615 Tile* tile = location->get();
1616 if (tile) {
1617 Tile* new_tile = tile->deepCopy(map);
1618 if (dodraw) {
1619 brush->draw(&map, new_tile, &alt);
1620 } else {
1621 brush->undraw(&map, new_tile);
1622 }
1623 action->addChange(newd Change(new_tile));
1624 } else if (dodraw) {
1625 Tile* new_tile = map.allocator(location);
1626 brush->draw(&map, new_tile, &alt);
1627 action->addChange(newd Change(new_tile));
1628 }
1629 }
1630 }
1631 addAction(action, 2);
1632
1633 // After drawing is complete, update minimap
1634 if (g_gui.minimap) {
1635 g_gui.minimap->UpdateDrawnTiles(tilestodraw);
1636 }
1637}
1638
1639void Editor::drawInternal(const PositionVector& tilestodraw, PositionVector& tilestoborder, bool alt, bool dodraw) {
1640 Brush* brush = g_gui.GetCurrentBrush();
1641 if (!brush) {
1642 return;
1643 }
1644
1645 if (brush->isGround() || brush->isEraser()) {
1646 BatchAction* batch = actionQueue->createBatch(ACTION_DRAW);
1647 Action* action = actionQueue->createAction(batch);
1648
1649 for (PositionVector::const_iterator it = tilestodraw.begin(); it != tilestodraw.end(); ++it) {
1650 TileLocation* location = map.createTileL(*it);
1651 Tile* tile = location->get();
1652 if (tile) {
1653 Tile* new_tile = tile->deepCopy(map);
1654 if (g_settings.getInteger(Config::USE_AUTOMAGIC)) {
1655 // Only clean borders if we're not preserving borders from other ground types
1656 if (!g_settings.getBoolean(Config::SAME_GROUND_TYPE_BORDER)) {
1657 new_tile->cleanBorders();
1658 }
1659 }
1660 if (dodraw) {
1661 if (brush->isGround() && alt) {
1662 std::pair<bool, GroundBrush*> param;
1663 if (replace_brush) {
1664 param.first = false;
1665 param.second = replace_brush;
1666 } else {
1667 param.first = true;
1668 param.second = nullptr;
1669 }
1670 g_gui.GetCurrentBrush()->draw(&map, new_tile, ¶m);
1671 } else {
1672 g_gui.GetCurrentBrush()->draw(&map, new_tile, nullptr);
1673 }
1674 } else {
1675 g_gui.GetCurrentBrush()->undraw(&map, new_tile);
1676 tilestoborder.push_back(*it);
1677 }
1678 action->addChange(newd Change(new_tile));
1679 } else if (dodraw) {
1680 Tile* new_tile = map.allocator(location);
1681 if (brush->isGround() && alt) {
1682 std::pair<bool, GroundBrush*> param;
1683 if (replace_brush) {
1684 param.first = false;
1685 param.second = replace_brush;
1686 } else {
1687 param.first = true;
1688 param.second = nullptr;
1689 }
1690 g_gui.GetCurrentBrush()->draw(&map, new_tile, ¶m);
1691 } else {
1692 g_gui.GetCurrentBrush()->draw(&map, new_tile, nullptr);
1693 }
1694 action->addChange(newd Change(new_tile));
1695 }
1696 }
1697
1698 // Commit changes to map
1699 batch->addAndCommitAction(action);
1700
1701 if (g_settings.getInteger(Config::USE_AUTOMAGIC)) {
1702 // Do borders!
1703 action = actionQueue->createAction(batch);
1704 for (PositionVector::const_iterator it = tilestoborder.begin(); it != tilestoborder.end(); ++it) {
1705 TileLocation* location = map.createTileL(*it);
1706 Tile* tile = location->get();
1707 if (tile) {
1708 Tile* new_tile = tile->deepCopy(map);
1709 if (brush->isEraser()) {
1710 new_tile->wallize(&map);
1711 new_tile->tableize(&map);
1712 new_tile->carpetize(&map);
1713 }
1714 new_tile->borderize(&map);
1715 action->addChange(newd Change(new_tile));
1716 } else {
1717 Tile* new_tile = map.allocator(location);
1718 if (brush->isEraser()) {
1719 // There are no carpets/tables/walls on empty tiles...
1720 // new_tile->wallize(map);
1721 // new_tile->tableize(map);
1722 // new_tile->carpetize(map);
1723 }
1724 new_tile->borderize(&map);
1725 if (new_tile->size() > 0) {
1726 action->addChange(newd Change(new_tile));
1727 } else {
1728 delete new_tile;
1729 }
1730 }
1731 }
1732 batch->addAndCommitAction(action);
1733 }
1734
1735 addBatch(batch, 2);
1736 } else if (brush->isTable() || brush->isCarpet()) {
1737 BatchAction* batch = actionQueue->createBatch(ACTION_DRAW);
1738 Action* action = actionQueue->createAction(batch);
1739
1740 for (PositionVector::const_iterator it = tilestodraw.begin(); it != tilestodraw.end(); ++it) {
1741 TileLocation* location = map.createTileL(*it);
1742 Tile* tile = location->get();
1743 if (tile) {
1744 Tile* new_tile = tile->deepCopy(map);
1745 if (dodraw) {
1746 g_gui.GetCurrentBrush()->draw(&map, new_tile, nullptr);
1747 } else {
1748 g_gui.GetCurrentBrush()->undraw(&map, new_tile);
1749 }
1750 action->addChange(newd Change(new_tile));
1751 } else if (dodraw) {
1752 Tile* new_tile = map.allocator(location);
1753 g_gui.GetCurrentBrush()->draw(&map, new_tile, nullptr);
1754 action->addChange(newd Change(new_tile));
1755 }
1756 }
1757
1758 // Commit changes to map
1759 batch->addAndCommitAction(action);
1760
1761 // Do borders!
1762 action = actionQueue->createAction(batch);
1763 for (PositionVector::const_iterator it = tilestoborder.begin(); it != tilestoborder.end(); ++it) {
1764 Tile* tile = map.getTile(*it);
1765 if (brush->isTable()) {
1766 if (tile && tile->hasTable()) {
1767 Tile* new_tile = tile->deepCopy(map);
1768 new_tile->tableize(&map);
1769 action->addChange(newd Change(new_tile));
1770 }
1771 } else if (brush->isCarpet()) {
1772 if (tile && tile->hasCarpet()) {
1773 Tile* new_tile = tile->deepCopy(map);
1774 new_tile->carpetize(&map);
1775 action->addChange(newd Change(new_tile));
1776 }
1777 }
1778 }
1779 batch->addAndCommitAction(action);
1780
1781 addBatch(batch, 2);
1782 } else if (brush->isWall()) {
1783 BatchAction* batch = actionQueue->createBatch(ACTION_DRAW);
1784 Action* action = actionQueue->createAction(batch);
1785
1786 if (alt && dodraw) {
1787 // This is exempt from USE_AUTOMAGIC
1788 g_gui.doodad_buffer_map->clear();
1789 BaseMap* draw_map = g_gui.doodad_buffer_map.get();
1790
1791 for (PositionVector::const_iterator it = tilestodraw.begin(); it != tilestodraw.end(); ++it) {
1792 TileLocation* location = map.createTileL(*it);
1793 Tile* tile = location->get();
1794 if (tile) {
1795 Tile* new_tile = tile->deepCopy(map);
1796 new_tile->cleanWalls(brush->isWall());
1797 g_gui.GetCurrentBrush()->draw(draw_map, new_tile);
1798 draw_map->setTile(*it, new_tile, true);
1799 } else if (dodraw) {
1800 Tile* new_tile = map.allocator(location);
1801 g_gui.GetCurrentBrush()->draw(draw_map, new_tile);
1802 draw_map->setTile(*it, new_tile, true);
1803 }
1804 }
1805 for (PositionVector::const_iterator it = tilestodraw.begin(); it != tilestodraw.end(); ++it) {
1806 // Get the correct tiles from the draw map instead of the editor map
1807 Tile* tile = draw_map->getTile(*it);
1808 if (tile) {
1809 tile->wallize(draw_map);
1810 action->addChange(newd Change(tile));
1811 }
1812 }
1813 draw_map->clear(false);
1814 // Commit
1815 batch->addAndCommitAction(action);
1816 } else {
1817 for (PositionVector::const_iterator it = tilestodraw.begin(); it != tilestodraw.end(); ++it) {
1818 TileLocation* location = map.createTileL(*it);
1819 Tile* tile = location->get();
1820 if (tile) {
1821 Tile* new_tile = tile->deepCopy(map);
1822 // Wall cleaning is exempt from automagic
1823 new_tile->cleanWalls(brush->isWall());
1824 if (dodraw) {
1825 g_gui.GetCurrentBrush()->draw(&map, new_tile);
1826 } else {
1827 g_gui.GetCurrentBrush()->undraw(&map, new_tile);
1828 }
1829 action->addChange(newd Change(new_tile));
1830 } else if (dodraw) {
1831 Tile* new_tile = map.allocator(location);
1832 g_gui.GetCurrentBrush()->draw(&map, new_tile);
1833 action->addChange(newd Change(new_tile));
1834 }
1835 }
1836
1837 // Commit changes to map
1838 batch->addAndCommitAction(action);
1839
1840 if (g_settings.getInteger(Config::USE_AUTOMAGIC)) {
1841 // Do borders!
1842 action = actionQueue->createAction(batch);
1843 for (PositionVector::const_iterator it = tilestoborder.begin(); it != tilestoborder.end(); ++it) {
1844 Tile* tile = map.getTile(*it);
1845 if (tile) {
1846 Tile* new_tile = tile->deepCopy(map);
1847 new_tile->wallize(&map);
1848 // if(*tile == *new_tile) delete new_tile;
1849 action->addChange(newd Change(new_tile));
1850 }
1851 }
1852 batch->addAndCommitAction(action);
1853 }
1854 }
1855
1856 actionQueue->addBatch(batch, 2);
1857 } else if (brush->isDoor()) {
1858 BatchAction* batch = actionQueue->createBatch(ACTION_DRAW);
1859 Action* action = actionQueue->createAction(batch);
1860 DoorBrush* door_brush = brush->asDoor();
1861
1862 // Loop is kind of redundant since there will only ever be one index.
1863 for (PositionVector::const_iterator it = tilestodraw.begin(); it != tilestodraw.end(); ++it) {
1864 TileLocation* location = map.createTileL(*it);
1865 Tile* tile = location->get();
1866 if (tile) {
1867 Tile* new_tile = tile->deepCopy(map);
1868 // Wall cleaning is exempt from automagic
1869 if (brush->isWall()) {
1870 new_tile->cleanWalls(brush->asWall());
1871 }
1872 if (dodraw) {
1873 door_brush->draw(&map, new_tile, &alt);
1874 } else {
1875 door_brush->undraw(&map, new_tile);
1876 }
1877 action->addChange(newd Change(new_tile));
1878 } else if (dodraw) {
1879 Tile* new_tile = map.allocator(location);
1880 door_brush->draw(&map, new_tile, &alt);
1881 action->addChange(newd Change(new_tile));
1882 }
1883 }
1884
1885 // Commit changes to map
1886 batch->addAndCommitAction(action);
1887
1888 if (g_settings.getInteger(Config::USE_AUTOMAGIC)) {
1889 // Do borders!
1890 action = actionQueue->createAction(batch);
1891 for (PositionVector::const_iterator it = tilestoborder.begin(); it != tilestoborder.end(); ++it) {
1892 Tile* tile = map.getTile(*it);
1893 if (tile) {
1894 Tile* new_tile = tile->deepCopy(map);
1895 new_tile->wallize(&map);
1896 // if(*tile == *new_tile) delete new_tile;
1897 action->addChange(newd Change(new_tile));
1898 }
1899 }
1900 batch->addAndCommitAction(action);
1901 }
1902
1903 addBatch(batch, 2);
1904 } else {
1905 Action* action = actionQueue->createAction(ACTION_DRAW);
1906 for (PositionVector::const_iterator it = tilestodraw.begin(); it != tilestodraw.end(); ++it) {
1907 TileLocation* location = map.createTileL(*it);
1908 Tile* tile = location->get();
1909 if (tile) {
1910 Tile* new_tile = tile->deepCopy(map);
1911 if (dodraw) {
1912 g_gui.GetCurrentBrush()->draw(&map, new_tile);
1913 } else {
1914 g_gui.GetCurrentBrush()->undraw(&map, new_tile);
1915 }
1916 action->addChange(newd Change(new_tile));
1917 } else if (dodraw) {
1918 Tile* new_tile = map.allocator(location);
1919 g_gui.GetCurrentBrush()->draw(&map, new_tile);
1920 action->addChange(newd Change(new_tile));
1921 }
1922 }
1923 addAction(action, 2);
1924 }
1925
1926 if (g_gui.minimap && !tilestoborder.empty()) {
1927 g_gui.minimap->UpdateDrawnTiles(tilestoborder);
1928 }
1929
1930 // After drawing is complete, update minimap
1931 if (g_gui.minimap) {
1932 PositionVector allPositions;
1933
1934 // Add drawn positions
1935 allPositions.insert(allPositions.end(), tilestodraw.begin(), tilestodraw.end());
1936
1937 // Add border positions
1938 allPositions.insert(allPositions.end(), tilestoborder.begin(), tilestoborder.end());
1939
1940 // Update minimap with all positions
1941 if (!allPositions.empty()) {
1942 g_gui.minimap->UpdateDrawnTiles(allPositions);
1943 }
1944 }
1945}
1946
1947///////////////////////////////////////////////////////////////////////////////
1948// Live!
1949
1950bool Editor::IsLiveClient() const {
1951 return live_client != nullptr;
1952}
1953
1954bool Editor::IsLiveServer() const {
1955 return live_server != nullptr;
1956}
1957
1958bool Editor::IsLive() const {
1959 return IsLiveClient() || IsLiveServer();
1960}
1961
1962bool Editor::IsLocal() const {
1963 return !IsLive();
1964}
1965
1966LiveClient* Editor::GetLiveClient() const {
1967 return live_client;
1968}
1969
1970LiveServer* Editor::GetLiveServer() const {
1971 return live_server;
1972}
1973
1974LiveSocket& Editor::GetLive() const {
1975 if (live_server) {
1976 return *live_server;
1977 }
1978 return *live_client;
1979}
1980
1981LiveServer* Editor::StartLiveServer() {
1982 ASSERT(IsLocal());
1983 live_server = newd LiveServer(*this);
1984
1985 delete actionQueue;
1986 actionQueue = newd NetworkedActionQueue(*this);
1987
1988 return live_server;
1989}
1990
1991void Editor::BroadcastNodes(DirtyList& dirtyList) {
1992 if (IsLiveClient()) {
1993 live_client->sendChanges(dirtyList);
1994 } else if (IsLiveServer()) {
1995 // Make sure the server itself applies the changes locally
1996 // This ensures the host can see their own drawing changes
1997 g_gui.RefreshView();
1998 g_gui.UpdateMinimap();
1999
2000 // Then broadcast the changes to all clients
2001 live_server->broadcastNodes(dirtyList);
2002 }
2003}
2004
2005void Editor::CloseLiveServer() {
2006 ASSERT(IsLive());
2007 if (live_client) {
2008 live_client->close();
2009
2010 delete live_client;
2011 live_client = nullptr;
2012 }
2013
2014 if (live_server) {
2015 live_server->close();
2016
2017 delete live_server;
2018 live_server = nullptr;
2019
2020 delete actionQueue;
2021 actionQueue = newd ActionQueue(*this);
2022 }
2023
2024 NetworkConnection& connection = NetworkConnection::getInstance();
2025 connection.stop();
2026}
2027
2028void Editor::QueryNode(int ndx, int ndy, bool underground) {
2029 ASSERT(live_client);
2030 live_client->queryNode(ndx, ndy, underground);
2031}
2032
2033void Editor::SendNodeRequests() {
2034 if (live_client) {
2035 live_client->sendNodeRequests();
2036 }
2037}
2038
2039// Add new helper method to update minimap for a single position
2040void Editor::updateMinimap(const Position& pos) {
2041 if (g_gui.minimap) {
2042 PositionVector positions;
2043 positions.push_back(pos);
2044 g_gui.minimap->UpdateDrawnTiles(positions);
2045 }
2046}
2047
2048// Add new helper method to update minimap for multiple positions
2049void Editor::updateMinimap(const PositionVector& positions) {
2050 if (g_gui.minimap && !positions.empty()) {
2051 g_gui.minimap->UpdateDrawnTiles(positions);
2052 }
2053}
2054
2055// Add new helper method to update minimap for a tile
2056void Editor::updateMinimapTile(Tile* tile) {
2057 if (g_gui.minimap && tile) {
2058 PositionVector positions;
2059 positions.push_back(tile->getPosition());
2060 g_gui.minimap->UpdateDrawnTiles(positions);
2061 }
2062}
2063
2064uint32_t Editor::validateGrounds(bool validateStack, bool generateEmpty, bool removeDuplicates) {
2065 uint32_t changes = 0;
2066 actionQueue->clear();
2067 selection.clear();
2068
2069 g_gui.CreateLoadBar("Validating ground tiles...");
2070
2071 if (validateStack) {
2072 changes += validateGroundStacks();
2073 }
2074
2075 if (generateEmpty) {
2076 changes += generateEmptySurroundedGrounds();
2077 }
2078
2079 if (removeDuplicates) {
2080 changes += removeDuplicateGrounds();
2081 }
2082
2083 g_gui.DestroyLoadBar();
2084
2085 if (changes > 0) {
2086 map.doChange();
2087 }
2088
2089 return changes;
2090}
2091
2092uint32_t Editor::validateGroundStacks() {
2093 uint32_t changes = 0;
2094 int done = 0;
2095 int total = map.getTileCount();
2096
2097 for (MapIterator iter = map.begin(); iter != map.end(); ++iter) {
2098 if (done % 8192 == 0) {
2099 g_gui.SetLoadDone(int(done / float(total) * 100.f));
2100 }
2101
2102 Tile* tile = (*iter)->get();
2103 if (!tile) {
2104 continue;
2105 }
2106
2107 // Check if there's a ground tile above other items
2108 if (tile->ground && !tile->items.empty()) {
2109 bool groundAboveItems = false;
2110 for (Item* item : tile->items) {
2111 if (item && !item->isGroundTile()) {
2112 groundAboveItems = true;
2113 break;
2114 }
2115 }
2116
2117 if (groundAboveItems) {
2118 // Move ground tile to bottom
2119 Item* ground = tile->ground;
2120 tile->ground = nullptr;
2121 tile->items.insert(tile->items.begin(), ground);
2122 changes++;
2123 }
2124 }
2125
2126 done++;
2127 }
2128
2129 return changes;
2130}
2131
2132uint32_t Editor::generateEmptySurroundedGrounds() {
2133 uint32_t changes = 0;
2134 int done = 0;
2135 int total = map.getTileCount();
2136
2137 for (MapIterator iter = map.begin(); iter != map.end(); ++iter) {
2138 if (done % 8192 == 0) {
2139 g_gui.SetLoadDone(int(done / float(total) * 100.f));
2140 }
2141
2142 Tile* tile = (*iter)->get();
2143 if (!tile || tile->ground) {
2144 continue;
2145 }
2146
2147 Position pos = tile->getPosition();
2148
2149 // Check surrounding tiles
2150 bool allSurroundingHaveGround = true;
2151 uint16_t surroundingGroundId = 0;
2152
2153 for (int x = -1; x <= 1 && allSurroundingHaveGround; ++x) {
2154 for (int y = -1; y <= 1 && allSurroundingHaveGround; ++y) {
2155 if (x == 0 && y == 0) continue;
2156
2157 Tile* surroundingTile = map.getTile(pos.x + x, pos.y + y, pos.z);
2158 if (!surroundingTile || !surroundingTile->ground) {
2159 allSurroundingHaveGround = false;
2160 break;
2161 }
2162
2163 if (surroundingGroundId == 0) {
2164 surroundingGroundId = surroundingTile->ground->getID();
2165 }
2166 }
2167 }
2168
2169 if (allSurroundingHaveGround && surroundingGroundId > 0) {
2170 // Create new ground tile matching surrounding tiles
2171 tile->ground = Item::Create(surroundingGroundId);
2172 changes++;
2173 }
2174
2175 done++;
2176 }
2177
2178 return changes;
2179}
2180
2181uint32_t Editor::removeDuplicateGrounds() {
2182 uint32_t changes = 0;
2183 int done = 0;
2184 int total = map.getTileCount();
2185
2186 for (MapIterator iter = map.begin(); iter != map.end(); ++iter) {
2187 if (done % 8192 == 0) {
2188 g_gui.SetLoadDone(int(done / float(total) * 100.f));
2189 }
2190
2191 Tile* tile = (*iter)->get();
2192 if (!tile) {
2193 continue;
2194 }
2195
2196 // Count ground tiles
2197 std::vector<Item*> groundTiles;
2198 if (tile->ground) {
2199 groundTiles.push_back(tile->ground);
2200 }
2201
2202 for (Item* item : tile->items) {
2203 if (item && item->isGroundTile()) {
2204 groundTiles.push_back(item);
2205 }
2206 }
2207
2208 // Remove duplicate grounds, keeping only the first one
2209 if (groundTiles.size() > 1) {
2210 // Keep the first ground tile
2211 tile->ground = groundTiles[0];
2212
2213 // Remove all other ground tiles
2214 auto it = std::remove_if(tile->items.begin(), tile->items.end(),
2215 [](Item* item) { return item && item->isGroundTile(); });
2216
2217 tile->items.erase(it, tile->items.end());
2218
2219 changes += groundTiles.size() - 1;
2220 }
2221
2222 done++;
2223 }
2224
2225 return changes;
2226}
2227
2228