Map Editor

RD Core / Commit #3

Commit Map Editor

Alex Alex committed 18/10/2026 23:40 main Patch
436 files +250,791 -0
A RD_Map_Editor/source/map_region.h +174-0 View file
@@ -0,0 +1,174 @@
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+#ifndef RME_MAP_REGION_H
19+#define RME_MAP_REGION_H
20+
21+#include "position.h"
22+
23+class Tile;
24+class Floor;
25+class BaseMap;
26+
27+class TileLocation {
28+ TileLocation();
29+
30+public:
31+ ~TileLocation();
32+
33+ TileLocation(const TileLocation&) = delete;
34+ TileLocation& operator=(const TileLocation&) = delete;
35+
36+protected:
37+ Tile* tile;
38+ Position position;
39+ size_t spawn_count;
40+ size_t waypoint_count;
41+ size_t town_count;
42+ HouseExitList* house_exits; // Any house exits pointing here
43+
44+public:
45+ // Access tile
46+ // Can't set directly since that does not update tile count
47+ Tile* get() {
48+ return tile;
49+ }
50+ const Tile* get() const {
51+ return tile;
52+ }
53+
54+ int size() const;
55+ bool empty() const;
56+
57+ Position getPosition() const {
58+ return position;
59+ }
60+
61+ int getX() const {
62+ return position.x;
63+ }
64+ int getY() const {
65+ return position.y;
66+ }
67+ int getZ() const {
68+ return position.z;
69+ }
70+
71+ size_t getSpawnCount() const {
72+ return spawn_count;
73+ }
74+ void increaseSpawnCount() {
75+ spawn_count++;
76+ }
77+ void decreaseSpawnCount() {
78+ spawn_count--;
79+ }
80+ size_t getWaypointCount() const {
81+ return waypoint_count;
82+ }
83+ void increaseWaypointCount() {
84+ waypoint_count++;
85+ }
86+ void decreaseWaypointCount() {
87+ waypoint_count--;
88+ }
89+ size_t getTownCount() const {
90+ return town_count;
91+ }
92+ void increaseTownCount() {
93+ town_count++;
94+ }
95+ void decreaseTownCount() {
96+ town_count--;
97+ }
98+ HouseExitList* createHouseExits() {
99+ if (house_exits) {
100+ return house_exits;
101+ }
102+ return house_exits = newd HouseExitList;
103+ }
104+ HouseExitList* getHouseExits() {
105+ return house_exits;
106+ }
107+
108+ friend class Floor;
109+ friend class QTreeNode;
110+ friend class Waypoints;
111+};
112+
113+class Floor {
114+public:
115+ Floor(int x, int y, int z);
116+ TileLocation locs[MAP_LAYERS];
117+};
118+
119+// This is not a QuadTree, but a HexTree (16 child nodes to every node), so the name is abit misleading
120+class QTreeNode {
121+public:
122+ QTreeNode(BaseMap& map);
123+ virtual ~QTreeNode();
124+
125+ QTreeNode(const QTreeNode&) = delete;
126+ QTreeNode& operator=(const QTreeNode&) = delete;
127+
128+ QTreeNode* getLeaf(int x, int y); // Might return nullptr
129+ QTreeNode* getLeafForce(int x, int y); // Will never return nullptr, it will create the node if it's not there
130+
131+ // Coordinates are NOT relative
132+ TileLocation* createTile(int x, int y, int z);
133+ TileLocation* getTile(int x, int y, int z);
134+ Tile* setTile(int x, int y, int z, Tile* tile);
135+ void clearTile(int x, int y, int z);
136+
137+ Floor* createFloor(int x, int y, int z);
138+ Floor* getFloor(uint32_t z) {
139+ ASSERT(isLeaf);
140+ return array[z];
141+ }
142+ Floor** getFloors() {
143+ return array;
144+ }
145+
146+ void setVisible(bool overground, bool underground);
147+ void setVisible(uint32_t client, bool underground, bool value);
148+ bool isVisible(uint32_t client, bool underground);
149+ void clearVisible(uint32_t client);
150+
151+ void setRequested(bool underground, bool r);
152+ bool isVisible(bool underground);
153+ bool isRequested(bool underground);
154+
155+protected:
156+ BaseMap& map;
157+ uint32_t visible;
158+
159+ bool isLeaf;
160+ union {
161+ QTreeNode* child[MAP_LAYERS];
162+ Floor* array[MAP_LAYERS];
163+ /*
164+ #if 16 != MAP_LAYERS
165+ # error "You need to rewrite the QuadTree in order to handle more or less than 16 floors"
166+ #endif
167+ */
168+ };
169+
170+ friend class BaseMap;
171+ friend class MapIterator;
172+};
173+
174+#endif
A RD_Map_Editor/source/map_summary_window.cpp +300-0 View file
@@ -0,0 +1,300 @@
1+//////////////////////////////////////////////////////////////////////
2+// This file is part of Remere's Map Editor
3+//////////////////////////////////////////////////////////////////////
4+// Remere's Map Editor is free software: you can redistribute it and/or modify
5+// it under the terms of the GNU General Public License as published by
6+// the Free Software Foundation, either version 3 of the License, or
7+// (at your option) any later version.
8+//
9+// Remere's Map Editor is distributed in the hope that it will be useful,
10+// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+// GNU General Public License for more details.
13+//
14+// You should have received a copy of the GNU General Public License
15+// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+//////////////////////////////////////////////////////////////////////
17+
18+#include "main.h"
19+#include "map_summary_window.h"
20+#include "gui.h"
21+#include "map.h"
22+#include "tile.h"
23+#include "item.h"
24+#include "items.h"
25+#include "string_utils.h"
26+#include <algorithm>
27+#include <map>
28+
29+BEGIN_EVENT_TABLE(MapSummaryWindow, wxPanel)
30+EVT_LISTBOX(wxID_ANY, MapSummaryWindow::OnClickResult)
31+EVT_BUTTON(wxID_FILE, MapSummaryWindow::OnClickExport)
32+EVT_BUTTON(wxID_CLEAR, MapSummaryWindow::OnClickClear)
33+EVT_BUTTON(wxID_REFRESH, MapSummaryWindow::OnClickSummarize)
34+EVT_TEXT(wxID_FIND, MapSummaryWindow::OnFilterText)
35+EVT_BUTTON(wxID_SORT_ASCENDING, MapSummaryWindow::OnClickSort)
36+END_EVENT_TABLE()
37+
38+MapSummaryWindow::MapSummaryWindow(wxWindow* parent) :
39+ wxPanel(parent, wxID_ANY) {
40+
41+ wxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
42+
43+ // Filter section
44+ wxSizer* filter_sizer = new wxBoxSizer(wxHORIZONTAL);
45+ filter_sizer->Add(new wxStaticText(this, wxID_ANY, "Filter:"), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
46+ filter_text = new wxTextCtrl(this, wxID_FIND, "", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
47+ filter_sizer->Add(filter_text, 1, wxEXPAND | wxALL, 5);
48+ main_sizer->Add(filter_sizer, 0, wxEXPAND | wxALL, 5);
49+
50+ // List
51+ result_list = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(300, 400), 0, nullptr, wxLB_SINGLE | wxLB_ALWAYS_SB);
52+ main_sizer->Add(result_list, 1, wxEXPAND | wxALL, 5);
53+
54+ // Buttons
55+ wxSizer* button_sizer = new wxBoxSizer(wxHORIZONTAL);
56+
57+ summarize_button = new wxButton(this, wxID_REFRESH, "Summarize Map");
58+ button_sizer->Add(summarize_button, 0, wxALL, 5);
59+
60+ // Sort buttons
61+ sort_count_button = new wxButton(this, wxID_SORT_ASCENDING, "Sort by Count");
62+ sort_id_button = new wxButton(this, wxID_SORT_ASCENDING + 1, "Sort by ID");
63+ sort_name_button = new wxButton(this, wxID_SORT_ASCENDING + 2, "Sort by Name");
64+
65+ button_sizer->Add(sort_count_button, 0, wxALL, 5);
66+ button_sizer->Add(sort_id_button, 0, wxALL, 5);
67+ button_sizer->Add(sort_name_button, 0, wxALL, 5);
68+
69+ button_sizer->Add(new wxButton(this, wxID_FILE, "Export"), 0, wxALL, 5);
70+ button_sizer->Add(new wxButton(this, wxID_CLEAR, "Clear"), 0, wxALL, 5);
71+
72+ main_sizer->Add(button_sizer, 0, wxCENTER | wxALL, 5);
73+
74+ SetSizerAndFit(main_sizer);
75+
76+ // Bind sort button events
77+ Bind(wxEVT_BUTTON, &MapSummaryWindow::OnClickSort, this, wxID_SORT_ASCENDING);
78+ Bind(wxEVT_BUTTON, &MapSummaryWindow::OnClickSort, this, wxID_SORT_ASCENDING + 1);
79+ Bind(wxEVT_BUTTON, &MapSummaryWindow::OnClickSort, this, wxID_SORT_ASCENDING + 2);
80+}
81+
82+MapSummaryWindow::~MapSummaryWindow() {
83+ Clear();
84+}
85+
86+void MapSummaryWindow::Clear() {
87+ result_list->Clear();
88+ item_summaries.clear();
89+}
90+
91+void MapSummaryWindow::SummarizeMap(Map& map) {
92+ Clear();
93+
94+ // Count all items on the map
95+ std::map<uint16_t, uint32_t> item_counts;
96+
97+ g_gui.CreateLoadBar("Summarizing map items...");
98+
99+ uint64_t total_tiles = map.getTileCount();
100+ uint64_t processed = 0;
101+
102+ for (MapIterator mit = map.begin(); mit != map.end(); ++mit) {
103+ Tile* tile = (*mit)->get();
104+ if (!tile) {
105+ processed++;
106+ continue;
107+ }
108+
109+ // Update progress bar every 1000 tiles
110+ if (processed % 1000 == 0) {
111+ g_gui.SetLoadDone((uint32_t)(100 * processed / total_tiles));
112+ }
113+
114+ // Count ground items
115+ if (tile->ground) {
116+ item_counts[tile->ground->getID()]++;
117+ }
118+
119+ // Count items on tile
120+ for (Item* item : tile->items) {
121+ if (item) {
122+ item_counts[item->getID()]++;
123+
124+ // Count items in containers recursively
125+ if (Container* container = dynamic_cast<Container*>(item)) {
126+ CountContainerItems(container, item_counts);
127+ }
128+ }
129+ }
130+
131+ processed++;
132+ }
133+
134+ g_gui.DestroyLoadBar();
135+
136+ // Convert to our internal format and add to list
137+ for (const auto& pair : item_counts) {
138+ uint16_t itemId = pair.first;
139+ uint32_t count = pair.second;
140+
141+ ItemType& itemType = g_items[itemId];
142+ wxString itemName = wxstr(itemType.name);
143+ if (itemName.empty()) {
144+ itemName = wxString::Format("Unknown Item %d", itemId);
145+ }
146+
147+ item_summaries.emplace_back(itemId, itemName, count);
148+ }
149+
150+ // Sort by count (descending) by default
151+ SortByCount();
152+ RefreshList();
153+
154+ wxString statusMsg = wxString::Format("Map summarized: %zu unique item types found", item_summaries.size());
155+ g_gui.SetStatusText(statusMsg);
156+}
157+
158+void MapSummaryWindow::CountContainerItems(Container* container, std::map<uint16_t, uint32_t>& item_counts) {
159+ const ItemVector& items = container->getVector();
160+ for (Item* item : items) {
161+ if (item) {
162+ item_counts[item->getID()]++;
163+
164+ // Recursively count items in nested containers
165+ if (Container* nested_container = dynamic_cast<Container*>(item)) {
166+ CountContainerItems(nested_container, item_counts);
167+ }
168+ }
169+ }
170+}
171+
172+void MapSummaryWindow::AddItemCount(uint16_t itemId, const wxString& itemName, uint32_t count) {
173+ item_summaries.emplace_back(itemId, itemName, count);
174+ RefreshList();
175+}
176+
177+void MapSummaryWindow::SetFilter(const wxString& filter) {
178+ current_filter = filter.Lower();
179+ RefreshList();
180+}
181+
182+void MapSummaryWindow::RefreshList() {
183+ result_list->Clear();
184+
185+ for (const auto& summary : item_summaries) {
186+ // Apply filter if set
187+ if (!current_filter.empty()) {
188+ wxString itemNameLower = summary.itemName.Lower();
189+ wxString itemIdStr = wxString::Format("%d", summary.itemId);
190+
191+ if (itemNameLower.Find(current_filter) == wxNOT_FOUND &&
192+ itemIdStr.Find(current_filter) == wxNOT_FOUND) {
193+ continue;
194+ }
195+ }
196+
197+ wxString display_text = wxString::Format("%s [ID: %d] - Count: %u",
198+ summary.itemName, summary.itemId, summary.count);
199+ result_list->Append(display_text);
200+ }
201+}
202+
203+void MapSummaryWindow::SortByCount() {
204+ std::sort(item_summaries.begin(), item_summaries.end(),
205+ [](const ItemSummary& a, const ItemSummary& b) {
206+ return a.count > b.count; // Descending order
207+ });
208+}
209+
210+void MapSummaryWindow::SortByID() {
211+ std::sort(item_summaries.begin(), item_summaries.end(),
212+ [](const ItemSummary& a, const ItemSummary& b) {
213+ return a.itemId < b.itemId; // Ascending order
214+ });
215+}
216+
217+void MapSummaryWindow::SortByName() {
218+ std::sort(item_summaries.begin(), item_summaries.end(),
219+ [](const ItemSummary& a, const ItemSummary& b) {
220+ return a.itemName.CmpNoCase(b.itemName) < 0; // Case-insensitive ascending
221+ });
222+}
223+
224+void MapSummaryWindow::OnClickResult(wxCommandEvent& event) {
225+ // Could implement going to first occurrence of selected item
226+ int selection = result_list->GetSelection();
227+ if (selection != wxNOT_FOUND) {
228+ // For now, just show a message with the selected item info
229+ wxString selected_text = result_list->GetString(selection);
230+ g_gui.SetStatusText("Selected: " + selected_text);
231+ }
232+}
233+
234+void MapSummaryWindow::OnClickExport(wxCommandEvent& WXUNUSED(event)) {
235+ wxFileDialog dialog(this, "Save map summary...", "", "", "Text Documents (*.txt) | *.txt", wxFD_SAVE);
236+ if (dialog.ShowModal() == wxID_OK) {
237+ wxFile file(dialog.GetPath(), wxFile::write);
238+ if (file.IsOpened()) {
239+ file.Write("Map Item Summary\n");
240+ file.Write("Generated by Remere's Map Editor " + __RME_VERSION__ + "\n");
241+ file.Write("======================================\n\n");
242+
243+ for (const auto& summary : item_summaries) {
244+ // Apply filter if set
245+ if (!current_filter.empty()) {
246+ wxString itemNameLower = summary.itemName.Lower();
247+ wxString itemIdStr = wxString::Format("%d", summary.itemId);
248+
249+ if (itemNameLower.Find(current_filter) == wxNOT_FOUND &&
250+ itemIdStr.Find(current_filter) == wxNOT_FOUND) {
251+ continue;
252+ }
253+ }
254+
255+ wxString line = wxString::Format("%s [ID: %d] - Count: %u\n",
256+ summary.itemName, summary.itemId, summary.count);
257+ file.Write(line);
258+ }
259+
260+ file.Close();
261+ g_gui.SetStatusText("Map summary exported successfully");
262+ }
263+ }
264+}
265+
266+void MapSummaryWindow::OnClickClear(wxCommandEvent& WXUNUSED(event)) {
267+ Clear();
268+ g_gui.SetStatusText("Map summary cleared");
269+}
270+
271+void MapSummaryWindow::OnClickSummarize(wxCommandEvent& WXUNUSED(event)) {
272+ if (!g_gui.IsEditorOpen()) {
273+ g_gui.PopupDialog("Error", "No map is currently open!", wxOK | wxICON_ERROR);
274+ return;
275+ }
276+
277+ Map& current_map = g_gui.GetCurrentMap();
278+ SummarizeMap(current_map);
279+}
280+
281+void MapSummaryWindow::OnFilterText(wxCommandEvent& WXUNUSED(event)) {
282+ SetFilter(filter_text->GetValue());
283+}
284+
285+void MapSummaryWindow::OnClickSort(wxCommandEvent& event) {
286+ int id = event.GetId();
287+
288+ if (id == wxID_SORT_ASCENDING) {
289+ // Sort by count
290+ SortByCount();
291+ } else if (id == wxID_SORT_ASCENDING + 1) {
292+ // Sort by ID
293+ SortByID();
294+ } else if (id == wxID_SORT_ASCENDING + 2) {
295+ // Sort by name
296+ SortByName();
297+ }
298+
299+ RefreshList();
300+}
A RD_Map_Editor/source/map_summary_window.h +76-0 View file
@@ -0,0 +1,76 @@
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+#ifndef RME_MAP_SUMMARY_WINDOW_H_
19+#define RME_MAP_SUMMARY_WINDOW_H_
20+
21+#include "main.h"
22+#include <map>
23+#include <vector>
24+
25+// Forward declarations
26+class Map;
27+class Container;
28+
29+class MapSummaryWindow : public wxPanel {
30+public:
31+ MapSummaryWindow(wxWindow* parent);
32+ virtual ~MapSummaryWindow();
33+
34+ void Clear();
35+ void SummarizeMap(Map& map);
36+ void AddItemCount(uint16_t itemId, const wxString& itemName, uint32_t count);
37+ void SetFilter(const wxString& filter);
38+
39+ // Event handlers
40+ void OnClickResult(wxCommandEvent& event);
41+ void OnClickExport(wxCommandEvent& event);
42+ void OnClickClear(wxCommandEvent& event);
43+ void OnClickSummarize(wxCommandEvent& event);
44+ void OnFilterText(wxCommandEvent& event);
45+
46+protected:
47+ struct ItemSummary {
48+ uint16_t itemId;
49+ wxString itemName;
50+ uint32_t count;
51+
52+ ItemSummary(uint16_t id, const wxString& name, uint32_t c)
53+ : itemId(id), itemName(name), count(c) {}
54+ };
55+
56+ void RefreshList();
57+ void SortByCount();
58+ void SortByID();
59+ void SortByName();
60+ void CountContainerItems(Container* container, std::map<uint16_t, uint32_t>& item_counts);
61+ void OnClickSort(wxCommandEvent& event);
62+
63+ wxListBox* result_list;
64+ wxTextCtrl* filter_text;
65+ wxButton* summarize_button;
66+ wxButton* sort_count_button;
67+ wxButton* sort_id_button;
68+ wxButton* sort_name_button;
69+
70+ std::vector<ItemSummary> item_summaries;
71+ wxString current_filter;
72+
73+ DECLARE_EVENT_TABLE()
74+};
75+
76+#endif // RME_MAP_SUMMARY_WINDOW_H_
A RD_Map_Editor/source/map_tab.cpp +109-0 View file
@@ -0,0 +1,109 @@
1+//////////////////////////////////////////////////////////////////////
2+// This file is part of Remere's Map Editor
3+//////////////////////////////////////////////////////////////////////
4+// Remere's Map Editor is free software: you can redistribute it and/or modify
5+// it under the terms of the GNU General Public License as published by
6+// the Free Software Foundation, either version 3 of the License, or
7+// (at your option) any later version.
8+//
9+// Remere's Map Editor is distributed in the hope that it will be useful,
10+// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+// GNU General Public License for more details.
13+//
14+// You should have received a copy of the GNU General Public License
15+// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+//////////////////////////////////////////////////////////////////////
17+
18+#include "main.h"
19+
20+#include "gui.h"
21+#include "editor.h"
22+#include "map.h"
23+#include "sprites.h"
24+#include "map_tab.h"
25+#include "editor_tabs.h"
26+#include "map_display.h"
27+
28+MapTab::MapTab(MapTabbook* aui, Editor* editor) :
29+ EditorTab(),
30+ MapWindow(aui, *editor),
31+ aui(aui) {
32+ iref = newd InternalReference;
33+ iref->editor = editor;
34+ iref->owner_count = 1;
35+
36+ aui->AddTab(this, true);
37+ FitToMap();
38+}
39+
40+MapTab::MapTab(const MapTab* other) :
41+ EditorTab(),
42+ MapWindow(other->aui, *other->iref->editor),
43+ aui(other->aui),
44+ iref(other->iref) {
45+ iref->owner_count++;
46+ aui->AddTab(this, true);
47+ FitToMap();
48+ int x, y;
49+ other->GetCanvas()->GetScreenCenter(&x, &y);
50+ SetScreenCenterPosition(Position(x, y, other->GetCanvas()->GetFloor()));
51+}
52+
53+MapTab::~MapTab() {
54+ iref->owner_count--;
55+ if (iref->owner_count <= 0) {
56+ delete iref->editor;
57+ delete iref;
58+ }
59+}
60+
61+bool MapTab::IsUniqueReference() const {
62+ return iref->owner_count == 1;
63+}
64+
65+wxWindow* MapTab::GetWindow() const {
66+ return const_cast<MapTab*>(this);
67+}
68+
69+MapCanvas* MapTab::GetCanvas() const {
70+ return canvas;
71+}
72+
73+MapWindow* MapTab::GetView() const {
74+ return const_cast<MapWindow*>((const MapWindow*)this);
75+}
76+
77+wxString MapTab::GetTitle() const {
78+ wxString ss;
79+ ss << wxstr(iref->editor->map.getName()) << (iref->editor->map.hasChanged() ? "*" : "");
80+ return ss;
81+}
82+
83+Editor* MapTab::GetEditor() const {
84+ return &editor;
85+}
86+
87+Map* MapTab::GetMap() const {
88+ return &editor.map;
89+}
90+
91+Position MapTab::GetScreenCenterPosition() const {
92+ // Forward to the MapWindow implementation
93+ return MapWindow::GetScreenCenterPosition();
94+}
95+
96+void MapTab::VisibilityCheck() {
97+ EditorTab* editorTab = aui->GetCurrentTab();
98+ MapTab* mapTab = dynamic_cast<MapTab*>(editorTab);
99+ UpdateDialogs(mapTab && HasSameReference(mapTab));
100+}
101+
102+void MapTab::OnSwitchEditorMode(EditorMode mode) {
103+ gem->SetSprite(mode == DRAWING_MODE ? EDITOR_SPRITE_DRAWING_GEM : EDITOR_SPRITE_SELECTION_GEM);
104+ if (mode == SELECTION_MODE) {
105+ canvas->EnterSelectionMode();
106+ } else {
107+ canvas->EnterDrawingMode();
108+ }
109+}
A RD_Map_Editor/source/map_tab.h +65-0 View file
@@ -0,0 +1,65 @@
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+#ifndef RME_MAP_TAB_H_
19+#define RME_MAP_TAB_H_
20+
21+#include "editor_tabs.h"
22+#include "application.h"
23+#include "map_window.h"
24+
25+class MapTab : public EditorTab, public MapWindow {
26+public:
27+ MapTab(MapTabbook* aui, Editor* editor);
28+ // Constructs a newd window, but it uses the same internal editor as 'other'
29+ // AND the same parent, aui_notebook etc.
30+ MapTab(const MapTab* other);
31+ ~MapTab();
32+
33+ bool IsUniqueReference() const;
34+ bool HasSameReference(MapTab* other) const;
35+
36+ // Properties
37+ MapWindow* GetView() const;
38+ MapCanvas* GetCanvas() const;
39+ wxWindow* GetWindow() const;
40+ wxString GetTitle() const;
41+ Editor* GetEditor() const;
42+ Map* GetMap() const;
43+
44+ // Position methods
45+ Position GetScreenCenterPosition() const;
46+
47+ void VisibilityCheck();
48+
49+ // Event handlers
50+ void OnSwitchEditorMode(EditorMode mode);
51+
52+protected:
53+ struct InternalReference {
54+ Editor* editor;
55+ int owner_count;
56+ };
57+ MapTabbook* aui;
58+ InternalReference* iref;
59+};
60+
61+inline bool MapTab::HasSameReference(MapTab* other) const {
62+ return other->iref == iref;
63+}
64+
65+#endif
A RD_Map_Editor/source/map_window.cpp +280-0 View file
@@ -0,0 +1,280 @@
1+//////////////////////////////////////////////////////////////////////
2+// This file is part of Remere's Map Editor
3+//////////////////////////////////////////////////////////////////////
4+// Remere's Map Editor is free software: you can redistribute it and/or modify
5+// it under the terms of the GNU General Public License as published by
6+// the Free Software Foundation, either version 3 of the License, or
7+// (at your option) any later version.
8+//
9+// Remere's Map Editor is distributed in the hope that it will be useful,
10+// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+// GNU General Public License for more details.
13+//
14+// You should have received a copy of the GNU General Public License
15+// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+//////////////////////////////////////////////////////////////////////
17+
18+#include "main.h"
19+
20+#include "map_window.h"
21+#include "gui.h"
22+#include "sprites.h"
23+#include "editor.h"
24+
25+MapWindow::MapWindow(wxWindow* parent, Editor& editor) :
26+ wxPanel(parent, PANE_MAIN),
27+ editor(editor),
28+ replaceItemsDialog(nullptr),
29+ islandGeneratorDialog(nullptr) {
30+ int GL_settings[3];
31+ GL_settings[0] = WX_GL_RGBA;
32+ GL_settings[1] = WX_GL_DOUBLEBUFFER;
33+ GL_settings[2] = 0;
34+ canvas = newd MapCanvas(this, editor, GL_settings);
35+
36+ vScroll = newd MapScrollBar(this, MAP_WINDOW_VSCROLL, wxVERTICAL, canvas);
37+ hScroll = newd MapScrollBar(this, MAP_WINDOW_HSCROLL, wxHORIZONTAL, canvas);
38+
39+ gem = newd DCButton(this, MAP_WINDOW_GEM, wxDefaultPosition, DC_BTN_NORMAL, RENDER_SIZE_16x16, EDITOR_SPRITE_SELECTION_GEM);
40+
41+ wxFlexGridSizer* topsizer = newd wxFlexGridSizer(2, 0, 0);
42+
43+ topsizer->AddGrowableCol(0);
44+ topsizer->AddGrowableRow(0);
45+
46+ topsizer->Add(canvas, wxSizerFlags(1).Expand());
47+ topsizer->Add(vScroll, wxSizerFlags(1).Expand());
48+ topsizer->Add(hScroll, wxSizerFlags(1).Expand());
49+ topsizer->Add(gem, wxSizerFlags(1));
50+
51+ SetSizerAndFit(topsizer);
52+}
53+
54+MapWindow::~MapWindow() {
55+ ////
56+}
57+
58+void MapWindow::ShowReplaceItemsDialog(bool selectionOnly) {
59+ if (replaceItemsDialog) {
60+ return;
61+ }
62+
63+ replaceItemsDialog = new ReplaceItemsDialog(this, selectionOnly);
64+ replaceItemsDialog->Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(MapWindow::OnReplaceItemsDialogClose), NULL, this);
65+ replaceItemsDialog->Show();
66+}
67+
68+void MapWindow::CloseReplaceItemsDialog() {
69+ if (replaceItemsDialog) {
70+ replaceItemsDialog->Close();
71+ }
72+}
73+
74+void MapWindow::OnReplaceItemsDialogClose(wxCloseEvent& event) {
75+ if (replaceItemsDialog) {
76+ replaceItemsDialog->Disconnect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(MapWindow::OnReplaceItemsDialogClose), NULL, this);
77+ replaceItemsDialog->Destroy();
78+ replaceItemsDialog = nullptr;
79+ }
80+}
81+
82+void MapWindow::ShowIslandGeneratorDialog() {
83+ if (islandGeneratorDialog) {
84+ return;
85+ }
86+
87+ islandGeneratorDialog = new IslandGeneratorDialog(this);
88+ islandGeneratorDialog->Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(MapWindow::OnIslandGeneratorDialogClose), NULL, this);
89+ islandGeneratorDialog->Show();
90+}
91+
92+void MapWindow::CloseIslandGeneratorDialog() {
93+ if (islandGeneratorDialog) {
94+ islandGeneratorDialog->Close();
95+ }
96+}
97+
98+void MapWindow::OnIslandGeneratorDialogClose(wxCloseEvent& event) {
99+ if (islandGeneratorDialog) {
100+ islandGeneratorDialog->Disconnect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(MapWindow::OnIslandGeneratorDialogClose), NULL, this);
101+ islandGeneratorDialog->Destroy();
102+ islandGeneratorDialog = nullptr;
103+ }
104+}
105+
106+void MapWindow::ShowGroundValidationDialog() {
107+ if (groundValidationDialog) {
108+ return;
109+ }
110+
111+ groundValidationDialog = new GroundValidationDialog(this);
112+ if (groundValidationDialog->ShowModal() == wxID_OK) {
113+ // Get validation options
114+ bool validateStack = groundValidationDialog->shouldValidateGroundStack();
115+ bool generateEmpty = groundValidationDialog->shouldGenerateEmptySurroundedGrounds();
116+ bool removeDuplicates = groundValidationDialog->shouldRemoveDuplicateGrounds();
117+
118+ // Perform validation
119+ uint32_t changes = editor.validateGrounds(validateStack, generateEmpty, removeDuplicates);
120+
121+ // Show results
122+ wxString message;
123+ message << changes << " ground tiles have been modified.";
124+ g_gui.PopupDialog("Ground Validation", message, wxOK);
125+
126+ // Refresh view
127+ g_gui.RefreshView();
128+ }
129+
130+ groundValidationDialog->Destroy();
131+ groundValidationDialog = nullptr;
132+}
133+
134+void MapWindow::SetSize(int x, int y, bool center) {
135+ if (x == 0 || y == 0) {
136+ return;
137+ }
138+
139+ int windowSizeX;
140+ int windowSizeY;
141+
142+ canvas->GetSize(&windowSizeX, &windowSizeY);
143+
144+ hScroll->SetScrollbar(center ? (x - windowSizeX) / 2 : hScroll->GetThumbPosition(), windowSizeX / x, x, windowSizeX / x);
145+ vScroll->SetScrollbar(center ? (y - windowSizeY) / 2 : vScroll->GetThumbPosition(), windowSizeY / y, y, windowSizeX / y);
146+ // wxPanel::SetSize(x, y);
147+}
148+
149+void MapWindow::UpdateScrollbars(int nx, int ny) {
150+ // nx and ny are size of this window
151+ hScroll->SetScrollbar(hScroll->GetThumbPosition(), nx / max(1, hScroll->GetRange()), max(1, hScroll->GetRange()), 96);
152+ vScroll->SetScrollbar(vScroll->GetThumbPosition(), ny / max(1, vScroll->GetRange()), max(1, vScroll->GetRange()), 96);
153+}
154+
155+void MapWindow::UpdateDialogs(bool show) {
156+ if (replaceItemsDialog) {
157+ replaceItemsDialog->Show(show);
158+ }
159+ if (islandGeneratorDialog) {
160+ islandGeneratorDialog->Show(show);
161+ }
162+}
163+
164+void MapWindow::GetViewStart(int* x, int* y) {
165+ *x = hScroll->GetThumbPosition();
166+ *y = vScroll->GetThumbPosition();
167+}
168+
169+void MapWindow::GetViewSize(int* x, int* y) {
170+ canvas->GetSize(x, y);
171+ *x *= canvas->GetContentScaleFactor();
172+ *y *= canvas->GetContentScaleFactor();
173+}
174+
175+void MapWindow::FitToMap() {
176+ SetSize(editor.map.getWidth() * TileSize, editor.map.getHeight() * TileSize, true);
177+}
178+
179+Position MapWindow::GetScreenCenterPosition() const {
180+ int x, y;
181+ canvas->GetScreenCenter(&x, &y);
182+ return Position(x, y, canvas->GetFloor());
183+}
184+
185+void MapWindow::SetScreenCenterPosition(const Position& position) {
186+ if (position == Position()) {
187+ return;
188+ }
189+
190+ int x = position.x * TileSize;
191+ int y = position.y * TileSize;
192+ if (position.z <= GROUND_LAYER) {
193+ // Compensate for floor offset above ground
194+ x -= (GROUND_LAYER - position.z) * TileSize;
195+ y -= (GROUND_LAYER - position.z) * TileSize;
196+ }
197+
198+ const Position& center = GetScreenCenterPosition();
199+ if (previous_position != center) {
200+ previous_position.x = center.x;
201+ previous_position.y = center.y;
202+ previous_position.z = center.z;
203+ }
204+
205+ Scroll(x, y, true);
206+ canvas->ChangeFloor(position.z);
207+}
208+
209+void MapWindow::GoToPreviousCenterPosition() {
210+ SetScreenCenterPosition(previous_position);
211+}
212+
213+void MapWindow::Scroll(int x, int y, bool center) {
214+ if (center) {
215+ int windowSizeX, windowSizeY;
216+
217+ canvas->GetSize(&windowSizeX, &windowSizeY);
218+ x -= int((windowSizeX * g_gui.GetCurrentZoom()) / 2.0);
219+ y -= int((windowSizeY * g_gui.GetCurrentZoom()) / 2.0);
220+ }
221+
222+ hScroll->SetThumbPosition(x);
223+ vScroll->SetThumbPosition(y);
224+ g_gui.UpdateMinimap();
225+}
226+
227+void MapWindow::ScrollRelative(int x, int y) {
228+ hScroll->SetThumbPosition(hScroll->GetThumbPosition() + x);
229+ vScroll->SetThumbPosition(vScroll->GetThumbPosition() + y);
230+ g_gui.UpdateMinimap();
231+}
232+
233+void MapWindow::OnGem(wxCommandEvent& WXUNUSED(event)) {
234+ g_gui.SwitchMode();
235+}
236+
237+void MapWindow::OnSize(wxSizeEvent& event) {
238+ UpdateScrollbars(event.GetSize().GetWidth(), event.GetSize().GetHeight());
239+ event.Skip();
240+}
241+
242+void MapWindow::OnScroll(wxScrollEvent& event) {
243+ Refresh();
244+}
245+
246+void MapWindow::OnScrollLineDown(wxScrollEvent& event) {
247+ if (event.GetOrientation() == wxHORIZONTAL) {
248+ ScrollRelative(96, 0);
249+ } else {
250+ ScrollRelative(0, 96);
251+ }
252+ Refresh();
253+}
254+
255+void MapWindow::OnScrollLineUp(wxScrollEvent& event) {
256+ if (event.GetOrientation() == wxHORIZONTAL) {
257+ ScrollRelative(-96, 0);
258+ } else {
259+ ScrollRelative(0, -96);
260+ }
261+ Refresh();
262+}
263+
264+void MapWindow::OnScrollPageDown(wxScrollEvent& event) {
265+ if (event.GetOrientation() == wxHORIZONTAL) {
266+ ScrollRelative(5 * 96, 0);
267+ } else {
268+ ScrollRelative(0, 5 * 96);
269+ }
270+ Refresh();
271+}
272+
273+void MapWindow::OnScrollPageUp(wxScrollEvent& event) {
274+ if (event.GetOrientation() == wxHORIZONTAL) {
275+ ScrollRelative(-5 * 96, 0);
276+ } else {
277+ ScrollRelative(0, -5 * 96);
278+ }
279+ Refresh();
280+}
A RD_Map_Editor/source/map_window.h +139-0 View file
@@ -0,0 +1,139 @@
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+#ifndef RME_MAP_WINDOW_H_
19+#define RME_MAP_WINDOW_H_
20+
21+#include "position.h"
22+#include "replace_items_window.h"
23+#include "island_generator_dialog.h"
24+#include "ground_validation_dialog.h"
25+
26+class MapCanvas;
27+class DCButton;
28+
29+// Map window, a window displaying a map, complete with scrollbars
30+// and everything. This is the window that's inside each tab in the
31+// editor. Does NOT control any map rendering or editing at all.
32+// MapCanvas does that. (mapdisplay.h)
33+class MapWindow : public wxPanel {
34+public:
35+ MapWindow(wxWindow* parent, Editor& editor);
36+ virtual ~MapWindow();
37+
38+ // Event handlers
39+ void OnSize(wxSizeEvent& event);
40+ void OnScroll(wxScrollEvent& event);
41+ void OnScrollLineDown(wxScrollEvent& event);
42+ void OnScrollLineUp(wxScrollEvent& event);
43+ void OnScrollPageDown(wxScrollEvent& event);
44+ void OnScrollPageUp(wxScrollEvent& event);
45+ void OnGem(wxCommandEvent& event);
46+
47+ // Custom interface for MapWindow
48+
49+ // GetViewSize returns the size of the containing canvas, in pixels
50+ void GetViewSize(int* x, int* y);
51+ // Returns the start of the camera on the map, in pixels
52+ // Actually is scroll thumb positions
53+ void GetViewStart(int* x, int* y);
54+
55+ // Set size of this window (in pixels)
56+ // if center is true, the camera will be moved to the center of the map.
57+ void SetSize(int x, int y, bool center = false);
58+
59+ // Scroll to the specified, absolute position (in pixels)
60+ void Scroll(int x, int y, bool center = false);
61+
62+ // Scroll this many pixels in X/Y, relative to current position
63+ void ScrollRelative(int x, int y);
64+
65+ // Resize scrollbars to fit to the map dimensions
66+ // This needs to be called after updating map height/width
67+ void FitToMap();
68+
69+ // Screen position.
70+ Position GetScreenCenterPosition() const;
71+ void SetScreenCenterPosition(const Position& position);
72+ void GoToPreviousCenterPosition();
73+
74+ // Return the containing canvas
75+ MapCanvas* GetCanvas() const {
76+ return canvas;
77+ }
78+
79+ void ShowReplaceItemsDialog(bool selectionOnly);
80+ void CloseReplaceItemsDialog();
81+ void OnReplaceItemsDialogClose(wxCloseEvent& event);
82+
83+ void ShowIslandGeneratorDialog();
84+ void CloseIslandGeneratorDialog();
85+ void OnIslandGeneratorDialogClose(wxCloseEvent& event);
86+
87+ void ShowGroundValidationDialog();
88+ void CloseGroundValidationDialog();
89+ void OnGroundValidationDialogClose(wxCloseEvent& event);
90+
91+protected:
92+ // For internal use, call to resize the scrollbars with
93+ // the newd dimensions of *this* window
94+ void UpdateScrollbars(int nx, int ny);
95+ void UpdateDialogs(bool show);
96+
97+protected:
98+ Editor& editor;
99+ DCButton* gem;
100+ MapCanvas* canvas;
101+ wxScrollBar* hScroll;
102+ wxScrollBar* vScroll;
103+
104+private:
105+ ReplaceItemsDialog* replaceItemsDialog;
106+ IslandGeneratorDialog* islandGeneratorDialog;
107+ GroundValidationDialog* groundValidationDialog;
108+ Position previous_position;
109+
110+ friend class MainFrame;
111+ friend class MapCanvas;
112+
113+ DECLARE_EVENT_TABLE()
114+};
115+
116+// MapScrollbar, a special scrollbar that relays alot of events
117+// to the canvas, which allows scrolling when the scrollbar has
118+// focus (even though it also resents focus as hard as it can.
119+class MapScrollBar : public wxScrollBar {
120+public:
121+ MapScrollBar(MapWindow* parent, wxWindowID id, long style, wxWindow* canvas) :
122+ wxScrollBar(parent, id, wxDefaultPosition, wxDefaultSize, style), canvas(canvas) { }
123+ virtual ~MapScrollBar() { }
124+
125+ void OnKey(wxKeyEvent& event) {
126+ canvas->GetEventHandler()->AddPendingEvent(event);
127+ }
128+ void OnWheel(wxMouseEvent& event) {
129+ canvas->GetEventHandler()->AddPendingEvent(event);
130+ }
131+ void OnFocus(wxFocusEvent& event) {
132+ canvas->SetFocus();
133+ }
134+
135+ wxWindow* canvas;
136+ DECLARE_EVENT_TABLE()
137+};
138+
139+#endif
A RD_Map_Editor/source/materials.cpp +381-0 View file
@@ -0,0 +1,381 @@
1+//////////////////////////////////////////////////////////////////////
2+// This file is part of Remere's Map Editor
3+//////////////////////////////////////////////////////////////////////
4+// Remere's Map Editor is free software: you can redistribute it and/or modify
5+// it under the terms of the GNU General Public License as published by
6+// the Free Software Foundation, either version 3 of the License, or
7+// (at your option) any later version.
8+//
9+// Remere's Map Editor is distributed in the hope that it will be useful,
10+// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+// GNU General Public License for more details.
13+//
14+// You should have received a copy of the GNU General Public License
15+// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+//////////////////////////////////////////////////////////////////////
17+
18+#include "main.h"
19+
20+#include <wx/dir.h>
21+
22+#include "editor.h"
23+#include "items.h"
24+#include "creatures.h"
25+
26+#include "gui.h"
27+#include "materials.h"
28+#include "brush.h"
29+#include "creature_brush.h"
30+#include "raw_brush.h"
31+
32+Materials g_materials;
33+
34+Materials::Materials() {
35+ ////
36+}
37+
38+Materials::~Materials() {
39+ clear();
40+}
41+
42+void Materials::clear() {
43+ for (TilesetContainer::iterator iter = tilesets.begin(); iter != tilesets.end(); ++iter) {
44+ delete iter->second;
45+ }
46+
47+ for (MaterialsExtensionList::iterator iter = extensions.begin(); iter != extensions.end(); ++iter) {
48+ delete *iter;
49+ }
50+
51+ tilesets.clear();
52+ extensions.clear();
53+}
54+
55+const MaterialsExtensionList& Materials::getExtensions() {
56+ return extensions;
57+}
58+
59+MaterialsExtensionList Materials::getExtensionsByVersion(uint16_t version_id) {
60+ MaterialsExtensionList ret_list;
61+ for (MaterialsExtensionList::iterator iter = extensions.begin(); iter != extensions.end(); ++iter) {
62+ if ((*iter)->isForVersion(version_id)) {
63+ ret_list.push_back(*iter);
64+ }
65+ }
66+ return ret_list;
67+}
68+
69+bool Materials::loadMaterials(const FileName& identifier, wxString& error, wxArrayString& warnings) {
70+ pugi::xml_document doc;
71+ pugi::xml_parse_result result = doc.load_file(identifier.GetFullPath().mb_str());
72+ if (!result) {
73+ warnings.push_back("Could not open " + identifier.GetFullName() + " (file not found or syntax error)");
74+ return false;
75+ }
76+
77+ pugi::xml_node node = doc.child("materials");
78+ if (!node) {
79+ warnings.push_back(identifier.GetFullName() + ": Invalid rootheader.");
80+ return false;
81+ }
82+
83+ unserializeMaterials(identifier, node, error, warnings);
84+ return true;
85+}
86+
87+bool Materials::loadExtensions(FileName directoryName, wxString& error, wxArrayString& warnings) {
88+ directoryName.Mkdir(0755, wxPATH_MKDIR_FULL); // Create if it doesn't exist
89+
90+ wxDir ext_dir(directoryName.GetPath());
91+ if (!ext_dir.IsOpened()) {
92+ error = "Could not open extensions directory.";
93+ return false;
94+ }
95+
96+ wxString filename;
97+ if (!ext_dir.GetFirst(&filename)) {
98+ // No extensions found
99+ return true;
100+ }
101+
102+ StringVector clientVersions;
103+ do {
104+ FileName fn;
105+ fn.SetPath(directoryName.GetPath());
106+ fn.SetFullName(filename);
107+ if (fn.GetExt() != "xml") {
108+ continue;
109+ }
110+
111+ pugi::xml_document doc;
112+ pugi::xml_parse_result result = doc.load_file(fn.GetFullPath().mb_str());
113+ if (!result) {
114+ warnings.push_back("Could not open " + filename + " (file not found or syntax error)");
115+ continue;
116+ }
117+
118+ pugi::xml_node extensionNode = doc.child("materialsextension");
119+ if (!extensionNode) {
120+ warnings.push_back(filename + ": Invalid rootheader.");
121+ continue;
122+ }
123+
124+ pugi::xml_attribute attribute;
125+ if (!(attribute = extensionNode.attribute("name"))) {
126+ warnings.push_back(filename + ": Couldn't read extension name.");
127+ continue;
128+ }
129+
130+ const std::string& extensionName = attribute.as_string();
131+ if (!(attribute = extensionNode.attribute("author"))) {
132+ warnings.push_back(filename + ": Couldn't read extension name.");
133+ continue;
134+ }
135+
136+ const std::string& extensionAuthor = attribute.as_string();
137+ if (!(attribute = extensionNode.attribute("description"))) {
138+ warnings.push_back(filename + ": Couldn't read extension name.");
139+ continue;
140+ }
141+
142+ const std::string& extensionDescription = attribute.as_string();
143+ if (extensionName.empty() || extensionAuthor.empty() || extensionDescription.empty()) {
144+ warnings.push_back(filename + ": Couldn't read extension attributes (name, author, description).");
145+ continue;
146+ }
147+
148+ std::string extensionUrl = extensionNode.attribute("url").as_string();
149+ extensionUrl.erase(std::remove(extensionUrl.begin(), extensionUrl.end(), '\''));
150+
151+ std::string extensionAuthorLink = extensionNode.attribute("authorurl").as_string();
152+ extensionAuthorLink.erase(std::remove(extensionAuthorLink.begin(), extensionAuthorLink.end(), '\''));
153+
154+ MaterialsExtension* materialExtension = newd MaterialsExtension(extensionName, extensionAuthor, extensionDescription);
155+ materialExtension->url = extensionUrl;
156+ materialExtension->author_url = extensionAuthorLink;
157+
158+ if ((attribute = extensionNode.attribute("client"))) {
159+ clientVersions.clear();
160+ const std::string& extensionClientString = attribute.as_string();
161+
162+ size_t lastPosition = 0;
163+ size_t position = extensionClientString.find(';');
164+ while (position != std::string::npos) {
165+ clientVersions.push_back(extensionClientString.substr(lastPosition, position - lastPosition));
166+ lastPosition = position + 1;
167+ position = extensionClientString.find(';', lastPosition);
168+ }
169+
170+ clientVersions.push_back(extensionClientString.substr(lastPosition));
171+ for (const std::string& version : clientVersions) {
172+ materialExtension->addVersion(version);
173+ }
174+
175+ std::sort(materialExtension->version_list.begin(), materialExtension->version_list.end(), VersionComparisonPredicate);
176+
177+ auto duplicate = std::unique(materialExtension->version_list.begin(), materialExtension->version_list.end());
178+ while (duplicate != materialExtension->version_list.end()) {
179+ materialExtension->version_list.erase(duplicate);
180+ duplicate = std::unique(materialExtension->version_list.begin(), materialExtension->version_list.end());
181+ }
182+ } else {
183+ warnings.push_back(filename + ": Extension is not available for any version.");
184+ }
185+
186+ extensions.push_back(materialExtension);
187+ if (materialExtension->isForVersion(g_gui.GetCurrentVersionID())) {
188+ unserializeMaterials(filename, extensionNode, error, warnings);
189+ }
190+ } while (ext_dir.GetNext(&filename));
191+
192+ return true;
193+}
194+
195+bool Materials::unserializeMaterials(const FileName& filename, pugi::xml_node node, wxString& error, wxArrayString& warnings) {
196+ wxString warning;
197+ pugi::xml_attribute attribute;
198+ for (pugi::xml_node childNode = node.first_child(); childNode; childNode = childNode.next_sibling()) {
199+ const std::string& childName = as_lower_str(childNode.name());
200+ if (childName == "include") {
201+ if (!(attribute = childNode.attribute("file"))) {
202+ continue;
203+ }
204+
205+ FileName includeName;
206+ includeName.SetPath(filename.GetPath());
207+ includeName.SetFullName(wxString(attribute.as_string(), wxConvUTF8));
208+
209+ wxString subError;
210+ if (!loadMaterials(includeName, subError, warnings)) {
211+ warnings.push_back("Error while loading file \"" + includeName.GetFullName() + "\": " + subError);
212+ }
213+ } else if (childName == "metaitem") {
214+ g_items.loadMetaItem(childNode);
215+ } else if (childName == "border") {
216+ g_brushes.unserializeBorder(childNode, warnings);
217+ if (warning.size()) {
218+ warnings.push_back("materials.xml: " + warning);
219+ }
220+ } else if (childName == "brush") {
221+ g_brushes.unserializeBrush(childNode, warnings);
222+ if (warning.size()) {
223+ warnings.push_back("materials.xml: " + warning);
224+ }
225+ } else if (childName == "tileset") {
226+ unserializeTileset(childNode, warnings);
227+ }
228+ }
229+ return true;
230+}
231+
232+void Materials::createOtherTileset() {
233+ Tileset* others;
234+ Tileset* npc_tileset;
235+
236+ if (tilesets["Others"] != nullptr) {
237+ others = tilesets["Others"];
238+ others->clear();
239+ } else {
240+ others = newd Tileset(g_brushes, "Others");
241+ tilesets["Others"] = others;
242+ }
243+
244+ if (tilesets["NPCs"] != nullptr) {
245+ npc_tileset = tilesets["NPCs"];
246+ npc_tileset->clear();
247+ } else {
248+ npc_tileset = newd Tileset(g_brushes, "NPCs");
249+ tilesets["NPCs"] = npc_tileset;
250+ }
251+
252+ // There should really be an iterator to do this
253+ for (int32_t id = 0; id <= g_items.getMaxID(); ++id) {
254+ ItemType& it = g_items[id];
255+ if (it.id == 0) {
256+ continue;
257+ }
258+
259+ if (!it.isMetaItem()) {
260+ Brush* brush;
261+ if (it.in_other_tileset) {
262+ others->getCategory(TILESET_RAW)->brushlist.push_back(it.raw_brush);
263+ continue;
264+ } else if (it.raw_brush == nullptr) {
265+ brush = it.raw_brush = newd RAWBrush(it.id);
266+ it.has_raw = true;
267+ g_brushes.addBrush(it.raw_brush);
268+ } else if (!it.has_raw) {
269+ brush = it.raw_brush;
270+ } else {
271+ continue;
272+ }
273+
274+ brush->flagAsVisible();
275+ others->getCategory(TILESET_RAW)->brushlist.push_back(it.raw_brush);
276+ it.in_other_tileset = true;
277+ }
278+ }
279+
280+ for (CreatureMap::iterator iter = g_creatures.begin(); iter != g_creatures.end(); ++iter) {
281+ CreatureType* type = iter->second;
282+ if (type->in_other_tileset) {
283+ if (type->isNpc) {
284+ npc_tileset->getCategory(TILESET_CREATURE)->brushlist.push_back(type->brush);
285+ } else {
286+ others->getCategory(TILESET_CREATURE)->brushlist.push_back(type->brush);
287+ }
288+ } else if (type->brush == nullptr) {
289+ type->brush = newd CreatureBrush(type);
290+ g_brushes.addBrush(type->brush);
291+ type->brush->flagAsVisible();
292+ type->in_other_tileset = true;
293+ if (type->isNpc) {
294+ npc_tileset->getCategory(TILESET_CREATURE)->brushlist.push_back(type->brush);
295+ } else {
296+ others->getCategory(TILESET_CREATURE)->brushlist.push_back(type->brush);
297+ }
298+ }
299+ }
300+}
301+
302+bool Materials::unserializeTileset(pugi::xml_node node, wxArrayString& warnings) {
303+ pugi::xml_attribute attribute;
304+ if (!(attribute = node.attribute("name"))) {
305+ warnings.push_back("Couldn't read tileset name");
306+ return false;
307+ }
308+
309+ const std::string& name = attribute.as_string();
310+
311+ Tileset* tileset;
312+ auto it = tilesets.find(name);
313+ if (it != tilesets.end()) {
314+ tileset = it->second;
315+ } else {
316+ tileset = newd Tileset(g_brushes, name);
317+ tilesets.insert(std::make_pair(name, tileset));
318+ }
319+
320+ for (pugi::xml_node childNode = node.first_child(); childNode; childNode = childNode.next_sibling()) {
321+ tileset->loadCategory(childNode, warnings);
322+ }
323+ return true;
324+}
325+
326+void Materials::addToTileset(std::string tilesetName, int itemId, TilesetCategoryType categoryType) {
327+ ItemType& it = g_items[itemId];
328+
329+ if (it.id == 0) {
330+ return;
331+ }
332+
333+ Tileset* tileset;
334+ auto _it = tilesets.find(tilesetName);
335+ if (_it != tilesets.end()) {
336+ tileset = _it->second;
337+ } else {
338+ tileset = newd Tileset(g_brushes, tilesetName);
339+ tilesets.insert(std::make_pair(tilesetName, tileset));
340+ }
341+
342+ TilesetCategory* category = tileset->getCategory(categoryType);
343+
344+ if (!it.isMetaItem()) {
345+ Brush* brush;
346+ if (it.in_other_tileset) {
347+ category->brushlist.push_back(it.raw_brush);
348+ return;
349+ } else if (it.raw_brush == nullptr) {
350+ brush = it.raw_brush = newd RAWBrush(it.id);
351+ it.has_raw = true;
352+ g_brushes.addBrush(it.raw_brush);
353+ } else {
354+ brush = it.raw_brush;
355+ }
356+
357+ brush->flagAsVisible();
358+ category->brushlist.push_back(it.raw_brush);
359+ it.in_other_tileset = true;
360+ }
361+}
362+
363+bool Materials::isInTileset(Item* item, std::string tilesetName) const {
364+ const ItemType& it = g_items[item->getID()];
365+
366+ return it.id != 0 && (isInTileset(it.brush, tilesetName) || isInTileset(it.doodad_brush, tilesetName) || isInTileset(it.raw_brush, tilesetName)) || isInTileset(it.collection_brush, tilesetName);
367+}
368+
369+bool Materials::isInTileset(Brush* brush, std::string tilesetName) const {
370+ if (!brush) {
371+ return false;
372+ }
373+
374+ TilesetContainer::const_iterator tilesetiter = tilesets.find(tilesetName);
375+ if (tilesetiter == tilesets.end()) {
376+ return false;
377+ }
378+ Tileset* tileset = tilesetiter->second;
379+
380+ return tileset->containsBrush(brush);
381+}
A RD_Map_Editor/source/materials.h +64-0 View file
@@ -0,0 +1,64 @@
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+#ifndef RME_MATERIALS_H_
19+#define RME_MATERIALS_H_
20+
21+#include "extension.h"
22+
23+class Materials {
24+public:
25+ Materials();
26+ ~Materials();
27+
28+ void clear();
29+
30+ const MaterialsExtensionList& getExtensions();
31+ MaterialsExtensionList getExtensionsByVersion(uint16_t version_id);
32+
33+ TilesetContainer tilesets;
34+
35+ bool loadMaterials(const FileName& identifier, wxString& error, wxArrayString& warnings);
36+ bool loadExtensions(FileName identifier, wxString& error, wxArrayString& warnings);
37+ void createOtherTileset();
38+ void addToTileset(std::string tilesetName, int itemId, TilesetCategoryType categoryType);
39+
40+ bool isInTileset(Item* item, std::string tileset) const;
41+ bool isInTileset(Brush* brush, std::string tileset) const;
42+ bool needSave() const {
43+ return modified;
44+ }
45+
46+ void modify(bool newValue = true) {
47+ this->modified = newValue;
48+ }
49+
50+protected:
51+ bool unserializeMaterials(const FileName& filename, pugi::xml_node node, wxString& error, wxArrayString& warnings);
52+ bool unserializeTileset(pugi::xml_node node, wxArrayString& warnings);
53+
54+ MaterialsExtensionList extensions;
55+
56+private:
57+ bool modified = false;
58+ Materials(const Materials&);
59+ Materials& operator=(const Materials&);
60+};
61+
62+extern Materials g_materials;
63+
64+#endif
A RD_Map_Editor/source/minimap_window.h +202-0 View file
@@ -0,0 +1,202 @@
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+#ifndef RME_MINIMAP_WINDOW_H_
19+#define RME_MINIMAP_WINDOW_H_
20+
21+#include "position.h"
22+#include <wx/panel.h>
23+#include <memory>
24+#include <map>
25+#include <thread>
26+#include <mutex>
27+#include <atomic>
28+#include <wx/timer.h>
29+#include <wx/pen.h>
30+#include <vector>
31+#include <wx/combobox.h>
32+#include <wx/button.h>
33+#include <wx/xml/xml.h>
34+#include <wx/checkbox.h>
35+
36+class MinimapWindow : public wxPanel {
37+public:
38+ enum {
39+ ID_MINIMAP_UPDATE = 45000, // Choose a number that won't conflict with other IDs
40+ ID_RESIZE_TIMER = 45001 // Timer ID for resize completion detection
41+ };
42+
43+ MinimapWindow(wxWindow* parent);
44+ virtual ~MinimapWindow();
45+
46+ void OnPaint(wxPaintEvent&);
47+ void OnEraseBackground(wxEraseEvent&) { }
48+ void OnMouseClick(wxMouseEvent&);
49+ void OnSize(wxSizeEvent&);
50+ void OnClose(wxCloseEvent&);
51+ void OnResizeTimer(wxTimerEvent&);
52+
53+ void DelayedUpdate();
54+ void OnDelayedUpdate(wxTimerEvent& event);
55+ void OnKey(wxKeyEvent& event);
56+
57+ void ClearCache();
58+
59+ // Pre-cache method for building the entire minimap at load time
60+ void PreCacheEntireMap();
61+
62+ // Method to initialize minimap loading progressively on startup
63+ void InitialLoad();
64+
65+ void UpdateDrawnTiles(const PositionVector& positions);
66+
67+ static const int BLOCK_SIZE = 256; // 256 IS most optimal 512 is too laggy and 64 is too small
68+
69+ struct MinimapBlock {
70+ wxBitmap bitmap;
71+ bool needsUpdate = true;
72+ bool wasSeen = false;
73+ int floor = -1;
74+
75+ MinimapBlock() : needsUpdate(true), wasSeen(false) {}
76+ };
77+
78+ using BlockPtr = std::shared_ptr<MinimapBlock>;
79+ using BlockMap = std::map<uint32_t, BlockPtr>;
80+
81+ bool needs_update;
82+
83+ void MarkBlockForUpdate(int x, int y) {
84+ if (auto block = getBlock(x, y)) {
85+ block->needsUpdate = true;
86+ }
87+ }
88+
89+ // Minimap waypoint support
90+ struct MinimapWaypoint {
91+ wxString name;
92+ Position pos;
93+ MinimapWaypoint(const wxString& n, const Position& p) : name(n), pos(p) {}
94+ };
95+ std::vector<MinimapWaypoint> minimap_waypoints;
96+ int selected_minimap_waypoint_idx = -1;
97+ wxComboBox* minimap_waypoint_combo = nullptr;
98+ wxButton* add_minimap_waypoint_btn = nullptr;
99+
100+ void UpdateMinimapWaypointCombo();
101+ void OnMinimapWaypointSelected(wxCommandEvent& event);
102+ void OnAddMinimapWaypoint(wxCommandEvent& event);
103+ void TeleportToMinimapWaypoint(int idx);
104+
105+ // Save/Load buttons for minimap waypoints
106+ wxButton* save_minimap_waypoints_btn = nullptr;
107+ wxButton* load_minimap_waypoints_btn = nullptr;
108+
109+ void SaveMinimapWaypointsToXML();
110+ void LoadMinimapWaypointsFromXML();
111+ void OnSaveMinimapWaypoints(wxCommandEvent& event);
112+ void OnLoadMinimapWaypoints(wxCommandEvent& event);
113+ void SetMinimapFloor(int floor);
114+
115+private:
116+ BlockMap m_blocks;
117+ std::mutex m_mutex;
118+
119+ // Empty tile atlas for faster rendering
120+ wxBitmap empty_tile_atlas;
121+ bool empty_tile_atlas_initialized;
122+
123+ // Minimap floor (separate from editor floor)
124+ int minimap_floor;
125+
126+ // Button rectangles for header UI
127+ wxRect btn_cache;
128+ wxRect btn_up;
129+ wxRect btn_down;
130+
131+ // Helper methods
132+ uint32_t getBlockIndex(int x, int y) const {
133+ return ((x / BLOCK_SIZE) * (65536 / BLOCK_SIZE)) + (y / BLOCK_SIZE);
134+ }
135+
136+ wxPoint getBlockOffset(int x, int y) {
137+ return wxPoint(x - x % BLOCK_SIZE, y - y % BLOCK_SIZE);
138+ }
139+
140+ BlockPtr getBlock(int x, int y);
141+ void updateBlock(BlockPtr block, int startX, int startY, int floor);
142+
143+ wxBitmap buffer;
144+ std::mutex buffer_mutex;
145+ std::thread render_thread;
146+ std::atomic<bool> thread_running;
147+
148+ // Window resizing handling
149+ bool is_resizing;
150+ wxTimer resize_timer;
151+
152+ void RenderThreadFunction();
153+ void StartRenderThread();
154+ void StopRenderThread();
155+
156+ // Store last known state to detect changes
157+ int last_center_x;
158+ int last_center_y;
159+ int last_floor;
160+
161+ wxPen* pens[256];
162+ wxTimer update_timer;
163+ int last_start_x;
164+ int last_start_y;
165+
166+ // Helper methods
167+ void InitializeEmptyTileAtlas();
168+ void DrawEmptyTileAtlas(wxDC& dc, int startX, int startY, int width, int height);
169+
170+ void DrawHeaderButtons(wxDC& dc, int windowWidth, int headerHeight);
171+ void HandleHeaderButtonClick(const wxPoint& pt);
172+ void StartCacheCurrentFloor();
173+ void BatchCacheFloor(int floor);
174+
175+ // Block-based cache
176+ struct BlockKey {
177+ int bx, by, z;
178+ bool operator<(const BlockKey& other) const {
179+ if (z != other.z) return z < other.z;
180+ if (bx != other.bx) return bx < other.bx;
181+ return by < other.by;
182+ }
183+ };
184+ std::map<BlockKey, wxBitmap> block_cache;
185+
186+ // UI: Save cache to disk checkbox
187+ wxCheckBox* save_cache_checkbox = nullptr;
188+ bool save_cache_to_disk = false;
189+
190+ // Block cache logic
191+ void CacheFilledBlocksForFloor(int floor);
192+ void SaveBlockCacheToDisk(int floor);
193+ void LoadBlockCacheFromDisk(int floor);
194+ void ClearBlockCache();
195+ bool IsBlockFilled(int bx, int by, int floor);
196+ wxBitmap RenderBlock(int bx, int by, int floor);
197+ wxString GetCurrentMapName() const;
198+
199+ DECLARE_EVENT_TABLE()
200+};
201+
202+#endif
A RD_Map_Editor/source/mkpch.cpp +18-0 View file
@@ -0,0 +1,18 @@
1+//////////////////////////////////////////////////////////////////////
2+// This file is part of Remere's Map Editor
3+//////////////////////////////////////////////////////////////////////
4+// Remere's Map Editor is free software: you can redistribute it and/or modify
5+// it under the terms of the GNU General Public License as published by
6+// the Free Software Foundation, either version 3 of the License, or
7+// (at your option) any later version.
8+//
9+// Remere's Map Editor is distributed in the hope that it will be useful,
10+// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+// GNU General Public License for more details.
13+//
14+// You should have received a copy of the GNU General Public License
15+// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+//////////////////////////////////////////////////////////////////////
17+
18+#include "main.h"
A RD_Map_Editor/source/monster_generator_dialog.cpp +239-0 View file
@@ -0,0 +1,239 @@
1+#include "monster_generator_dialog.h"
2+#include "sprites.h"
3+#include "outfit.h"
4+#include "creatures.h"
5+#include "pugixml.h"
6+
7+BEGIN_EVENT_TABLE(MonsterGeneratorDialog, wxDialog)
8+ EVT_BUTTON(wxID_SAVE, MonsterGeneratorDialog::OnSave)
9+ EVT_BUTTON(wxID_OPEN, MonsterGeneratorDialog::OnLoad)
10+ EVT_SPINCTRL(SPIN_OUTFIT_TYPE, MonsterGeneratorDialog::OnOutfitChange)
11+ EVT_BUTTON(BUTTON_ADD_ATTACK, MonsterGeneratorDialog::OnAddAttack)
12+ EVT_BUTTON(BUTTON_ADD_DEFENSE, MonsterGeneratorDialog::OnAddDefense)
13+ EVT_BUTTON(BUTTON_ADD_LOOT, MonsterGeneratorDialog::OnAddLoot)
14+END_EVENT_TABLE()
15+
16+MonsterGeneratorDialog::MonsterGeneratorDialog(wxWindow* parent)
17+ : wxDialog(parent, wxID_ANY, "Monster Generator", wxDefaultPosition, wxSize(800, 600))
18+{
19+ CreateGUI();
20+}
21+
22+MonsterGeneratorDialog::~MonsterGeneratorDialog()
23+{
24+}
25+
26+void MonsterGeneratorDialog::CreateGUI()
27+{
28+ // Create main sizer
29+ wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
30+
31+ // Create notebook
32+ notebook = new wxNotebook(this, wxID_ANY);
33+
34+ // Basic Info Page
35+ basicPanel = new wxPanel(notebook);
36+ wxBoxSizer* basicSizer = new wxBoxSizer(wxVERTICAL);
37+
38+ // Name
39+ wxBoxSizer* nameSizer = new wxBoxSizer(wxHORIZONTAL);
40+ nameSizer->Add(new wxStaticText(basicPanel, wxID_ANY, "Name:"), 0, wxALL, 5);
41+ nameCtrl = new wxTextCtrl(basicPanel, wxID_ANY);
42+ nameSizer->Add(nameCtrl, 1, wxEXPAND | wxALL, 5);
43+ basicSizer->Add(nameSizer, 0, wxEXPAND);
44+
45+ // Description
46+ wxBoxSizer* descSizer = new wxBoxSizer(wxHORIZONTAL);
47+ descSizer->Add(new wxStaticText(basicPanel, wxID_ANY, "Description:"), 0, wxALL, 5);
48+ descriptionCtrl = new wxTextCtrl(basicPanel, wxID_ANY);
49+ descSizer->Add(descriptionCtrl, 1, wxEXPAND | wxALL, 5);
50+ basicSizer->Add(descSizer, 0, wxEXPAND);
51+
52+ // Race
53+ wxBoxSizer* raceSizer = new wxBoxSizer(wxHORIZONTAL);
54+ raceSizer->Add(new wxStaticText(basicPanel, wxID_ANY, "Race:"), 0, wxALL, 5);
55+ wxArrayString races;
56+ races.Add("blood");
57+ races.Add("venom");
58+ races.Add("undead");
59+ races.Add("fire");
60+ races.Add("energy");
61+ raceChoice = new wxChoice(basicPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, races);
62+ raceSizer->Add(raceChoice, 1, wxEXPAND | wxALL, 5);
63+ basicSizer->Add(raceSizer, 0, wxEXPAND);
64+
65+ // Stats
66+ wxFlexGridSizer* statsSizer = new wxFlexGridSizer(2, 5, 5);
67+
68+ statsSizer->Add(new wxStaticText(basicPanel, wxID_ANY, "Experience:"));
69+ experienceCtrl = new wxSpinCtrl(basicPanel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 999999999);
70+ statsSizer->Add(experienceCtrl);
71+
72+ statsSizer->Add(new wxStaticText(basicPanel, wxID_ANY, "Health:"));
73+ healthCtrl = new wxSpinCtrl(basicPanel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 1, 999999999);
74+ statsSizer->Add(healthCtrl);
75+
76+ statsSizer->Add(new wxStaticText(basicPanel, wxID_ANY, "Speed:"));
77+ speedCtrl = new wxSpinCtrl(basicPanel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 1, 9999);
78+ statsSizer->Add(speedCtrl);
79+
80+ basicSizer->Add(statsSizer, 0, wxALL, 5);
81+ basicPanel->SetSizer(basicSizer);
82+
83+ // Outfit Page
84+ outfitPanel = new wxPanel(notebook);
85+ wxBoxSizer* outfitSizer = new wxBoxSizer(wxVERTICAL);
86+
87+ wxFlexGridSizer* outfitGridSizer = new wxFlexGridSizer(2, 5, 5);
88+
89+ outfitGridSizer->Add(new wxStaticText(outfitPanel, wxID_ANY, "Look Type:"));
90+ lookTypeCtrl = new wxSpinCtrl(outfitPanel, SPIN_OUTFIT_TYPE, "", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 1, 999);
91+ outfitGridSizer->Add(lookTypeCtrl);
92+
93+ outfitGridSizer->Add(new wxStaticText(outfitPanel, wxID_ANY, "Head:"));
94+ headCtrl = new wxSpinCtrl(outfitPanel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 255);
95+ outfitGridSizer->Add(headCtrl);
96+
97+ outfitGridSizer->Add(new wxStaticText(outfitPanel, wxID_ANY, "Body:"));
98+ bodyCtrl = new wxSpinCtrl(outfitPanel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 255);
99+ outfitGridSizer->Add(bodyCtrl);
100+
101+ outfitSizer->Add(outfitGridSizer, 0, wxALL, 5);
102+
103+ // Outfit preview
104+ outfitPreview = new wxStaticBitmap(outfitPanel, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize(64, 64));
105+ outfitSizer->Add(outfitPreview, 0, wxALL | wxCENTER, 5);
106+
107+ outfitPanel->SetSizer(outfitSizer);
108+
109+ // Add pages to notebook
110+ notebook->AddPage(basicPanel, "Basic Info");
111+ notebook->AddPage(outfitPanel, "Outfit");
112+
113+ // Add notebook to main sizer
114+ mainSizer->Add(notebook, 1, wxEXPAND | wxALL, 5);
115+
116+ // Buttons
117+ wxBoxSizer* buttonSizer = new wxBoxSizer(wxHORIZONTAL);
118+ buttonSizer->Add(new wxButton(this, wxID_SAVE, "Save"), 0, wxALL, 5);
119+ buttonSizer->Add(new wxButton(this, wxID_OPEN, "Load"), 0, wxALL, 5);
120+ buttonSizer->Add(new wxButton(this, wxID_CANCEL, "Close"), 0, wxALL, 5);
121+ mainSizer->Add(buttonSizer, 0, wxALIGN_RIGHT | wxALL, 5);
122+
123+ SetSizer(mainSizer);
124+ Layout();
125+}
126+
127+void MonsterGeneratorDialog::UpdatePreview()
128+{
129+ // Get the outfit values
130+ int type = lookTypeCtrl->GetValue();
131+ int head = headCtrl->GetValue();
132+ int body = bodyCtrl->GetValue();
133+ int legs = legsCtrl->GetValue();
134+ int feet = feetCtrl->GetValue();
135+ int addons = addonsCtrl->GetValue();
136+
137+ // Create outfit object
138+ Outfit outfit;
139+ outfit.lookType = type;
140+ outfit.lookHead = head;
141+ outfit.lookBody = body;
142+ outfit.lookLegs = legs;
143+ outfit.lookFeet = feet;
144+ outfit.lookAddons = addons;
145+
146+ // Get the sprite and convert to wxBitmap
147+ if(g_sprites.GetOutfitSprite(outfit)) {
148+ wxBitmap bmp = wxBitmap(g_sprites.GetOutfitSprite(outfit)->getImage());
149+ outfitPreview->SetBitmap(bmp);
150+ }
151+}
152+
153+void MonsterGeneratorDialog::OnOutfitChange(wxSpinEvent& event)
154+{
155+ UpdatePreview();
156+}
157+
158+void MonsterGeneratorDialog::SaveMonsterFile()
159+{
160+ wxFileDialog saveFileDialog(this, "Save Monster File", "", "",
161+ "XML files (*.xml)|*.xml", wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
162+ if (saveFileDialog.ShowModal() == wxID_CANCEL)
163+ return;
164+
165+ pugi::xml_document doc;
166+ pugi::xml_node monster = doc.append_child("monster");
167+
168+ // Basic attributes
169+ monster.append_attribute("name") = nameCtrl->GetValue();
170+ monster.append_attribute("nameDescription") = descriptionCtrl->GetValue();
171+ monster.append_attribute("race") = raceChoice->GetStringSelection();
172+ monster.append_attribute("experience") = experienceCtrl->GetValue();
173+ monster.append_attribute("speed") = speedCtrl->GetValue();
174+
175+ // Health
176+ pugi::xml_node health = monster.append_child("health");
177+ health.append_attribute("now") = healthCtrl->GetValue();
178+ health.append_attribute("max") = healthCtrl->GetValue();
179+
180+ // Look
181+ pugi::xml_node look = monster.append_child("look");
182+ look.append_attribute("type") = lookTypeCtrl->GetValue();
183+ look.append_attribute("head") = headCtrl->GetValue();
184+ look.append_attribute("body") = bodyCtrl->GetValue();
185+ look.append_attribute("legs") = legsCtrl->GetValue();
186+ look.append_attribute("feet") = feetCtrl->GetValue();
187+ look.append_attribute("addons") = addonsCtrl->GetValue();
188+
189+ // Save the file
190+ doc.save_file(saveFileDialog.GetPath().mb_str());
191+}
192+
193+void MonsterGeneratorDialog::OnSave(wxCommandEvent& event)
194+{
195+ SaveMonsterFile();
196+}
197+
198+void MonsterGeneratorDialog::OnLoad(wxCommandEvent& event)
199+{
200+ wxFileDialog openFileDialog(this, "Open Monster File", "", "",
201+ "XML files (*.xml)|*.xml", wxFD_OPEN|wxFD_FILE_MUST_EXIST);
202+ if (openFileDialog.ShowModal() == wxID_CANCEL)
203+ return;
204+
205+ pugi::xml_document doc;
206+ if(!doc.load_file(openFileDialog.GetPath().mb_str())) {
207+ wxMessageBox("Failed to load monster file!", "Error", wxICON_ERROR);
208+ return;
209+ }
210+
211+ pugi::xml_node monster = doc.child("monster");
212+ if(!monster) {
213+ wxMessageBox("Invalid monster file format!", "Error", wxICON_ERROR);
214+ return;
215+ }
216+
217+ // Load basic attributes
218+ nameCtrl->SetValue(monster.attribute("name").as_string());
219+ descriptionCtrl->SetValue(monster.attribute("nameDescription").as_string());
220+ raceChoice->SetStringSelection(monster.attribute("race").as_string());
221+ experienceCtrl->SetValue(monster.attribute("experience").as_int());
222+ speedCtrl->SetValue(monster.attribute("speed").as_int());
223+
224+ // Load health
225+ if(pugi::xml_node health = monster.child("health")) {
226+ healthCtrl->SetValue(health.attribute("max").as_int());
227+ }
228+
229+ // Load look
230+ if(pugi::xml_node look = monster.child("look")) {
231+ lookTypeCtrl->SetValue(look.attribute("type").as_int());
232+ headCtrl->SetValue(look.attribute("head").as_int());
233+ bodyCtrl->SetValue(look.attribute("body").as_int());
234+ legsCtrl->SetValue(look.attribute("legs").as_int());
235+ feetCtrl->SetValue(look.attribute("feet").as_int());
236+ addonsCtrl->SetValue(look.attribute("addons").as_int());
237+ UpdatePreview();
238+ }
239+}
Top