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/RunMultipleInstances.bat +10-0 View file
@@ -0,0 +1,10 @@
1+@echo off
2+setlocal
3+cd /d "%~dp0"
4+if not exist "RD_Map_Editor.exe" (
5+ echo ERROR: RD_Map_Editor.exe was not found beside this launcher.
6+ pause
7+ exit /b 1
8+)
9+start "" "RD_Map_Editor.exe" -force-multi-instance
10+exit /b 0
A RD_Map_Editor/server.json +9-0 View file
@@ -0,0 +1,9 @@
1+// server.json example
2+{
3+ "map": "mymap.otbm",
4+ "port": 3333,
5+ "password": "secret",
6+ "max_clients": 16,
7+ "auto_save": true,
8+ "save_interval": 300
9+}
A RD_Map_Editor/source/about_window.h +43-0 View file
@@ -0,0 +1,43 @@
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_ABOUT_WINDOW_H_
19+#define RME_ABOUT_WINDOW_H_
20+
21+#include "main.h"
22+
23+class GamePanel;
24+
25+class AboutWindow : public wxDialog {
26+public:
27+ AboutWindow(wxWindow* parent);
28+ ~AboutWindow();
29+
30+ void OnClickOK(wxCommandEvent&);
31+ void OnClickLicense(wxCommandEvent&);
32+
33+ void OnTetris(wxCommandEvent&);
34+ void OnSnake(wxCommandEvent&);
35+
36+private:
37+ wxSizer* topsizer;
38+ GamePanel* game_panel;
39+
40+ DECLARE_EVENT_TABLE()
41+};
42+
43+#endif
A RD_Map_Editor/source/action.h +234-0 View file
@@ -0,0 +1,234 @@
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_ACTION_H_
19+#define RME_ACTION_H_
20+
21+#include "position.h"
22+
23+#include <deque>
24+
25+class Editor;
26+class Tile;
27+class House;
28+class Waypoint;
29+class Change;
30+class Action;
31+class BatchAction;
32+class ActionQueue;
33+
34+enum ChangeType {
35+ CHANGE_NONE,
36+ CHANGE_TILE,
37+ CHANGE_MOVE_HOUSE_EXIT,
38+ CHANGE_MOVE_WAYPOINT,
39+};
40+
41+class Change {
42+private:
43+ ChangeType type;
44+ void* data;
45+
46+ Change();
47+
48+public:
49+ Change(Tile* tile);
50+ static Change* Create(House* house, const Position& where);
51+ static Change* Create(Waypoint* wp, const Position& where);
52+ ~Change();
53+ void clear();
54+
55+ ChangeType getType() const {
56+ return type;
57+ }
58+ void* getData() const {
59+ return data;
60+ }
61+
62+ // Get memory footprint
63+ uint32_t memsize() const;
64+
65+ friend class Action;
66+};
67+
68+typedef std::vector<Change*> ChangeList;
69+
70+// A dirty list represents a list of all tiles that was changed in an action
71+class DirtyList {
72+public:
73+ DirtyList();
74+ ~DirtyList();
75+
76+ struct ValueType {
77+ uint32_t pos;
78+ uint32_t floors;
79+ };
80+
81+ uint32_t owner;
82+
83+protected:
84+ struct Comparator {
85+ bool operator()(const ValueType& a, const ValueType& b) const {
86+ return a.pos < b.pos;
87+ }
88+ };
89+
90+public:
91+ typedef std::set<ValueType, Comparator> SetType;
92+
93+ void AddPosition(int x, int y, int z);
94+ void AddChange(Change* c);
95+ bool Empty() const {
96+ return iset.empty() && ichanges.empty();
97+ }
98+ SetType& GetPosList();
99+ ChangeList& GetChanges();
100+
101+protected:
102+ SetType iset;
103+ ChangeList ichanges;
104+};
105+
106+enum ActionIdentifier {
107+ ACTION_MOVE,
108+ ACTION_REMOTE,
109+ ACTION_SELECT,
110+ ACTION_DELETE_TILES,
111+ ACTION_CUT_TILES,
112+ ACTION_PASTE_TILES,
113+ ACTION_RANDOMIZE,
114+ ACTION_BORDERIZE,
115+ ACTION_DRAW,
116+ ACTION_SWITCHDOOR,
117+ ACTION_ROTATE_ITEM,
118+ ACTION_REPLACE_ITEMS,
119+ ACTION_CHANGE_PROPERTIES,
120+};
121+
122+class Action {
123+public:
124+ virtual ~Action();
125+
126+ void addChange(Change* t) {
127+ changes.push_back(t);
128+ }
129+
130+ // Get memory footprint
131+ size_t approx_memsize() const;
132+ size_t memsize() const;
133+ size_t size() const {
134+ return changes.size();
135+ }
136+ ActionIdentifier getType() const {
137+ return type;
138+ }
139+
140+ void commit(DirtyList* dirty_list);
141+ bool isCommited() const {
142+ return commited;
143+ }
144+ void undo(DirtyList* dirty_list);
145+ void redo(DirtyList* dirty_list) {
146+ commit(dirty_list);
147+ }
148+
149+protected:
150+ Action(Editor& editor, ActionIdentifier ident);
151+
152+ bool commited;
153+ ChangeList changes;
154+ Editor& editor;
155+ ActionIdentifier type;
156+
157+ friend class ActionQueue;
158+};
159+
160+typedef std::vector<Action*> ActionVector;
161+
162+class BatchAction {
163+public:
164+ virtual ~BatchAction();
165+
166+ void resetTimer() {
167+ timestamp = 0;
168+ }
169+
170+ // Get memory footprint
171+ size_t memsize(bool resize = false) const;
172+ size_t size() const {
173+ return batch.size();
174+ }
175+ ActionIdentifier getType() const {
176+ return type;
177+ }
178+
179+ virtual void addAction(Action* action);
180+ virtual void addAndCommitAction(Action* action);
181+
182+protected:
183+ BatchAction(Editor& editor, ActionIdentifier ident);
184+
185+ virtual void commit();
186+ virtual void undo();
187+ virtual void redo();
188+
189+ void merge(BatchAction* other);
190+
191+ Editor& editor;
192+ int timestamp;
193+ uint32_t memory_size;
194+ ActionIdentifier type;
195+ ActionVector batch;
196+
197+ friend class ActionQueue;
198+};
199+
200+class ActionQueue {
201+public:
202+ ActionQueue(Editor& editor);
203+ virtual ~ActionQueue();
204+
205+ typedef std::deque<BatchAction*> ActionList;
206+
207+ void resetTimer();
208+
209+ virtual Action* createAction(ActionIdentifier ident);
210+ virtual Action* createAction(BatchAction* parent);
211+ virtual BatchAction* createBatch(ActionIdentifier ident);
212+
213+ void addBatch(BatchAction* action, int stacking_delay = 0);
214+ void addAction(Action* action, int stacking_delay = 0);
215+
216+ void undo();
217+ void redo();
218+ void clear();
219+
220+ bool canUndo() {
221+ return current > 0;
222+ }
223+ bool canRedo() {
224+ return current < actions.size();
225+ }
226+
227+protected:
228+ size_t current;
229+ size_t memory_size;
230+ Editor& editor;
231+ ActionList actions;
232+};
233+
234+#endif
A RD_Map_Editor/source/add_creature_dialog.cpp +129-0 View file
@@ -0,0 +1,129 @@
1+#include "main.h"
2+#include "add_creature_dialog.h"
3+#include "materials.h"
4+#include "creature_brush.h"
5+
6+#include <wx/spinctrl.h>
7+#include <wx/statbox.h>
8+#include <wx/sizer.h>
9+#include <wx/stattext.h>
10+#include <wx/textctrl.h>
11+#include <wx/radiobox.h>
12+
13+BEGIN_EVENT_TABLE(AddCreatureDialog, wxDialog)
14+ EVT_BUTTON(wxID_OK, AddCreatureDialog::OnClickOK)
15+ EVT_BUTTON(wxID_CANCEL, AddCreatureDialog::OnClickCancel)
16+END_EVENT_TABLE()
17+
18+AddCreatureDialog::AddCreatureDialog(wxWindow* parent, const wxString& name) :
19+ wxDialog(parent, wxID_ANY, "Add New Creature", wxDefaultPosition, wxDefaultSize),
20+ creature_type(nullptr)
21+{
22+ // Create sizers
23+ wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
24+ wxBoxSizer* form_sizer = new wxBoxSizer(wxVERTICAL);
25+
26+ // Create form fields
27+ wxStaticBoxSizer* name_box = new wxStaticBoxSizer(wxVERTICAL, this, "Name");
28+ name_field = new wxTextCtrl(this, wxID_ANY, name);
29+ name_box->Add(name_field, 0, wxEXPAND | wxALL, 5);
30+ form_sizer->Add(name_box, 0, wxEXPAND | wxALL, 5);
31+
32+ // Type selection
33+ wxArrayString type_choices;
34+ type_choices.Add("Monster");
35+ type_choices.Add("NPC");
36+ type_radio = new wxRadioBox(this, wxID_ANY, "Creature Type",
37+ wxDefaultPosition, wxDefaultSize,
38+ type_choices, 1, wxRA_SPECIFY_ROWS);
39+ form_sizer->Add(type_radio, 0, wxEXPAND | wxALL, 5);
40+
41+ // Outfit settings
42+ wxStaticBoxSizer* outfit_box = new wxStaticBoxSizer(wxVERTICAL, this, "Outfit");
43+ wxFlexGridSizer* outfit_sizer = new wxFlexGridSizer(2, 5, 5);
44+
45+ outfit_sizer->Add(new wxStaticText(this, wxID_ANY, "Look Type:"));
46+ looktype_field = new wxSpinCtrl(this, wxID_ANY, "1", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 1, 1500, 1);
47+ outfit_sizer->Add(looktype_field);
48+
49+ outfit_sizer->Add(new wxStaticText(this, wxID_ANY, "Head:"));
50+ lookhead_field = new wxSpinCtrl(this, wxID_ANY, "0", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 255, 0);
51+ outfit_sizer->Add(lookhead_field);
52+
53+ outfit_sizer->Add(new wxStaticText(this, wxID_ANY, "Body:"));
54+ lookbody_field = new wxSpinCtrl(this, wxID_ANY, "0", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 255, 0);
55+ outfit_sizer->Add(lookbody_field);
56+
57+ outfit_sizer->Add(new wxStaticText(this, wxID_ANY, "Legs:"));
58+ looklegs_field = new wxSpinCtrl(this, wxID_ANY, "0", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 255, 0);
59+ outfit_sizer->Add(looklegs_field);
60+
61+ outfit_sizer->Add(new wxStaticText(this, wxID_ANY, "Feet:"));
62+ lookfeet_field = new wxSpinCtrl(this, wxID_ANY, "0", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 255, 0);
63+ outfit_sizer->Add(lookfeet_field);
64+
65+ outfit_box->Add(outfit_sizer, 0, wxEXPAND | wxALL, 5);
66+ form_sizer->Add(outfit_box, 0, wxEXPAND | wxALL, 5);
67+
68+ // Add buttons
69+ wxSizer* button_sizer = CreateButtonSizer(wxOK | wxCANCEL);
70+
71+ // Add everything to the dialog
72+ topsizer->Add(form_sizer, 1, wxEXPAND | wxALL, 5);
73+ topsizer->Add(button_sizer, 0, wxEXPAND | wxALL, 5);
74+
75+ SetSizerAndFit(topsizer);
76+}
77+
78+AddCreatureDialog::~AddCreatureDialog()
79+{
80+ // Clean up if needed
81+}
82+
83+void AddCreatureDialog::OnClickOK(wxCommandEvent& event)
84+{
85+ // Get values from form
86+ std::string name = name_field->GetValue().ToStdString();
87+ bool isNpc = (type_radio->GetSelection() == 1);
88+
89+ // Create outfit
90+ Outfit outfit;
91+ outfit.lookType = looktype_field->GetValue();
92+ outfit.lookHead = lookhead_field->GetValue();
93+ outfit.lookBody = lookbody_field->GetValue();
94+ outfit.lookLegs = looklegs_field->GetValue();
95+ outfit.lookFeet = lookfeet_field->GetValue();
96+
97+ // Create new creature type
98+ creature_type = g_creatures.addCreatureType(name, isNpc, outfit);
99+
100+ if (creature_type) {
101+ // Create brush and add to appropriate tileset
102+ Tileset* tileset = nullptr;
103+ if (isNpc) {
104+ tileset = g_materials.tilesets["NPCs"];
105+ } else {
106+ tileset = g_materials.tilesets["Others"];
107+ }
108+
109+ if (tileset) {
110+ Brush* brush = newd CreatureBrush(creature_type);
111+ g_brushes.addBrush(brush);
112+
113+ TilesetCategory* category = tileset->getCategory(TILESET_CREATURE);
114+ if (category) {
115+ category->brushlist.push_back(brush);
116+ }
117+ }
118+
119+ EndModal(wxID_OK);
120+ } else {
121+ wxMessageBox("Failed to create creature. The name might already be in use.",
122+ "Error", wxOK | wxICON_ERROR, this);
123+ }
124+}
125+
126+void AddCreatureDialog::OnClickCancel(wxCommandEvent& event)
127+{
128+ EndModal(wxID_CANCEL);
129+}
A RD_Map_Editor/source/add_creature_dialog.h +36-0 View file
@@ -0,0 +1,36 @@
1+#ifndef RME_ADD_CREATURE_DIALOG_H_
2+#define RME_ADD_CREATURE_DIALOG_H_
3+
4+#include <wx/dialog.h>
5+#include "creatures.h"
6+
7+class wxSpinCtrl;
8+class wxTextCtrl;
9+class wxRadioBox;
10+
11+class AddCreatureDialog : public wxDialog {
12+public:
13+ AddCreatureDialog(wxWindow* parent, const wxString& name);
14+ virtual ~AddCreatureDialog();
15+
16+ CreatureType* GetCreatureType() const { return creature_type; }
17+
18+protected:
19+ void OnClickOK(wxCommandEvent& event);
20+ void OnClickCancel(wxCommandEvent& event);
21+
22+private:
23+ wxTextCtrl* name_field;
24+ wxSpinCtrl* looktype_field;
25+ wxSpinCtrl* lookhead_field;
26+ wxSpinCtrl* lookbody_field;
27+ wxSpinCtrl* looklegs_field;
28+ wxSpinCtrl* lookfeet_field;
29+ wxRadioBox* type_radio;
30+
31+ CreatureType* creature_type;
32+
33+ DECLARE_EVENT_TABLE()
34+};
35+
36+#endif // RME_ADD_CREATURE_DIALOG_H_
A RD_Map_Editor/source/add_item_window.cpp +441-0 View file
@@ -0,0 +1,441 @@
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/grid.h>
21+#include <wx/choice.h>
22+#include <wx/spinctrl.h>
23+
24+#include "tile.h"
25+#include "item.h"
26+#include "complexitem.h"
27+#include "town.h"
28+#include "house.h"
29+#include "map.h"
30+#include "editor.h"
31+#include "creature.h"
32+#include "materials.h"
33+#include "tileset.h"
34+
35+#include "gui.h"
36+#include "application.h"
37+#include "add_item_window.h"
38+#include "container_properties_window.h"
39+#include "find_item_window.h"
40+#include "palette_window.h"
41+#include "palette_brushlist.h"
42+#include "brush.h"
43+#include "raw_brush.h"
44+
45+// Forward declarations
46+class PaletteWindow;
47+class BrushPalettePanel;
48+
49+// ============================================================================
50+// Add Item Window
51+
52+static constexpr int OUTFIT_COLOR_MAX = 133;
53+
54+BEGIN_EVENT_TABLE(AddItemWindow, wxDialog)
55+EVT_BUTTON(wxID_OK, AddItemWindow::OnClickOK)
56+EVT_BUTTON(wxID_CANCEL, AddItemWindow::OnClickCancel)
57+EVT_CHECKBOX(wxID_ANY, AddItemWindow::OnRangeToggle)
58+EVT_SPINCTRL(wxID_ANY, AddItemWindow::OnRangeFieldChange)
59+END_EVENT_TABLE()
60+
61+AddItemWindow::AddItemWindow(wxWindow* win_parent, TilesetCategoryType categoryType, Tileset* tilesetItem, wxPoint pos) :
62+ ObjectPropertiesWindowBase(win_parent, "Add a Item", pos),
63+ item_id(0),
64+ tileset_item(tilesetItem),
65+ category_type(categoryType),
66+ item_id_field(nullptr),
67+ item_id_label(nullptr),
68+ item_name_label(nullptr),
69+ item_button(nullptr),
70+ tileset_choice(nullptr),
71+ range_checkbox(nullptr),
72+ range_start_field(nullptr),
73+ range_end_field(nullptr),
74+ range_start_label(nullptr),
75+ range_end_label(nullptr),
76+ range_info_label(nullptr) {
77+ wxSizer* topsizer = newd wxBoxSizer(wxVERTICAL);
78+ wxString description = "Add a Item";
79+
80+ wxSizer* boxsizer = newd wxStaticBoxSizer(wxVERTICAL, this, description);
81+
82+ wxFlexGridSizer* subsizer = newd wxFlexGridSizer(2, 10, 10);
83+ subsizer->AddGrowableCol(1);
84+
85+ uint16_t itemId = 0;
86+ std::string itemName = "None";
87+ item_id_label = newd wxStaticText(this, wxID_ANY, "ID " + i2ws(itemId));
88+ subsizer->Add(item_id_label);
89+ item_name_label = newd wxStaticText(this, wxID_ANY, "\"" + wxstr(itemName) + "\"");
90+ subsizer->Add(item_name_label);
91+
92+ subsizer->Add(newd wxStaticText(this, wxID_ANY, "Item"), wxSizerFlags(1).CenterVertical());
93+ item_button = newd DCButton(this, wxID_ANY, wxDefaultPosition, DC_BTN_TOGGLE, RENDER_SIZE_32x32, 0);
94+ subsizer->Add(item_button);
95+
96+ subsizer->Add(newd wxStaticText(this, wxID_ANY, "Item Id"));
97+ item_id_field = newd wxSpinCtrl(this, wxID_ANY, i2ws(itemId), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 100, 100000);
98+ item_id_field->Connect(wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler(AddItemWindow::OnChangeItemId), NULL, this);
99+ subsizer->Add(item_id_field, wxSizerFlags(1).Expand());
100+
101+ // Add range selection checkbox
102+ subsizer->Add(newd wxStaticText(this, wxID_ANY, "Add multiple items"));
103+ wxBoxSizer* range_checkbox_sizer = newd wxBoxSizer(wxHORIZONTAL);
104+ range_checkbox = newd wxCheckBox(this, wxID_ANY, "Add a range of items");
105+ range_checkbox_sizer->Add(range_checkbox, 1, wxEXPAND | wxRIGHT, 5);
106+
107+ // Add a "Quick Range" button
108+ wxButton* quick_range_button = newd wxButton(this, wxID_ANY, "Quick +10 Range", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
109+ quick_range_button->Connect(wxEVT_BUTTON, wxCommandEventHandler(AddItemWindow::OnQuickRange), NULL, this);
110+ quick_range_button->SetToolTip("Quickly set up a range from current ID to current ID + 10");
111+ range_checkbox_sizer->Add(quick_range_button, 0, wxALIGN_CENTER_VERTICAL);
112+
113+ subsizer->Add(range_checkbox_sizer, wxSizerFlags(1).Expand());
114+
115+ // Add range selection fields (initially hidden)
116+ range_start_label = newd wxStaticText(this, wxID_ANY, "Range start");
117+ subsizer->Add(range_start_label);
118+ range_start_field = newd wxSpinCtrl(this, wxID_ANY, i2ws(itemId), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 100, 100000);
119+ range_start_field->Connect(wxEVT_SPINCTRL, wxSpinEventHandler(AddItemWindow::OnRangeFieldChange), NULL, this);
120+ subsizer->Add(range_start_field, wxSizerFlags(1).Expand());
121+
122+ range_end_label = newd wxStaticText(this, wxID_ANY, "Range end");
123+ subsizer->Add(range_end_label);
124+
125+ // Create a horizontal sizer for the end field and button
126+ wxBoxSizer* end_field_sizer = newd wxBoxSizer(wxHORIZONTAL);
127+ range_end_field = newd wxSpinCtrl(this, wxID_ANY, i2ws(itemId), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 100, 100000);
128+ range_end_field->Connect(wxEVT_SPINCTRL, wxSpinEventHandler(AddItemWindow::OnRangeFieldChange), NULL, this);
129+ end_field_sizer->Add(range_end_field, 1, wxEXPAND | wxRIGHT, 5);
130+
131+ // Add a "Use Current" button
132+ wxButton* use_current_button = newd wxButton(this, wxID_ANY, "Use Current Item", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
133+ use_current_button->Connect(wxEVT_BUTTON, wxCommandEventHandler(AddItemWindow::OnUseCurrentItem), NULL, this);
134+ end_field_sizer->Add(use_current_button, 0, wxALIGN_CENTER_VERTICAL);
135+
136+ subsizer->Add(end_field_sizer, wxSizerFlags(1).Expand());
137+
138+ // Info label
139+ subsizer->Add(newd wxStaticText(this, wxID_ANY, "Status"));
140+ range_info_label = newd wxStaticText(this, wxID_ANY, "");
141+ subsizer->Add(range_info_label);
142+
143+ // Initially hide range controls
144+ UpdateRangeFields(false);
145+
146+ // Add tileset selection dropdown
147+ subsizer->Add(newd wxStaticText(this, wxID_ANY, "Tileset"));
148+ tileset_choice = newd wxChoice(this, wxID_ANY);
149+
150+ // Populate the choice with all available tilesets
151+ int currentSelection = 0;
152+ int index = 0;
153+ for (TilesetContainer::iterator iter = g_materials.tilesets.begin(); iter != g_materials.tilesets.end(); ++iter) {
154+ if (iter->second->getCategory(category_type)) {
155+ tileset_choice->Append(wxstr(iter->first));
156+ // Set current selection to the provided tileset if it matches
157+ if (tileset_item && iter->second == tileset_item) {
158+ currentSelection = index;
159+ }
160+ index++;
161+ }
162+ }
163+
164+ // Select the current tileset if available
165+ if (tileset_choice->GetCount() > 0) {
166+ tileset_choice->SetSelection(currentSelection);
167+ }
168+
169+ subsizer->Add(tileset_choice, wxSizerFlags(1).Expand());
170+
171+ // Add "Quick Selection Range" button that will use selected brushes from palette
172+ wxButton* quick_selection_button = newd wxButton(this, wxID_ANY, "Add Selection from Palette",
173+ wxDefaultPosition, wxDefaultSize, 0);
174+ quick_selection_button->Connect(wxEVT_BUTTON, wxCommandEventHandler(AddItemWindow::OnQuickRange), NULL, this);
175+ quick_selection_button->SetToolTip("Add all items currently selected in the palette to the tileset");
176+ subsizer->Add(newd wxStaticText(this, wxID_ANY, "Quick Add"));
177+ subsizer->Add(quick_selection_button, wxSizerFlags(1).Expand());
178+
179+ boxsizer->Add(subsizer, wxSizerFlags(1).Expand());
180+
181+ topsizer->Add(boxsizer, wxSizerFlags(0).Expand().Border(wxLEFT | wxRIGHT, 20));
182+
183+ wxSizer* subsizer_ = newd wxBoxSizer(wxHORIZONTAL);
184+ subsizer_->Add(newd wxButton(this, wxID_OK, "Add"), wxSizerFlags(1).Center().Border(wxTOP | wxBOTTOM, 10));
185+ subsizer_->Add(newd wxButton(this, wxID_CANCEL, "Cancel"), wxSizerFlags(1).Center().Border(wxTOP | wxBOTTOM, 10));
186+ topsizer->Add(subsizer_, wxSizerFlags(0).Center().Border(wxLEFT | wxRIGHT, 20));
187+
188+ SetSizerAndFit(topsizer);
189+ Centre(wxBOTH);
190+
191+ item_button->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(AddItemWindow::OnItemClicked), NULL, this);
192+}
193+
194+void AddItemWindow::OnClickOK(wxCommandEvent& WXUNUSED(event)) {
195+ // Get the selected tileset name from the dropdown
196+ wxString tilesetName;
197+ if (tileset_choice && tileset_choice->GetSelection() != wxNOT_FOUND) {
198+ tilesetName = tileset_choice->GetString(tileset_choice->GetSelection());
199+ }
200+
201+ // If no tileset is selected, use the initial tileset
202+ if (tilesetName.IsEmpty() && tileset_item) {
203+ tilesetName = wxstr(tileset_item->name);
204+ }
205+
206+ // Make sure we have a valid tileset name
207+ if (tilesetName.IsEmpty()) {
208+ g_gui.PopupDialog("Error", "No tileset selected", wxOK);
209+ return;
210+ }
211+
212+ std::string stdTilesetName = nstr(tilesetName);
213+
214+ // Check if we're in range mode
215+ if (range_checkbox && range_checkbox->GetValue() &&
216+ range_start_field && range_end_field) {
217+
218+ int start = range_start_field->GetValue();
219+ int end = range_end_field->GetValue();
220+
221+ // Validate range
222+ if (start > end) {
223+ g_gui.PopupDialog("Error", "Invalid range: start ID is greater than end ID", wxOK);
224+ return;
225+ }
226+
227+ // Add all items in range
228+ int added_count = 0;
229+ for (int id = start; id <= end; id++) {
230+ const ItemType& it = g_items.getItemType(id);
231+ if (it.id != 0) {
232+ g_materials.addToTileset(stdTilesetName, it.id, category_type);
233+ added_count++;
234+ }
235+ }
236+
237+ // Save changes
238+ g_materials.modify();
239+
240+ // Show success message
241+ if (added_count > 0) {
242+ g_gui.PopupDialog("Items Added",
243+ wxString::Format("Successfully added %d items (IDs %d-%d) to tileset '%s'",
244+ added_count, start, end, tilesetName), wxOK);
245+ EndModal(1);
246+ } else {
247+ g_gui.PopupDialog("Error", "No valid items found in the specified range", wxOK);
248+ }
249+ }
250+ // Single item mode
251+ else {
252+ const ItemType& it = g_items.getItemType(item_id_field->GetValue());
253+ if (it.id != 0) {
254+ g_materials.addToTileset(stdTilesetName, it.id, category_type);
255+ g_materials.modify();
256+ g_gui.PopupDialog("Item Added",
257+ wxString::Format("'%s' (ID: %d) has been added to tileset '%s'",
258+ wxString(it.name.c_str()), it.id, tilesetName), wxOK);
259+ EndModal(1);
260+ } else {
261+ g_gui.PopupDialog("Error", "You need to select an item", wxOK);
262+ }
263+ }
264+}
265+
266+void AddItemWindow::OnClickCancel(wxCommandEvent& WXUNUSED(event)) {
267+ // Just close this window
268+ EndModal(0);
269+}
270+
271+void AddItemWindow::OnChangeItemId(wxCommandEvent& WXUNUSED(event)) {
272+ uint16_t itemId = item_id_field->GetValue();
273+ ItemType& it = g_items[itemId];
274+ if (it.id != 0) {
275+ item_id_label->SetLabelText("ID " + i2ws(it.id));
276+ item_name_label->SetLabelText("\"" + wxstr(it.name) + "\"");
277+
278+ item_button->SetSprite(it.clientID);
279+
280+ // Update range start field if range mode is enabled
281+ if (range_checkbox && range_checkbox->GetValue() && range_start_field) {
282+ range_start_field->SetValue(itemId);
283+ UpdateRangeInfo();
284+ }
285+ } else {
286+ item_id_field->SetValue(100);
287+ }
288+}
289+
290+void AddItemWindow::OnItemClicked(wxMouseEvent& WXUNUSED(event)) {
291+ // Get the GUI instance to access the current palette and selected brush
292+ GUI* gui = &g_gui;
293+ if (!gui) return;
294+
295+ // Get the selected brush from the current palette
296+ Brush* brush = gui->GetCurrentBrush();
297+ if (brush) {
298+ uint16_t id = 0;
299+
300+ // Try to get the ID from the brush
301+ if (brush->isRaw()) {
302+ RAWBrush* raw_brush = brush->asRaw();
303+ if (raw_brush) {
304+ id = raw_brush->getItemID();
305+ }
306+ }
307+
308+ // If we got a valid ID, set it
309+ if (id > 0) {
310+ SetItemIdToItemButton(id);
311+ return;
312+ }
313+ }
314+
315+ // Fall back to the FindItemDialog if needed
316+ FindItemDialog dialog(this, "Item");
317+ if (dialog.ShowModal() == wxID_OK) {
318+ uint16_t id = dialog.getResultID();
319+ SetItemIdToItemButton(id);
320+ }
321+ dialog.Destroy();
322+}
323+
324+void AddItemWindow::SetItemIdToItemButton(uint16_t id) {
325+ if (!item_button) {
326+ return;
327+ }
328+
329+ if (id != 0) {
330+ const ItemType& it = g_items.getItemType(id);
331+ if (it.id != 0) {
332+ item_id_field->SetValue(it.id);
333+ item_id_label->SetLabelText("ID " + i2ws(it.id));
334+ item_name_label->SetLabelText("\"" + wxstr(it.name) + "\"");
335+
336+ item_button->SetSprite(it.clientID);
337+ return;
338+ }
339+ }
340+
341+ item_button->SetSprite(0);
342+}
343+
344+void AddItemWindow::UpdateRangeFields(bool show) {
345+ if (range_start_label) range_start_label->Show(show);
346+ if (range_start_field) range_start_field->Show(show);
347+ if (range_end_label) range_end_label->Show(show);
348+ if (range_end_field) range_end_field->Show(show);
349+ if (range_info_label) range_info_label->Show(show);
350+
351+ // Set range fields values if showing
352+ if (show && item_id_field) {
353+ int current_id = item_id_field->GetValue();
354+ if (range_start_field) range_start_field->SetValue(current_id);
355+ if (range_end_field) range_end_field->SetValue(current_id + 10); // Default to 10 item range
356+
357+ // Update info label
358+ UpdateRangeInfo();
359+ }
360+
361+ Layout();
362+ Fit();
363+}
364+
365+void AddItemWindow::UpdateRangeInfo() {
366+ if (!range_info_label || !range_start_field || !range_end_field)
367+ return;
368+
369+ int start = range_start_field->GetValue();
370+ int end = range_end_field->GetValue();
371+
372+ if (start > end) {
373+ range_info_label->SetLabel(wxString::Format("Invalid range (start > end)"));
374+ range_info_label->SetForegroundColour(*wxRED);
375+ } else {
376+ int count = end - start + 1;
377+ range_info_label->SetLabel(wxString::Format("%d items will be added", count));
378+ range_info_label->SetForegroundColour(*wxBLACK);
379+ }
380+}
381+
382+void AddItemWindow::OnRangeToggle(wxCommandEvent& event) {
383+ if (range_checkbox) {
384+ bool checked = range_checkbox->GetValue();
385+ UpdateRangeFields(checked);
386+ }
387+}
388+
389+void AddItemWindow::OnRangeFieldChange(wxSpinEvent& event) {
390+ UpdateRangeInfo();
391+
392+ // If the event is from the end field, update the sprite preview
393+ if (event.GetId() == range_end_field->GetId()) {
394+ uint16_t id = range_end_field->GetValue();
395+ const ItemType& it = g_items.getItemType(id);
396+ if (it.id != 0) {
397+ item_id_field->SetValue(id);
398+ item_id_label->SetLabelText("ID " + i2ws(it.id));
399+ item_name_label->SetLabelText("\"" + wxstr(it.name) + "\"");
400+ item_button->SetSprite(it.clientID);
401+ }
402+ }
403+}
404+
405+void AddItemWindow::OnUseCurrentItem(wxCommandEvent& event) {
406+ // Get the current item ID from the field instead of the button
407+ if (item_id_field) {
408+ uint16_t id = item_id_field->GetValue();
409+ if (id != 0) {
410+ range_end_field->SetValue(id);
411+ UpdateRangeInfo();
412+ }
413+ }
414+}
415+
416+void AddItemWindow::OnQuickRange(wxCommandEvent& WXUNUSED(event)) {
417+ // For now, just set a range of +10 items from the current item ID
418+ if (item_id_field) {
419+ uint16_t id = item_id_field->GetValue();
420+ if (id != 0) {
421+ // Enable range mode
422+ if (range_checkbox) {
423+ range_checkbox->SetValue(true);
424+ UpdateRangeFields(true);
425+ }
426+
427+ // Set range start to current ID
428+ if (range_start_field) {
429+ range_start_field->SetValue(id);
430+ }
431+
432+ // Set range end to current ID + 10
433+ if (range_end_field) {
434+ range_end_field->SetValue(id + 10);
435+ }
436+
437+ // Update range info
438+ UpdateRangeInfo();
439+ }
440+ }
441+}
A RD_Map_Editor/source/add_item_window.h +69-0 View file
@@ -0,0 +1,69 @@
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_ADD_ITEM_WINDOW_H_
19+#define RME_ADD_ITEM_WINDOW_H_
20+
21+#include "main.h"
22+
23+#include "common_windows.h"
24+
25+class ContainerItemButton;
26+class ContainerItemPopupMenu;
27+
28+class AddItemWindow : public ObjectPropertiesWindowBase {
29+public:
30+ AddItemWindow(wxWindow* parent, TilesetCategoryType categoryType, Tileset* tilesetItem, wxPoint = wxDefaultPosition);
31+
32+ void OnItemClicked(wxMouseEvent& event);
33+ void SetItemIdToItemButton(uint16_t id);
34+ void OnChangeItemId(wxCommandEvent& WXUNUSED(event));
35+ void OnRangeToggle(wxCommandEvent& WXUNUSED(event));
36+ void OnRangeFieldChange(wxSpinEvent& event);
37+ void OnUseCurrentItem(wxCommandEvent& event);
38+ void OnQuickRange(wxCommandEvent& event);
39+ void UpdateRangeFields(bool show);
40+ void UpdateRangeInfo();
41+
42+ void OnClickOK(wxCommandEvent&);
43+ void OnClickCancel(wxCommandEvent&);
44+
45+ wxChoice* tileset_choice;
46+
47+protected:
48+ int item_id;
49+
50+ wxSpinCtrl* item_id_field;
51+ wxStaticText* item_id_label;
52+ wxStaticText* item_name_label;
53+ DCButton* item_button;
54+
55+ // Range selection controls
56+ wxCheckBox* range_checkbox;
57+ wxSpinCtrl* range_start_field;
58+ wxSpinCtrl* range_end_field;
59+ wxStaticText* range_start_label;
60+ wxStaticText* range_end_label;
61+ wxStaticText* range_info_label;
62+
63+ TilesetCategoryType category_type;
64+ Tileset* tileset_item;
65+
66+ DECLARE_EVENT_TABLE();
67+};
68+
69+#endif
A RD_Map_Editor/source/add_tileset_window.cpp +160-0 View file
@@ -0,0 +1,160 @@
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/grid.h>
21+
22+#include "tile.h"
23+#include "item.h"
24+#include "complexitem.h"
25+#include "town.h"
26+#include "house.h"
27+#include "map.h"
28+#include "editor.h"
29+#include "creature.h"
30+#include "materials.h"
31+#include "tileset.h"
32+
33+#include "gui.h"
34+#include "application.h"
35+#include "add_tileset_window.h"
36+#include "container_properties_window.h"
37+#include "find_item_window.h"
38+
39+// ============================================================================
40+// Add Tileset Window
41+
42+BEGIN_EVENT_TABLE(AddTilesetWindow, wxDialog)
43+EVT_BUTTON(wxID_OK, AddTilesetWindow::OnClickOK)
44+EVT_BUTTON(wxID_CANCEL, AddTilesetWindow::OnClickCancel)
45+END_EVENT_TABLE()
46+
47+static constexpr int OUTFIT_COLOR_MAX = 133;
48+
49+AddTilesetWindow::AddTilesetWindow(wxWindow* win_parent, TilesetCategoryType categoryType, wxPoint pos) :
50+ ObjectPropertiesWindowBase(win_parent, "Add a Tileset", pos),
51+ category_type(categoryType),
52+ item_id_label(nullptr),
53+ item_name_label(nullptr),
54+ tileset_name_field(nullptr),
55+ item_id_field(nullptr),
56+ item_button(nullptr) {
57+ wxSizer* topsizer = newd wxBoxSizer(wxVERTICAL);
58+ wxString description = "Add a Tileset";
59+
60+ wxSizer* boxsizer = newd wxStaticBoxSizer(wxVERTICAL, this, description);
61+
62+ wxFlexGridSizer* subsizer = newd wxFlexGridSizer(2, 10, 10);
63+ subsizer->AddGrowableCol(1);
64+
65+ uint16_t itemId = 0;
66+ std::string itemName = "None";
67+ item_id_label = newd wxStaticText(this, wxID_ANY, "ID " + i2ws(itemId));
68+ subsizer->Add(item_id_label);
69+ item_name_label = newd wxStaticText(this, wxID_ANY, "\"" + wxstr(itemName) + "\"");
70+ subsizer->Add(item_name_label);
71+
72+ subsizer->Add(newd wxStaticText(this, wxID_ANY, "Item"), wxSizerFlags(1).CenterVertical());
73+ item_button = newd DCButton(this, wxID_ANY, wxDefaultPosition, DC_BTN_TOGGLE, RENDER_SIZE_32x32, 0);
74+ subsizer->Add(item_button);
75+
76+ subsizer->Add(newd wxStaticText(this, wxID_ANY, "Item Id of First Item"));
77+ item_id_field = newd wxSpinCtrl(this, wxID_ANY, i2ws(itemId), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 100, 100000);
78+ subsizer->Add(item_id_field, wxSizerFlags(1).Expand());
79+
80+ subsizer->Add(newd wxStaticText(this, wxID_ANY, "Tileset Name"));
81+ tileset_name_field = newd wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE);
82+ subsizer->Add(tileset_name_field);
83+
84+ boxsizer->Add(subsizer, wxSizerFlags(1).Expand());
85+
86+ topsizer->Add(boxsizer, wxSizerFlags(0).Expand().Border(wxLEFT | wxRIGHT, 20));
87+
88+ wxSizer* subsizer_ = newd wxBoxSizer(wxHORIZONTAL);
89+ subsizer_->Add(newd wxButton(this, wxID_OK, "Add"), wxSizerFlags(1).Center().Border(wxTOP | wxBOTTOM, 10));
90+ subsizer_->Add(newd wxButton(this, wxID_CANCEL, "Cancel"), wxSizerFlags(1).Center().Border(wxTOP | wxBOTTOM, 10));
91+ topsizer->Add(subsizer_, wxSizerFlags(0).Center().Border(wxLEFT | wxRIGHT, 20));
92+
93+ SetSizerAndFit(topsizer);
94+ Centre(wxBOTH);
95+
96+ item_id_field->Connect(wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler(AddTilesetWindow::OnChangeItemId), NULL, this);
97+ item_button->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(AddTilesetWindow::OnItemClicked), NULL, this);
98+}
99+
100+void AddTilesetWindow::OnChangeItemId(wxCommandEvent& WXUNUSED(event)) {
101+ uint16_t itemId = item_id_field->GetValue();
102+ ItemType& it = g_items[itemId];
103+ if (it.id != 0) {
104+ item_id_label->SetLabelText("ID " + i2ws(it.id));
105+ item_name_label->SetLabelText("\"" + wxstr(it.name) + "\"");
106+
107+ item_button->SetSprite(it.clientID);
108+ } else {
109+ item_id_field->SetValue(100);
110+ }
111+}
112+
113+void AddTilesetWindow::OnItemClicked(wxMouseEvent& WXUNUSED(event)) {
114+ FindItemDialog dialog(this, "Item");
115+ if (dialog.ShowModal() == wxID_OK) {
116+ uint16_t id = dialog.getResultID();
117+ SetItemIdToItemButton(id);
118+ }
119+ dialog.Destroy();
120+}
121+
122+void AddTilesetWindow::SetItemIdToItemButton(uint16_t id) {
123+ if (!item_button) {
124+ return;
125+ }
126+
127+ if (id != 0) {
128+ const ItemType& it = g_items.getItemType(id);
129+ if (it.id != 0) {
130+ item_id_field->SetValue(it.id);
131+ item_id_label->SetLabelText("ID " + i2ws(it.id));
132+ item_name_label->SetLabelText("\"" + wxstr(it.name) + "\"");
133+
134+ item_button->SetSprite(it.clientID);
135+ return;
136+ }
137+ }
138+
139+ item_button->SetSprite(0);
140+}
141+
142+void AddTilesetWindow::OnClickOK(wxCommandEvent& WXUNUSED(event)) {
143+ uint16_t itemId = item_id_field->GetValue();
144+ ItemType& it = g_items[itemId];
145+ if (it.id != 0) {
146+ std::string tilesetName = std::string(tileset_name_field->GetValue().mb_str());
147+ g_materials.addToTileset(tilesetName, it.id, category_type);
148+ g_materials.modify();
149+ g_gui.PopupDialog("Added Tileset", "'" + it.name + "' has been added to new tileset '" + tilesetName + "'", wxOK);
150+
151+ EndModal(1);
152+ } else {
153+ g_gui.PopupDialog(this, "Error", "Item does not exist.", wxOK);
154+ }
155+}
156+
157+void AddTilesetWindow::OnClickCancel(wxCommandEvent& WXUNUSED(event)) {
158+ // Just close this window
159+ EndModal(0);
160+}
A RD_Map_Editor/source/add_tileset_window.h +52-0 View file
@@ -0,0 +1,52 @@
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_ADD_TILESET_WINDOW_H_
19+#define RME_ADD_TILESET_WINDOW_H_
20+
21+#include "main.h"
22+
23+#include "common_windows.h"
24+
25+class ContainerItemButton;
26+class ContainerItemPopupMenu;
27+
28+class AddTilesetWindow : public ObjectPropertiesWindowBase {
29+public:
30+ AddTilesetWindow(wxWindow* parent, TilesetCategoryType categoryType, wxPoint = wxDefaultPosition);
31+
32+ void OnItemClicked(wxMouseEvent& event);
33+ void SetItemIdToItemButton(uint16_t id);
34+ void OnChangeItemId(wxCommandEvent& WXUNUSED(event));
35+
36+ void OnClickOK(wxCommandEvent&);
37+ void OnClickCancel(wxCommandEvent&);
38+
39+protected:
40+ wxTextCtrl* tileset_name_field;
41+ wxSpinCtrl* item_id_field;
42+
43+ wxStaticText* item_id_label;
44+ wxStaticText* item_name_label;
45+
46+ TilesetCategoryType category_type;
47+ DCButton* item_button;
48+
49+ DECLARE_EVENT_TABLE();
50+};
51+
52+#endif
Top