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 * AUTOMAGIC SYSTEM OVERVIEW
20 * -------------------------
21 * The Automagic system in Remere's Map Editor provides automatic border and wall handling.
22 *
23 * Files involved:
24 * - settings.h/cpp: Defines USE_AUTOMAGIC, BORDERIZE_PASTE, BORDERIZE_DRAG settings
25 * - main_menubar.h/cpp: Implements menu options for toggling Automagic and borderizing
26 * - tile.h/cpp: Contains borderize() and wallize() methods that apply automatic borders/walls
27 * - ground_brush.cpp: Implements GroundBrush::doBorders() which handles automatic borders
28 * - wall_brush.cpp: Implements WallBrush::doWalls() which handles automatic walls
29 * - borderize_window.cpp: UI for borderizing large selections or the entire map
30 * - editor.cpp: Contains borderizeSelection() and borderizeMap() methods
31 * - copybuffer.cpp: Applies borderize to pasted content
32 *
33 * How it works:
34 * 1. When enabled (via Config::USE_AUTOMAGIC), the editor automatically applies borders
35 * and wall connections when tiles are placed, moved, or modified.
36 * 2. Borderizing examines neighboring tiles to determine appropriate borders between
37 * different terrain types.
38 * 3. Wallizing connects wall segments automatically based on adjacent walls.
39 * 4. The system can be triggered:
40 * - Automatically during editing when Automagic is enabled
41 * - Manually via Map > Borderize Selection (Ctrl+B)
42 * - Manually via Map > Borderize Map (processes the entire map)
43 *
44 * Settings:
45 * - BORDERIZE_PASTE: Automatically borderize after pasting
46 * - BORDERIZE_DRAG: Automatically borderize after drag operations
47 * - BORDERIZE_DRAG_THRESHOLD: Maximum selection size for auto-borderizing during drag
48 * - BORDERIZE_PASTE_THRESHOLD: Maximum selection size for auto-borderizing during paste
49 *
50 * The BorderizeWindow provides a UI for processing large maps in chunks to avoid
51 * performance issues when borderizing extensive areas.
52 */
53
54#include "main.h"
55
56#include "main_menubar.h"
57#include "application.h"
58#include "preferences.h"
59#include "about_window.h"
60#include "minimap_window.h"
61#include "dat_debug_view.h"
62#include "result_window.h"
63#include "extension_window.h"
64#include "find_item_window.h"
65#include "settings.h"
66#include "automagic_settings.h"
67#include "find_creature_window.h"
68#include "map.h"
69#include "editor.h"
70#include "gui.h"
71#include "border_editor_window.h"
72#include "map_summary_window.h"
73#include "otmapgen_dialog.h"
74
75#include <wx/chartype.h>
76
77#include "editor.h"
78#include "materials.h"
79#include "live_client.h"
80#include "live_server.h"
81#include "string_utils.h"
82#include "hotkey_manager.h"
83
84const wxEventType EVT_MENU = wxEVT_COMMAND_MENU_SELECTED;
85
86BEGIN_EVENT_TABLE(MainMenuBar, wxEvtHandler)
87 EVT_MENU(MenuBar::NEW, MainMenuBar::OnNew)
88 EVT_MENU(MenuBar::OPEN, MainMenuBar::OnOpen)
89 EVT_MENU(MenuBar::SAVE, MainMenuBar::OnSave)
90 EVT_MENU(MenuBar::SAVE_AS, MainMenuBar::OnSaveAs)
91 EVT_MENU(MenuBar::GENERATE_MAP, MainMenuBar::OnGenerateMap)
92 EVT_MENU(MenuBar::GENERATE_PROCEDURAL_MAP, MainMenuBar::OnGenerateProceduralMap)
93 EVT_MENU(MenuBar::MAP_MENU_GENERATE_ISLAND, MainMenuBar::OnGenerateIsland)
94 EVT_MENU(MenuBar::FIND_CREATURE, MainMenuBar::OnSearchForCreature)
95END_EVENT_TABLE()
96
97MainMenuBar::MainMenuBar(MainFrame* frame) :
98 frame(frame) {
99 using namespace MenuBar;
100 checking_programmaticly = false;
101
102#define MAKE_ACTION(id, kind, handler) actions[#id] = new MenuBar::Action(#id, id, kind, wxCommandEventFunction(&MainMenuBar::handler))
103#define MAKE_SET_ACTION(id, kind, setting_, handler) \
104 actions[#id] = new MenuBar::Action(#id, id, kind, wxCommandEventFunction(&MainMenuBar::handler)); \
105 actions[#id].setting = setting_
106
107 MAKE_ACTION(NEW, wxITEM_NORMAL, OnNew);
108 MAKE_ACTION(OPEN, wxITEM_NORMAL, OnOpen);
109 MAKE_ACTION(SAVE, wxITEM_NORMAL, OnSave);
110 MAKE_ACTION(SAVE_AS, wxITEM_NORMAL, OnSaveAs);
111 MAKE_ACTION(GENERATE_MAP, wxITEM_NORMAL, OnGenerateMap);
112 MAKE_ACTION(GENERATE_PROCEDURAL_MAP, wxITEM_NORMAL, OnGenerateProceduralMap);
113 MAKE_ACTION(CLOSE, wxITEM_NORMAL, OnClose);
114
115 MAKE_ACTION(IMPORT_MAP, wxITEM_NORMAL, OnImportMap);
116 MAKE_ACTION(IMPORT_MONSTERS, wxITEM_NORMAL, OnImportMonsterData);
117 MAKE_ACTION(IMPORT_MINIMAP, wxITEM_NORMAL, OnImportMinimap);
118 MAKE_ACTION(EXPORT_MINIMAP, wxITEM_NORMAL, OnExportMinimap);
119 MAKE_ACTION(EXPORT_TILESETS, wxITEM_NORMAL, OnExportTilesets);
120
121 MAKE_ACTION(RELOAD_DATA, wxITEM_NORMAL, OnReloadDataFiles);
122 // MAKE_ACTION(RECENT_FILES, wxITEM_NORMAL, OnRecent);
123 MAKE_ACTION(PREFERENCES, wxITEM_NORMAL, OnPreferences);
124 MAKE_ACTION(EXIT, wxITEM_NORMAL, OnQuit);
125
126 MAKE_ACTION(UNDO, wxITEM_NORMAL, OnUndo);
127 MAKE_ACTION(REDO, wxITEM_NORMAL, OnRedo);
128
129 MAKE_ACTION(FIND_ITEM, wxITEM_NORMAL, OnSearchForItem);
130 MAKE_ACTION(REPLACE_ITEMS, wxITEM_NORMAL, OnReplaceItems);
131 MAKE_ACTION(SEARCH_ON_MAP_EVERYTHING, wxITEM_NORMAL, OnSearchForStuffOnMap);
132 MAKE_ACTION(SEARCH_ON_MAP_ZONES, wxITEM_NORMAL, OnSearchForZonesOnMap);
133 MAKE_ACTION(SEARCH_ON_MAP_UNIQUE, wxITEM_NORMAL, OnSearchForUniqueOnMap);
134 MAKE_ACTION(SEARCH_ON_MAP_ACTION, wxITEM_NORMAL, OnSearchForActionOnMap);
135 MAKE_ACTION(SEARCH_ON_MAP_CONTAINER, wxITEM_NORMAL, OnSearchForContainerOnMap);
136 MAKE_ACTION(SEARCH_ON_MAP_WRITEABLE, wxITEM_NORMAL, OnSearchForWriteableOnMap);
137 MAKE_ACTION(SEARCH_ON_SELECTION_EVERYTHING, wxITEM_NORMAL, OnSearchForStuffOnSelection);
138 MAKE_ACTION(SEARCH_ON_SELECTION_ZONES, wxITEM_NORMAL, OnSearchForZonesOnSelection);
139 MAKE_ACTION(SEARCH_ON_SELECTION_UNIQUE, wxITEM_NORMAL, OnSearchForUniqueOnSelection);
140 MAKE_ACTION(SEARCH_ON_SELECTION_ACTION, wxITEM_NORMAL, OnSearchForActionOnSelection);
141 MAKE_ACTION(SEARCH_ON_SELECTION_CONTAINER, wxITEM_NORMAL, OnSearchForContainerOnSelection);
142 MAKE_ACTION(SEARCH_ON_SELECTION_WRITEABLE, wxITEM_NORMAL, OnSearchForWriteableOnSelection);
143 MAKE_ACTION(SEARCH_ON_SELECTION_ITEM, wxITEM_NORMAL, OnSearchForItemOnSelection);
144 MAKE_ACTION(REPLACE_ON_SELECTION_ITEMS, wxITEM_NORMAL, OnReplaceItemsOnSelection);
145 MAKE_ACTION(REMOVE_ON_SELECTION_ITEM, wxITEM_NORMAL, OnRemoveItemOnSelection);
146 MAKE_ACTION(SELECT_MODE_COMPENSATE, wxITEM_RADIO, OnSelectionTypeChange);
147 MAKE_ACTION(SELECT_MODE_LOWER, wxITEM_RADIO, OnSelectionTypeChange);
148 MAKE_ACTION(SELECT_MODE_CURRENT, wxITEM_RADIO, OnSelectionTypeChange);
149 MAKE_ACTION(SELECT_MODE_VISIBLE, wxITEM_RADIO, OnSelectionTypeChange);
150
151 // Remove the AUTOMAGIC action as it's now handled by 'A' hotkey
152 // MAKE_ACTION(AUTOMAGIC, wxITEM_CHECK, OnToggleAutomagic);
153
154 MAKE_ACTION(BORDERIZE_SELECTION, wxITEM_NORMAL, OnBorderizeSelection);
155 MAKE_ACTION(BORDERIZE_MAP, wxITEM_NORMAL, OnBorderizeMap);
156 MAKE_ACTION(RANDOMIZE_SELECTION, wxITEM_NORMAL, OnRandomizeSelection);
157 MAKE_ACTION(RANDOMIZE_MAP, wxITEM_NORMAL, OnRandomizeMap);
158 MAKE_ACTION(GOTO_PREVIOUS_POSITION, wxITEM_NORMAL, OnGotoPreviousPosition);
159 MAKE_ACTION(GOTO_POSITION, wxITEM_NORMAL, OnGotoPosition);
160 MAKE_ACTION(JUMP_TO_BRUSH, wxITEM_NORMAL, OnJumpToBrush);
161 MAKE_ACTION(JUMP_TO_ITEM_BRUSH, wxITEM_NORMAL, OnJumpToItemBrush);
162
163 MAKE_ACTION(CUT, wxITEM_NORMAL, OnCut);
164 MAKE_ACTION(COPY, wxITEM_NORMAL, OnCopy);
165 MAKE_ACTION(PASTE, wxITEM_NORMAL, OnPaste);
166
167 MAKE_ACTION(EDIT_TOWNS, wxITEM_NORMAL, OnMapEditTowns);
168 MAKE_ACTION(EDIT_ITEMS, wxITEM_NORMAL, OnMapEditItems);
169 MAKE_ACTION(EDIT_MONSTERS, wxITEM_NORMAL, OnMapEditMonsters);
170
171 MAKE_ACTION(CLEAR_INVALID_HOUSES, wxITEM_NORMAL, OnClearHouseTiles);
172 MAKE_ACTION(CLEAR_MODIFIED_STATE, wxITEM_NORMAL, OnClearModifiedState);
173 MAKE_ACTION(MAP_REMOVE_ITEMS, wxITEM_NORMAL, OnMapRemoveItems);
174 MAKE_ACTION(MAP_REMOVE_CORPSES, wxITEM_NORMAL, OnMapRemoveCorpses);
175 MAKE_ACTION(MAP_REMOVE_DUPLICATES, wxITEM_NORMAL, OnMapRemoveDuplicates);
176 MAKE_ACTION(MAP_VALIDATE_GROUND, wxITEM_NORMAL, OnMapValidateGround);
177 MAKE_ACTION(MAP_REMOVE_UNREACHABLE_TILES, wxITEM_NORMAL, OnMapRemoveUnreachable);
178 MAKE_ACTION(MAP_CLEANUP, wxITEM_NORMAL, OnMapCleanup);
179 MAKE_ACTION(MAP_CLEAN_HOUSE_ITEMS, wxITEM_NORMAL, OnMapCleanHouseItems);
180 MAKE_ACTION(MAP_PROPERTIES, wxITEM_NORMAL, OnMapProperties);
181 MAKE_ACTION(MAP_STATISTICS, wxITEM_NORMAL, OnMapStatistics);
182
183 MAKE_ACTION(VIEW_TOOLBARS_BRUSHES, wxITEM_CHECK, OnToolbars);
184 MAKE_ACTION(VIEW_TOOLBARS_POSITION, wxITEM_CHECK, OnToolbars);
185 MAKE_ACTION(VIEW_TOOLBARS_SIZES, wxITEM_CHECK, OnToolbars);
186 MAKE_ACTION(VIEW_TOOLBARS_STANDARD, wxITEM_CHECK, OnToolbars);
187 MAKE_ACTION(NEW_VIEW, wxITEM_NORMAL, OnNewView);
188 MAKE_ACTION(NEW_DETACHED_VIEW, wxITEM_NORMAL, OnNewDetachedView);
189 MAKE_ACTION(TOGGLE_FULLSCREEN, wxITEM_NORMAL, OnToggleFullscreen);
190
191 MAKE_ACTION(ZOOM_IN, wxITEM_NORMAL, OnZoomIn);
192 MAKE_ACTION(ZOOM_OUT, wxITEM_NORMAL, OnZoomOut);
193 MAKE_ACTION(ZOOM_NORMAL, wxITEM_NORMAL, OnZoomNormal);
194
195 MAKE_ACTION(SHOW_SHADE, wxITEM_CHECK, OnChangeViewSettings);
196 MAKE_ACTION(SHOW_ALL_FLOORS, wxITEM_CHECK, OnChangeViewSettings);
197 MAKE_ACTION(GHOST_ITEMS, wxITEM_CHECK, OnChangeViewSettings);
198 MAKE_ACTION(GHOST_HIGHER_FLOORS, wxITEM_CHECK, OnChangeViewSettings);
199 MAKE_ACTION(HIGHLIGHT_ITEMS, wxITEM_CHECK, OnChangeViewSettings);
200 MAKE_ACTION(HIGHLIGHT_LOCKED_DOORS, wxITEM_CHECK, OnChangeViewSettings);
201 MAKE_ACTION(SHOW_EXTRA, wxITEM_CHECK, OnChangeViewSettings);
202 MAKE_ACTION(SHOW_INGAME_BOX, wxITEM_CHECK, OnChangeViewSettings);
203 MAKE_ACTION(SHOW_LIGHTS, wxITEM_CHECK, OnChangeViewSettings);
204 MAKE_ACTION(SHOW_LIGHT_STR, wxITEM_CHECK, OnChangeViewSettings);
205 MAKE_ACTION(SHOW_TECHNICAL_ITEMS, wxITEM_CHECK, OnChangeViewSettings);
206 MAKE_ACTION(SHOW_WAYPOINTS, wxITEM_CHECK, OnChangeViewSettings);
207 MAKE_ACTION(SHOW_GRID, wxITEM_CHECK, OnChangeViewSettings);
208 MAKE_ACTION(SHOW_CREATURES, wxITEM_CHECK, OnChangeViewSettings);
209 MAKE_ACTION(SHOW_SPAWNS, wxITEM_CHECK, OnChangeViewSettings);
210 MAKE_ACTION(SHOW_SPECIAL, wxITEM_CHECK, OnChangeViewSettings);
211 MAKE_ACTION(SHOW_ZONES, wxITEM_CHECK, OnChangeViewSettings);
212 MAKE_ACTION(SHOW_AS_MINIMAP, wxITEM_CHECK, OnChangeViewSettings);
213 MAKE_ACTION(SHOW_ONLY_COLORS, wxITEM_CHECK, OnChangeViewSettings);
214 MAKE_ACTION(SHOW_ONLY_MODIFIED, wxITEM_CHECK, OnChangeViewSettings);
215 MAKE_ACTION(SHOW_HOUSES, wxITEM_CHECK, OnChangeViewSettings);
216 MAKE_ACTION(SHOW_PATHING, wxITEM_CHECK, OnChangeViewSettings);
217 MAKE_ACTION(SHOW_TOOLTIPS, wxITEM_CHECK, OnChangeViewSettings);
218 MAKE_ACTION(SHOW_PREVIEW, wxITEM_CHECK, OnChangeViewSettings);
219 MAKE_ACTION(SHOW_WALL_HOOKS, wxITEM_CHECK, OnChangeViewSettings);
220 MAKE_ACTION(SHOW_TOWNS, wxITEM_CHECK, OnChangeViewSettings);
221 MAKE_ACTION(ALWAYS_SHOW_ZONES, wxITEM_CHECK, OnChangeViewSettings);
222 MAKE_ACTION(EXT_HOUSE_SHADER, wxITEM_CHECK, OnChangeViewSettings);
223
224 MAKE_ACTION(EXPERIMENTAL_FOG, wxITEM_CHECK, OnChangeViewSettings); // experimental
225
226 MAKE_ACTION(WIN_MINIMAP, wxITEM_NORMAL, OnMinimapWindow);
227 MAKE_ACTION(NEW_PALETTE, wxITEM_NORMAL, OnNewPalette);
228 MAKE_ACTION(TAKE_SCREENSHOT, wxITEM_NORMAL, OnTakeScreenshot);
229
230 MAKE_ACTION(LIVE_START, wxITEM_NORMAL, OnStartLive);
231 MAKE_ACTION(LIVE_JOIN, wxITEM_NORMAL, OnJoinLive);
232 MAKE_ACTION(LIVE_CLOSE, wxITEM_NORMAL, OnCloseLive);
233 MAKE_ACTION(ID_MENU_SERVER_HOST, wxITEM_NORMAL, onServerHost);
234 MAKE_ACTION(ID_MENU_SERVER_CONNECT, wxITEM_NORMAL, onServerConnect);
235
236 MAKE_ACTION(SELECT_TERRAIN, wxITEM_NORMAL, OnSelectTerrainPalette);
237 MAKE_ACTION(SELECT_DOODAD, wxITEM_NORMAL, OnSelectDoodadPalette);
238 MAKE_ACTION(SELECT_ITEM, wxITEM_NORMAL, OnSelectItemPalette);
239 MAKE_ACTION(SELECT_COLLECTION, wxITEM_NORMAL, OnSelectCollectionPalette);
240 MAKE_ACTION(SELECT_CREATURE, wxITEM_NORMAL, OnSelectCreaturePalette);
241 MAKE_ACTION(SELECT_HOUSE, wxITEM_NORMAL, OnSelectHousePalette);
242 MAKE_ACTION(SELECT_WAYPOINT, wxITEM_NORMAL, OnSelectWaypointPalette);
243 MAKE_ACTION(SELECT_RAW, wxITEM_NORMAL, OnSelectRawPalette);
244
245 MAKE_ACTION(FLOOR_0, wxITEM_RADIO, OnChangeFloor);
246 MAKE_ACTION(FLOOR_1, wxITEM_RADIO, OnChangeFloor);
247 MAKE_ACTION(FLOOR_2, wxITEM_RADIO, OnChangeFloor);
248 MAKE_ACTION(FLOOR_3, wxITEM_RADIO, OnChangeFloor);
249 MAKE_ACTION(FLOOR_4, wxITEM_RADIO, OnChangeFloor);
250 MAKE_ACTION(FLOOR_5, wxITEM_RADIO, OnChangeFloor);
251 MAKE_ACTION(FLOOR_6, wxITEM_RADIO, OnChangeFloor);
252 MAKE_ACTION(FLOOR_7, wxITEM_RADIO, OnChangeFloor);
253 MAKE_ACTION(FLOOR_8, wxITEM_RADIO, OnChangeFloor);
254 MAKE_ACTION(FLOOR_9, wxITEM_RADIO, OnChangeFloor);
255 MAKE_ACTION(FLOOR_10, wxITEM_RADIO, OnChangeFloor);
256 MAKE_ACTION(FLOOR_11, wxITEM_RADIO, OnChangeFloor);
257 MAKE_ACTION(FLOOR_12, wxITEM_RADIO, OnChangeFloor);
258 MAKE_ACTION(FLOOR_13, wxITEM_RADIO, OnChangeFloor);
259 MAKE_ACTION(FLOOR_14, wxITEM_RADIO, OnChangeFloor);
260 MAKE_ACTION(FLOOR_15, wxITEM_RADIO, OnChangeFloor);
261
262 MAKE_ACTION(DEBUG_VIEW_DAT, wxITEM_NORMAL, OnDebugViewDat);
263 MAKE_ACTION(EXTENSIONS, wxITEM_NORMAL, OnListExtensions);
264 MAKE_ACTION(GOTO_WEBSITE, wxITEM_NORMAL, OnGotoWebsite);
265 MAKE_ACTION(ABOUT, wxITEM_NORMAL, OnAbout);
266 MAKE_ACTION(SHOW_HOTKEYS, wxITEM_NORMAL, OnShowHotkeys); // Add this line
267 MAKE_ACTION(REFRESH_ITEMS, wxITEM_NORMAL, OnRefreshItems);
268 // 669
269 MAKE_ACTION(FIND_CREATURE, wxITEM_NORMAL, OnSearchForCreature);
270 MAKE_ACTION(MAP_CREATE_BORDER, wxITEM_NORMAL, OnCreateBorder);
271 MAKE_ACTION(MAP_SUMMARIZE, wxITEM_NORMAL, OnMapSummarize);
272
273 // A deleter, this way the frame does not need
274 // to bother deleting us.
275 class CustomMenuBar : public wxMenuBar {
276 public:
277 CustomMenuBar(MainMenuBar* mb) :
278 mb(mb) { }
279 ~CustomMenuBar() {
280 delete mb;
281 }
282
283 private:
284 MainMenuBar* mb;
285 };
286
287 menubar = newd CustomMenuBar(this);
288 frame->SetMenuBar(menubar);
289
290 // Tie all events to this handler!
291
292 for (std::map<std::string, MenuBar::Action*>::iterator ai = actions.begin(); ai != actions.end(); ++ai) {
293 frame->Connect(MAIN_FRAME_MENU + ai->second->id, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)(wxEventFunction)(ai->second->handler), nullptr, this);
294 }
295 for (size_t i = 0; i < 10; ++i) {
296 frame->Connect(recentFiles.GetBaseId() + i, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainMenuBar::OnOpenRecent), nullptr, this);
297 }
298}
299
300MainMenuBar::~MainMenuBar() {
301 // Don't need to delete menubar, it's owned by the frame
302
303 for (std::map<std::string, MenuBar::Action*>::iterator ai = actions.begin(); ai != actions.end(); ++ai) {
304 delete ai->second;
305 }
306}
307
308namespace OnMapRemoveItems {
309 struct RemoveItemCondition {
310 RemoveItemCondition(uint16_t itemId) :
311 itemId(itemId) { }
312
313 uint16_t itemId;
314
315 bool operator()(Map& map, Item* item, int64_t removed, int64_t done) {
316 if (done % 0x8000 == 0) {
317 g_gui.SetLoadDone((uint32_t)(100 * done / map.getTileCount()));
318 }
319 return item->getID() == itemId && !item->isComplex();
320 }
321 };
322}
323
324void MainMenuBar::EnableItem(MenuBar::ActionID id, bool enable) {
325 std::map<MenuBar::ActionID, std::list<wxMenuItem*>>::iterator fi = items.find(id);
326 if (fi == items.end()) {
327 return;
328 }
329
330 std::list<wxMenuItem*>& li = fi->second;
331
332 for (std::list<wxMenuItem*>::iterator i = li.begin(); i != li.end(); ++i) {
333 (*i)->Enable(enable);
334 }
335}
336
337void MainMenuBar::CheckItem(MenuBar::ActionID id, bool enable) {
338 std::map<MenuBar::ActionID, std::list<wxMenuItem*>>::iterator fi = items.find(id);
339 if (fi == items.end()) {
340 return;
341 }
342
343 std::list<wxMenuItem*>& li = fi->second;
344
345 checking_programmaticly = true;
346 for (std::list<wxMenuItem*>::iterator i = li.begin(); i != li.end(); ++i) {
347 (*i)->Check(enable);
348 }
349 checking_programmaticly = false;
350}
351
352bool MainMenuBar::IsItemChecked(MenuBar::ActionID id) const {
353 std::map<MenuBar::ActionID, std::list<wxMenuItem*>>::const_iterator fi = items.find(id);
354 if (fi == items.end()) {
355 return false;
356 }
357
358 const std::list<wxMenuItem*>& li = fi->second;
359
360 for (std::list<wxMenuItem*>::const_iterator i = li.begin(); i != li.end(); ++i) {
361 if ((*i)->IsChecked()) {
362 return true;
363 }
364 }
365
366 return false;
367}
368
369void MainMenuBar::Update() {
370 using namespace MenuBar;
371 // This updates all buttons and sets them to proper enabled/disabled state
372
373 bool enable = !g_gui.IsWelcomeDialogShown();
374 menubar->Enable(enable);
375 if (!enable) {
376 return;
377 }
378
379 Editor* editor = g_gui.GetCurrentEditor();
380 if (editor) {
381 EnableItem(UNDO, editor->actionQueue->canUndo());
382 EnableItem(REDO, editor->actionQueue->canRedo());
383 EnableItem(PASTE, editor->copybuffer.canPaste());
384 } else {
385 EnableItem(UNDO, false);
386 EnableItem(REDO, false);
387 EnableItem(PASTE, false);
388 }
389
390 bool loaded = g_gui.IsVersionLoaded();
391 bool has_map = editor != nullptr;
392 bool has_selection = editor && editor->hasSelection();
393 bool is_live = editor && editor->IsLive();
394 bool is_host = has_map && !editor->IsLiveClient();
395 bool is_local = has_map && !is_live;
396
397 EnableItem(CLOSE, is_local);
398 EnableItem(SAVE, is_host);
399 EnableItem(SAVE_AS, is_host);
400 EnableItem(GENERATE_MAP, false);
401
402 EnableItem(IMPORT_MAP, is_local);
403 EnableItem(IMPORT_MONSTERS, is_local);
404 EnableItem(IMPORT_MINIMAP, false);
405 EnableItem(EXPORT_MINIMAP, is_local);
406 EnableItem(EXPORT_TILESETS, loaded);
407
408 EnableItem(FIND_ITEM, is_host);
409 EnableItem(REPLACE_ITEMS, is_local);
410 EnableItem(SEARCH_ON_MAP_EVERYTHING, is_host);
411 EnableItem(SEARCH_ON_MAP_UNIQUE, is_host);
412 EnableItem(SEARCH_ON_MAP_ACTION, is_host);
413 EnableItem(SEARCH_ON_MAP_CONTAINER, is_host);
414 EnableItem(SEARCH_ON_MAP_WRITEABLE, is_host);
415 EnableItem(SEARCH_ON_SELECTION_EVERYTHING, has_selection && is_host);
416 EnableItem(SEARCH_ON_SELECTION_UNIQUE, has_selection && is_host);
417 EnableItem(SEARCH_ON_SELECTION_ACTION, has_selection && is_host);
418 EnableItem(SEARCH_ON_SELECTION_CONTAINER, has_selection && is_host);
419 EnableItem(SEARCH_ON_SELECTION_WRITEABLE, has_selection && is_host);
420 EnableItem(SEARCH_ON_SELECTION_ITEM, has_selection && is_host);
421 EnableItem(REPLACE_ON_SELECTION_ITEMS, has_selection && is_host);
422 EnableItem(REMOVE_ON_SELECTION_ITEM, has_selection && is_host);
423
424 EnableItem(CUT, has_map);
425 EnableItem(COPY, has_map);
426
427 EnableItem(BORDERIZE_SELECTION, has_map && has_selection);
428 EnableItem(BORDERIZE_MAP, is_local);
429 EnableItem(RANDOMIZE_SELECTION, has_map && has_selection);
430 EnableItem(RANDOMIZE_MAP, is_local);
431
432 EnableItem(GOTO_PREVIOUS_POSITION, has_map);
433 EnableItem(GOTO_POSITION, has_map);
434 EnableItem(JUMP_TO_BRUSH, loaded);
435 EnableItem(JUMP_TO_ITEM_BRUSH, loaded);
436
437 EnableItem(MAP_REMOVE_ITEMS, is_host);
438 EnableItem(MAP_REMOVE_CORPSES, is_local);
439 EnableItem(MAP_REMOVE_DUPLICATES, is_local);
440 EnableItem(MAP_REMOVE_UNREACHABLE_TILES, is_local);
441 EnableItem(CLEAR_INVALID_HOUSES, is_local);
442 EnableItem(CLEAR_MODIFIED_STATE, is_local);
443
444 EnableItem(EDIT_TOWNS, is_local);
445 EnableItem(EDIT_ITEMS, false);
446 EnableItem(EDIT_MONSTERS, false);
447
448 EnableItem(MAP_CLEANUP, is_local);
449 EnableItem(MAP_PROPERTIES, is_local);
450 EnableItem(MAP_STATISTICS, is_local);
451
452 EnableItem(NEW_VIEW, has_map);
453 EnableItem(NEW_DETACHED_VIEW, has_map);
454 EnableItem(ZOOM_IN, has_map);
455 EnableItem(ZOOM_OUT, has_map);
456 EnableItem(ZOOM_NORMAL, has_map);
457
458 if (has_map) {
459 CheckItem(SHOW_SPAWNS, g_settings.getBoolean(Config::SHOW_SPAWNS));
460 }
461
462 EnableItem(WIN_MINIMAP, loaded);
463 EnableItem(NEW_PALETTE, loaded);
464 EnableItem(SELECT_TERRAIN, loaded);
465 EnableItem(SELECT_DOODAD, loaded);
466 EnableItem(SELECT_ITEM, loaded);
467 EnableItem(SELECT_COLLECTION, loaded);
468 EnableItem(SELECT_HOUSE, loaded);
469 EnableItem(SELECT_CREATURE, loaded);
470 EnableItem(SELECT_WAYPOINT, loaded);
471 EnableItem(SELECT_RAW, loaded);
472
473 EnableItem(LIVE_START, is_local);
474 EnableItem(LIVE_JOIN, loaded);
475 EnableItem(LIVE_CLOSE, is_live);
476 EnableItem(ID_MENU_SERVER_HOST, is_local);
477 EnableItem(ID_MENU_SERVER_CONNECT, loaded);
478
479 EnableItem(DEBUG_VIEW_DAT, loaded);
480
481 UpdateFloorMenu();
482}
483
484void MainMenuBar::LoadValues() {
485 using namespace MenuBar;
486
487 CheckItem(VIEW_TOOLBARS_BRUSHES, g_settings.getBoolean(Config::SHOW_TOOLBAR_BRUSHES));
488 CheckItem(VIEW_TOOLBARS_POSITION, g_settings.getBoolean(Config::SHOW_TOOLBAR_POSITION));
489 CheckItem(VIEW_TOOLBARS_SIZES, g_settings.getBoolean(Config::SHOW_TOOLBAR_SIZES));
490 CheckItem(VIEW_TOOLBARS_STANDARD, g_settings.getBoolean(Config::SHOW_TOOLBAR_STANDARD));
491
492 CheckItem(SELECT_MODE_COMPENSATE, g_settings.getBoolean(Config::COMPENSATED_SELECT));
493
494 if (IsItemChecked(MenuBar::SELECT_MODE_CURRENT)) {
495 g_settings.setInteger(Config::SELECTION_TYPE, SELECT_CURRENT_FLOOR);
496 } else if (IsItemChecked(MenuBar::SELECT_MODE_LOWER)) {
497 g_settings.setInteger(Config::SELECTION_TYPE, SELECT_ALL_FLOORS);
498 } else if (IsItemChecked(MenuBar::SELECT_MODE_VISIBLE)) {
499 g_settings.setInteger(Config::SELECTION_TYPE, SELECT_VISIBLE_FLOORS);
500 }
501
502 switch (g_settings.getInteger(Config::SELECTION_TYPE)) {
503 case SELECT_CURRENT_FLOOR:
504 CheckItem(SELECT_MODE_CURRENT, true);
505 break;
506 case SELECT_ALL_FLOORS:
507 CheckItem(SELECT_MODE_LOWER, true);
508 break;
509 default:
510 case SELECT_VISIBLE_FLOORS:
511 CheckItem(SELECT_MODE_VISIBLE, true);
512 break;
513 }
514
515 // No longer needed as automagic is controlled through preferences and hotkey 'A'
516 CheckItem(AUTOMAGIC, g_settings.getBoolean(Config::USE_AUTOMAGIC));
517
518 CheckItem(SHOW_SHADE, g_settings.getBoolean(Config::SHOW_SHADE));
519 CheckItem(SHOW_INGAME_BOX, g_settings.getBoolean(Config::SHOW_INGAME_BOX));
520 CheckItem(SHOW_LIGHTS, g_settings.getBoolean(Config::SHOW_LIGHTS));
521 CheckItem(SHOW_LIGHT_STR, g_settings.getBoolean(Config::SHOW_LIGHT_STR));
522 CheckItem(SHOW_TECHNICAL_ITEMS, g_settings.getBoolean(Config::SHOW_TECHNICAL_ITEMS));
523 CheckItem(SHOW_WAYPOINTS, g_settings.getBoolean(Config::SHOW_WAYPOINTS));
524 CheckItem(SHOW_ALL_FLOORS, g_settings.getBoolean(Config::SHOW_ALL_FLOORS));
525 CheckItem(GHOST_ITEMS, g_settings.getBoolean(Config::TRANSPARENT_ITEMS));
526 CheckItem(GHOST_HIGHER_FLOORS, g_settings.getBoolean(Config::TRANSPARENT_FLOORS));
527 CheckItem(SHOW_EXTRA, !g_settings.getBoolean(Config::SHOW_EXTRA));
528 CheckItem(SHOW_GRID, g_settings.getBoolean(Config::SHOW_GRID));
529 CheckItem(HIGHLIGHT_ITEMS, g_settings.getBoolean(Config::HIGHLIGHT_ITEMS));
530 CheckItem(HIGHLIGHT_LOCKED_DOORS, g_settings.getBoolean(Config::HIGHLIGHT_LOCKED_DOORS));
531 CheckItem(SHOW_CREATURES, g_settings.getBoolean(Config::SHOW_CREATURES));
532 CheckItem(SHOW_SPAWNS, g_settings.getBoolean(Config::SHOW_SPAWNS));
533 CheckItem(SHOW_SPECIAL, g_settings.getBoolean(Config::SHOW_SPECIAL_TILES));
534 CheckItem(SHOW_ZONES, g_settings.getBoolean(Config::SHOW_ZONE_AREAS));
535 CheckItem(SHOW_AS_MINIMAP, g_settings.getBoolean(Config::SHOW_AS_MINIMAP));
536 CheckItem(SHOW_ONLY_COLORS, g_settings.getBoolean(Config::SHOW_ONLY_TILEFLAGS));
537 CheckItem(SHOW_ONLY_MODIFIED, g_settings.getBoolean(Config::SHOW_ONLY_MODIFIED_TILES));
538 CheckItem(SHOW_HOUSES, g_settings.getBoolean(Config::SHOW_HOUSES));
539 CheckItem(SHOW_PATHING, g_settings.getBoolean(Config::SHOW_BLOCKING));
540 CheckItem(SHOW_TOOLTIPS, g_settings.getBoolean(Config::SHOW_TOOLTIPS));
541 CheckItem(SHOW_PREVIEW, g_settings.getBoolean(Config::SHOW_PREVIEW));
542 CheckItem(SHOW_WALL_HOOKS, g_settings.getBoolean(Config::SHOW_WALL_HOOKS));
543 CheckItem(SHOW_TOWNS, g_settings.getBoolean(Config::SHOW_TOWNS));
544 CheckItem(ALWAYS_SHOW_ZONES, g_settings.getBoolean(Config::ALWAYS_SHOW_ZONES));
545 CheckItem(EXT_HOUSE_SHADER, g_settings.getBoolean(Config::EXT_HOUSE_SHADER));
546
547 CheckItem(EXPERIMENTAL_FOG, g_settings.getBoolean(Config::EXPERIMENTAL_FOG));
548}
549
550void MainMenuBar::LoadRecentFiles() {
551 recentFiles.Load(g_settings.getConfigObject());
552}
553
554void MainMenuBar::SaveRecentFiles() {
555 recentFiles.Save(g_settings.getConfigObject());
556}
557
558void MainMenuBar::AddRecentFile(FileName file) {
559 recentFiles.AddFileToHistory(file.GetFullPath());
560}
561
562std::vector<wxString> MainMenuBar::GetRecentFiles() {
563 std::vector<wxString> files(recentFiles.GetCount());
564 for (size_t i = 0; i < recentFiles.GetCount(); ++i) {
565 files[i] = recentFiles.GetHistoryFile(i);
566 }
567 return files;
568}
569
570void MainMenuBar::UpdateFloorMenu() {
571 // this will have to be changed if you want to have more floors
572 // see MAKE_ACTION(FLOOR_0, wxITEM_RADIO, OnChangeFloor);
573 if (MAP_MAX_LAYER < 16) {
574 if (g_gui.IsEditorOpen()) {
575 for (int i = 0; i < MAP_LAYERS; ++i) {
576 CheckItem(MenuBar::ActionID(MenuBar::FLOOR_0 + i), false);
577 }
578 CheckItem(MenuBar::ActionID(MenuBar::FLOOR_0 + g_gui.GetCurrentFloor()), true);
579 }
580 }
581}
582
583bool MainMenuBar::Load(const FileName& path, wxArrayString& warnings, wxString& error) {
584 // Open the XML file
585 pugi::xml_document doc;
586 pugi::xml_parse_result result = doc.load_file(path.GetFullPath().mb_str());
587 if (!result) {
588 error = "Could not open " + path.GetFullName() + " (file not found or syntax error)";
589 return false;
590 }
591
592 pugi::xml_node node = doc.child("menubar");
593 if (!node) {
594 error = path.GetFullName() + ": Invalid rootheader.";
595 return false;
596 }
597
598 // Clear the menu
599 while (menubar->GetMenuCount() > 0) {
600 menubar->Remove(0);
601 }
602
603 // Load succeded
604 for (pugi::xml_node menuNode = node.first_child(); menuNode; menuNode = menuNode.next_sibling()) {
605 // For each child node, load it
606 wxObject* i = LoadItem(menuNode, nullptr, warnings, error);
607 wxMenu* m = dynamic_cast<wxMenu*>(i);
608 if (m) {
609 menubar->Append(m, m->GetTitle());
610#ifdef __APPLE__
611 m->SetTitle(m->GetTitle());
612#else
613 m->SetTitle("");
614#endif
615 } else if (i) {
616 delete i;
617 warnings.push_back(path.GetFullName() + ": Only menus can be subitems of main menu");
618 }
619 }
620
621#ifdef __LINUX__
622 const int count = 44;
623 wxAcceleratorEntry entries[count];
624 // Edit
625 entries[0].Set(wxACCEL_CTRL, (int)'Z', MAIN_FRAME_MENU + MenuBar::UNDO);
626 entries[1].Set(wxACCEL_CTRL | wxACCEL_SHIFT, (int)'Z', MAIN_FRAME_MENU + MenuBar::REDO);
627 entries[2].Set(wxACCEL_CTRL, (int)'F', MAIN_FRAME_MENU + MenuBar::FIND_ITEM);
628 entries[3].Set(wxACCEL_CTRL | wxACCEL_SHIFT, (int)'F', MAIN_FRAME_MENU + MenuBar::REPLACE_ITEMS);
629 entries[4].Set(wxACCEL_NORMAL, (int)'A', MAIN_FRAME_MENU + MenuBar::AUTOMAGIC);
630 entries[5].Set(wxACCEL_CTRL, (int)'B', MAIN_FRAME_MENU + MenuBar::BORDERIZE_SELECTION);
631 entries[6].Set(wxACCEL_NORMAL, (int)'P', MAIN_FRAME_MENU + MenuBar::GOTO_PREVIOUS_POSITION);
632 entries[7].Set(wxACCEL_CTRL, (int)'G', MAIN_FRAME_MENU + MenuBar::GOTO_POSITION);
633 entries[8].Set(wxACCEL_NORMAL, (int)'J', MAIN_FRAME_MENU + MenuBar::JUMP_TO_BRUSH);
634 entries[9].Set(wxACCEL_CTRL, (int)'X', MAIN_FRAME_MENU + MenuBar::CUT);
635 entries[10].Set(wxACCEL_CTRL, (int)'C', MAIN_FRAME_MENU + MenuBar::COPY);
636 entries[11].Set(wxACCEL_CTRL, (int)'V', MAIN_FRAME_MENU + MenuBar::PASTE);
637 // View
638 entries[12].Set(wxACCEL_CTRL, (int)'=', MAIN_FRAME_MENU + MenuBar::ZOOM_IN);
639 entries[13].Set(wxACCEL_CTRL, (int)'-', MAIN_FRAME_MENU + MenuBar::ZOOM_OUT);
640 entries[14].Set(wxACCEL_CTRL, (int)'0', MAIN_FRAME_MENU + MenuBar::ZOOM_NORMAL);
641 entries[15].Set(wxACCEL_NORMAL, (int)'Q', MAIN_FRAME_MENU + MenuBar::SHOW_SHADE);
642 entries[16].Set(wxACCEL_CTRL, (int)'W', MAIN_FRAME_MENU + MenuBar::SHOW_ALL_FLOORS);
643 entries[17].Set(wxACCEL_NORMAL, (int)'Q', MAIN_FRAME_MENU + MenuBar::GHOST_ITEMS);
644 entries[18].Set(wxACCEL_CTRL, (int)'L', MAIN_FRAME_MENU + MenuBar::GHOST_HIGHER_FLOORS);
645 entries[19].Set(wxACCEL_SHIFT, (int)'I', MAIN_FRAME_MENU + MenuBar::SHOW_INGAME_BOX);
646 entries[20].Set(wxACCEL_SHIFT, (int)'L', MAIN_FRAME_MENU + MenuBar::SHOW_LIGHTS);
647 entries[21].Set(wxACCEL_SHIFT, (int)'G', MAIN_FRAME_MENU + MenuBar::SHOW_GRID);
648 entries[22].Set(wxACCEL_NORMAL, (int)'V', MAIN_FRAME_MENU + MenuBar::HIGHLIGHT_ITEMS);
649 entries[23].Set(wxACCEL_NORMAL, (int)'X', MAIN_FRAME_MENU + MenuBar::HIGHLIGHT_LOCKED_DOORS);
650 entries[24].Set(wxACCEL_NORMAL, (int)'F', MAIN_FRAME_MENU + MenuBar::SHOW_CREATURES);
651 entries[25].Set(wxACCEL_NORMAL, (int)'S', MAIN_FRAME_MENU + MenuBar::SHOW_SPAWNS);
652 entries[26].Set(wxACCEL_NORMAL, (int)'E', MAIN_FRAME_MENU + MenuBar::SHOW_SPECIAL);
653 entries[27].Set(wxACCEL_SHIFT, (int)'E', MAIN_FRAME_MENU + MenuBar::SHOW_AS_MINIMAP);
654 entries[28].Set(wxACCEL_CTRL, (int)'E', MAIN_FRAME_MENU + MenuBar::SHOW_ONLY_COLORS);
655 entries[29].Set(wxACCEL_CTRL, (int)'M', MAIN_FRAME_MENU + MenuBar::SHOW_ONLY_MODIFIED);
656 entries[30].Set(wxACCEL_CTRL, (int)'H', MAIN_FRAME_MENU + MenuBar::SHOW_HOUSES);
657 entries[31].Set(wxACCEL_NORMAL, (int)'O', MAIN_FRAME_MENU + MenuBar::SHOW_PATHING);
658 entries[32].Set(wxACCEL_NORMAL, (int)'Y', MAIN_FRAME_MENU + MenuBar::SHOW_TOOLTIPS);
659 entries[33].Set(wxACCEL_NORMAL, (int)'L', MAIN_FRAME_MENU + MenuBar::SHOW_PREVIEW);
660 entries[34].Set(wxACCEL_NORMAL, (int)'K', MAIN_FRAME_MENU + MenuBar::SHOW_WALL_HOOKS);
661
662 // Window
663 entries[35].Set(wxACCEL_NORMAL, (int)'M', MAIN_FRAME_MENU + MenuBar::WIN_MINIMAP);
664 entries[36].Set(wxACCEL_NORMAL, (int)'T', MAIN_FRAME_MENU + MenuBar::SELECT_TERRAIN);
665 entries[37].Set(wxACCEL_NORMAL, (int)'D', MAIN_FRAME_MENU + MenuBar::SELECT_DOODAD);
666 entries[38].Set(wxACCEL_NORMAL, (int)'I', MAIN_FRAME_MENU + MenuBar::SELECT_ITEM);
667 entries[39].Set(wxACCEL_NORMAL, (int)'N', MAIN_FRAME_MENU + MenuBar::SELECT_COLLECTION);
668 entries[40].Set(wxACCEL_NORMAL, (int)'H', MAIN_FRAME_MENU + MenuBar::SELECT_HOUSE);
669 entries[41].Set(wxACCEL_NORMAL, (int)'C', MAIN_FRAME_MENU + MenuBar::SELECT_CREATURE);
670 entries[42].Set(wxACCEL_NORMAL, (int)'W', MAIN_FRAME_MENU + MenuBar::SELECT_WAYPOINT);
671 entries[43].Set(wxACCEL_NORMAL, (int)'R', MAIN_FRAME_MENU + MenuBar::SELECT_RAW);
672
673 wxAcceleratorTable accelerator(count, entries);
674 frame->SetAcceleratorTable(accelerator);
675#endif
676
677 /*
678 // Create accelerator table
679 accelerator_table = newd wxAcceleratorTable(accelerators.size(), &accelerators[0]);
680
681 // Tell all clients of the renewed accelerators
682 RenewClients();
683 */
684
685 recentFiles.AddFilesToMenu();
686 Update();
687 LoadValues();
688 return true;
689}
690
691wxObject* MainMenuBar::LoadItem(pugi::xml_node node, wxMenu* parent, wxArrayString& warnings, wxString& error) {
692 pugi::xml_attribute attribute;
693
694 const std::string& nodeName = as_lower_str(node.name());
695 if (nodeName == "menu") {
696 if (!(attribute = node.attribute("name"))) {
697 return nullptr;
698 }
699
700 std::string name = attribute.as_string();
701 std::replace(name.begin(), name.end(), '$', '&');
702
703 wxMenu* menu = newd wxMenu;
704 if ((attribute = node.attribute("special")) && std::string(attribute.as_string()) == "RECENT_FILES") {
705 recentFiles.UseMenu(menu);
706 } else {
707 for (pugi::xml_node menuNode = node.first_child(); menuNode; menuNode = menuNode.next_sibling()) {
708 // Load an add each item in order
709 LoadItem(menuNode, menu, warnings, error);
710 }
711 }
712
713 // If we have a parent, add ourselves.
714 // If not, we just return the item and the parent function
715 // is responsible for adding us to wherever
716 if (parent) {
717 parent->AppendSubMenu(menu, wxstr(name));
718 } else {
719 menu->SetTitle((name));
720 }
721 return menu;
722 } else if (nodeName == "item") {
723 // We must have a parent when loading items
724 if (!parent) {
725 return nullptr;
726 } else if (!(attribute = node.attribute("name"))) {
727 return nullptr;
728 }
729
730 std::string name = attribute.as_string();
731 std::replace(name.begin(), name.end(), '$', '&');
732 if (!(attribute = node.attribute("action"))) {
733 return nullptr;
734 }
735
736 const std::string& action = attribute.as_string();
737 std::string hotkey = node.attribute("hotkey").as_string();
738 if (!hotkey.empty()) {
739 hotkey = '\t' + hotkey;
740 }
741
742 const std::string& help = node.attribute("help").as_string();
743 name += hotkey;
744
745 auto it = actions.find(action);
746 if (it == actions.end()) {
747 warnings.push_back("Invalid action type '" + wxstr(action) + "'.");
748 return nullptr;
749 }
750
751 const MenuBar::Action& act = *it->second;
752 wxAcceleratorEntry* entry = wxAcceleratorEntry::Create(wxstr(hotkey));
753 if (entry) {
754 delete entry; // accelerators.push_back(entry);
755 } else {
756 warnings.push_back("Invalid hotkey.");
757 }
758
759 wxMenuItem* tmp = parent->Append(
760 MAIN_FRAME_MENU + act.id, // ID
761 wxstr(name), // Title of button
762 wxstr(help), // Help text
763 act.kind // Kind of item
764 );
765 items[MenuBar::ActionID(act.id)].push_back(tmp);
766 return tmp;
767 } else if (nodeName == "separator") {
768 // We must have a parent when loading items
769 if (!parent) {
770 return nullptr;
771 }
772 return parent->AppendSeparator();
773 }
774 return nullptr;
775}
776
777void MainMenuBar::OnNew(wxCommandEvent& WXUNUSED(event)) {
778 g_gui.NewMap();
779}
780
781void MainMenuBar::OnGenerateMap(wxCommandEvent& WXUNUSED(event)) {
782 /*
783 if(!DoQuerySave()) return;
784
785 std::ostringstream os;
786 os << "Untitled-" << untitled_counter << ".otbm";
787 ++untitled_counter;
788
789 editor.generateMap(wxstr(os.str()));
790
791 g_gui.SetStatusText("Generated newd map");
792
793 g_gui.UpdateTitle();
794 g_gui.RefreshPalettes();
795 g_gui.UpdateMinimap();
796 g_gui.FitViewToMap();
797 UpdateMenubar();
798 Refresh();
799 */
800}
801
802void MainMenuBar::OnGenerateProceduralMap(wxCommandEvent& WXUNUSED(event)) {
803 if (!g_gui.IsEditorOpen()) {
804 wxMessageBox("Please create or open a map first before generating procedural content.", "No Map Open", wxOK | wxICON_WARNING);
805 return;
806 }
807
808 OTMapGenDialog dialog(frame);
809 if (dialog.ShowModal() == wxID_OK) {
810 // Map was generated successfully
811 g_gui.RefreshView();
812 g_gui.UpdateMinimap();
813 }
814}
815
816void MainMenuBar::OnOpenRecent(wxCommandEvent& event) {
817 FileName fn(recentFiles.GetHistoryFile(event.GetId() - recentFiles.GetBaseId()));
818 frame->LoadMap(fn);
819}
820
821void MainMenuBar::OnOpen(wxCommandEvent& WXUNUSED(event)) {
822 g_gui.OpenMap();
823}
824
825void MainMenuBar::OnClose(wxCommandEvent& WXUNUSED(event)) {
826 frame->DoQuerySave(true); // It closes the editor too
827}
828
829void MainMenuBar::OnSave(wxCommandEvent& WXUNUSED(event)) {
830 g_gui.SaveMap();
831}
832
833void MainMenuBar::OnSaveAs(wxCommandEvent& WXUNUSED(event)) {
834 g_gui.SaveMapAs();
835}
836
837void MainMenuBar::OnPreferences(wxCommandEvent& WXUNUSED(event)) {
838 PreferencesWindow dialog(frame);
839 dialog.ShowModal();
840 dialog.Destroy();
841}
842
843void MainMenuBar::OnQuit(wxCommandEvent& WXUNUSED(event)) {
844 /*
845 while(g_gui.IsEditorOpen())
846 if(!frame->DoQuerySave(true))
847 return;
848 */
849 //((Application*)wxTheApp)->Unload();
850 g_gui.root->Close();
851}
852
853void MainMenuBar::OnImportMap(wxCommandEvent& WXUNUSED(event)) {
854 ASSERT(g_gui.GetCurrentEditor());
855 wxDialog* importmap = newd ImportMapWindow(frame, *g_gui.GetCurrentEditor());
856 importmap->ShowModal();
857}
858
859void MainMenuBar::OnImportMonsterData(wxCommandEvent& WXUNUSED(event)) {
860 wxFileDialog dlg(g_gui.root, "Import monster/npc file", "", "", "*.xml", wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
861 if (dlg.ShowModal() == wxID_OK) {
862 wxArrayString paths;
863 dlg.GetPaths(paths);
864 for (uint32_t i = 0; i < paths.GetCount(); ++i) {
865 wxString error;
866 wxArrayString warnings;
867 bool ok = g_creatures.importXMLFromOT(FileName(paths[i]), error, warnings);
868 if (ok) {
869 g_gui.ListDialog("Monster loader errors", warnings);
870 } else {
871 wxMessageBox("Error OT data file \"" + paths[i] + "\".\n" + error, "Error", wxOK | wxICON_INFORMATION, g_gui.root);
872 }
873 }
874 }
875}
876
877void MainMenuBar::OnImportMinimap(wxCommandEvent& WXUNUSED(event)) {
878 ASSERT(g_gui.IsEditorOpen());
879 // wxDialog* importmap = newd ImportMapWindow();
880 // importmap->ShowModal();
881}
882
883void MainMenuBar::OnExportMinimap(wxCommandEvent& WXUNUSED(event)) {
884 if (g_gui.GetCurrentEditor()) {
885 ExportMiniMapWindow dlg(frame, *g_gui.GetCurrentEditor());
886 dlg.ShowModal();
887 dlg.Destroy();
888 }
889}
890
891void MainMenuBar::OnExportTilesets(wxCommandEvent& WXUNUSED(event)) {
892 if (g_gui.GetCurrentEditor()) {
893 ExportTilesetsWindow dlg(frame, *g_gui.GetCurrentEditor());
894 dlg.ShowModal();
895 dlg.Destroy();
896 }
897}
898
899void MainMenuBar::OnDebugViewDat(wxCommandEvent& WXUNUSED(event)) {
900 wxDialog dlg(frame, wxID_ANY, "Debug .dat file", wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
901 new DatDebugView(&dlg);
902 dlg.ShowModal();
903}
904
905void MainMenuBar::OnReloadDataFiles(wxCommandEvent& WXUNUSED(event)) {
906 wxString error;
907 wxArrayString warnings;
908 g_gui.LoadVersion(g_gui.GetCurrentVersionID(), error, warnings, true);
909 g_gui.PopupDialog("Error", error, wxOK);
910 g_gui.ListDialog("Warnings", warnings);
911}
912
913void MainMenuBar::OnListExtensions(wxCommandEvent& WXUNUSED(event)) {
914 ExtensionsDialog exts(frame);
915 exts.ShowModal();
916}
917
918void MainMenuBar::OnGotoWebsite(wxCommandEvent& WXUNUSED(event)) {
919 ::wxLaunchDefaultBrowser(__SITE_URL__, wxBROWSER_NEW_WINDOW);
920}
921
922void MainMenuBar::OnAbout(wxCommandEvent& WXUNUSED(event)) {
923 AboutWindow about(frame);
924 about.ShowModal();
925}
926
927void MainMenuBar::OnUndo(wxCommandEvent& WXUNUSED(event)) {
928 g_gui.DoUndo();
929}
930
931void MainMenuBar::OnRedo(wxCommandEvent& WXUNUSED(event)) {
932 g_gui.DoRedo();
933}
934
935namespace OnSearchForItem {
936 struct Finder {
937 Finder(uint16_t itemId, uint32_t maxCount) :
938 itemId(itemId), maxCount(maxCount) { }
939
940 uint16_t itemId;
941 uint32_t maxCount;
942 std::vector<std::pair<Tile*, Item*>> result;
943
944 bool limitReached() const {
945 return result.size() >= (size_t)maxCount;
946 }
947
948 void operator()(Map& map, Tile* tile, Item* item, long long done) {
949 if (result.size() >= (size_t)maxCount) {
950 return;
951 }
952
953 if (done % 0x8000 == 0) {
954 g_gui.SetLoadDone((unsigned int)(100 * done / map.getTileCount()));
955 }
956
957 if (item->getID() == itemId) {
958 result.push_back(std::make_pair(tile, item));
959 }
960 }
961 };
962
963 struct RangeFinder {
964 RangeFinder(const std::vector<std::pair<uint16_t, uint16_t>>& ranges,
965 const std::vector<uint16_t>& ignored_ids = {},
966 const std::vector<std::pair<uint16_t, uint16_t>>& ignored_ranges = {}) :
967 ranges(ranges),
968 ignored_ids(ignored_ids),
969 ignored_ranges(ignored_ranges),
970 maxCount((uint32_t)g_settings.getInteger(Config::REPLACE_SIZE)) { }
971
972 std::vector<std::pair<uint16_t, uint16_t>> ranges;
973 std::vector<uint16_t> ignored_ids;
974 std::vector<std::pair<uint16_t, uint16_t>> ignored_ranges;
975 uint32_t maxCount;
976 std::vector<std::pair<Tile*, Item*>> result;
977
978 bool limitReached() const {
979 return maxCount > 0 && result.size() >= size_t(maxCount);
980 }
981
982 void operator()(Map& map, Tile* tile, Item* item, long long done) {
983 if(limitReached()) return;
984
985 if(done % 0x8000 == 0) {
986 g_gui.SetLoadDone((unsigned int)(100 * done / map.getTileCount()));
987 }
988
989 uint16_t itemId = item->getID();
990
991 // Check if item should be ignored
992 for(const auto& id : ignored_ids) {
993 if(itemId == id) return;
994 }
995
996 for(const auto& range : ignored_ranges) {
997 if(itemId >= range.first && itemId <= range.second) return;
998 }
999
1000 // Check if item is in search ranges
1001 for(const auto& range : ranges) {
1002 if(itemId >= range.first && itemId <= range.second) {
1003 result.push_back(std::make_pair(tile, item));
1004 break;
1005 }
1006 }
1007 }
1008 };
1009}
1010
1011void MainMenuBar::OnSearchForItem(wxCommandEvent& WXUNUSED(event)) {
1012 if (!g_gui.IsEditorOpen()) {
1013 return;
1014 }
1015
1016 FindItemDialog dialog(frame, "Search for Item");
1017 dialog.setSearchMode((FindItemDialog::SearchMode)g_settings.getInteger(Config::FIND_ITEM_MODE));
1018 if (dialog.ShowModal() == wxID_OK) {
1019 if (dialog.getUseRange()) {
1020 auto ranges = dialog.ParseRangeString(dialog.GetRangeInput());
1021 if (!ranges.empty()) {
1022 // Parse ignored IDs if enabled
1023 std::vector<uint16_t> ignored_ids;
1024 std::vector<std::pair<uint16_t, uint16_t>> ignored_ranges;
1025 if(dialog.IsIgnoreIdsEnabled()) {
1026 ignored_ids = dialog.GetIgnoredIds();
1027 ignored_ranges = dialog.ParseRangeString(dialog.GetIgnoreIdsText());
1028 }
1029
1030 OnSearchForItem::RangeFinder finder(ranges, ignored_ids, ignored_ranges);
1031 g_gui.CreateLoadBar("Searching map...");
1032
1033 foreach_ItemOnMap(g_gui.GetCurrentMap(), finder, false);
1034 std::vector<std::pair<Tile*, Item*>>& result = finder.result;
1035
1036 g_gui.DestroyLoadBar();
1037
1038 if (finder.limitReached()) {
1039 wxString msg;
1040 msg << "The configured limit has been reached. Only " << finder.maxCount << " results will be displayed.";
1041 g_gui.PopupDialog("Notice", msg, wxOK);
1042 }
1043
1044 SearchResultWindow* resultWindow = g_gui.ShowSearchWindow();
1045 resultWindow->Clear();
1046 for (const auto& pair : result) {
1047 resultWindow->AddPosition(wxString::Format("%s (ID: %d)",
1048 wxstr(pair.second->getName()),
1049 pair.second->getID()), pair.first->getPosition());
1050 }
1051 }
1052 } else {
1053 OnSearchForItem::Finder finder(dialog.getResultID(), (uint32_t)g_settings.getInteger(Config::REPLACE_SIZE));
1054 g_gui.CreateLoadBar("Searching map...");
1055
1056 foreach_ItemOnMap(g_gui.GetCurrentMap(), finder, false);
1057 std::vector<std::pair<Tile*, Item*>>& result = finder.result;
1058
1059 g_gui.DestroyLoadBar();
1060
1061 if (finder.limitReached()) {
1062 wxString msg;
1063 msg << "The configured limit has been reached. Only " << finder.maxCount << " results will be displayed.";
1064 g_gui.PopupDialog("Notice", msg, wxOK);
1065 }
1066
1067 SearchResultWindow* window = g_gui.ShowSearchWindow();
1068 window->Clear();
1069
1070 for (std::vector<std::pair<Tile*, Item*>>::const_iterator iter = result.begin(); iter != result.end(); ++iter) {
1071 Tile* tile = iter->first;
1072 Item* item = iter->second;
1073
1074 // Format description to include both name and ID
1075 wxString description = wxString::Format("%s (ID: %d)",
1076 wxstr(item->getName()),
1077 item->getID());
1078
1079 OutputDebugStringA(wxString::Format("Adding search result: %s at pos(%d,%d,%d)\n",
1080 description, tile->getPosition().x, tile->getPosition().y, tile->getPosition().z).c_str());
1081
1082 window->AddPosition(description, tile->getPosition());
1083 }
1084 }
1085
1086 g_settings.setInteger(Config::FIND_ITEM_MODE, (int)dialog.getSearchMode());
1087 }
1088 dialog.Destroy();
1089}
1090
1091void MainMenuBar::OnSearchForCreature(wxCommandEvent& WXUNUSED(event)) {
1092 if (!g_gui.IsEditorOpen()) {
1093 return;
1094 }
1095
1096 FindCreatureDialog dialog(frame, "Search for Creature");
1097 if (dialog.ShowModal() == wxID_OK) {
1098 wxString creatureName = dialog.getResultName();
1099 if (!creatureName.IsEmpty()) {
1100 Editor* editor = g_gui.GetCurrentEditor();
1101 if (editor) {
1102 Map& map = editor->getMap();
1103 SearchResultWindow* resultWindow = g_gui.ShowSearchWindow();
1104 resultWindow->Clear();
1105
1106 g_gui.CreateLoadBar("Searching for creatures...");
1107
1108 size_t creatureFoundCount = 0;
1109 const std::string creatureNameStr = creatureName.ToStdString();
1110
1111 // Get the spawn XML data
1112 pugi::xml_document doc;
1113 pugi::xml_parse_result result = doc.load_file(map.getSpawnFilename().c_str());
1114
1115 if (result) {
1116 pugi::xml_node spawnsNode = doc.child("spawns");
1117 if (spawnsNode) {
1118 // Iterate through all spawns
1119 for (pugi::xml_node spawnNode = spawnsNode.first_child(); spawnNode; spawnNode = spawnNode.next_sibling()) {
1120 if (as_lower_str(spawnNode.name()) != "spawn") {
1121 continue;
1122 }
1123
1124 Position spawnPos;
1125 spawnPos.x = spawnNode.attribute("centerx").as_int();
1126 spawnPos.y = spawnNode.attribute("centery").as_int();
1127 spawnPos.z = spawnNode.attribute("centerz").as_int();
1128
1129 // Check each creature in the spawn
1130 for (pugi::xml_node creatureNode = spawnNode.first_child(); creatureNode; creatureNode = creatureNode.next_sibling()) {
1131 const std::string& creatureNodeName = as_lower_str(creatureNode.name());
1132 if (creatureNodeName != "monster" && creatureNodeName != "npc") {
1133 continue;
1134 }
1135
1136 const std::string& name = creatureNode.attribute("name").as_string();
1137 if (name == creatureNameStr) {
1138 // Calculate the actual position of the creature
1139 Position creaturePos = spawnPos;
1140 creaturePos.x += creatureNode.attribute("x").as_int();
1141 creaturePos.y += creatureNode.attribute("y").as_int();
1142
1143 wxString description = wxString::Format("%s at (%d,%d,%d)",
1144 creatureName,
1145 creaturePos.x,
1146 creaturePos.y,
1147 creaturePos.z);
1148 resultWindow->AddPosition(description, creaturePos);
1149 ++creatureFoundCount;
1150 }
1151 }
1152 }
1153 }
1154 }
1155
1156 // Also check for loose creatures (not in spawns)
1157 for (MapIterator mit = map.begin(); mit != map.end(); ++mit) {
1158 Tile* tile = (*mit)->get();
1159 if (!tile) continue;
1160
1161 if (tile->creature && !tile->spawn) { // Only check tiles with creatures that aren't part of a spawn
1162 if (tile->creature->getName() == creatureNameStr) {
1163 wxString description = wxString::Format("%s (loose) at (%d,%d,%d)",
1164 creatureName,
1165 tile->getPosition().x,
1166 tile->getPosition().y,
1167 tile->getPosition().z);
1168 resultWindow->AddPosition(description, tile->getPosition());
1169 ++creatureFoundCount;
1170 }
1171 }
1172 }
1173
1174 g_gui.DestroyLoadBar();
1175
1176 wxString resultMessage;
1177 if (creatureFoundCount == 0) {
1178 resultMessage = wxString::Format("No %s found on the map.", creatureName);
1179 g_gui.PopupDialog("Search completed", resultMessage, wxOK);
1180 } else {
1181 resultMessage = wxString::Format("Found %d instances of %s on the map.",
1182 creatureFoundCount, creatureName);
1183 g_gui.SetStatusText(resultMessage);
1184 }
1185 }
1186 }
1187 }
1188 dialog.Destroy();
1189}
1190
1191void MainMenuBar::OnReplaceItems(wxCommandEvent& WXUNUSED(event)) {
1192 if (!g_gui.IsVersionLoaded()) {
1193 return;
1194 }
1195
1196 if (MapTab* tab = g_gui.GetCurrentMapTab()) {
1197 if (MapWindow* window = tab->GetView()) {
1198 window->ShowReplaceItemsDialog(false);
1199 }
1200 }
1201}
1202
1203namespace OnSearchForStuff {
1204 struct Searcher {
1205 Searcher() :
1206 search_zones(false),
1207 search_unique(false),
1208 search_action(false),
1209 search_container(false),
1210 search_writeable(false) { }
1211
1212 bool search_zones;
1213 bool search_unique;
1214 bool search_action;
1215 bool search_container;
1216 bool search_writeable;
1217 std::vector<std::pair<uint16_t, uint16_t>> uniqueRanges;
1218 std::vector<std::pair<uint16_t, uint16_t>> actionRanges;
1219 std::vector<std::pair<Tile*, Item*>> found;
1220
1221 bool isInRanges(uint16_t id, const std::vector<std::pair<uint16_t, uint16_t>>& ranges) {
1222 if (ranges.empty()) return true;
1223 for (const auto& range : ranges) {
1224 if (id >= range.first && id <= range.second) {
1225 return true;
1226 }
1227 }
1228 return false;
1229 }
1230
1231 void operator()(Map& map, Tile* tile, Item* item, long long done) {
1232 if (done % 0x8000 == 0) {
1233 g_gui.SetLoadDone((unsigned int)(100 * done / map.getTileCount()));
1234 }
1235 Container* container;
1236 bool shouldAdd = false;
1237
1238 if (search_zones && item->isGroundTile() && !tile->getZoneIds().empty()) {
1239 shouldAdd = true;
1240 }
1241
1242 if (search_unique && item->getUniqueID() > 0 && isInRanges(item->getUniqueID(), uniqueRanges)) {
1243 shouldAdd = true;
1244 }
1245 if (search_action && item->getActionID() > 0 && isInRanges(item->getActionID(), actionRanges)) {
1246 shouldAdd = true;
1247 }
1248 if (search_container && ((container = dynamic_cast<Container*>(item)) && container->getItemCount())) {
1249 shouldAdd = true;
1250 }
1251 if (search_writeable && item->getText().length() > 0) {
1252 shouldAdd = true;
1253 }
1254
1255 if (shouldAdd) {
1256 found.push_back(std::make_pair(tile, item));
1257 }
1258 }
1259
1260 wxString desc(Tile* tile, Item* item) {
1261 wxString label;
1262 if (search_zones) {
1263 label << "Zone ID: ";
1264 size_t zones = tile->getZoneIds().size();
1265 for (const auto& zoneId : tile->getZoneIds()) {
1266 label << zoneId;
1267 if (--zones > 0) {
1268 label << "/";
1269 }
1270 }
1271 } else {
1272 if (item->getUniqueID() > 0) {
1273 label << "UID: " << item->getUniqueID() << " ";
1274 }
1275
1276 if (item->getActionID() > 0) {
1277 label << "AID:" << item->getActionID() << " ";
1278 }
1279
1280 label << wxstr(item->getName());
1281
1282 if (dynamic_cast<Container*>(item)) {
1283 label << " (Container) ";
1284 }
1285
1286 if (item->getText().length() > 0) {
1287 label << " (Text: " << wxstr(item->getText()) << ") ";
1288 }
1289 }
1290
1291 return label;
1292 }
1293
1294 void sort() {
1295 if (search_unique || search_action) {
1296 std::sort(found.begin(), found.end(), Searcher::compare);
1297 } else if (search_zones) {
1298 std::sort(found.begin(), found.end(), Searcher::compareZones);
1299 }
1300 }
1301
1302 static bool compare(const std::pair<Tile*, Item*>& pair1, const std::pair<Tile*, Item*>& pair2) {
1303 const Item* item1 = pair1.second;
1304 const Item* item2 = pair2.second;
1305
1306 if (item1->getActionID() != 0 || item2->getActionID() != 0) {
1307 return item1->getActionID() < item2->getActionID();
1308 } else if (item1->getUniqueID() != 0 || item2->getUniqueID() != 0) {
1309 return item1->getUniqueID() < item2->getUniqueID();
1310 }
1311
1312 return false;
1313 }
1314
1315 static bool compareZones(const std::pair<Tile*, Item*>& pair1, const std::pair<Tile*, Item*>& pair2) {
1316 return pair1.first->getZoneId() < pair2.first->getZoneId();
1317 }
1318 };
1319}
1320
1321void MainMenuBar::OnSearchForStuffOnMap(wxCommandEvent& WXUNUSED(event)) {
1322 SearchItems(true, true, true, true, false);
1323}
1324
1325void MainMenuBar::OnSearchForZonesOnMap(wxCommandEvent& WXUNUSED(event)) {
1326 SearchItems(false, false, false, false, true);
1327}
1328
1329void MainMenuBar::OnSearchForUniqueOnMap(wxCommandEvent& WXUNUSED(event)) {
1330 SearchItems(true, false, false, false, false);
1331}
1332
1333void MainMenuBar::OnSearchForActionOnMap(wxCommandEvent& WXUNUSED(event)) {
1334 SearchItems(false, true, false, false, false);
1335}
1336
1337void MainMenuBar::OnSearchForContainerOnMap(wxCommandEvent& WXUNUSED(event)) {
1338 SearchItems(false, false, true, false, false);
1339}
1340
1341void MainMenuBar::OnSearchForWriteableOnMap(wxCommandEvent& WXUNUSED(event)) {
1342 SearchItems(false, false, false, true, false);
1343}
1344
1345void MainMenuBar::OnSearchForStuffOnSelection(wxCommandEvent& WXUNUSED(event)) {
1346 SearchItems(true, true, true, true, false, true);
1347}
1348
1349void MainMenuBar::OnSearchForZonesOnSelection(wxCommandEvent& WXUNUSED(event)) {
1350 SearchItems(false, false, false, false, true, true);
1351}
1352
1353void MainMenuBar::OnSearchForUniqueOnSelection(wxCommandEvent& WXUNUSED(event)) {
1354 SearchItems(true, false, false, false, false, true);
1355}
1356
1357void MainMenuBar::OnSearchForActionOnSelection(wxCommandEvent& WXUNUSED(event)) {
1358 SearchItems(false, true, false, false, false, true);
1359}
1360
1361void MainMenuBar::OnSearchForContainerOnSelection(wxCommandEvent& WXUNUSED(event)) {
1362 SearchItems(false, false, true, false, false, true);
1363}
1364
1365void MainMenuBar::OnSearchForWriteableOnSelection(wxCommandEvent& WXUNUSED(event)) {
1366 SearchItems(false, false, false, true, false, true);
1367}
1368
1369void MainMenuBar::OnSearchForItemOnSelection(wxCommandEvent& WXUNUSED(event)) {
1370 if (!g_gui.IsEditorOpen()) {
1371 return;
1372 }
1373
1374 FindItemDialog dialog(frame, "Search on Selection");
1375 dialog.setSearchMode((FindItemDialog::SearchMode)g_settings.getInteger(Config::FIND_ITEM_MODE));
1376 if (dialog.ShowModal() == wxID_OK) {
1377 if (dialog.getUseRange()) {
1378 auto ranges = dialog.ParseRangeString(dialog.GetRangeInput());
1379 if (!ranges.empty()) {
1380 OnSearchForItem::RangeFinder finder(ranges);
1381 g_gui.CreateLoadBar("Searching on selected area...");
1382
1383 foreach_ItemOnMap(g_gui.GetCurrentMap(), finder, true);
1384 std::vector<std::pair<Tile*, Item*>>& result = finder.result;
1385
1386 g_gui.DestroyLoadBar();
1387
1388 if (finder.limitReached()) {
1389 wxString msg;
1390 msg << "The configured limit has been reached. Only " << finder.maxCount << " results will be displayed.";
1391 g_gui.PopupDialog("Notice", msg, wxOK);
1392 }
1393
1394 SearchResultWindow* resultWindow = g_gui.ShowSearchWindow();
1395 resultWindow->Clear();
1396
1397 // Pass the ignored IDs configuration from the dialog
1398 resultWindow->SetIgnoredIds(dialog.GetIgnoreIdsText(), dialog.IsIgnoreIdsEnabled());
1399
1400 // Store search parameters for range searches to enable continuation
1401 uint16_t firstItemId = result.empty() ? 0 : result[0].second->getID();
1402 resultWindow->StoreSearchInfo(firstItemId, true);
1403
1404 for (const auto& pair : result) {
1405 resultWindow->AddPosition(wxString::Format("%s (ID: %d)",
1406 wxstr(pair.second->getName()),
1407 pair.second->getID()), pair.first->getPosition());
1408 }
1409 }
1410 } else {
1411 OnSearchForItem::Finder finder(dialog.getResultID(), (uint32_t)g_settings.getInteger(Config::REPLACE_SIZE));
1412 g_gui.CreateLoadBar("Searching on selected area...");
1413
1414 foreach_ItemOnMap(g_gui.GetCurrentMap(), finder, true);
1415 std::vector<std::pair<Tile*, Item*>>& result = finder.result;
1416
1417 g_gui.DestroyLoadBar();
1418
1419 if (finder.limitReached()) {
1420 wxString msg;
1421 msg << "The configured limit has been reached. Only " << finder.maxCount << " results will be displayed.";
1422 g_gui.PopupDialog("Notice", msg, wxOK);
1423 }
1424
1425 SearchResultWindow* window = g_gui.ShowSearchWindow();
1426 window->Clear();
1427
1428 // Pass the ignored IDs configuration from the dialog
1429 window->SetIgnoredIds(dialog.GetIgnoreIdsText(), dialog.IsIgnoreIdsEnabled());
1430
1431 // Store search parameters for continuation
1432 window->StoreSearchInfo(dialog.getResultID(), true);
1433
1434 for (std::vector<std::pair<Tile*, Item*>>::const_iterator iter = result.begin(); iter != result.end(); ++iter) {
1435 Tile* tile = iter->first;
1436 Item* item = iter->second;
1437
1438 // Format description to include both name and ID
1439 wxString description = wxString::Format("%s (ID: %d)",
1440 wxstr(item->getName()),
1441 item->getID());
1442
1443 OutputDebugStringA(wxString::Format("Adding search result: %s at pos(%d,%d,%d)\n",
1444 description, tile->getPosition().x, tile->getPosition().y, tile->getPosition().z).c_str());
1445
1446 window->AddPosition(description, tile->getPosition());
1447 }
1448 }
1449
1450 g_settings.setInteger(Config::FIND_ITEM_MODE, (int)dialog.getSearchMode());
1451 }
1452
1453 dialog.Destroy();
1454}
1455
1456void MainMenuBar::OnReplaceItemsOnSelection(wxCommandEvent& WXUNUSED(event)) {
1457 if (!g_gui.IsVersionLoaded()) {
1458 return;
1459 }
1460
1461 if (MapTab* tab = g_gui.GetCurrentMapTab()) {
1462 if (MapWindow* window = tab->GetView()) {
1463 window->ShowReplaceItemsDialog(true);
1464 }
1465 }
1466}
1467
1468void MainMenuBar::OnRemoveItemOnSelection(wxCommandEvent& WXUNUSED(event)) {
1469 if (!g_gui.IsEditorOpen()) {
1470 return;
1471 }
1472
1473 FindItemDialog dialog(frame, "Remove Items on Selection");
1474 dialog.setSearchMode((FindItemDialog::SearchMode)g_settings.getInteger(Config::FIND_ITEM_MODE));
1475
1476 if (dialog.ShowModal() == wxID_OK) {
1477 g_gui.GetCurrentEditor()->actionQueue->clear();
1478 g_gui.CreateLoadBar("Searching items on selection to remove...");
1479
1480 int64_t count = 0;
1481
1482 if (dialog.getUseRange()) {
1483 auto ranges = dialog.ParseRangeString(dialog.GetRangeInput());
1484 if (!ranges.empty()) {
1485 // Create a condition that checks if an item's ID is within any of the ranges
1486 struct RangeRemoveCondition {
1487 std::vector<std::pair<uint16_t, uint16_t>> ranges;
1488
1489 RangeRemoveCondition(const std::vector<std::pair<uint16_t, uint16_t>>& r) : ranges(r) {}
1490
1491 bool operator()(Map& map, Item* item, long long removed, long long done) {
1492 if (done % 0x800 == 0) {
1493 g_gui.SetLoadDone((unsigned int)(100 * done / map.getTileCount()));
1494 }
1495
1496 for (const auto& range : ranges) {
1497 if (item->getID() >= range.first && item->getID() <= range.second) {
1498 return true;
1499 }
1500 }
1501 return false;
1502 }
1503 } condition(ranges);
1504
1505 count = RemoveItemOnMap(g_gui.GetCurrentMap(), condition, true);
1506 }
1507 } else {
1508 OnMapRemoveItems::RemoveItemCondition condition(dialog.getResultID());
1509 count = RemoveItemOnMap(g_gui.GetCurrentMap(), condition, true);
1510 }
1511
1512 g_gui.DestroyLoadBar();
1513
1514 wxString msg;
1515 msg << count << " items removed.";
1516 g_gui.PopupDialog("Remove Items", msg, wxOK);
1517 g_gui.GetCurrentMap().doChange();
1518 g_gui.RefreshView();
1519 }
1520 dialog.Destroy();
1521}
1522
1523void MainMenuBar::OnSelectionTypeChange(wxCommandEvent& WXUNUSED(event)) {
1524 g_settings.setInteger(Config::COMPENSATED_SELECT, IsItemChecked(MenuBar::SELECT_MODE_COMPENSATE));
1525
1526 if (IsItemChecked(MenuBar::SELECT_MODE_CURRENT)) {
1527 g_settings.setInteger(Config::SELECTION_TYPE, SELECT_CURRENT_FLOOR);
1528 } else if (IsItemChecked(MenuBar::SELECT_MODE_LOWER)) {
1529 g_settings.setInteger(Config::SELECTION_TYPE, SELECT_ALL_FLOORS);
1530 } else if (IsItemChecked(MenuBar::SELECT_MODE_VISIBLE)) {
1531 g_settings.setInteger(Config::SELECTION_TYPE, SELECT_VISIBLE_FLOORS);
1532 }
1533}
1534
1535void MainMenuBar::OnCopy(wxCommandEvent& WXUNUSED(event)) {
1536 g_gui.DoCopy();
1537}
1538
1539void MainMenuBar::OnCut(wxCommandEvent& WXUNUSED(event)) {
1540 g_gui.DoCut();
1541}
1542
1543void MainMenuBar::OnPaste(wxCommandEvent& WXUNUSED(event)) {
1544 g_gui.PreparePaste();
1545}
1546
1547
1548void MainMenuBar::OnBorderizeSelection(wxCommandEvent& WXUNUSED(event)) {
1549 if (!g_gui.IsEditorOpen()) {
1550 return;
1551 }
1552
1553 g_gui.GetCurrentEditor()->borderizeSelection();
1554 g_gui.RefreshView();
1555}
1556
1557void MainMenuBar::OnBorderizeMap(wxCommandEvent& WXUNUSED(event)) {
1558 if (!g_gui.IsEditorOpen()) {
1559 return;
1560 }
1561
1562 int ret = g_gui.PopupDialog("Borderize Map",
1563 "Do you want to borderize the entire map? This will process the map in chunks.", wxYES | wxNO);
1564 if (ret == wxID_YES) {
1565 g_gui.GetCurrentEditor()->borderizeMap(true);
1566 }
1567
1568 g_gui.RefreshView();
1569}
1570
1571void MainMenuBar::OnRandomizeSelection(wxCommandEvent& WXUNUSED(event)) {
1572 if (!g_gui.IsEditorOpen()) {
1573 return;
1574 }
1575
1576 g_gui.GetCurrentEditor()->randomizeSelection();
1577 g_gui.RefreshView();
1578}
1579
1580void MainMenuBar::OnRandomizeMap(wxCommandEvent& WXUNUSED(event)) {
1581 if (!g_gui.IsEditorOpen()) {
1582 return;
1583 }
1584
1585 int ret = g_gui.PopupDialog("Randomize Map", "Are you sure you want to randomize the entire map (this action cannot be undone)?", wxYES | wxNO);
1586 if (ret == wxID_YES) {
1587 g_gui.GetCurrentEditor()->randomizeMap(true);
1588 }
1589
1590 g_gui.RefreshView();
1591}
1592
1593void MainMenuBar::OnJumpToBrush(wxCommandEvent& WXUNUSED(event)) {
1594 if (!g_gui.IsVersionLoaded()) {
1595 return;
1596 }
1597
1598 // Create the jump to dialog
1599 FindDialog* dlg = newd FindBrushDialog(frame);
1600
1601 // Display dialog to user
1602 dlg->ShowModal();
1603
1604 // Retrieve result, if null user canceled
1605 const Brush* brush = dlg->getResult();
1606 if (brush) {
1607 g_gui.SelectBrush(brush, TILESET_UNKNOWN);
1608 }
1609 delete dlg;
1610}
1611
1612void MainMenuBar::OnJumpToItemBrush(wxCommandEvent& WXUNUSED(event)) {
1613 if (!g_gui.IsVersionLoaded()) {
1614 return;
1615 }
1616
1617 // Create the jump to dialog
1618 FindItemDialog dialog(frame, "Jump to Item");
1619 dialog.setSearchMode((FindItemDialog::SearchMode)g_settings.getInteger(Config::JUMP_TO_ITEM_MODE));
1620 if (dialog.ShowModal() == wxID_OK) {
1621 // Retrieve result, if null user canceled
1622 const Brush* brush = dialog.getResult();
1623 if (brush) {
1624 g_gui.SelectBrush(brush, TILESET_RAW);
1625 }
1626 g_settings.setInteger(Config::JUMP_TO_ITEM_MODE, (int)dialog.getSearchMode());
1627 }
1628 dialog.Destroy();
1629}
1630
1631void MainMenuBar::OnGotoPreviousPosition(wxCommandEvent& WXUNUSED(event)) {
1632 MapTab* mapTab = g_gui.GetCurrentMapTab();
1633 if (mapTab) {
1634 mapTab->GoToPreviousCenterPosition();
1635 }
1636}
1637
1638void MainMenuBar::OnGotoPosition(wxCommandEvent& WXUNUSED(event)) {
1639 if (!g_gui.IsEditorOpen()) {
1640 return;
1641 }
1642
1643 // Display dialog, it also controls the actual jump
1644 GotoPositionDialog dlg(frame, *g_gui.GetCurrentEditor());
1645 dlg.ShowModal();
1646}
1647
1648void MainMenuBar::OnMapRemoveItems(wxCommandEvent& WXUNUSED(event)) {
1649 if (!g_gui.IsEditorOpen()) {
1650 return;
1651 }
1652
1653 FindItemDialog dialog(frame, "Item Type to Remove");
1654 if (dialog.ShowModal() == wxID_OK) {
1655 uint16_t itemid = dialog.getResultID();
1656
1657 g_gui.GetCurrentEditor()->selection.clear();
1658 g_gui.GetCurrentEditor()->actionQueue->clear();
1659
1660 OnMapRemoveItems::RemoveItemCondition condition(itemid);
1661 g_gui.CreateLoadBar("Searching map for items to remove...");
1662
1663 int64_t count = RemoveItemOnMap(g_gui.GetCurrentMap(), condition, false);
1664
1665 g_gui.DestroyLoadBar();
1666
1667 wxString msg;
1668 msg << count << " items deleted.";
1669
1670 g_gui.PopupDialog("Search completed", msg, wxOK);
1671 g_gui.GetCurrentMap().doChange();
1672 g_gui.RefreshView();
1673 }
1674 dialog.Destroy();
1675}
1676
1677namespace OnMapRemoveCorpses {
1678 struct condition {
1679 condition() { }
1680
1681 bool operator()(Map& map, Item* item, long long removed, long long done) {
1682 if (done % 0x800 == 0) {
1683 g_gui.SetLoadDone((unsigned int)(100 * done / map.getTileCount()));
1684 }
1685
1686 return g_materials.isInTileset(item, "Corpses") & !item->isComplex();
1687 }
1688 };
1689}
1690
1691void MainMenuBar::OnMapRemoveCorpses(wxCommandEvent& WXUNUSED(event)) {
1692 if (!g_gui.IsEditorOpen()) {
1693 return;
1694 }
1695
1696 int ok = g_gui.PopupDialog("Remove Corpses", "Do you want to remove all corpses from the map?", wxYES | wxNO);
1697
1698 if (ok == wxID_YES) {
1699 g_gui.GetCurrentEditor()->selection.clear();
1700 g_gui.GetCurrentEditor()->actionQueue->clear();
1701
1702 OnMapRemoveCorpses::condition func;
1703 g_gui.CreateLoadBar("Searching map for items to remove...");
1704
1705 int64_t count = RemoveItemOnMap(g_gui.GetCurrentMap(), func, false);
1706
1707 g_gui.DestroyLoadBar();
1708
1709 wxString msg;
1710 msg << count << " items deleted.";
1711 g_gui.PopupDialog("Search completed", msg, wxOK);
1712 g_gui.GetCurrentMap().doChange();
1713 }
1714}
1715
1716namespace OnMapRemoveUnreachable {
1717 struct condition {
1718 condition() { }
1719
1720 bool isReachable(Tile* tile) {
1721 if (tile == nullptr) {
1722 return false;
1723 }
1724 if (!tile->isBlocking()) {
1725 return true;
1726 }
1727 return false;
1728 }
1729
1730 bool operator()(Map& map, Tile* tile, long long removed, long long done, long long total) {
1731 if (done % 0x1000 == 0) {
1732 g_gui.SetLoadDone((unsigned int)(100 * done / total));
1733 }
1734
1735 Position pos = tile->getPosition();
1736 int sx = std::max(pos.x - 10, 0);
1737 int ex = std::min(pos.x + 10, 65535);
1738 int sy = std::max(pos.y - 8, 0);
1739 int ey = std::min(pos.y + 8, 65535);
1740 int sz, ez;
1741
1742 if (pos.z <= GROUND_LAYER) {
1743 sz = 0;
1744 ez = 9;
1745 } else {
1746 // underground
1747 sz = std::max(pos.z - 2, GROUND_LAYER);
1748 ez = std::min(pos.z + 2, MAP_MAX_LAYER);
1749 }
1750
1751 for (int z = sz; z <= ez; ++z) {
1752 for (int y = sy; y <= ey; ++y) {
1753 for (int x = sx; x <= ex; ++x) {
1754 if (isReachable(map.getTile(x, y, z))) {
1755 return false;
1756 }
1757 }
1758 }
1759 }
1760 return true;
1761 }
1762 };
1763}
1764
1765void MainMenuBar::OnMapRemoveUnreachable(wxCommandEvent& WXUNUSED(event)) {
1766 if (!g_gui.IsEditorOpen()) {
1767 return;
1768 }
1769
1770 // Create custom dialog
1771 wxDialog* dialog = new wxDialog(frame, wxID_ANY, "Remove Unreachable Tiles",
1772 wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE);
1773
1774 wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
1775 wxBoxSizer* gridSizer = new wxBoxSizer(wxHORIZONTAL);
1776
1777 // Create spin controls for X and Y ranges
1778 wxStaticText* xLabel = new wxStaticText(dialog, wxID_ANY, "X Range:");
1779 wxSpinCtrl* xRange = new wxSpinCtrl(dialog, wxID_ANY, "10",
1780 wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 1, 100, 10);
1781
1782 wxStaticText* yLabel = new wxStaticText(dialog, wxID_ANY, "Y Range:");
1783 wxSpinCtrl* yRange = new wxSpinCtrl(dialog, wxID_ANY, "8",
1784 wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 1, 100, 8);
1785
1786 gridSizer->Add(xLabel, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
1787 gridSizer->Add(xRange, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
1788 gridSizer->Add(yLabel, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
1789 gridSizer->Add(yRange, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
1790
1791 mainSizer->Add(gridSizer, 0, wxALL | wxALIGN_CENTER, 5);
1792
1793 // Add warning text
1794 wxStaticText* warning = new wxStaticText(dialog, wxID_ANY,
1795 "Warning: This operation will remove all tiles that are not\n"
1796 "reachable within the specified X and Y ranges.");
1797 mainSizer->Add(warning, 0, wxALL | wxALIGN_CENTER, 10);
1798
1799 // Add buttons
1800 wxBoxSizer* buttonSizer = new wxBoxSizer(wxHORIZONTAL);
1801 wxButton* okButton = new wxButton(dialog, wxID_OK, "OK");
1802 wxButton* cancelButton = new wxButton(dialog, wxID_CANCEL, "Cancel");
1803
1804 buttonSizer->Add(okButton, 0, wxALL, 5);
1805 buttonSizer->Add(cancelButton, 0, wxALL, 5);
1806 mainSizer->Add(buttonSizer, 0, wxALIGN_CENTER | wxALL, 5);
1807
1808 dialog->SetSizer(mainSizer);
1809 mainSizer->Fit(dialog);
1810 dialog->Center();
1811
1812 // Show dialog and process result
1813 if (dialog->ShowModal() == wxID_OK) {
1814 // Create modified condition with custom ranges
1815 struct CustomRangeCondition : public OnMapRemoveUnreachable::condition {
1816 int xRange;
1817 int yRange;
1818
1819 CustomRangeCondition(int x, int y) : xRange(x), yRange(y) {}
1820
1821 bool operator()(Map& map, Tile* tile, long long removed, long long done, long long total) {
1822 if (done % 0x1000 == 0) {
1823 g_gui.SetLoadDone((unsigned int)(100 * done / total));
1824 }
1825
1826 Position pos = tile->getPosition();
1827 int sx = std::max(pos.x - xRange, 0);
1828 int ex = std::min(pos.x + xRange, 65535);
1829 int sy = std::max(pos.y - yRange, 0);
1830 int ey = std::min(pos.y + yRange, 65535);
1831 int sz, ez;
1832
1833 if (pos.z <= GROUND_LAYER) {
1834 sz = 0;
1835 ez = 9;
1836 } else {
1837 sz = std::max(pos.z - 2, GROUND_LAYER);
1838 ez = std::min(pos.z + 2, MAP_MAX_LAYER);
1839 }
1840
1841 for (int z = sz; z <= ez; ++z) {
1842 for (int y = sy; y <= ey; ++y) {
1843 for (int x = sx; x <= ex; ++x) {
1844 if (isReachable(map.getTile(x, y, z))) {
1845 return false;
1846 }
1847 }
1848 }
1849 }
1850 return true;
1851 }
1852 };
1853
1854 g_gui.GetCurrentEditor()->selection.clear();
1855 g_gui.GetCurrentEditor()->actionQueue->clear();
1856
1857 CustomRangeCondition func(xRange->GetValue(), yRange->GetValue());
1858 g_gui.CreateLoadBar("Searching map for tiles to remove...");
1859
1860 long long removed = remove_if_TileOnMap(g_gui.GetCurrentMap(), func);
1861
1862 g_gui.DestroyLoadBar();
1863
1864 wxString msg;
1865 msg << removed << " tiles deleted.";
1866 g_gui.PopupDialog("Search completed", msg, wxOK);
1867
1868 g_gui.GetCurrentMap().doChange();
1869 }
1870
1871 dialog->Destroy();
1872}
1873
1874void MainMenuBar::OnClearHouseTiles(wxCommandEvent& WXUNUSED(event)) {
1875 Editor* editor = g_gui.GetCurrentEditor();
1876 if (!editor) {
1877 return;
1878 }
1879
1880 int ret = g_gui.PopupDialog(
1881 "Clear Invalid House Tiles",
1882 "Are you sure you want to remove all house tiles that do not belong to a house (this action cannot be undone)?",
1883 wxYES | wxNO
1884 );
1885
1886 if (ret == wxID_YES) {
1887 // Editor will do the work
1888 editor->clearInvalidHouseTiles(true);
1889 }
1890
1891 g_gui.RefreshView();
1892}
1893
1894void MainMenuBar::OnClearModifiedState(wxCommandEvent& WXUNUSED(event)) {
1895 Editor* editor = g_gui.GetCurrentEditor();
1896 if (!editor) {
1897 return;
1898 }
1899
1900 int ret = g_gui.PopupDialog(
1901 "Clear Modified State",
1902 "This will have the same effect as closing the map and opening it again. Do you want to proceed?",
1903 wxYES | wxNO
1904 );
1905
1906 if (ret == wxID_YES) {
1907 // Editor will do the work
1908 editor->clearModifiedTileState(true);
1909 }
1910
1911 g_gui.RefreshView();
1912}
1913
1914void MainMenuBar::OnMapCleanHouseItems(wxCommandEvent& WXUNUSED(event)) {
1915 Editor* editor = g_gui.GetCurrentEditor();
1916 if (!editor) {
1917 return;
1918 }
1919
1920 int ret = g_gui.PopupDialog(
1921 "Clear Moveable House Items",
1922 "Are you sure you want to remove all items inside houses that can be moved (this action cannot be undone)?",
1923 wxYES | wxNO
1924 );
1925
1926 if (ret == wxID_YES) {
1927 // Editor will do the work
1928 // editor->removeHouseItems(true);
1929 }
1930
1931 g_gui.RefreshView();
1932}
1933
1934void MainMenuBar::OnMapEditTowns(wxCommandEvent& WXUNUSED(event)) {
1935 if (g_gui.GetCurrentEditor()) {
1936 wxDialog* town_dialog = newd EditTownsDialog(frame, *g_gui.GetCurrentEditor());
1937 town_dialog->ShowModal();
1938 town_dialog->Destroy();
1939 }
1940}
1941
1942void MainMenuBar::OnMapEditItems(wxCommandEvent& WXUNUSED(event)) {
1943 ;
1944}
1945
1946void MainMenuBar::OnMapEditMonsters(wxCommandEvent& WXUNUSED(event)) {
1947 ;
1948}
1949
1950void MainMenuBar::OnMapStatistics(wxCommandEvent& WXUNUSED(event)) {
1951 if (!g_gui.IsEditorOpen()) {
1952 return;
1953 }
1954
1955 g_gui.CreateLoadBar("Collecting data...");
1956
1957 Map* map = &g_gui.GetCurrentMap();
1958
1959 int load_counter = 0;
1960
1961 uint64_t tile_count = 0;
1962 uint64_t detailed_tile_count = 0;
1963 uint64_t blocking_tile_count = 0;
1964 uint64_t walkable_tile_count = 0;
1965 double percent_pathable = 0.0;
1966 double percent_detailed = 0.0;
1967 uint64_t spawn_count = 0;
1968 uint64_t creature_count = 0;
1969 double creatures_per_spawn = 0.0;
1970
1971 uint64_t item_count = 0;
1972 uint64_t loose_item_count = 0;
1973 uint64_t depot_count = 0;
1974 uint64_t action_item_count = 0;
1975 uint64_t unique_item_count = 0;
1976 uint64_t container_count = 0; // Only includes containers containing more than 1 item
1977
1978 int town_count = map->towns.count();
1979 int house_count = map->houses.count();
1980 std::map<uint32_t, uint32_t> town_sqm_count;
1981 const Town* largest_town = nullptr;
1982 uint64_t largest_town_size = 0;
1983 uint64_t total_house_sqm = 0;
1984 const House* largest_house = nullptr;
1985 uint64_t largest_house_size = 0;
1986 double houses_per_town = 0.0;
1987 double sqm_per_house = 0.0;
1988 double sqm_per_town = 0.0;
1989
1990 for (MapIterator mit = map->begin(); mit != map->end(); ++mit) {
1991 Tile* tile = (*mit)->get();
1992 if (load_counter % 8192 == 0) {
1993 g_gui.SetLoadDone((unsigned int)(int64_t(load_counter) * 95ll / int64_t(map->getTileCount())));
1994 }
1995
1996 if (tile->empty()) {
1997 continue;
1998 }
1999
2000 tile_count += 1;
2001
2002 bool is_detailed = false;
2003#define ANALYZE_ITEM(_item) \
2004 { \
2005 item_count += 1; \
2006 if (!(_item)->isGroundTile() && !(_item)->isBorder()) { \
2007 is_detailed = true; \
2008 ItemType& it = g_items[(_item)->getID()]; \
2009 if (it.moveable) { \
2010 loose_item_count += 1; \
2011 } \
2012 if (it.isDepot()) { \
2013 depot_count += 1; \
2014 } \
2015 if ((_item)->getActionID() > 0) { \
2016 action_item_count += 1; \
2017 } \
2018 if ((_item)->getUniqueID() > 0) { \
2019 unique_item_count += 1; \
2020 } \
2021 if (Container* c = dynamic_cast<Container*>((_item))) { \
2022 if (c->getVector().size()) { \
2023 container_count += 1; \
2024 } \
2025 } \
2026 } \
2027 }
2028
2029 if (tile->ground) {
2030 ANALYZE_ITEM(tile->ground);
2031 }
2032
2033 for (ItemVector::const_iterator item_iter = tile->items.begin(); item_iter != tile->items.end(); ++item_iter) {
2034 Item* item = *item_iter;
2035 ANALYZE_ITEM(item);
2036 }
2037#undef ANALYZE_ITEM
2038
2039 if (tile->spawn) {
2040 spawn_count += 1;
2041 }
2042
2043 if (tile->creature) {
2044 creature_count += 1;
2045 }
2046
2047 if (tile->isBlocking()) {
2048 blocking_tile_count += 1;
2049 } else {
2050 walkable_tile_count += 1;
2051 }
2052
2053 if (is_detailed) {
2054 detailed_tile_count += 1;
2055 }
2056
2057 load_counter += 1;
2058 }
2059
2060 creatures_per_spawn = (spawn_count != 0 ? double(creature_count) / double(spawn_count) : -1.0);
2061 percent_pathable = 100.0 * (tile_count != 0 ? double(walkable_tile_count) / double(tile_count) : -1.0);
2062 percent_detailed = 100.0 * (tile_count != 0 ? double(detailed_tile_count) / double(tile_count) : -1.0);
2063
2064 load_counter = 0;
2065 Houses& houses = map->houses;
2066 for (HouseMap::const_iterator hit = houses.begin(); hit != houses.end(); ++hit) {
2067 const House* house = hit->second;
2068
2069 if (load_counter % 64) {
2070 g_gui.SetLoadDone((unsigned int)(95ll + int64_t(load_counter) * 5ll / int64_t(house_count)));
2071 }
2072
2073 if (house->size() > largest_house_size) {
2074 largest_house = house;
2075 largest_house_size = house->size();
2076 }
2077 total_house_sqm += house->size();
2078 town_sqm_count[house->townid] += house->size();
2079 }
2080
2081 houses_per_town = (town_count != 0 ? double(house_count) / double(town_count) : -1.0);
2082 sqm_per_house = (house_count != 0 ? double(total_house_sqm) / double(house_count) : -1.0);
2083 sqm_per_town = (town_count != 0 ? double(total_house_sqm) / double(town_count) : -1.0);
2084
2085 Towns& towns = map->towns;
2086 for (std::map<uint32_t, uint32_t>::iterator town_iter = town_sqm_count.begin();
2087 town_iter != town_sqm_count.end();
2088 ++town_iter) {
2089 // No load bar for this, load is non-existant
2090 uint32_t town_id = town_iter->first;
2091 uint32_t town_sqm = town_iter->second;
2092 Town* town = towns.getTown(town_id);
2093 if (town && town_sqm > largest_town_size) {
2094 largest_town = town;
2095 largest_town_size = town_sqm;
2096 } else {
2097 // Non-existant town!
2098 }
2099 }
2100
2101 g_gui.DestroyLoadBar();
2102
2103 std::ostringstream os;
2104 os.setf(std::ios::fixed, std::ios::floatfield);
2105 os.precision(2);
2106 os << "Map statistics for the map \"" << map->getMapDescription() << "\"\n";
2107
2108 // Add map dimensions information
2109 os << "\tMap dimensions:\n";
2110 os << "\t\tWidth: " << map->getWidth() << " tiles\n";
2111 os << "\t\tHeight: " << map->getHeight() << " tiles\n";
2112 os << "\t\tTotal area: " << (map->getWidth() * map->getHeight()) << " square tiles\n";
2113 os << "\t\tNumber of floors: " << (MAP_MAX_LAYER + 1) << "\n";
2114
2115 os << "\tTile data:\n";
2116 os << "\t\tTotal number of tiles: " << tile_count << "\n";
2117 os << "\t\tNumber of pathable tiles: " << walkable_tile_count << "\n";
2118 os << "\t\tNumber of unpathable tiles: " << blocking_tile_count << "\n";
2119 if (percent_pathable >= 0.0) {
2120 os << "\t\tPercent walkable tiles: " << percent_pathable << "%\n";
2121 }
2122 os << "\t\tDetailed tiles: " << detailed_tile_count << "\n";
2123 if (percent_detailed >= 0.0) {
2124 os << "\t\tPercent detailed tiles: " << percent_detailed << "%\n";
2125 }
2126
2127 os << "\tItem data:\n";
2128 os << "\t\tTotal number of items: " << item_count << "\n";
2129 os << "\t\tNumber of moveable tiles: " << loose_item_count << "\n";
2130 os << "\t\tNumber of depots: " << depot_count << "\n";
2131 os << "\t\tNumber of containers: " << container_count << "\n";
2132 os << "\t\tNumber of items with Action ID: " << action_item_count << "\n";
2133 os << "\t\tNumber of items with Unique ID: " << unique_item_count << "\n";
2134 os << "\t\tItems per tile ratio: " << (tile_count > 0 ? (double)item_count / tile_count : 0) << "\n";
2135
2136 os << "\tCreature data:\n";
2137 os << "\t\tTotal creature count: " << creature_count << "\n";
2138 os << "\t\tTotal spawn count: " << spawn_count << "\n";
2139 if (creatures_per_spawn >= 0) {
2140 os << "\t\tMean creatures per spawn: " << creatures_per_spawn << "\n";
2141 }
2142 os << "\t\tCreature density: " << (tile_count > 0 ? (double)creature_count / tile_count * 100 : 0) << "% of tiles\n";
2143
2144 os << "\tTown/House data:\n";
2145 os << "\t\tTotal number of towns: " << town_count << "\n";
2146 os << "\t\tTotal number of houses: " << house_count << "\n";
2147 if (houses_per_town >= 0) {
2148 os << "\t\tMean houses per town: " << houses_per_town << "\n";
2149 }
2150 os << "\t\tTotal amount of housetiles: " << total_house_sqm << "\n";
2151 if (sqm_per_house >= 0) {
2152 os << "\t\tMean tiles per house: " << sqm_per_house << "\n";
2153 }
2154 if (sqm_per_town >= 0) {
2155 os << "\t\tMean tiles per town: " << sqm_per_town << "\n";
2156 }
2157 os << "\t\tPercentage of map covered by houses: " << (tile_count > 0 ? (double)total_house_sqm / tile_count * 100 : 0) << "%\n";
2158 /*
2159 // Add waypoint statistics
2160 int waypoint_count = map->waypoints.getWaypointCount();
2161 os << "\tWaypoint data:\n";
2162 os << "\t\tTotal number of waypoints: " << waypoint_count << "\n";
2163
2164 // Add zone statistics if available
2165 os << "\tZone data:\n";
2166 os << "\t\tTotal number of zones: " << map->zones.size() << "\n";
2167 */
2168 if (largest_town) {
2169 os << "\t\tLargest Town: \"" << largest_town->getName() << "\" (" << largest_town_size << " sqm)\n";
2170 }
2171 if (largest_house) {
2172 os << "\t\tLargest House: \"" << largest_house->name << "\" (" << largest_house_size << " sqm)\n";
2173 }
2174
2175 // Add map file information
2176 os << "\tMap file information:\n";
2177 os << "\t\tOTBM version: " << map->getVersion().otbm << "\n";
2178 os << "\t\tClient version: " << map->getVersion().client << "\n";
2179 os << "\t\tFile size (approximate): " << (map->getTileCount() * 512 / 1024) << " KB\n";
2180
2181 os << "\n";
2182 os << "Generated by RD Map Editor (OTARME RD) version " + __RME_VERSION__ + "\n";
2183
2184 wxDialog* dg = newd wxDialog(frame, wxID_ANY, "Map Statistics", wxDefaultPosition, wxDefaultSize, wxRESIZE_BORDER | wxCAPTION | wxCLOSE_BOX);
2185 wxSizer* topsizer = newd wxBoxSizer(wxVERTICAL);
2186 wxTextCtrl* text_field = newd wxTextCtrl(dg, wxID_ANY, wxstr(os.str()), wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY);
2187 text_field->SetMinSize(wxSize(400, 300));
2188 topsizer->Add(text_field, wxSizerFlags(5).Expand());
2189
2190 wxSizer* choicesizer = newd wxBoxSizer(wxHORIZONTAL);
2191 wxButton* export_button = newd wxButton(dg, wxID_OK, "Export as XML");
2192 choicesizer->Add(export_button, wxSizerFlags(1).Center());
2193 export_button->Enable(false);
2194 choicesizer->Add(newd wxButton(dg, wxID_CANCEL, "OK"), wxSizerFlags(1).Center());
2195 topsizer->Add(choicesizer, wxSizerFlags(1).Center());
2196 dg->SetSizerAndFit(topsizer);
2197 dg->Centre(wxBOTH);
2198
2199 int ret = dg->ShowModal();
2200
2201 if (ret == wxID_OK) {
2202 // std::cout << "XML EXPORT";
2203 } else if (ret == wxID_CANCEL) {
2204 // std::cout << "OK";
2205 }
2206}
2207
2208void MainMenuBar::OnMapCleanup(wxCommandEvent& WXUNUSED(event)) {
2209 if (!g_gui.IsEditorOpen()) {
2210 return;
2211 }
2212
2213 // Create custom cleanup dialog
2214 wxDialog* dialog = new wxDialog(frame, wxID_ANY, "Map Cleanup Options",
2215 wxDefaultPosition, wxSize(600, 500), wxDEFAULT_DIALOG_STYLE);
2216
2217 wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
2218
2219 // Create cleanup options
2220 wxStaticBoxSizer* optionsSizer = new wxStaticBoxSizer(wxVERTICAL, dialog, "Cleanup Options");
2221
2222 // Invalid items checkbox
2223 wxCheckBox* cleanInvalid = new wxCheckBox(dialog, wxID_ANY, "Remove Invalid Items");
2224 optionsSizer->Add(cleanInvalid, 0, wxALL, 5);
2225
2226 // Add monster cleanup option
2227 wxCheckBox* cleanMonsters = new wxCheckBox(dialog, wxID_ANY, "Remove Monsters in Blocking Tiles");
2228 optionsSizer->Add(cleanMonsters, 0, wxALL, 5);
2229
2230 // Add empty spawn cleanup option
2231 wxCheckBox* cleanEmptySpawns = new wxCheckBox(dialog, wxID_ANY, "Remove Empty Spawns");
2232 optionsSizer->Add(cleanEmptySpawns, 0, wxALL, 5);
2233
2234 // Add problematic items cleanup option
2235 wxCheckBox* cleanWheyItems = new wxCheckBox(dialog, wxID_ANY, "Remove Problematic Items (whey/invalid)");
2236 optionsSizer->Add(cleanWheyItems, 0, wxALL, 5);
2237
2238 // ID Range cleanup section
2239 wxStaticBoxSizer* rangeSizer = new wxStaticBoxSizer(wxVERTICAL, dialog, "Clean Items by ID Range");
2240
2241 // Enable ID range checkbox
2242 wxCheckBox* useRange = new wxCheckBox(dialog, wxID_ANY, "Clean Items by ID Range");
2243 rangeSizer->Add(useRange, 0, wxALL, 5);
2244
2245 // ID range input
2246 wxTextCtrl* rangeInput = new wxTextCtrl(dialog, wxID_ANY);
2247 rangeInput->SetToolTip("Enter IDs or ranges separated by commas (e.g., 2222,2244-2266,5219)");
2248 rangeInput->Enable(false);
2249 rangeSizer->Add(rangeInput, 0, wxEXPAND | wxALL, 5);
2250
2251 // Bind enable/disable of range input
2252 useRange->Bind(wxEVT_CHECKBOX, [rangeInput](wxCommandEvent& evt) {
2253 rangeInput->Enable(evt.IsChecked());
2254 });
2255
2256 // Ignored IDs section
2257 wxStaticBoxSizer* ignoreSizer = new wxStaticBoxSizer(wxVERTICAL, dialog, "Ignored IDs");
2258
2259 // Enable ignored IDs checkbox
2260 wxCheckBox* useIgnored = new wxCheckBox(dialog, wxID_ANY, "Use Ignored IDs");
2261 ignoreSizer->Add(useIgnored, 0, wxALL, 5);
2262
2263 // Ignored IDs input
2264 wxTextCtrl* ignoreInput = new wxTextCtrl(dialog, wxID_ANY);
2265 ignoreInput->SetToolTip("Enter IDs to ignore, separated by commas. Use '-' for ranges (e.g., 1212,1241,1256-1261)");
2266 ignoreInput->Enable(false);
2267 ignoreSizer->Add(ignoreInput, 0, wxEXPAND | wxALL, 5);
2268
2269 // Bind enable/disable of ignore input
2270 useIgnored->Bind(wxEVT_CHECKBOX, [ignoreInput](wxCommandEvent& evt) {
2271 ignoreInput->Enable(evt.IsChecked());
2272 });
2273
2274 // Add options to main sizer
2275 mainSizer->Add(optionsSizer, 0, wxEXPAND | wxALL, 5);
2276 mainSizer->Add(rangeSizer, 0, wxEXPAND | wxALL, 5);
2277 mainSizer->Add(ignoreSizer, 0, wxEXPAND | wxALL, 5);
2278
2279 // Add warning text
2280 wxStaticText* warning = new wxStaticText(dialog, wxID_ANY,
2281 "Warning: This operation cannot be undone!\nPlease save your map before proceeding.");
2282 warning->SetForegroundColour(*wxRED);
2283 mainSizer->Add(warning, 0, wxALL | wxALIGN_CENTER, 10);
2284
2285 // Add buttons
2286 wxStdDialogButtonSizer* buttonSizer = new wxStdDialogButtonSizer();
2287 wxButton* okButton = new wxButton(dialog, wxID_OK, "Clean");
2288 wxButton* cancelButton = new wxButton(dialog, wxID_CANCEL, "Cancel");
2289 buttonSizer->AddButton(okButton);
2290 buttonSizer->AddButton(cancelButton);
2291 buttonSizer->Realize();
2292 mainSizer->Add(buttonSizer, 0, wxALIGN_CENTER | wxALL, 5);
2293
2294 dialog->SetSizer(mainSizer);
2295 mainSizer->Fit(dialog);
2296 dialog->Center();
2297
2298 // Show dialog and process result
2299 if (dialog->ShowModal() == wxID_OK) {
2300 bool hasOptions = cleanInvalid->GetValue() || useRange->GetValue() || cleanMonsters->GetValue() ||
2301 cleanEmptySpawns->GetValue() || cleanWheyItems->GetValue();
2302 if (!hasOptions) {
2303 g_gui.PopupDialog("Error", "Please select at least one cleanup option!", wxOK);
2304 dialog->Destroy();
2305 return;
2306 }
2307
2308 int64_t totalCount = 0;
2309 Map& currentMap = g_gui.GetCurrentMap();
2310
2311 g_gui.CreateLoadBar("Cleaning map...");
2312
2313 try {
2314 int progressStep = 0;
2315 int numOptions = 0;
2316 if (cleanInvalid->GetValue()) numOptions++;
2317 if (useRange->GetValue()) numOptions++;
2318 if (cleanMonsters->GetValue()) numOptions++;
2319 if (cleanEmptySpawns->GetValue()) numOptions++;
2320 if (cleanWheyItems->GetValue()) numOptions++;
2321 int progressIncrement = numOptions > 0 ? 100 / numOptions : 0;
2322
2323 // Process invalid items if selected
2324 if (cleanInvalid->GetValue()) {
2325 // Update loading bar message but keep the same bar
2326 g_gui.SetLoadDone(progressStep, "Removing invalid tiles...");
2327 currentMap.cleanInvalidTiles(true);
2328 progressStep += progressIncrement;
2329 g_gui.SetLoadDone(progressStep);
2330 }
2331
2332 // Process monster cleanup if selected
2333 if (cleanMonsters->GetValue()) {
2334 // Define condition for monsters in blocking tiles
2335 struct RemoveMonsterCondition {
2336 int removedCount = 0;
2337 long long totalTiles;
2338 int startProgress;
2339 int endProgress;
2340
2341 bool operator()(Map& map, Tile* tile, long long done, long long total) {
2342 if (done % 1024 == 0) {
2343 int progress = startProgress + ((done * (endProgress - startProgress)) / totalTiles);
2344 g_gui.SetLoadDone(progress);
2345 }
2346
2347 // Check for monsters in invalid locations:
2348 // 1. Blocking tiles
2349 // 2. Tiles without ground
2350 // 3. Empty tiles
2351 if (tile->creature && (tile->isBlocking() || !tile->hasGround() || tile->empty())) {
2352 // Monster is in an invalid location, remove it
2353 delete tile->creature;
2354 tile->creature = nullptr;
2355 tile->modify(); // Mark as modified for saving
2356 removedCount++;
2357 return true;
2358 }
2359 return false;
2360 }
2361 } monsterCondition;
2362
2363 monsterCondition.totalTiles = currentMap.getTileCount();
2364 monsterCondition.startProgress = progressStep;
2365 monsterCondition.endProgress = progressStep + progressIncrement;
2366
2367 g_gui.SetLoadDone(progressStep, "Removing monsters in blocking tiles...");
2368
2369 // Need to iterate through all map tiles
2370 long long total = currentMap.getTileCount();
2371 long long done = 0;
2372 for (MapIterator mit = currentMap.begin(); mit != currentMap.end(); ++mit) {
2373 if (Tile* tile = (*mit)->get()) {
2374 monsterCondition(currentMap, tile, done, total);
2375 }
2376 done++;
2377 }
2378
2379 totalCount += monsterCondition.removedCount;
2380 progressStep += progressIncrement;
2381 g_gui.SetLoadDone(progressStep);
2382 }
2383
2384 // Process empty spawn cleanup if selected
2385 if (cleanEmptySpawns->GetValue()) {
2386 // Define condition for empty spawns
2387 struct RemoveEmptySpawnCondition {
2388 int removedCount = 0;
2389 long long totalTiles;
2390 int startProgress;
2391 int endProgress;
2392
2393 bool operator()(Map& map, Tile* tile, long long done, long long total) {
2394 if (done % 1024 == 0) {
2395 int progress = startProgress + ((done * (endProgress - startProgress)) / totalTiles);
2396 g_gui.SetLoadDone(progress);
2397 }
2398
2399 if (tile->spawn) {
2400 // Check if there are no monsters in the spawn radius
2401 bool hasMonster = false;
2402 Position pos = tile->getPosition();
2403 int radius = tile->spawn->getSize();
2404
2405 for (int x = -radius; x <= radius && !hasMonster; ++x) {
2406 for (int y = -radius; y <= radius && !hasMonster; ++y) {
2407 Tile* checkTile = map.getTile(pos.x + x, pos.y + y, pos.z);
2408 if (checkTile && checkTile->creature) {
2409 hasMonster = true;
2410 break;
2411 }
2412 }
2413 }
2414
2415 if (!hasMonster) {
2416 // No monsters in spawn radius, remove it properly
2417 map.removeSpawn(tile); // Remove from map's spawn registry
2418 delete tile->spawn;
2419 tile->spawn = nullptr;
2420 tile->deselect(); // Make sure tile is not selected
2421 tile->update(); // Update tile to refresh display state
2422 tile->modify(); // Mark as modified for saving
2423 removedCount++;
2424 return true;
2425 }
2426 }
2427 return false;
2428 }
2429 } spawnCondition;
2430
2431 spawnCondition.totalTiles = currentMap.getTileCount();
2432 spawnCondition.startProgress = progressStep;
2433 spawnCondition.endProgress = progressStep + progressIncrement;
2434
2435 g_gui.SetLoadDone(progressStep, "Removing empty spawns...");
2436
2437 // Need to iterate through all map tiles
2438 long long total = currentMap.getTileCount();
2439 long long done = 0;
2440 for (MapIterator mit = currentMap.begin(); mit != currentMap.end(); ++mit) {
2441 if (Tile* tile = (*mit)->get()) {
2442 spawnCondition(currentMap, tile, done, total);
2443 }
2444 done++;
2445 }
2446
2447 totalCount += spawnCondition.removedCount;
2448 progressStep += progressIncrement;
2449 g_gui.SetLoadDone(progressStep);
2450 }
2451
2452 // Process ID range cleanup if selected
2453 if (useRange->GetValue()) {
2454 auto ranges = ParseRangeString(rangeInput->GetValue());
2455 if (!ranges.empty()) {
2456 std::vector<uint16_t> ignoredIds;
2457 std::vector<std::pair<uint16_t, uint16_t>> ignoredRanges;
2458
2459 // Parse ignored IDs if enabled
2460 if (useIgnored->GetValue()) {
2461 wxString ignoreText = ignoreInput->GetValue();
2462 auto ignoredPairs = ParseRangeString(ignoreText);
2463 for (const auto& pair : ignoredPairs) {
2464 if (pair.first == pair.second) {
2465 ignoredIds.push_back(pair.first);
2466 } else {
2467 ignoredRanges.push_back(pair);
2468 }
2469 }
2470 }
2471
2472 // Create cleanup condition with progress tracking
2473 struct CleanupCondition {
2474 std::vector<std::pair<uint16_t, uint16_t>> ranges;
2475 std::vector<uint16_t> ignoredIds;
2476 std::vector<std::pair<uint16_t, uint16_t>> ignoredRanges;
2477 long long totalTiles;
2478 int startProgress;
2479 int endProgress;
2480
2481 bool operator()(Map& map, Item* item, long long removed, long long done) {
2482 // Update progress every 1024 tiles
2483 if (done % 1024 == 0) {
2484 int progress = startProgress + ((done * (endProgress - startProgress)) / totalTiles);
2485 g_gui.SetLoadDone(progress);
2486 }
2487
2488 uint16_t id = item->getID();
2489
2490 // Check if item should be ignored
2491 for (uint16_t ignoredId : ignoredIds) {
2492 if (id == ignoredId) return false;
2493 }
2494 for (const auto& range : ignoredRanges) {
2495 if (id >= range.first && id <= range.second) return false;
2496 }
2497
2498 // Check if item is in cleanup ranges
2499 for (const auto& range : ranges) {
2500 if (id >= range.first && id <= range.second) return true;
2501 }
2502 return false;
2503 }
2504 } condition;
2505
2506 condition.ranges = ranges;
2507 condition.ignoredIds = ignoredIds;
2508 condition.ignoredRanges = ignoredRanges;
2509 condition.totalTiles = currentMap.getTileCount();
2510 condition.startProgress = progressStep;
2511 condition.endProgress = 100;
2512
2513 // Update loading bar message but keep the same bar
2514 g_gui.SetLoadDone(condition.startProgress, "Removing items by ID range...");
2515 int64_t count = RemoveItemOnMap(currentMap, condition, false);
2516 totalCount += count;
2517 }
2518 }
2519
2520 // Process whey items cleanup if selected
2521 if (cleanWheyItems->GetValue()) {
2522 // Define condition for whey items (ID 53)
2523 struct RemoveWheyItemsCondition {
2524 int removedCount = 0;
2525 long long totalTiles;
2526 int startProgress;
2527 int endProgress;
2528
2529 bool operator()(Map& map, Item* item, long long removed, long long done) {
2530 if (done % 1024 == 0) {
2531 int progress = startProgress + ((done * (endProgress - startProgress)) / totalTiles);
2532 g_gui.SetLoadDone(progress);
2533 }
2534
2535 // Check for problematic items
2536 std::string name = item->getName();
2537
2538 // Original check for "whey" items
2539 if (name == "whey") {
2540 removedCount++;
2541 return true;
2542 }
2543
2544
2545
2546 // Check for items with ID 0 - these are invalid
2547 if (item->getID() == 0) {
2548 removedCount++;
2549 return true;
2550 }
2551
2552 return false;
2553 }
2554 } wheyCondition;
2555
2556 wheyCondition.totalTiles = currentMap.getTileCount();
2557 wheyCondition.startProgress = progressStep;
2558 wheyCondition.endProgress = progressStep + progressIncrement;
2559
2560 g_gui.SetLoadDone(progressStep, "Removing problematic items...");
2561 int64_t count = RemoveItemOnMap(currentMap, wheyCondition, false);
2562
2563 // Update tiles after removing items to ensure proper state
2564 for (MapIterator it = currentMap.begin(); it != currentMap.end(); ++it) {
2565 Tile* tile = (*it)->get();
2566 if (tile) {
2567 tile->update();
2568 }
2569 }
2570
2571 totalCount += count;
2572
2573 progressStep += progressIncrement;
2574 g_gui.SetLoadDone(progressStep);
2575 }
2576
2577 // Ensure progress bar reaches 100%
2578 g_gui.SetLoadDone(100);
2579
2580 // Destroy the loading bar before showing the popup
2581 g_gui.DestroyLoadBar();
2582
2583 // Show results
2584 wxString msg;
2585 msg << totalCount << " items removed in total.";
2586 g_gui.PopupDialog("Cleanup Complete", msg, wxOK);
2587
2588 currentMap.doChange();
2589 }
2590 catch (...) {
2591 // Make sure to destroy the loading bar on error
2592 g_gui.DestroyLoadBar();
2593 g_gui.PopupDialog("Error", "An error occurred during cleanup.", wxOK | wxICON_ERROR);
2594 }
2595 }
2596
2597 dialog->Destroy();
2598}
2599
2600void MainMenuBar::OnMapProperties(wxCommandEvent& WXUNUSED(event)) {
2601 wxDialog* properties = newd MapPropertiesWindow(
2602 frame,
2603 static_cast<MapTab*>(g_gui.GetCurrentTab()),
2604 *g_gui.GetCurrentEditor()
2605 );
2606
2607 if (properties->ShowModal() == 0) {
2608 // FAIL!
2609 g_gui.CloseAllEditors();
2610 }
2611 properties->Destroy();
2612}
2613
2614void MainMenuBar::OnToolbars(wxCommandEvent& event) {
2615 using namespace MenuBar;
2616
2617 ActionID id = static_cast<ActionID>(event.GetId() - (wxID_HIGHEST + 1));
2618 switch (id) {
2619 case VIEW_TOOLBARS_BRUSHES:
2620 g_gui.ShowToolbar(TOOLBAR_BRUSHES, event.IsChecked());
2621 g_settings.setInteger(Config::SHOW_TOOLBAR_BRUSHES, event.IsChecked());
2622 break;
2623 case VIEW_TOOLBARS_POSITION:
2624 g_gui.ShowToolbar(TOOLBAR_POSITION, event.IsChecked());
2625 g_settings.setInteger(Config::SHOW_TOOLBAR_POSITION, event.IsChecked());
2626 break;
2627 case VIEW_TOOLBARS_SIZES:
2628 g_gui.ShowToolbar(TOOLBAR_SIZES, event.IsChecked());
2629 g_settings.setInteger(Config::SHOW_TOOLBAR_SIZES, event.IsChecked());
2630 break;
2631 case VIEW_TOOLBARS_STANDARD:
2632 g_gui.ShowToolbar(TOOLBAR_STANDARD, event.IsChecked());
2633 g_settings.setInteger(Config::SHOW_TOOLBAR_STANDARD, event.IsChecked());
2634 break;
2635 default:
2636 break;
2637 }
2638}
2639
2640void MainMenuBar::OnNewView(wxCommandEvent& WXUNUSED(event)) {
2641 g_gui.NewMapView();
2642}
2643
2644void MainMenuBar::OnNewDetachedView(wxCommandEvent& WXUNUSED(event)) {
2645 g_gui.NewDetachedMapView();
2646}
2647
2648void MainMenuBar::OnToggleFullscreen(wxCommandEvent& WXUNUSED(event)) {
2649 if (frame->IsFullScreen()) {
2650 frame->ShowFullScreen(false);
2651 } else {
2652 frame->ShowFullScreen(true, wxFULLSCREEN_NOBORDER | wxFULLSCREEN_NOCAPTION);
2653 }
2654}
2655
2656void MainMenuBar::OnTakeScreenshot(wxCommandEvent& WXUNUSED(event)) {
2657 wxString path = wxstr(g_settings.getString(Config::SCREENSHOT_DIRECTORY));
2658 if (path.size() > 0 && (path.Last() == '/' || path.Last() == '\\')) {
2659 path = path + "/";
2660 }
2661
2662 g_gui.GetCurrentMapTab()->GetView()->GetCanvas()->TakeScreenshot(
2663 path, wxstr(g_settings.getString(Config::SCREENSHOT_FORMAT))
2664 );
2665}
2666
2667void MainMenuBar::OnZoomIn(wxCommandEvent& event) {
2668 double zoom = g_gui.GetCurrentZoom();
2669 g_gui.SetCurrentZoom(zoom - 0.1);
2670}
2671
2672void MainMenuBar::OnZoomOut(wxCommandEvent& event) {
2673 double zoom = g_gui.GetCurrentZoom();
2674 g_gui.SetCurrentZoom(zoom + 0.1);
2675}
2676
2677void MainMenuBar::OnZoomNormal(wxCommandEvent& event) {
2678 g_gui.SetCurrentZoom(1.0);
2679}
2680
2681void MainMenuBar::OnChangeViewSettings(wxCommandEvent& event) {
2682 g_settings.setInteger(Config::SHOW_ALL_FLOORS, IsItemChecked(MenuBar::SHOW_ALL_FLOORS));
2683 if (IsItemChecked(MenuBar::SHOW_ALL_FLOORS)) {
2684 EnableItem(MenuBar::SELECT_MODE_VISIBLE, true);
2685 EnableItem(MenuBar::SELECT_MODE_LOWER, true);
2686 } else {
2687 EnableItem(MenuBar::SELECT_MODE_VISIBLE, false);
2688 EnableItem(MenuBar::SELECT_MODE_LOWER, false);
2689 CheckItem(MenuBar::SELECT_MODE_CURRENT, true);
2690 g_settings.setInteger(Config::SELECTION_TYPE, SELECT_CURRENT_FLOOR);
2691 }
2692 g_settings.setInteger(Config::TRANSPARENT_FLOORS, IsItemChecked(MenuBar::GHOST_HIGHER_FLOORS));
2693 g_settings.setInteger(Config::TRANSPARENT_ITEMS, IsItemChecked(MenuBar::GHOST_ITEMS));
2694 g_settings.setInteger(Config::SHOW_INGAME_BOX, IsItemChecked(MenuBar::SHOW_INGAME_BOX));
2695 g_settings.setInteger(Config::SHOW_LIGHTS, IsItemChecked(MenuBar::SHOW_LIGHTS));
2696 g_settings.setInteger(Config::SHOW_LIGHT_STR, IsItemChecked(MenuBar::SHOW_LIGHT_STR));
2697 g_settings.setInteger(Config::SHOW_TECHNICAL_ITEMS, IsItemChecked(MenuBar::SHOW_TECHNICAL_ITEMS));
2698 g_settings.setInteger(Config::SHOW_WAYPOINTS, IsItemChecked(MenuBar::SHOW_WAYPOINTS));
2699 g_settings.setInteger(Config::SHOW_GRID, IsItemChecked(MenuBar::SHOW_GRID));
2700 g_settings.setInteger(Config::SHOW_EXTRA, !IsItemChecked(MenuBar::SHOW_EXTRA));
2701
2702 g_settings.setInteger(Config::SHOW_SHADE, IsItemChecked(MenuBar::SHOW_SHADE));
2703 g_settings.setInteger(Config::SHOW_SPECIAL_TILES, IsItemChecked(MenuBar::SHOW_SPECIAL));
2704 g_settings.setInteger(Config::SHOW_ZONE_AREAS, IsItemChecked(MenuBar::SHOW_ZONES));
2705 g_settings.setInteger(Config::SHOW_AS_MINIMAP, IsItemChecked(MenuBar::SHOW_AS_MINIMAP));
2706 g_settings.setInteger(Config::SHOW_ONLY_TILEFLAGS, IsItemChecked(MenuBar::SHOW_ONLY_COLORS));
2707 g_settings.setInteger(Config::SHOW_ONLY_MODIFIED_TILES, IsItemChecked(MenuBar::SHOW_ONLY_MODIFIED));
2708 g_settings.setInteger(Config::SHOW_CREATURES, IsItemChecked(MenuBar::SHOW_CREATURES));
2709 g_settings.setInteger(Config::SHOW_SPAWNS, IsItemChecked(MenuBar::SHOW_SPAWNS));
2710 g_settings.setInteger(Config::SHOW_HOUSES, IsItemChecked(MenuBar::SHOW_HOUSES));
2711 g_settings.setInteger(Config::HIGHLIGHT_ITEMS, IsItemChecked(MenuBar::HIGHLIGHT_ITEMS));
2712 g_settings.setInteger(Config::HIGHLIGHT_LOCKED_DOORS, IsItemChecked(MenuBar::HIGHLIGHT_LOCKED_DOORS));
2713 g_settings.setInteger(Config::SHOW_BLOCKING, IsItemChecked(MenuBar::SHOW_PATHING));
2714 g_settings.setInteger(Config::SHOW_TOOLTIPS, IsItemChecked(MenuBar::SHOW_TOOLTIPS));
2715 g_settings.setInteger(Config::SHOW_PREVIEW, IsItemChecked(MenuBar::SHOW_PREVIEW));
2716 g_settings.setInteger(Config::SHOW_WALL_HOOKS, IsItemChecked(MenuBar::SHOW_WALL_HOOKS));
2717 g_settings.setInteger(Config::SHOW_TOWNS, IsItemChecked(MenuBar::SHOW_TOWNS));
2718 g_settings.setInteger(Config::ALWAYS_SHOW_ZONES, IsItemChecked(MenuBar::ALWAYS_SHOW_ZONES));
2719 g_settings.setInteger(Config::EXT_HOUSE_SHADER, IsItemChecked(MenuBar::EXT_HOUSE_SHADER));
2720
2721 g_settings.setInteger(Config::EXPERIMENTAL_FOG, IsItemChecked(MenuBar::EXPERIMENTAL_FOG));
2722
2723 g_gui.RefreshView();
2724}
2725
2726void MainMenuBar::OnChangeFloor(wxCommandEvent& event) {
2727 // Workaround to stop events from looping
2728 if (checking_programmaticly) {
2729 return;
2730 }
2731
2732 // this will have to be changed if you want to have more floors
2733 // see MAKE_ACTION(FLOOR_0, wxITEM_RADIO, OnChangeFloor);
2734 //
2735 if (MAP_MAX_LAYER < 16) {
2736 for (int i = 0; i < MAP_LAYERS; ++i) {
2737 if (IsItemChecked(MenuBar::ActionID(MenuBar::FLOOR_0 + i))) {
2738 g_gui.ChangeFloor(i);
2739 }
2740 }
2741 }
2742}
2743
2744void MainMenuBar::OnMinimapWindow(wxCommandEvent& event) {
2745 g_gui.CreateMinimap();
2746}
2747
2748void MainMenuBar::OnNewPalette(wxCommandEvent& event) {
2749 g_gui.NewPalette();
2750}
2751
2752void MainMenuBar::OnSelectTerrainPalette(wxCommandEvent& WXUNUSED(event)) {
2753 g_gui.SelectPalettePage(TILESET_TERRAIN);
2754}
2755
2756void MainMenuBar::OnSelectDoodadPalette(wxCommandEvent& WXUNUSED(event)) {
2757 g_gui.SelectPalettePage(TILESET_DOODAD);
2758}
2759
2760void MainMenuBar::OnSelectItemPalette(wxCommandEvent& WXUNUSED(event)) {
2761 g_gui.SelectPalettePage(TILESET_ITEM);
2762}
2763
2764void MainMenuBar::OnSelectCollectionPalette(wxCommandEvent& WXUNUSED(event)) {
2765 g_gui.SelectPalettePage(TILESET_COLLECTION);
2766}
2767
2768void MainMenuBar::OnSelectHousePalette(wxCommandEvent& WXUNUSED(event)) {
2769 g_gui.SelectPalettePage(TILESET_HOUSE);
2770}
2771
2772void MainMenuBar::OnSelectCreaturePalette(wxCommandEvent& WXUNUSED(event)) {
2773 g_gui.SelectPalettePage(TILESET_CREATURE);
2774}
2775
2776void MainMenuBar::OnSelectWaypointPalette(wxCommandEvent& WXUNUSED(event)) {
2777 g_gui.SelectPalettePage(TILESET_WAYPOINT);
2778}
2779
2780void MainMenuBar::OnSelectRawPalette(wxCommandEvent& WXUNUSED(event)) {
2781 g_gui.SelectPalettePage(TILESET_RAW);
2782}
2783
2784void MainMenuBar::OnStartLive(wxCommandEvent& event) {
2785 Editor* editor = g_gui.GetCurrentEditor();
2786 if (!editor) {
2787 g_gui.PopupDialog("Error", "You need to have a map open to start a live mapping session.", wxOK);
2788 return;
2789 }
2790 if (editor->IsLive()) {
2791 g_gui.PopupDialog("Error", "You can not start two live servers on the same map (or a server using a remote map).", wxOK);
2792 return;
2793 }
2794
2795 wxDialog* live_host_dlg = newd wxDialog(frame, wxID_ANY, "Host Live Server", wxDefaultPosition, wxDefaultSize);
2796
2797 wxSizer* top_sizer = newd wxBoxSizer(wxVERTICAL);
2798 wxFlexGridSizer* gsizer = newd wxFlexGridSizer(2, 10, 10);
2799 gsizer->AddGrowableCol(0, 2);
2800 gsizer->AddGrowableCol(1, 3);
2801
2802 // Data fields
2803 wxTextCtrl* hostname;
2804 wxSpinCtrl* port;
2805 wxTextCtrl* password;
2806 wxCheckBox* allow_copy;
2807
2808 gsizer->Add(newd wxStaticText(live_host_dlg, wxID_ANY, "Server Name:"));
2809 gsizer->Add(hostname = newd wxTextCtrl(live_host_dlg, wxID_ANY, "RME Live Server"), 0, wxEXPAND);
2810
2811 gsizer->Add(newd wxStaticText(live_host_dlg, wxID_ANY, "Port:"));
2812 gsizer->Add(port = newd wxSpinCtrl(live_host_dlg, wxID_ANY, "31313", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 1, 65535, 31313), 0, wxEXPAND);
2813
2814 gsizer->Add(newd wxStaticText(live_host_dlg, wxID_ANY, "Password:"));
2815 gsizer->Add(password = newd wxTextCtrl(live_host_dlg, wxID_ANY), 0, wxEXPAND);
2816
2817 top_sizer->Add(gsizer, 0, wxALL, 20);
2818
2819 top_sizer->Add(allow_copy = newd wxCheckBox(live_host_dlg, wxID_ANY, "Allow copy & paste between maps."), 0, wxRIGHT | wxLEFT, 20);
2820 allow_copy->SetToolTip("Allows remote clients to copy & paste from the hosted map to local maps.");
2821
2822 wxSizer* ok_sizer = newd wxBoxSizer(wxHORIZONTAL);
2823 ok_sizer->Add(newd wxButton(live_host_dlg, wxID_OK, "OK"), 1, wxCENTER);
2824 ok_sizer->Add(newd wxButton(live_host_dlg, wxID_CANCEL, "Cancel"), wxCENTER, 1);
2825 top_sizer->Add(ok_sizer, 0, wxCENTER | wxALL, 20);
2826
2827 live_host_dlg->SetSizerAndFit(top_sizer);
2828
2829 while (true) {
2830 int ret = live_host_dlg->ShowModal();
2831 if (ret == wxID_OK) {
2832 LiveServer* liveServer = editor->StartLiveServer();
2833 liveServer->setName(hostname->GetValue());
2834 liveServer->setPassword(password->GetValue());
2835 liveServer->setPort(port->GetValue());
2836
2837 const wxString& error = liveServer->getLastError();
2838 if (!error.empty()) {
2839 g_gui.PopupDialog(live_host_dlg, "Error", error, wxOK);
2840 editor->CloseLiveServer();
2841 continue;
2842 }
2843
2844 if (!liveServer->bind()) {
2845 g_gui.PopupDialog("Socket Error", "Could not bind socket! Try another port?", wxOK);
2846 editor->CloseLiveServer();
2847 } else {
2848 liveServer->createLogWindow(g_gui.tabbook);
2849 }
2850 break;
2851 } else {
2852 break;
2853 }
2854 }
2855 live_host_dlg->Destroy();
2856 Update();
2857}
2858
2859void MainMenuBar::OnJoinLive(wxCommandEvent& event) {
2860 wxDialog* live_join_dlg = newd wxDialog(frame, wxID_ANY, "Join Live Server", wxDefaultPosition, wxDefaultSize);
2861
2862 wxSizer* top_sizer = newd wxBoxSizer(wxVERTICAL);
2863 wxFlexGridSizer* gsizer = newd wxFlexGridSizer(2, 10, 10);
2864 gsizer->AddGrowableCol(0, 2);
2865 gsizer->AddGrowableCol(1, 3);
2866
2867 // Data fields
2868 wxTextCtrl* name;
2869 wxTextCtrl* ip;
2870 wxSpinCtrl* port;
2871 wxTextCtrl* password;
2872
2873 gsizer->Add(newd wxStaticText(live_join_dlg, wxID_ANY, "Name:"));
2874 gsizer->Add(name = newd wxTextCtrl(live_join_dlg, wxID_ANY, ""), 0, wxEXPAND);
2875
2876 gsizer->Add(newd wxStaticText(live_join_dlg, wxID_ANY, "IP:"));
2877 gsizer->Add(ip = newd wxTextCtrl(live_join_dlg, wxID_ANY, "localhost"), 0, wxEXPAND);
2878
2879 gsizer->Add(newd wxStaticText(live_join_dlg, wxID_ANY, "Port:"));
2880 gsizer->Add(port = newd wxSpinCtrl(live_join_dlg, wxID_ANY, "31313", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 1, 65535, 31313), 0, wxEXPAND);
2881
2882 gsizer->Add(newd wxStaticText(live_join_dlg, wxID_ANY, "Password:"));
2883 gsizer->Add(password = newd wxTextCtrl(live_join_dlg, wxID_ANY), 0, wxEXPAND);
2884
2885 top_sizer->Add(gsizer, 0, wxALL, 20);
2886
2887 wxSizer* ok_sizer = newd wxBoxSizer(wxHORIZONTAL);
2888 ok_sizer->Add(newd wxButton(live_join_dlg, wxID_OK, "OK"), 1, wxRIGHT);
2889 ok_sizer->Add(newd wxButton(live_join_dlg, wxID_CANCEL, "Cancel"), 1, wxRIGHT);
2890 top_sizer->Add(ok_sizer, 0, wxCENTER | wxALL, 20);
2891
2892 live_join_dlg->SetSizerAndFit(top_sizer);
2893
2894 while (true) {
2895 int ret = live_join_dlg->ShowModal();
2896 if (ret == wxID_OK) {
2897 LiveClient* liveClient = newd LiveClient();
2898 liveClient->setPassword(password->GetValue());
2899
2900 wxString tmp = name->GetValue();
2901 if (tmp.empty()) {
2902 tmp = "User";
2903 }
2904 liveClient->setName(tmp);
2905
2906 const wxString& error = liveClient->getLastError();
2907 if (!error.empty()) {
2908 g_gui.PopupDialog(live_join_dlg, "Error", error, wxOK);
2909 delete liveClient;
2910 continue;
2911 }
2912
2913 const wxString& address = ip->GetValue();
2914 int32_t portNumber = port->GetValue();
2915
2916 liveClient->createLogWindow(g_gui.tabbook);
2917 if (!liveClient->connect(nstr(address), portNumber)) {
2918 g_gui.PopupDialog("Connection Error", liveClient->getLastError(), wxOK);
2919 delete liveClient;
2920 }
2921
2922 break;
2923 } else {
2924 break;
2925 }
2926 }
2927 live_join_dlg->Destroy();
2928 Update();
2929}
2930
2931void MainMenuBar::OnCloseLive(wxCommandEvent& event) {
2932 Editor* editor = g_gui.GetCurrentEditor();
2933 if (editor && editor->IsLive()) {
2934 g_gui.CloseLiveEditors(&editor->GetLive());
2935 }
2936
2937 Update();
2938}
2939
2940void MainMenuBar::SearchItems(bool unique, bool action, bool container, bool writable, bool zones, bool onSelection /* = false*/) {
2941 if (!unique && !action && !container && !writable && !zones) {
2942 return;
2943 }
2944
2945 if (!g_gui.IsEditorOpen()) {
2946 return;
2947 }
2948
2949 // Create search dialog
2950 wxDialog dialog(frame, wxID_ANY, "Advanced Search", wxDefaultPosition, wxDefaultSize);
2951 wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
2952
2953 // Create input fields based on search type
2954 wxTextCtrl* uniqueRangeCtrl = nullptr;
2955 wxTextCtrl* actionRangeCtrl = nullptr;
2956
2957 if (unique) {
2958 wxStaticBoxSizer* uniqueSizer = new wxStaticBoxSizer(wxVERTICAL, &dialog, "Unique ID Range");
2959 wxStaticText* uniqueHelp = new wxStaticText(&dialog, wxID_ANY,
2960 "Enter ranges (e.g., 1000-2000) or individual IDs separated by commas");
2961 uniqueRangeCtrl = new wxTextCtrl(&dialog, wxID_ANY);
2962 uniqueSizer->Add(uniqueHelp, 0, wxALL, 5);
2963 uniqueSizer->Add(uniqueRangeCtrl, 0, wxEXPAND | wxALL, 5);
2964 mainSizer->Add(uniqueSizer, 0, wxEXPAND | wxALL, 5);
2965 }
2966
2967 if (action) {
2968 wxStaticBoxSizer* actionSizer = new wxStaticBoxSizer(wxVERTICAL, &dialog, "Action ID Range");
2969 wxStaticText* actionHelp = new wxStaticText(&dialog, wxID_ANY,
2970 "Enter ranges (e.g., 100-200) or individual IDs separated by commas");
2971 actionRangeCtrl = new wxTextCtrl(&dialog, wxID_ANY);
2972 actionSizer->Add(actionHelp, 0, wxALL, 5);
2973 actionSizer->Add(actionRangeCtrl, 0, wxEXPAND | wxALL, 5);
2974 mainSizer->Add(actionSizer, 0, wxEXPAND | wxALL, 5);
2975 }
2976
2977 // Add OK/Cancel buttons
2978 wxStdDialogButtonSizer* buttonSizer = new wxStdDialogButtonSizer();
2979 buttonSizer->AddButton(new wxButton(&dialog, wxID_OK));
2980 buttonSizer->AddButton(new wxButton(&dialog, wxID_CANCEL));
2981 buttonSizer->Realize();
2982 mainSizer->Add(buttonSizer, 0, wxALIGN_CENTER | wxALL, 5);
2983
2984 dialog.SetSizer(mainSizer);
2985 mainSizer->Fit(&dialog);
2986 dialog.Center();
2987
2988 // Show dialog and process result
2989 if (dialog.ShowModal() == wxID_OK) {
2990 std::vector<std::pair<uint16_t, uint16_t>> uniqueRanges;
2991 std::vector<std::pair<uint16_t, uint16_t>> actionRanges;
2992
2993 if (uniqueRangeCtrl) {
2994 uniqueRanges = ParseRangeString(uniqueRangeCtrl->GetValue());
2995 }
2996 if (actionRangeCtrl) {
2997 actionRanges = ParseRangeString(actionRangeCtrl->GetValue());
2998 }
2999
3000 if (onSelection) {
3001 g_gui.CreateLoadBar("Searching on selected area...");
3002 } else {
3003 g_gui.CreateLoadBar("Searching on map...");
3004 }
3005
3006 OnSearchForStuff::Searcher searcher;
3007 searcher.search_zones = zones;
3008 searcher.search_unique = unique;
3009 searcher.search_action = action;
3010 searcher.search_container = container;
3011 searcher.search_writeable = writable;
3012 searcher.uniqueRanges = uniqueRanges;
3013 searcher.actionRanges = actionRanges;
3014
3015 foreach_ItemOnMap(g_gui.GetCurrentMap(), searcher, onSelection);
3016 searcher.sort();
3017 std::vector<std::pair<Tile*, Item*>>& found = searcher.found;
3018
3019 g_gui.DestroyLoadBar();
3020
3021 SearchResultWindow* result = g_gui.ShowSearchWindow();
3022 result->Clear();
3023 for (std::vector<std::pair<Tile*, Item*>>::iterator iter = found.begin(); iter != found.end(); ++iter) {
3024 result->AddPosition(searcher.desc(iter->first, iter->second), iter->first->getPosition());
3025 }
3026 }
3027}
3028
3029// Helper function to parse range string into pairs of IDs
3030std::vector<std::pair<uint16_t, uint16_t>> ParseRangeString(const wxString& input) {
3031 std::vector<std::pair<uint16_t, uint16_t>> ranges;
3032 std::string str = as_lower_str(nstr(input));
3033 std::vector<std::string> parts = splitString(str, ',');
3034
3035 for(const auto& part : parts) {
3036 if(part.find('-') != std::string::npos) {
3037 std::vector<std::string> range = splitString(part, '-');
3038 if(range.size() == 2 && isInteger(range[0]) && isInteger(range[1])) {
3039 uint16_t from = static_cast<uint16_t>(std::stoi(range[0]));
3040 uint16_t to = static_cast<uint16_t>(std::stoi(range[1]));
3041 if(from <= to) {
3042 ranges.emplace_back(from, to);
3043 }
3044 }
3045 } else if(isInteger(part)) {
3046 uint16_t id = static_cast<uint16_t>(std::stoi(part));
3047 ranges.emplace_back(id, id);
3048 }
3049 }
3050
3051 return ranges;
3052}
3053
3054void MainMenuBar::OnMapRemoveDuplicates(wxCommandEvent& WXUNUSED(event)) {
3055 Editor* editor = g_gui.GetCurrentEditor();
3056 if (!editor) return;
3057
3058 wxDialog dialog(frame, wxID_ANY, "Remove Duplicates", wxDefaultPosition, wxSize(800, 600));
3059 wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
3060
3061 // Warning text
3062 wxStaticText* warning = new wxStaticText(&dialog, wxID_ANY,
3063 "WARNING: Save your map before proceeding!\n"
3064 "Choose removal mode and properties to ignore:");
3065 main_sizer->Add(warning, 0, wxALL, 5);
3066
3067 // Create horizontal sizer for buttons and properties
3068 wxBoxSizer* content_sizer = new wxBoxSizer(wxHORIZONTAL);
3069
3070 // Left side - Buttons
3071 wxStaticBoxSizer* button_sizer = new wxStaticBoxSizer(wxVERTICAL, &dialog, "Removal Options");
3072
3073 wxButton* removeAll = new wxButton(&dialog, wxID_ANY, "Remove All Duplicates");
3074 button_sizer->Add(removeAll, 0, wxALL | wxEXPAND, 5);
3075
3076 wxButton* removeSelected = new wxButton(&dialog, wxID_ANY, "Remove Selected Item Duplicates");
3077 const Brush* brush = g_gui.GetCurrentBrush();
3078 removeSelected->Enable(brush && brush->isRaw());
3079 button_sizer->Add(removeSelected, 0, wxALL | wxEXPAND, 5);
3080
3081 wxButton* removeFromSelection = new wxButton(&dialog, wxID_ANY, "Remove Duplicates of Selected Items");
3082 removeFromSelection->Enable(editor->selection.size() > 0);
3083 button_sizer->Add(removeFromSelection, 0, wxALL | wxEXPAND, 5);
3084
3085 wxButton* removeInSelection = new wxButton(&dialog, wxID_ANY, "Remove Duplicates in Selection Area");
3086 removeInSelection->Enable(editor->selection.size() > 0);
3087 button_sizer->Add(removeInSelection, 0, wxALL | wxEXPAND, 5);
3088
3089 content_sizer->Add(button_sizer, 1, wxEXPAND | wxALL, 5);
3090
3091 // Right side - Properties to ignore
3092 wxStaticBoxSizer* props_sizer = new wxStaticBoxSizer(wxVERTICAL, &dialog, "Ignore Property Differences");
3093
3094 // Create property checkboxes
3095 wxCheckBox* ignore_unpassable = new wxCheckBox(&dialog, wxID_ANY, "Unpassable");
3096 wxCheckBox* ignore_unmovable = new wxCheckBox(&dialog, wxID_ANY, "Unmovable");
3097 wxCheckBox* ignore_block_missiles = new wxCheckBox(&dialog, wxID_ANY, "Block Missiles");
3098 wxCheckBox* ignore_block_pathfinder = new wxCheckBox(&dialog, wxID_ANY, "Block Pathfinder");
3099 wxCheckBox* ignore_readable = new wxCheckBox(&dialog, wxID_ANY, "Readable");
3100 wxCheckBox* ignore_writeable = new wxCheckBox(&dialog, wxID_ANY, "Writeable");
3101 wxCheckBox* ignore_pickupable = new wxCheckBox(&dialog, wxID_ANY, "Pickupable");
3102 wxCheckBox* ignore_stackable = new wxCheckBox(&dialog, wxID_ANY, "Stackable");
3103 wxCheckBox* ignore_rotatable = new wxCheckBox(&dialog, wxID_ANY, "Rotatable");
3104 wxCheckBox* ignore_hangable = new wxCheckBox(&dialog, wxID_ANY, "Hangable");
3105 wxCheckBox* ignore_hook_east = new wxCheckBox(&dialog, wxID_ANY, "Hook East");
3106 wxCheckBox* ignore_hook_south = new wxCheckBox(&dialog, wxID_ANY, "Hook South");
3107 wxCheckBox* ignore_elevation = new wxCheckBox(&dialog, wxID_ANY, "Has Elevation");
3108
3109 // Add checkboxes to properties sizer
3110 props_sizer->Add(ignore_unpassable, 0, wxALL, 3);
3111 props_sizer->Add(ignore_unmovable, 0, wxALL, 3);
3112 props_sizer->Add(ignore_block_missiles, 0, wxALL, 3);
3113 props_sizer->Add(ignore_block_pathfinder, 0, wxALL, 3);
3114 props_sizer->Add(ignore_readable, 0, wxALL, 3);
3115 props_sizer->Add(ignore_writeable, 0, wxALL, 3);
3116 props_sizer->Add(ignore_pickupable, 0, wxALL, 3);
3117 props_sizer->Add(ignore_stackable, 0, wxALL, 3);
3118 props_sizer->Add(ignore_rotatable, 0, wxALL, 3);
3119 props_sizer->Add(ignore_hangable, 0, wxALL, 3);
3120 props_sizer->Add(ignore_hook_east, 0, wxALL, 3);
3121 props_sizer->Add(ignore_hook_south, 0, wxALL, 3);
3122 props_sizer->Add(ignore_elevation, 0, wxALL, 3);
3123
3124 content_sizer->Add(props_sizer, 1, wxEXPAND | wxALL, 5);
3125
3126 main_sizer->Add(content_sizer, 1, wxEXPAND);
3127
3128 // Cancel button at bottom
3129 wxButton* cancel = new wxButton(&dialog, wxID_CANCEL, "Cancel");
3130 main_sizer->Add(cancel, 0, wxALL | wxCENTER, 5);
3131//main_sizer
3132 dialog.SetSizer(main_sizer);
3133
3134 // Previous button handlers remain the same, but need to capture checkbox states
3135 // and pass them to the cleanDuplicateItems function...
3136
3137 // Create a struct to hold property flags
3138 struct PropertyFlags {
3139 bool ignore_unpassable;
3140 bool ignore_unmovable;
3141 bool ignore_block_missiles;
3142 bool ignore_block_pathfinder;
3143 bool ignore_readable;
3144 bool ignore_writeable;
3145 bool ignore_pickupable;
3146 bool ignore_stackable;
3147 bool ignore_rotatable;
3148 bool ignore_hangable;
3149 bool ignore_hook_east;
3150 bool ignore_hook_south;
3151 bool ignore_elevation;
3152 };
3153
3154 // Button 1 handler: Remove all duplicates
3155 removeAll->Bind(wxEVT_BUTTON, [&](wxCommandEvent&) {
3156 // Use global PropertyFlags instead of local definition
3157 ::PropertyFlags flags = {
3158 ignore_unpassable->GetValue(),
3159 ignore_unmovable->GetValue(),
3160 ignore_block_missiles->GetValue(),
3161 ignore_block_pathfinder->GetValue(),
3162 ignore_readable->GetValue(),
3163 ignore_writeable->GetValue(),
3164 ignore_pickupable->GetValue(),
3165 ignore_stackable->GetValue(),
3166 ignore_rotatable->GetValue(),
3167 ignore_hangable->GetValue(),
3168 ignore_hook_east->GetValue(),
3169 ignore_hook_south->GetValue(),
3170 ignore_elevation->GetValue()
3171 };
3172
3173 g_gui.CreateLoadBar("Removing all duplicate items...");
3174 uint32_t removed = editor->map.cleanDuplicateItems(std::vector<std::pair<uint16_t, uint16_t>>(), flags);
3175 g_gui.DestroyLoadBar();
3176
3177 std::ostringstream ss;
3178 ss << "Remove Duplicates completed:\n" << removed << " duplicate items removed.";
3179 g_gui.PopupDialog("Remove Duplicates", ss.str(), wxOK);
3180 dialog.EndModal(wxID_OK);
3181 });
3182
3183 // Button 2 handler: Remove RAW brush item duplicates
3184 removeSelected->Bind(wxEVT_BUTTON, [&](wxCommandEvent&) {
3185 const RAWBrush* rawBrush = dynamic_cast<const RAWBrush*>(g_gui.GetCurrentBrush());
3186 if (!rawBrush) return;
3187
3188 // Use global PropertyFlags instead of local definition
3189 ::PropertyFlags flags = {
3190 ignore_unpassable->GetValue(),
3191 ignore_unmovable->GetValue(),
3192 ignore_block_missiles->GetValue(),
3193 ignore_block_pathfinder->GetValue(),
3194 ignore_readable->GetValue(),
3195 ignore_writeable->GetValue(),
3196 ignore_pickupable->GetValue(),
3197 ignore_stackable->GetValue(),
3198 ignore_rotatable->GetValue(),
3199 ignore_hangable->GetValue(),
3200 ignore_hook_east->GetValue(),
3201 ignore_hook_south->GetValue(),
3202 ignore_elevation->GetValue()
3203 };
3204
3205 uint16_t itemId = rawBrush->getItemType()->id;
3206 std::vector<std::pair<uint16_t, uint16_t>> range = {{itemId, itemId}};
3207
3208 g_gui.CreateLoadBar("Removing selected item duplicates...");
3209 uint32_t removed = editor->map.cleanDuplicateItems(range, flags);
3210 g_gui.DestroyLoadBar();
3211
3212 std::ostringstream ss;
3213 ss << "Remove Duplicates completed:\n" << removed << " duplicates of item " << itemId << " removed.";
3214 g_gui.PopupDialog("Remove Duplicates", ss.str(), wxOK);
3215 dialog.EndModal(wxID_OK);
3216 });
3217
3218 // Button 3 handler: Remove duplicates of items in selection
3219 removeFromSelection->Bind(wxEVT_BUTTON, [&](wxCommandEvent&) {
3220 const TileSet& tiles = editor->selection.getTiles();
3221 if(tiles.empty()) {
3222 g_gui.PopupDialog("Error", "No area selected!", wxOK);
3223 return;
3224 }
3225
3226 // Collect all unique item IDs from selection
3227 std::set<uint16_t> selectedIds;
3228 for(Tile* tile : tiles) {
3229 if(!tile) continue;
3230
3231 if(tile->ground) {
3232 selectedIds.insert(tile->ground->getID());
3233 }
3234
3235 for(Item* item : tile->items) {
3236 if(item) {
3237 selectedIds.insert(item->getID());
3238 }
3239 }
3240 }
3241
3242 // Convert to ranges for single IDs
3243 std::vector<std::pair<uint16_t, uint16_t>> ranges;
3244 for(uint16_t id : selectedIds) {
3245 ranges.push_back({id, id});
3246 }
3247
3248 // Use global PropertyFlags instead of local definition
3249 ::PropertyFlags flags = {
3250 ignore_unpassable->GetValue(),
3251 ignore_unmovable->GetValue(),
3252 ignore_block_missiles->GetValue(),
3253 ignore_block_pathfinder->GetValue(),
3254 ignore_readable->GetValue(),
3255 ignore_writeable->GetValue(),
3256 ignore_pickupable->GetValue(),
3257 ignore_stackable->GetValue(),
3258 ignore_rotatable->GetValue(),
3259 ignore_hangable->GetValue(),
3260 ignore_hook_east->GetValue(),
3261 ignore_hook_south->GetValue(),
3262 ignore_elevation->GetValue()
3263 };
3264
3265 g_gui.CreateLoadBar("Removing duplicates of selected items...");
3266 uint32_t removed = editor->map.cleanDuplicateItems(ranges, flags);
3267 g_gui.DestroyLoadBar();
3268
3269 std::ostringstream ss;
3270 ss << "Remove Duplicates completed:\n" << removed << " duplicates of "
3271 << selectedIds.size() << " selected item types removed.";
3272 g_gui.PopupDialog("Remove Duplicates", ss.str(), wxOK);
3273 dialog.EndModal(wxID_OK);
3274 });
3275
3276 // Button 4 handler: Remove duplicates within selection area
3277 removeInSelection->Bind(wxEVT_BUTTON, [&](wxCommandEvent&) {
3278 const TileSet& tiles = editor->selection.getTiles();
3279 if(tiles.empty()) {
3280 g_gui.PopupDialog("Error", "No area selected!", wxOK);
3281 return;
3282 }
3283
3284 // Use global PropertyFlags instead of local definition
3285 ::PropertyFlags flags = {
3286 ignore_unpassable->GetValue(),
3287 ignore_unmovable->GetValue(),
3288 ignore_block_missiles->GetValue(),
3289 ignore_block_pathfinder->GetValue(),
3290 ignore_readable->GetValue(),
3291 ignore_writeable->GetValue(),
3292 ignore_pickupable->GetValue(),
3293 ignore_stackable->GetValue(),
3294 ignore_rotatable->GetValue(),
3295 ignore_hangable->GetValue(),
3296 ignore_hook_east->GetValue(),
3297 ignore_hook_south->GetValue(),
3298 ignore_elevation->GetValue()
3299 };
3300
3301 uint32_t total_removed = 0;
3302 g_gui.CreateLoadBar("Removing duplicates in selection...");
3303
3304 // Process each selected tile independently
3305 for(Tile* tile : tiles) {
3306 if(!tile) continue;
3307
3308 std::vector<std::pair<uint16_t, uint16_t>> empty_ranges; // Process all IDs within tile
3309 // Create a temporary map with single tile for processing
3310 Map tempMap;
3311 Tile* tempTile = tile->deepCopy(tempMap); // Pass tempMap instead of Position
3312 tempMap.setTile(tempTile->getPosition(), tempTile);
3313
3314 uint32_t removed = tempMap.cleanDuplicateItems(empty_ranges, flags);
3315 if(removed > 0) {
3316 // If items were removed, replace original tile with cleaned tile
3317 Tile* cleanedTile = tempMap.getTile(tempTile->getPosition());
3318 editor->map.setTile(tile->getPosition(), cleanedTile->deepCopy(editor->map)); // Pass editor->map
3319 total_removed += removed;
3320 }
3321 }
3322
3323 g_gui.DestroyLoadBar();
3324
3325 std::ostringstream ss;
3326 ss << "Remove Duplicates completed:\n" << total_removed
3327 << " duplicate items removed from " << tiles.size() << " selected tiles.";
3328 g_gui.PopupDialog("Remove Duplicates", ss.str(), wxOK);
3329 dialog.EndModal(wxID_OK);
3330 });
3331
3332 dialog.ShowModal();
3333}
3334
3335void MainMenuBar::OnShowHotkeys(wxCommandEvent& WXUNUSED(event)) {
3336 g_hotkey_manager.ShowHotkeyDialog(frame);
3337}
3338
3339void MainMenuBar::onServerHost(wxCommandEvent& event) {
3340 wxDialog* hostDialog = new wxDialog(frame, wxID_ANY, "Host Server", wxDefaultPosition, wxSize(300, 200));
3341
3342 wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
3343 wxFlexGridSizer* gridSizer = new wxFlexGridSizer(2, 10, 10);
3344
3345 // Port
3346 gridSizer->Add(new wxStaticText(hostDialog, wxID_ANY, "Port:"));
3347 wxSpinCtrl* portCtrl = new wxSpinCtrl(hostDialog, wxID_ANY);
3348 portCtrl->SetRange(1, 65535);
3349 portCtrl->SetValue(g_settings.getInteger(Config::LIVE_PORT));
3350 gridSizer->Add(portCtrl);
3351
3352 // Password
3353 gridSizer->Add(new wxStaticText(hostDialog, wxID_ANY, "Password:"));
3354 wxTextCtrl* passwordCtrl = new wxTextCtrl(hostDialog, wxID_ANY);
3355 passwordCtrl->SetValue(wxstr(g_settings.getString(Config::LIVE_PASSWORD)));
3356 gridSizer->Add(passwordCtrl);
3357
3358 sizer->Add(gridSizer, 0, wxALL, 10);
3359
3360 // Host button
3361 wxBoxSizer* buttonSizer = new wxBoxSizer(wxHORIZONTAL);
3362 wxButton* hostButton = new wxButton(hostDialog, wxID_OK, "Host");
3363 wxButton* cancelButton = new wxButton(hostDialog, wxID_CANCEL, "Cancel");
3364 buttonSizer->Add(hostButton);
3365 buttonSizer->Add(cancelButton);
3366
3367 sizer->Add(buttonSizer, 0, wxALIGN_CENTER | wxALL, 10);
3368 hostDialog->SetSizer(sizer);
3369
3370 if (hostDialog->ShowModal() == wxID_OK) {
3371 // Get port and password from controls
3372 int port = portCtrl->GetValue();
3373 wxString password = passwordCtrl->GetValue();
3374
3375 // Save settings
3376 g_settings.setInteger(Config::LIVE_PORT, port);
3377 g_settings.setString(Config::LIVE_PASSWORD, nstr(password));
3378
3379 // Create server
3380 LiveServer* server = new LiveServer(*g_gui.GetCurrentEditor());
3381
3382 // Set the server name to HOST for easy identification in chat
3383 server->setName("HOST");
3384
3385 if (!server->setPort(port)) {
3386 wxMessageBox(wxString(server->getLastError()), "Error", wxOK | wxICON_ERROR);
3387 delete server;
3388 return;
3389 }
3390
3391 if (!server->setPassword(password)) {
3392 wxMessageBox(wxString(server->getLastError()), "Error", wxOK | wxICON_ERROR);
3393 delete server;
3394 return;
3395 }
3396
3397 // Start server
3398 if (!server->bind()) {
3399 wxMessageBox(wxString(server->getLastError()), "Error", wxOK | wxICON_ERROR);
3400 delete server;
3401 return;
3402 }
3403
3404 // Create log window
3405 LiveLogTab* log = server->createLogWindow(g_gui.tabbook);
3406 g_gui.RefreshPalettes();
3407 }
3408
3409 hostDialog->Destroy();
3410}
3411
3412void MainMenuBar::onServerConnect(wxCommandEvent& event) {
3413 wxDialog* connectDialog = new wxDialog(frame, wxID_ANY, "Connect to Server", wxDefaultPosition, wxSize(300, 240));
3414
3415 wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
3416 wxFlexGridSizer* gridSizer = new wxFlexGridSizer(2, 10, 10);
3417
3418 // Host
3419 gridSizer->Add(new wxStaticText(connectDialog, wxID_ANY, "Host:"));
3420 wxTextCtrl* hostCtrl = new wxTextCtrl(connectDialog, wxID_ANY);
3421 hostCtrl->SetValue(wxstr(g_settings.getString(Config::LIVE_HOST)));
3422 gridSizer->Add(hostCtrl);
3423
3424 // Port
3425 gridSizer->Add(new wxStaticText(connectDialog, wxID_ANY, "Port:"));
3426 wxSpinCtrl* portCtrl = new wxSpinCtrl(connectDialog, wxID_ANY);
3427 portCtrl->SetRange(1, 65535);
3428 portCtrl->SetValue(g_settings.getInteger(Config::LIVE_PORT));
3429 gridSizer->Add(portCtrl);
3430
3431 // Username
3432 gridSizer->Add(new wxStaticText(connectDialog, wxID_ANY, "Username:"));
3433 wxTextCtrl* usernameCtrl = new wxTextCtrl(connectDialog, wxID_ANY);
3434 usernameCtrl->SetValue(wxstr(g_settings.getString(Config::LIVE_USERNAME)));
3435 gridSizer->Add(usernameCtrl);
3436
3437 // Password
3438 gridSizer->Add(new wxStaticText(connectDialog, wxID_ANY, "Password:"));
3439 wxTextCtrl* passwordCtrl = new wxTextCtrl(connectDialog, wxID_ANY);
3440 passwordCtrl->SetValue(wxstr(g_settings.getString(Config::LIVE_PASSWORD)));
3441 gridSizer->Add(passwordCtrl);
3442
3443 sizer->Add(gridSizer, 0, wxALL, 10);
3444
3445 // Connect button
3446 wxBoxSizer* buttonSizer = new wxBoxSizer(wxHORIZONTAL);
3447 wxButton* connectButton = new wxButton(connectDialog, wxID_OK, "Connect");
3448 wxButton* cancelButton = new wxButton(connectDialog, wxID_CANCEL, "Cancel");
3449 buttonSizer->Add(connectButton);
3450 buttonSizer->Add(cancelButton);
3451
3452 sizer->Add(buttonSizer, 0, wxALIGN_CENTER | wxALL, 10);
3453 connectDialog->SetSizer(sizer);
3454
3455 if (connectDialog->ShowModal() == wxID_OK) {
3456 // Get connection parameters from controls
3457 wxString host = hostCtrl->GetValue();
3458 int port = portCtrl->GetValue();
3459 wxString username = usernameCtrl->GetValue();
3460 wxString password = passwordCtrl->GetValue();
3461
3462 // Save settings
3463 g_settings.setString(Config::LIVE_HOST, nstr(host));
3464 g_settings.setInteger(Config::LIVE_PORT, port);
3465 g_settings.setString(Config::LIVE_USERNAME, nstr(username));
3466 g_settings.setString(Config::LIVE_PASSWORD, nstr(password));
3467
3468 // Create client
3469 LiveClient* client = new LiveClient();
3470 if (!client->setName(username)) {
3471 wxMessageBox(wxString(client->getLastError()), "Error", wxOK | wxICON_ERROR);
3472 delete client;
3473 return;
3474 }
3475
3476 if (!client->setPassword(password)) {
3477 wxMessageBox(wxString(client->getLastError()), "Error", wxOK | wxICON_ERROR);
3478 delete client;
3479 return;
3480 }
3481
3482 // Connect to server
3483 if (!client->connect(nstr(host), port)) {
3484 wxMessageBox(wxString(client->getLastError()), "Error", wxOK | wxICON_ERROR);
3485 delete client;
3486 return;
3487 }
3488
3489 // Create log window
3490 client->createLogWindow(g_gui.tabbook);
3491 }
3492
3493 connectDialog->Destroy();
3494}
3495
3496void MainMenuBar::OnRefreshItems(wxCommandEvent& WXUNUSED(event)) {
3497 if (!g_gui.IsEditorOpen()) {
3498 return;
3499 }
3500
3501 FindItemDialog dialog(frame, "Refresh Items");
3502 dialog.setSearchMode((FindItemDialog::SearchMode)g_settings.getInteger(Config::FIND_ITEM_MODE));
3503
3504 if (dialog.ShowModal() == wxID_OK) {
3505 Editor* editor = g_gui.GetCurrentEditor();
3506 if (!editor) return;
3507
3508 g_gui.CreateLoadBar("Refreshing items...");
3509
3510 // First find all matching items
3511 OnSearchForItem::Finder finder(dialog.getResultID(), (uint32_t)g_settings.getInteger(Config::REPLACE_SIZE));
3512 foreach_ItemOnMap(g_gui.GetCurrentMap(), finder, false);
3513 std::vector<std::pair<Tile*, Item*>>& items = finder.result;
3514
3515 // Store properties of found items
3516 struct ItemData {
3517 Position pos;
3518 uint16_t id;
3519 uint32_t actionId;
3520 uint32_t uniqueId;
3521 std::string text;
3522 size_t stackpos; // Store index in tile's item vector
3523 Container* container; // Store container if item is inside one
3524 size_t containerIndex; // Store index in container
3525 };
3526 std::vector<ItemData> itemsToRecreate;
3527
3528 for(const auto& pair : items) {
3529 Tile* tile = pair.first;
3530 Item* item = pair.second;
3531
3532 ItemData data;
3533 data.pos = tile->getPosition();
3534 data.id = item->getID();
3535 data.actionId = item->getActionID();
3536 data.uniqueId = item->getUniqueID();
3537 data.text = item->getText();
3538 data.container = nullptr;
3539 data.containerIndex = 0;
3540
3541 // Find item's position in tile or container
3542 bool found = false;
3543
3544 // First check if item is in a container on this tile
3545 for(Item* tileItem : tile->items) {
3546 if(Container* container = dynamic_cast<Container*>(tileItem)) {
3547 const ItemVector& containerItems = container->getVector();
3548 for(size_t idx = 0; idx < containerItems.size(); ++idx) {
3549 if(containerItems[idx] == item) {
3550 data.container = container;
3551 data.containerIndex = idx;
3552 found = true;
3553 break;
3554 }
3555 }
3556 }
3557 if(found) break;
3558 }
3559
3560 // If not in container, find position in tile
3561 if(!found) {
3562 for(size_t idx = 0; idx < tile->items.size(); ++idx) {
3563 if(tile->items[idx] == item) {
3564 data.stackpos = idx;
3565 break;
3566 }
3567 }
3568 }
3569
3570 itemsToRecreate.push_back(data);
3571 }
3572
3573 // Remove and recreate items
3574 for(const auto& data : itemsToRecreate) {
3575 Item* oldItem = nullptr;
3576
3577 if(data.container) {
3578 // Item is in container
3579 ItemVector& containerItems = data.container->getVector();
3580 if(data.containerIndex < containerItems.size()) {
3581 oldItem = containerItems[data.containerIndex];
3582 containerItems.erase(containerItems.begin() + data.containerIndex);
3583 }
3584 } else {
3585 // Item is on tile
3586 Tile* tile = editor->map.getTile(data.pos);
3587 if(!tile) continue;
3588
3589 if(data.stackpos < tile->items.size()) {
3590 oldItem = tile->items[data.stackpos];
3591 tile->items.erase(tile->items.begin() + data.stackpos);
3592 }
3593 }
3594
3595 if(oldItem) {
3596 delete oldItem;
3597 }
3598
3599 Item* newItem = Item::Create(data.id);
3600 if(!newItem) continue;
3601
3602 newItem->setActionID(data.actionId);
3603 newItem->setUniqueID(data.uniqueId);
3604 newItem->setText(data.text);
3605
3606 if(data.container) {
3607 // Insert back into container at same position
3608 ItemVector& containerItems = data.container->getVector();
3609 if(data.containerIndex >= containerItems.size()) {
3610 containerItems.push_back(newItem);
3611 } else {
3612 containerItems.insert(
3613 containerItems.begin() + data.containerIndex,
3614 newItem
3615 );
3616 }
3617 } else {
3618 // Insert back into tile at same position
3619 Tile* tile = editor->map.getTile(data.pos);
3620 if(!tile) continue;
3621
3622 if(data.stackpos >= tile->items.size()) {
3623 tile->items.push_back(newItem);
3624 } else {
3625 tile->items.insert(
3626 tile->items.begin() + data.stackpos,
3627 newItem
3628 );
3629 }
3630 }
3631 }
3632
3633 g_gui.DestroyLoadBar();
3634
3635 wxString msg;
3636 msg << itemsToRecreate.size() << " items have been refreshed.";
3637 g_gui.PopupDialog("Refresh completed", msg, wxOK);
3638
3639 editor->map.doChange();
3640 g_gui.RefreshView();
3641 }
3642 dialog.Destroy();
3643}
3644
3645void MainMenuBar::OnGenerateIsland(wxCommandEvent& WXUNUSED(event)) {
3646 if (!g_gui.IsVersionLoaded()) {
3647 return;
3648 }
3649
3650 if (MapTab* tab = g_gui.GetCurrentMapTab()) {
3651 if (MapWindow* window = tab->GetView()) {
3652 window->ShowIslandGeneratorDialog();
3653 }
3654 }
3655}
3656
3657void MainMenuBar::OnMapValidateGround(wxCommandEvent& WXUNUSED(event)) {
3658 if (!g_gui.IsEditorOpen()) {
3659 return;
3660 }
3661
3662 Editor* editor = g_gui.GetCurrentEditor();
3663 if (!editor) {
3664 return;
3665 }
3666
3667 if (MapTab* tab = g_gui.GetCurrentMapTab()) {
3668 if (MapWindow* window = tab->GetView()) {
3669 window->ShowGroundValidationDialog();
3670 }
3671 }
3672}
3673
3674void MainMenuBar::OnCreateBorder(wxCommandEvent& WXUNUSED(event)) {
3675 // Open the Border Editor to create or edit auto-borders
3676 BorderEditorDialog* dialog = new BorderEditorDialog(g_gui.root, "Auto Border Editor");
3677 dialog->Show();
3678
3679 // After editing borders, refresh view to show any changes
3680 g_gui.RefreshView();
3681}
3682
3683void MainMenuBar::OnMapSummarize(wxCommandEvent& WXUNUSED(event)) {
3684 if (!g_gui.IsEditorOpen()) {
3685 g_gui.PopupDialog("Error", "No map is currently open!", wxOK | wxICON_ERROR);
3686 return;
3687 }
3688
3689 // Show the map summary window
3690 g_gui.ShowMapSummaryWindow();
3691}
3692