otmapgen_dialog.cpp

main 1196 lines · 47.4 KB Raw
Alex Alex Commit Map Editor 18/10/2026 23:40
1//////////////////////////////////////////////////////////////////////
2// This file is part of Remere's Map Editor
3//////////////////////////////////////////////////////////////////////
4// Remere's Map Editor is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Remere's Map Editor is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//////////////////////////////////////////////////////////////////////
17
18#include "main.h"
19#include "otmapgen_dialog.h"
20#include "otmapgen.h"
21#include "application.h"
22#include "editor.h"
23#include "settings.h"
24#include "action.h"
25#include "iomap.h"
26#include "gui.h"
27
28#include <wx/process.h>
29#include <wx/stream.h>
30#include <wx/stdpaths.h>
31#include <wx/filename.h>
32#include <wx/dir.h>
33
34wxBEGIN_EVENT_TABLE(OTMapGenDialog, wxDialog)
35 EVT_BUTTON(ID_GENERATE, OTMapGenDialog::OnGenerate)
36 EVT_BUTTON(ID_PREVIEW, OTMapGenDialog::OnPreview)
37 EVT_BUTTON(wxID_CANCEL, OTMapGenDialog::OnCancel)
38 EVT_BUTTON(ID_FLOOR_UP, OTMapGenDialog::OnFloorUp)
39 EVT_BUTTON(ID_FLOOR_DOWN, OTMapGenDialog::OnFloorDown)
40 EVT_BUTTON(ID_ZOOM_IN, OTMapGenDialog::OnZoomIn)
41 EVT_BUTTON(ID_ZOOM_OUT, OTMapGenDialog::OnZoomOut)
42 EVT_TEXT(ID_SEED_TEXT, OTMapGenDialog::OnSeedChange)
43 EVT_SPINCTRL(ID_WIDTH_SPIN, OTMapGenDialog::OnParameterChange)
44 EVT_SPINCTRL(ID_HEIGHT_SPIN, OTMapGenDialog::OnParameterChange)
45 EVT_CHOICE(ID_VERSION_CHOICE, OTMapGenDialog::OnParameterChangeText)
46 EVT_CHOICE(ID_MOUNTAIN_TYPE_CHOICE, OTMapGenDialog::OnMountainTypeChange)
47
48 // Terrain layer management events
49 EVT_LIST_ITEM_SELECTED(ID_TERRAIN_LAYER_LIST, OTMapGenDialog::OnTerrainLayerSelect)
50 EVT_BUTTON(ID_ADD_LAYER, OTMapGenDialog::OnTerrainLayerAdd)
51 EVT_BUTTON(ID_REMOVE_LAYER, OTMapGenDialog::OnTerrainLayerRemove)
52 EVT_BUTTON(ID_MOVE_UP_LAYER, OTMapGenDialog::OnTerrainLayerMoveUp)
53 EVT_BUTTON(ID_MOVE_DOWN_LAYER, OTMapGenDialog::OnTerrainLayerMoveDown)
54 EVT_BUTTON(ID_EDIT_LAYER, OTMapGenDialog::OnTerrainLayerEdit)
55 EVT_CHOICE(ID_LAYER_BRUSH_CHOICE, OTMapGenDialog::OnBrushChoice)
56 EVT_COMMAND(ID_LAYER_ITEM_ID_SPIN, wxEVT_COMMAND_SPINCTRL_UPDATED, OTMapGenDialog::OnItemIdChange)
57 EVT_CHOICE(ID_CAVE_BRUSH_CHOICE, OTMapGenDialog::OnBrushChoice)
58 EVT_COMMAND(ID_CAVE_ITEM_ID_SPIN, wxEVT_COMMAND_SPINCTRL_UPDATED, OTMapGenDialog::OnItemIdChange)
59 EVT_CHOICE(ID_WATER_BRUSH_CHOICE, OTMapGenDialog::OnBrushChoice)
60 EVT_COMMAND(ID_WATER_ITEM_ID_SPIN, wxEVT_COMMAND_SPINCTRL_UPDATED, OTMapGenDialog::OnItemIdChange)
61wxEND_EVENT_TABLE()
62
63OTMapGenDialog::OTMapGenDialog(wxWindow* parent) :
64 wxDialog(parent, wxID_ANY, "Procedural Map Generator", wxDefaultPosition, wxSize(1000, 700), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
65 current_preview(nullptr),
66 current_preview_floor(7), // Start with ground level (floor 7)
67 current_zoom(1.0),
68 preview_offset_x(0),
69 preview_offset_y(0) {
70
71 // Create main sizer
72 wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
73
74 // Create notebook for tabs
75 wxNotebook* notebook = new wxNotebook(this, wxID_ANY);
76
77 // === Main Settings Tab (merged basic + advanced) ===
78 wxPanel* main_panel = new wxPanel(notebook);
79 wxBoxSizer* settings_main_sizer = new wxBoxSizer(wxHORIZONTAL);
80
81 // Left side - All Settings
82 wxBoxSizer* left_main_sizer = new wxBoxSizer(wxVERTICAL);
83
84 // Basic parameters group (more compact)
85 wxStaticBoxSizer* basic_params_sizer = new wxStaticBoxSizer(wxVERTICAL, main_panel, "Basic Parameters");
86 wxFlexGridSizer* basic_grid_sizer = new wxFlexGridSizer(3, 4, 5, 5);
87 basic_grid_sizer->AddGrowableCol(1);
88 basic_grid_sizer->AddGrowableCol(3);
89
90 // Row 1: Seed and Width
91 basic_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Seed:"), 0, wxALIGN_CENTER_VERTICAL);
92 seed_text_ctrl = new wxTextCtrl(main_panel, ID_SEED_TEXT, wxString::Format("%lld", (long long)time(nullptr) * 1000));
93 seed_text_ctrl->SetToolTip("Enter any integer value (supports 64-bit seeds)");
94 basic_grid_sizer->Add(seed_text_ctrl, 1, wxEXPAND);
95
96 basic_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Width:"), 0, wxALIGN_CENTER_VERTICAL);
97 width_spin_ctrl = new wxSpinCtrl(main_panel, ID_WIDTH_SPIN, "256", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 64, 2048, 256);
98 basic_grid_sizer->Add(width_spin_ctrl, 1, wxEXPAND);
99
100 // Row 2: Version and Height
101 basic_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Version:"), 0, wxALIGN_CENTER_VERTICAL);
102 wxArrayString versions;
103 versions.Add("10.98");
104 versions.Add("11.00");
105 versions.Add("12.00");
106 version_choice = new wxChoice(main_panel, ID_VERSION_CHOICE, wxDefaultPosition, wxDefaultSize, versions);
107 version_choice->SetSelection(0);
108 basic_grid_sizer->Add(version_choice, 1, wxEXPAND);
109
110 basic_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Height:"), 0, wxALIGN_CENTER_VERTICAL);
111 height_spin_ctrl = new wxSpinCtrl(main_panel, ID_HEIGHT_SPIN, "256", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 64, 2048, 256);
112 basic_grid_sizer->Add(height_spin_ctrl, 1, wxEXPAND);
113
114 // Row 3: Mountain Type and Water Level
115 basic_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Mountain Type:"), 0, wxALIGN_CENTER_VERTICAL);
116 wxArrayString mountain_types;
117 mountain_types.Add("MOUNTAIN");
118 mountain_types.Add("SNOW");
119 mountain_types.Add("SAND");
120 mountain_type_choice = new wxChoice(main_panel, ID_MOUNTAIN_TYPE_CHOICE, wxDefaultPosition, wxDefaultSize, mountain_types);
121 mountain_type_choice->SetSelection(0);
122 basic_grid_sizer->Add(mountain_type_choice, 1, wxEXPAND);
123
124 basic_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Water Level:"), 0, wxALIGN_CENTER_VERTICAL);
125 water_level_text = new wxTextCtrl(main_panel, ID_WATER_LEVEL_TEXT, "7");
126 water_level_text->SetToolTip("Tibia Z-coordinate (0-15, 7 = ground level)");
127 basic_grid_sizer->Add(water_level_text, 1, wxEXPAND);
128
129 basic_params_sizer->Add(basic_grid_sizer, 0, wxEXPAND | wxALL, 5);
130
131 // Checkboxes in horizontal layout
132 wxBoxSizer* checkbox_sizer = new wxBoxSizer(wxHORIZONTAL);
133 terrain_only_checkbox = new wxCheckBox(main_panel, wxID_ANY, "Terrain Only");
134 sand_biome_checkbox = new wxCheckBox(main_panel, wxID_ANY, "Sand Biome");
135 sand_biome_checkbox->SetValue(true);
136 smooth_coastline_checkbox = new wxCheckBox(main_panel, wxID_ANY, "Smooth Coastlines");
137 smooth_coastline_checkbox->SetValue(true);
138 add_caves_checkbox = new wxCheckBox(main_panel, wxID_ANY, "Underground Caves");
139 add_caves_checkbox->SetValue(true);
140
141 checkbox_sizer->Add(terrain_only_checkbox, 0, wxALL, 5);
142 checkbox_sizer->Add(sand_biome_checkbox, 0, wxALL, 5);
143 checkbox_sizer->Add(smooth_coastline_checkbox, 0, wxALL, 5);
144 checkbox_sizer->Add(add_caves_checkbox, 0, wxALL, 5);
145
146 basic_params_sizer->Add(checkbox_sizer, 0, wxEXPAND | wxALL, 5);
147 left_main_sizer->Add(basic_params_sizer, 0, wxEXPAND | wxALL, 5);
148
149 // Advanced parameters group (more compact)
150 wxStaticBoxSizer* advanced_params_sizer = new wxStaticBoxSizer(wxVERTICAL, main_panel, "Noise & Generation Parameters");
151 wxFlexGridSizer* advanced_grid_sizer = new wxFlexGridSizer(4, 4, 5, 5);
152 advanced_grid_sizer->AddGrowableCol(1);
153 advanced_grid_sizer->AddGrowableCol(3);
154
155 // Row 1: Noise Increment and Island Distance
156 advanced_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Noise Increment:"), 0, wxALIGN_CENTER_VERTICAL);
157 noise_increment_text = new wxTextCtrl(main_panel, ID_NOISE_INCREMENT_TEXT, "1.0");
158 noise_increment_text->SetToolTip("Range: 0.001 - 100.0 (higher = more detail)");
159 advanced_grid_sizer->Add(noise_increment_text, 1, wxEXPAND);
160
161 advanced_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Island Distance:"), 0, wxALIGN_CENTER_VERTICAL);
162 island_distance_text = new wxTextCtrl(main_panel, ID_ISLAND_DISTANCE_TEXT, "0.92");
163 island_distance_text->SetToolTip("Range: 0.001 - 100.0 (lower = more island effect)");
164 advanced_grid_sizer->Add(island_distance_text, 1, wxEXPAND);
165
166 // Row 2: Exponent and Linear
167 advanced_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Exponent:"), 0, wxALIGN_CENTER_VERTICAL);
168 exponent_text = new wxTextCtrl(main_panel, ID_EXPONENT_TEXT, "1.4");
169 exponent_text->SetToolTip("Range: 0.001 - 100.0 (height curve shaping)");
170 advanced_grid_sizer->Add(exponent_text, 1, wxEXPAND);
171
172 advanced_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Linear:"), 0, wxALIGN_CENTER_VERTICAL);
173 linear_text = new wxTextCtrl(main_panel, ID_LINEAR_TEXT, "6.0");
174 linear_text->SetToolTip("Range: 0.001 - 100.0 (height multiplier)");
175 advanced_grid_sizer->Add(linear_text, 1, wxEXPAND);
176
177 // Row 3: Cave Depth and Cave Roughness
178 advanced_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Cave Depth:"), 0, wxALIGN_CENTER_VERTICAL);
179 cave_depth_text = new wxTextCtrl(main_panel, ID_CAVE_DEPTH_TEXT, "20");
180 cave_depth_text->SetToolTip("Range: 1 - 100 (number of underground floors)");
181 advanced_grid_sizer->Add(cave_depth_text, 1, wxEXPAND);
182
183 advanced_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Cave Roughness:"), 0, wxALIGN_CENTER_VERTICAL);
184 cave_roughness_text = new wxTextCtrl(main_panel, ID_CAVE_ROUGHNESS_TEXT, "0.45");
185 cave_roughness_text->SetToolTip("Range: 0.001 - 100.0 (noise scale for caves)");
186 advanced_grid_sizer->Add(cave_roughness_text, 1, wxEXPAND);
187
188 // Row 4: Cave Chance (spanning two columns)
189 advanced_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Cave Chance:"), 0, wxALIGN_CENTER_VERTICAL);
190 cave_chance_text = new wxTextCtrl(main_panel, ID_CAVE_CHANCE_TEXT, "0.09");
191 cave_chance_text->SetToolTip("Range: 0.001 - 1.0 (probability of cave generation)");
192 advanced_grid_sizer->Add(cave_chance_text, 1, wxEXPAND);
193
194 // Add spacers for the remaining cells
195 advanced_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, ""), 0);
196 advanced_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, ""), 0);
197
198 advanced_params_sizer->Add(advanced_grid_sizer, 0, wxEXPAND | wxALL, 5);
199 left_main_sizer->Add(advanced_params_sizer, 0, wxEXPAND | wxALL, 5);
200
201 // Right side - Large Preview section
202 wxBoxSizer* right_main_sizer = new wxBoxSizer(wxVERTICAL);
203
204 wxStaticBoxSizer* preview_sizer = new wxStaticBoxSizer(wxVERTICAL, main_panel, "Map Preview");
205 preview_bitmap = new wxStaticBitmap(main_panel, wxID_ANY, wxBitmap(400, 400));
206 preview_bitmap->SetBackgroundColour(*wxBLACK);
207 preview_bitmap->SetMinSize(wxSize(400, 400));
208 preview_sizer->Add(preview_bitmap, 1, wxEXPAND | wxALL, 5);
209
210 // Floor navigation
211 wxBoxSizer* floor_nav_sizer = new wxBoxSizer(wxHORIZONTAL);
212 floor_down_button = new wxButton(main_panel, ID_FLOOR_DOWN, "Floor -");
213 floor_label = new wxStaticText(main_panel, wxID_ANY, "Floor: 7 (Ground)");
214 floor_up_button = new wxButton(main_panel, ID_FLOOR_UP, "Floor +");
215
216 floor_nav_sizer->Add(floor_down_button, 0, wxALL, 2);
217 floor_nav_sizer->AddStretchSpacer();
218 floor_nav_sizer->Add(floor_label, 0, wxALIGN_CENTER_VERTICAL | wxALL, 2);
219 floor_nav_sizer->AddStretchSpacer();
220 floor_nav_sizer->Add(floor_up_button, 0, wxALL, 2);
221
222 preview_sizer->Add(floor_nav_sizer, 0, wxEXPAND | wxALL, 5);
223
224 // Zoom navigation
225 wxBoxSizer* zoom_nav_sizer = new wxBoxSizer(wxHORIZONTAL);
226 zoom_out_button = new wxButton(main_panel, ID_ZOOM_OUT, "Zoom -");
227 zoom_label = new wxStaticText(main_panel, wxID_ANY, "Zoom: 100%");
228 zoom_in_button = new wxButton(main_panel, ID_ZOOM_IN, "Zoom +");
229
230 zoom_nav_sizer->Add(zoom_out_button, 0, wxALL, 2);
231 zoom_nav_sizer->AddStretchSpacer();
232 zoom_nav_sizer->Add(zoom_label, 0, wxALIGN_CENTER_VERTICAL | wxALL, 2);
233 zoom_nav_sizer->AddStretchSpacer();
234 zoom_nav_sizer->Add(zoom_in_button, 0, wxALL, 2);
235
236 preview_sizer->Add(zoom_nav_sizer, 0, wxEXPAND | wxALL, 5);
237
238 // Preview buttons
239 wxBoxSizer* preview_buttons_sizer = new wxBoxSizer(wxHORIZONTAL);
240 preview_button = new wxButton(main_panel, ID_PREVIEW, "Generate Preview");
241 wxButton* refresh_preview_button = new wxButton(main_panel, ID_PREVIEW, "Refresh");
242
243 preview_buttons_sizer->Add(preview_button, 1, wxEXPAND | wxALL, 2);
244 preview_buttons_sizer->Add(refresh_preview_button, 0, wxALL, 2);
245
246 preview_sizer->Add(preview_buttons_sizer, 0, wxEXPAND | wxALL, 5);
247
248 right_main_sizer->Add(preview_sizer, 1, wxEXPAND | wxALL, 5);
249
250 // Add left and right sides to main tab
251 settings_main_sizer->Add(left_main_sizer, 0, wxEXPAND | wxALL, 5);
252 settings_main_sizer->Add(right_main_sizer, 1, wxEXPAND | wxALL, 5);
253
254 main_panel->SetSizer(settings_main_sizer);
255 notebook->AddPage(main_panel, "Map Generation", true);
256
257 // === Layout Design Tab ===
258 wxPanel* layout_panel = new wxPanel(notebook);
259 wxBoxSizer* layout_sizer = new wxBoxSizer(wxVERTICAL);
260
261 // Terrain Layers section
262 wxStaticBoxSizer* terrain_layers_sizer = new wxStaticBoxSizer(wxHORIZONTAL, layout_panel, "Terrain Layers");
263
264 // Terrain layer list
265 terrain_layer_list = new wxListCtrl(layout_panel, ID_TERRAIN_LAYER_LIST, wxDefaultPosition, wxSize(300, 200),
266 wxLC_REPORT | wxLC_SINGLE_SEL);
267 terrain_layer_list->InsertColumn(0, "Name", wxLIST_FORMAT_LEFT, 100);
268 terrain_layer_list->InsertColumn(1, "Brush", wxLIST_FORMAT_LEFT, 100);
269 terrain_layer_list->InsertColumn(2, "Item ID", wxLIST_FORMAT_LEFT, 60);
270 terrain_layer_list->InsertColumn(3, "Height", wxLIST_FORMAT_LEFT, 80);
271 terrain_layer_list->InsertColumn(4, "Enabled", wxLIST_FORMAT_LEFT, 60);
272
273 terrain_layers_sizer->Add(terrain_layer_list, 1, wxEXPAND | wxALL, 5);
274
275 // Layer control buttons
276 wxBoxSizer* layer_buttons_sizer = new wxBoxSizer(wxVERTICAL);
277 add_layer_button = new wxButton(layout_panel, ID_ADD_LAYER, "Add Layer");
278 remove_layer_button = new wxButton(layout_panel, ID_REMOVE_LAYER, "Remove Layer");
279 move_up_button = new wxButton(layout_panel, ID_MOVE_UP_LAYER, "Move Up");
280 move_down_button = new wxButton(layout_panel, ID_MOVE_DOWN_LAYER, "Move Down");
281 edit_layer_button = new wxButton(layout_panel, ID_EDIT_LAYER, "Edit Layer");
282
283 layer_buttons_sizer->Add(add_layer_button, 0, wxEXPAND | wxALL, 2);
284 layer_buttons_sizer->Add(remove_layer_button, 0, wxEXPAND | wxALL, 2);
285 layer_buttons_sizer->Add(move_up_button, 0, wxEXPAND | wxALL, 2);
286 layer_buttons_sizer->Add(move_down_button, 0, wxEXPAND | wxALL, 2);
287 layer_buttons_sizer->Add(edit_layer_button, 0, wxEXPAND | wxALL, 2);
288 layer_buttons_sizer->AddStretchSpacer();
289
290 terrain_layers_sizer->Add(layer_buttons_sizer, 0, wxEXPAND | wxALL, 5);
291
292 // Layer Properties section
293 wxStaticBoxSizer* layer_props_sizer = new wxStaticBoxSizer(wxVERTICAL, layout_panel, "Layer Properties");
294 layer_properties_panel = new wxPanel(layout_panel);
295 wxFlexGridSizer* props_grid_sizer = new wxFlexGridSizer(5, 4, 5, 10);
296 props_grid_sizer->AddGrowableCol(1);
297 props_grid_sizer->AddGrowableCol(3);
298
299 // Row 1: Name and Brush
300 props_grid_sizer->Add(new wxStaticText(layer_properties_panel, wxID_ANY, "Name:"), 0, wxALIGN_CENTER_VERTICAL);
301 layer_name_text = new wxTextCtrl(layer_properties_panel, wxID_ANY, "");
302 props_grid_sizer->Add(layer_name_text, 1, wxEXPAND);
303
304 props_grid_sizer->Add(new wxStaticText(layer_properties_panel, wxID_ANY, "Brush:"), 0, wxALIGN_CENTER_VERTICAL);
305 layer_brush_choice = new wxChoice(layer_properties_panel, ID_LAYER_BRUSH_CHOICE);
306 props_grid_sizer->Add(layer_brush_choice, 1, wxEXPAND);
307
308 // Row 2: Item ID and Z-Order
309 props_grid_sizer->Add(new wxStaticText(layer_properties_panel, wxID_ANY, "Item ID:"), 0, wxALIGN_CENTER_VERTICAL);
310 layer_item_id_spin = new wxSpinCtrl(layer_properties_panel, ID_LAYER_ITEM_ID_SPIN, "100", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 100, 65535, 100);
311 props_grid_sizer->Add(layer_item_id_spin, 1, wxEXPAND);
312
313 props_grid_sizer->Add(new wxStaticText(layer_properties_panel, wxID_ANY, "Z-Order:"), 0, wxALIGN_CENTER_VERTICAL);
314 z_order_spin = new wxSpinCtrl(layer_properties_panel, wxID_ANY, "1000", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 10000, 1000);
315 props_grid_sizer->Add(z_order_spin, 1, wxEXPAND);
316
317 // Row 3: Height range
318 props_grid_sizer->Add(new wxStaticText(layer_properties_panel, wxID_ANY, "Height Min:"), 0, wxALIGN_CENTER_VERTICAL);
319 height_min_text = new wxTextCtrl(layer_properties_panel, wxID_ANY, "0.0");
320 props_grid_sizer->Add(height_min_text, 1, wxEXPAND);
321
322 props_grid_sizer->Add(new wxStaticText(layer_properties_panel, wxID_ANY, "Height Max:"), 0, wxALIGN_CENTER_VERTICAL);
323 height_max_text = new wxTextCtrl(layer_properties_panel, wxID_ANY, "1.0");
324 props_grid_sizer->Add(height_max_text, 1, wxEXPAND);
325
326 // Row 4: Moisture range
327 props_grid_sizer->Add(new wxStaticText(layer_properties_panel, wxID_ANY, "Moisture Min:"), 0, wxALIGN_CENTER_VERTICAL);
328 moisture_min_text = new wxTextCtrl(layer_properties_panel, wxID_ANY, "-1.0");
329 props_grid_sizer->Add(moisture_min_text, 1, wxEXPAND);
330
331 props_grid_sizer->Add(new wxStaticText(layer_properties_panel, wxID_ANY, "Moisture Max:"), 0, wxALIGN_CENTER_VERTICAL);
332 moisture_max_text = new wxTextCtrl(layer_properties_panel, wxID_ANY, "1.0");
333 props_grid_sizer->Add(moisture_max_text, 1, wxEXPAND);
334
335 // Row 5: Noise scale and Coverage
336 props_grid_sizer->Add(new wxStaticText(layer_properties_panel, wxID_ANY, "Noise Scale:"), 0, wxALIGN_CENTER_VERTICAL);
337 noise_scale_text = new wxTextCtrl(layer_properties_panel, wxID_ANY, "1.0");
338 props_grid_sizer->Add(noise_scale_text, 1, wxEXPAND);
339
340 props_grid_sizer->Add(new wxStaticText(layer_properties_panel, wxID_ANY, "Coverage:"), 0, wxALIGN_CENTER_VERTICAL);
341 coverage_text = new wxTextCtrl(layer_properties_panel, wxID_ANY, "1.0");
342 props_grid_sizer->Add(coverage_text, 1, wxEXPAND);
343
344 layer_properties_panel->SetSizer(props_grid_sizer);
345 layer_props_sizer->Add(layer_properties_panel, 1, wxEXPAND | wxALL, 5);
346
347 // Checkboxes for layer options
348 wxBoxSizer* layer_options_sizer = new wxBoxSizer(wxHORIZONTAL);
349 use_borders_checkbox = new wxCheckBox(layout_panel, wxID_ANY, "Use Borders");
350 use_borders_checkbox->SetValue(true);
351 layer_enabled_checkbox = new wxCheckBox(layout_panel, wxID_ANY, "Layer Enabled");
352 layer_enabled_checkbox->SetValue(true);
353
354 layer_options_sizer->Add(use_borders_checkbox, 0, wxALL, 5);
355 layer_options_sizer->Add(layer_enabled_checkbox, 0, wxALL, 5);
356 layer_props_sizer->Add(layer_options_sizer, 0, wxEXPAND | wxALL, 5);
357
358 layout_sizer->Add(layer_props_sizer, 0, wxEXPAND | wxALL, 5);
359
360 // Cave and Water Configuration section
361 wxStaticBoxSizer* special_terrain_sizer = new wxStaticBoxSizer(wxHORIZONTAL, layout_panel, "Cave & Water Configuration");
362
363 // Cave configuration
364 wxBoxSizer* cave_config_sizer = new wxBoxSizer(wxVERTICAL);
365 cave_config_sizer->Add(new wxStaticText(layout_panel, wxID_ANY, "Cave Configuration"), 0, wxALL, 2);
366
367 wxFlexGridSizer* cave_grid_sizer = new wxFlexGridSizer(2, 2, 5, 10);
368 cave_grid_sizer->AddGrowableCol(1);
369
370 cave_grid_sizer->Add(new wxStaticText(layout_panel, wxID_ANY, "Cave Brush:"), 0, wxALIGN_CENTER_VERTICAL);
371 cave_brush_choice = new wxChoice(layout_panel, ID_CAVE_BRUSH_CHOICE);
372 cave_grid_sizer->Add(cave_brush_choice, 1, wxEXPAND);
373
374 cave_grid_sizer->Add(new wxStaticText(layout_panel, wxID_ANY, "Cave Item ID:"), 0, wxALIGN_CENTER_VERTICAL);
375 cave_item_id_spin = new wxSpinCtrl(layout_panel, ID_CAVE_ITEM_ID_SPIN, "351", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 100, 65535, 351);
376 cave_grid_sizer->Add(cave_item_id_spin, 1, wxEXPAND);
377
378 cave_config_sizer->Add(cave_grid_sizer, 0, wxEXPAND | wxALL, 5);
379 special_terrain_sizer->Add(cave_config_sizer, 1, wxEXPAND | wxALL, 5);
380
381 // Water configuration
382 wxBoxSizer* water_config_sizer = new wxBoxSizer(wxVERTICAL);
383 water_config_sizer->Add(new wxStaticText(layout_panel, wxID_ANY, "Water Configuration"), 0, wxALL, 2);
384
385 wxFlexGridSizer* water_grid_sizer = new wxFlexGridSizer(2, 2, 5, 10);
386 water_grid_sizer->AddGrowableCol(1);
387
388 water_grid_sizer->Add(new wxStaticText(layout_panel, wxID_ANY, "Water Brush:"), 0, wxALIGN_CENTER_VERTICAL);
389 water_brush_choice = new wxChoice(layout_panel, ID_WATER_BRUSH_CHOICE);
390 water_grid_sizer->Add(water_brush_choice, 1, wxEXPAND);
391
392 water_grid_sizer->Add(new wxStaticText(layout_panel, wxID_ANY, "Water Item ID:"), 0, wxALIGN_CENTER_VERTICAL);
393 water_item_id_spin = new wxSpinCtrl(layout_panel, ID_WATER_ITEM_ID_SPIN, "4608", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 100, 65535, 4608);
394 water_grid_sizer->Add(water_item_id_spin, 1, wxEXPAND);
395
396 water_config_sizer->Add(water_grid_sizer, 0, wxEXPAND | wxALL, 5);
397 special_terrain_sizer->Add(water_config_sizer, 1, wxEXPAND | wxALL, 5);
398
399 layout_sizer->Add(special_terrain_sizer, 0, wxEXPAND | wxALL, 5);
400 layout_sizer->Add(terrain_layers_sizer, 1, wxEXPAND | wxALL, 5);
401
402 layout_panel->SetSizer(layout_sizer);
403 notebook->AddPage(layout_panel, "Layout Design", false);
404
405 main_sizer->Add(notebook, 1, wxEXPAND | wxALL, 5);
406
407 // Buttons
408 wxBoxSizer* button_sizer = new wxBoxSizer(wxHORIZONTAL);
409 generate_button = new wxButton(this, ID_GENERATE, "Generate Map");
410 cancel_button = new wxButton(this, wxID_CANCEL, "Cancel");
411
412 button_sizer->Add(generate_button, 0, wxALL, 5);
413 button_sizer->AddStretchSpacer();
414 button_sizer->Add(cancel_button, 0, wxALL, 5);
415
416 main_sizer->Add(button_sizer, 0, wxEXPAND | wxALL, 5);
417
418 SetSizer(main_sizer);
419 Center();
420
421 // Initialize terrain layers and brush choices
422 PopulateBrushChoices();
423
424 // Initialize default terrain layers
425 GenerationConfig defaultConfig;
426 defaultConfig.initializeDefaultLayers();
427 working_terrain_layers = defaultConfig.terrain_layers;
428
429 // Populate the terrain layer list
430 PopulateTerrainLayerList();
431
432 // Initially disable layer property controls
433 UpdateLayerControls();
434
435 // Generate initial random 64-bit seed
436 srand(time(nullptr));
437 long long initial_seed = ((long long)rand() << 32) | rand();
438 seed_text_ctrl->SetValue(wxString::Format("%lld", initial_seed));
439}
440
441OTMapGenDialog::~OTMapGenDialog() {
442 if (current_preview) {
443 delete current_preview;
444 }
445}
446
447void OTMapGenDialog::OnGenerate(wxCommandEvent& event) {
448 if (GenerateMap()) {
449 EndModal(wxID_OK);
450 }
451}
452
453void OTMapGenDialog::OnCancel(wxCommandEvent& event) {
454 EndModal(wxID_CANCEL);
455}
456
457void OTMapGenDialog::OnPreview(wxCommandEvent& event) {
458 UpdatePreview();
459}
460
461void OTMapGenDialog::OnSeedChange(wxCommandEvent& event) {
462 // Optional: Auto-update preview when seed changes
463}
464
465void OTMapGenDialog::OnParameterChange(wxSpinEvent& event) {
466 // Optional: Auto-update preview when parameters change
467}
468
469void OTMapGenDialog::OnParameterChangeText(wxCommandEvent& event) {
470 // Optional: Auto-update preview when parameters change
471}
472
473void OTMapGenDialog::OnMountainTypeChange(wxCommandEvent& event) {
474 // Optional: Auto-update preview when mountain type changes
475}
476
477void OTMapGenDialog::OnFloorUp(wxCommandEvent& event) {
478 if (current_preview_floor > 0) {
479 current_preview_floor--;
480 UpdateFloorLabel();
481 UpdatePreviewFloor();
482 }
483}
484
485void OTMapGenDialog::OnFloorDown(wxCommandEvent& event) {
486 if (current_preview_floor < 7) { // Only go up to floor 7
487 current_preview_floor++;
488 UpdateFloorLabel();
489 UpdatePreviewFloor();
490 }
491}
492
493void OTMapGenDialog::OnZoomIn(wxCommandEvent& event) {
494 if (current_zoom < 4.0) { // Max zoom 400%
495 current_zoom *= 1.25; // 25% increase
496 UpdateZoomLabel();
497 UpdatePreviewFloor();
498 }
499}
500
501void OTMapGenDialog::OnZoomOut(wxCommandEvent& event) {
502 if (current_zoom > 0.25) { // Min zoom 25%
503 current_zoom /= 1.25; // 25% decrease
504 UpdateZoomLabel();
505 UpdatePreviewFloor();
506 }
507}
508
509void OTMapGenDialog::UpdatePreview() {
510 preview_button->SetLabel("Generating...");
511 preview_button->Enable(false);
512
513 try {
514 // Create configuration from dialog values
515 GenerationConfig config = BuildGenerationConfig();
516
517 // Generate preview using C++ implementation
518 OTMapGenerator generator;
519 current_layers = generator.generateLayers(config);
520
521 // Update the preview for the current floor
522 UpdatePreviewFloor();
523
524 } catch (const std::exception& e) {
525 wxMessageBox(wxString::Format("Failed to generate preview: %s", e.what()), "Preview Error", wxOK | wxICON_ERROR);
526 }
527
528 preview_button->SetLabel("Generate Preview");
529 preview_button->Enable(true);
530}
531
532void OTMapGenDialog::UpdatePreviewFloor() {
533 if (current_layers.empty()) {
534 return; // No data to preview
535 }
536
537 try {
538 // Create configuration for scaling
539 GenerationConfig config = BuildGenerationConfig();
540
541 // Use the SAME mapping as OTBM generation:
542 // Preview floor 7 = layers[0] (ground level)
543 // Preview floor 6 = layers[1] (above ground 1)
544 // ...
545 // Preview floor 0 = layers[7] (highest above ground)
546 int layerIndex = 7 - current_preview_floor;
547
548 // Clamp to valid range
549 layerIndex = std::max(0, std::min(7, layerIndex));
550
551 if (layerIndex < static_cast<int>(current_layers.size()) && !current_layers[layerIndex].empty()) {
552 const auto& layerData = current_layers[layerIndex];
553 int preview_width = 400; // Updated to match the larger preview
554 int preview_height = 400; // Updated to match the larger preview
555 wxImage preview_image(preview_width, preview_height);
556
557 // Calculate zoom-adjusted scale and center point
558 double base_scale_x = (double)config.width / preview_width;
559 double base_scale_y = (double)config.height / preview_height;
560
561 // Apply zoom - higher zoom means smaller scale (show less area)
562 double scale_x = base_scale_x / current_zoom;
563 double scale_y = base_scale_y / current_zoom;
564
565 // Calculate center offset for zoomed view
566 int center_x = config.width / 2 + preview_offset_x;
567 int center_y = config.height / 2 + preview_offset_y;
568
569 for (int y = 0; y < preview_height; ++y) {
570 for (int x = 0; x < preview_width; ++x) {
571 // Calculate source coordinates with zoom and offset
572 int src_x = center_x + (int)((x - preview_width/2) * scale_x);
573 int src_y = center_y + (int)((y - preview_height/2) * scale_y);
574
575 // Set default color (black for out of bounds)
576 unsigned char r = 0, g = 0, b = 0;
577
578 if (src_x >= 0 && src_x < config.width && src_y >= 0 && src_y < config.height) {
579 // Calculate index in the 1D layer
580 int tileIndex = src_y * config.width + src_x;
581
582 if (tileIndex < static_cast<int>(layerData.size())) {
583 uint16_t tileId = layerData[tileIndex];
584
585 // Convert tile ID to color for preview
586 GetTilePreviewColor(tileId, r, g, b);
587 }
588 }
589
590 preview_image.SetRGB(x, y, r, g, b);
591 }
592 }
593
594 if (current_preview) {
595 delete current_preview;
596 }
597 current_preview = new wxBitmap(preview_image);
598 preview_bitmap->SetBitmap(*current_preview);
599 preview_bitmap->Refresh();
600 }
601
602 } catch (const std::exception& e) {
603 wxMessageBox(wxString::Format("Failed to update preview floor: %s", e.what()), "Preview Error", wxOK | wxICON_ERROR);
604 }
605}
606
607void OTMapGenDialog::UpdateFloorLabel() {
608 wxString label;
609 if (current_preview_floor == 7) {
610 label = "Floor: 7 (Ground)";
611 } else if (current_preview_floor < 7) {
612 label = wxString::Format("Floor: %d (Above Ground %d)", current_preview_floor, 7 - current_preview_floor);
613 } else {
614 label = wxString::Format("Floor: %d", current_preview_floor);
615 }
616 floor_label->SetLabel(label);
617}
618
619void OTMapGenDialog::UpdateZoomLabel() {
620 wxString label = wxString::Format("Zoom: %.0f%%", current_zoom * 100);
621 zoom_label->SetLabel(label);
622}
623
624bool OTMapGenDialog::GenerateMap() {
625 try {
626 // Create configuration from dialog values
627 GenerationConfig config = BuildGenerationConfig();
628
629 wxProgressDialog progress("Generating Map", "Please wait while the map is being generated...", 100, this, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
630 progress.Pulse();
631
632 // Generate the map data using C++ implementation
633 OTMapGenerator generator;
634 auto layers = generator.generateLayers(config);
635
636 if (layers.empty()) {
637 wxMessageBox("Failed to generate map data.", "Generation Error", wxOK | wxICON_ERROR);
638 return false;
639 }
640
641 // Create a temporary map to hold our generated content
642 Map tempMap;
643 tempMap.setWidth(config.width);
644 tempMap.setHeight(config.height);
645 tempMap.setName("Generated Map");
646 tempMap.setMapDescription("Procedurally generated map");
647
648 // Set map properties
649 tempMap.setSpawnFilename("");
650 tempMap.setHouseFilename("");
651
652 // Place generated tiles into the temporary map
653 // Floor mapping for Tibia coordinates:
654 // - Floor 7 = ground level (surface) ← layers[0] should go here
655 // - Floors 6-0 = above ground (6 is just above surface, 0 is highest) ← layers[1-7] go here
656 //
657 // We need to REVERSE the mapping when writing to OTBM:
658 // - layers[0] → Tibia floor 7 (ground level)
659 // - layers[1] → Tibia floor 6 (above ground level 1)
660 // - layers[7] → Tibia floor 0 (highest above ground)
661
662 for (int layerIndex = 0; layerIndex < static_cast<int>(layers.size()) && layerIndex < 8; ++layerIndex) {
663 // REVERSE the mapping: layers[0]→floor 7, layers[1]→floor 6, ..., layers[7]→floor 0
664 int tibiaZ = 7 - layerIndex; // This puts ground level (layers[0]) at floor 7
665
666 // Extract tiles from the 1D layer data
667 const auto& layerData = layers[layerIndex];
668 int tileIndex = 0;
669
670 for (int y = 0; y < config.height; ++y) {
671 for (int x = 0; x < config.width; ++x) {
672 if (tileIndex >= static_cast<int>(layerData.size())) {
673 break; // Safety check
674 }
675
676 uint16_t tileId = layerData[tileIndex++];
677 if (tileId != 0) {
678 Position pos(x, y, tibiaZ);
679
680 // Create tile in temp map using the safe method
681 TileLocation* location = tempMap.createTileL(pos);
682 Tile* tile = tempMap.allocator(location);
683
684 // Create ground item
685 Item* groundItem = Item::Create(tileId);
686 if (groundItem) {
687 tile->ground = groundItem;
688 tempMap.setTile(pos, tile);
689 } else {
690 delete tile; // Clean up if item creation failed
691 }
692 }
693 }
694 }
695 }
696
697 // Add decorations if enabled (only on surface level - Tibia Z=7)
698 if (!config.terrain_only && layers.size() >= 8) {
699 std::mt19937 decoration_rng(std::hash<std::string>{}(config.seed));
700 std::uniform_real_distribution<double> dist(0.0, 1.0);
701
702 // Use the ground level layer (layers[0] will become Tibia floor 7)
703 const auto& surfaceLayer = layers[0];
704 int tileIndex = 0;
705
706 for (int y = 0; y < config.height; ++y) {
707 for (int x = 0; x < config.width; ++x) {
708 if (tileIndex >= static_cast<int>(surfaceLayer.size())) {
709 break; // Safety check
710 }
711
712 uint16_t tileId = surfaceLayer[tileIndex++];
713 if (tileId == 4526 && dist(decoration_rng) < 0.03) { // Use grass item ID directly
714 Position pos(x, y, 7); // Surface level in Tibia coordinates
715 Tile* tile = tempMap.getTile(pos);
716 if (tile) {
717 // Choose random decoration
718 uint16_t decorationId;
719 double rand_val = dist(decoration_rng);
720 if (rand_val < 0.6) {
721 decorationId = 2700; // Tree ID
722 } else if (rand_val < 0.8) {
723 decorationId = 2785; // Bush ID
724 } else {
725 decorationId = 2782; // Flower ID
726 }
727
728 Item* decoration = Item::Create(decorationId);
729 if (decoration) {
730 tile->addItem(decoration);
731 }
732 }
733 }
734 }
735 }
736 }
737
738 // Create a temporary OTBM file
739 wxString tempDir = wxStandardPaths::Get().GetTempDir();
740 wxString tempFileName = wxString::Format("generated_map_%ld.otbm", wxGetLocalTime());
741 wxString tempFilePath = tempDir + wxFileName::GetPathSeparator() + tempFileName;
742
743 // Save the temporary map as OTBM
744 progress.SetLabel("Saving temporary map file...");
745
746 // Use the map's save functionality
747 bool saveSuccess = false;
748 try {
749 IOMapOTBM mapLoader(tempMap.getVersion());
750 saveSuccess = mapLoader.saveMap(tempMap, wxFileName(tempFilePath));
751 } catch (...) {
752 saveSuccess = false;
753 }
754
755 if (!saveSuccess) {
756 wxMessageBox("Failed to save temporary map file.", "Save Error", wxOK | wxICON_ERROR);
757 return false;
758 }
759
760 progress.SetLabel("Loading generated map...");
761 progress.Pulse();
762
763 // Load the temporary file into the editor
764 bool loadSuccess = false;
765 try {
766 // Use the GUI's LoadMap function which handles everything properly
767 loadSuccess = g_gui.LoadMap(wxFileName(tempFilePath));
768 } catch (...) {
769 loadSuccess = false;
770 }
771
772 // Clean up the temporary file
773 if (wxFileExists(tempFilePath)) {
774 wxRemoveFile(tempFilePath);
775 }
776
777 if (loadSuccess) {
778 wxMessageBox("Procedural map generated and loaded successfully!", "Success", wxOK | wxICON_INFORMATION);
779 return true;
780 } else {
781 wxMessageBox("Failed to load the generated map.", "Load Error", wxOK | wxICON_ERROR);
782 return false;
783 }
784
785 } catch (const std::exception& e) {
786 wxMessageBox(wxString::Format("Map generation failed with error: %s", e.what()), "Generation Error", wxOK | wxICON_ERROR);
787 }
788
789 return false;
790}
791
792GenerationConfig OTMapGenDialog::BuildGenerationConfig() {
793 GenerationConfig config;
794
795 // Basic parameters
796 config.seed = seed_text_ctrl->GetValue().ToStdString();
797 config.width = width_spin_ctrl->GetValue();
798 config.height = height_spin_ctrl->GetValue();
799 config.version = version_choice->GetStringSelection().ToStdString();
800 config.mountain_type = mountain_type_choice->GetStringSelection().ToStdString();
801
802 // Boolean flags
803 config.terrain_only = terrain_only_checkbox->GetValue();
804 config.sand_biome = sand_biome_checkbox->GetValue();
805 config.smooth_coastline = smooth_coastline_checkbox->GetValue();
806 config.add_caves = add_caves_checkbox->GetValue();
807
808 // Advanced parameters - parse text inputs with enhanced validation and ranges
809 double noise_increment = 1.0;
810 double island_distance = 0.92;
811 double cave_depth = 20.0;
812 double cave_roughness = 0.45;
813 double cave_chance = 0.09;
814 double water_level = 7.0;
815 double exponent = 1.4;
816 double linear = 6.0;
817
818 if (!noise_increment_text->GetValue().ToDouble(&noise_increment)) noise_increment = 1.0;
819 if (!island_distance_text->GetValue().ToDouble(&island_distance)) island_distance = 0.92;
820 if (!cave_depth_text->GetValue().ToDouble(&cave_depth)) cave_depth = 20.0;
821 if (!cave_roughness_text->GetValue().ToDouble(&cave_roughness)) cave_roughness = 0.45;
822 if (!cave_chance_text->GetValue().ToDouble(&cave_chance)) cave_chance = 0.09;
823 if (!water_level_text->GetValue().ToDouble(&water_level)) water_level = 7.0;
824 if (!exponent_text->GetValue().ToDouble(&exponent)) exponent = 1.4;
825 if (!linear_text->GetValue().ToDouble(&linear)) linear = 6.0;
826
827 // Apply enhanced bounds checking with expanded ranges
828 noise_increment = std::max(0.001, std::min(100.0, noise_increment));
829 island_distance = std::max(0.001, std::min(100.0, island_distance));
830 cave_depth = std::max(1.0, std::min(100.0, cave_depth));
831 cave_roughness = std::max(0.001, std::min(100.0, cave_roughness));
832 cave_chance = std::max(0.001, std::min(1.0, cave_chance));
833 water_level = std::max(0.0, std::min(15.0, water_level));
834 exponent = std::max(0.001, std::min(100.0, exponent));
835 linear = std::max(0.001, std::min(100.0, linear));
836
837 config.noise_increment = noise_increment;
838 config.island_distance_decrement = island_distance;
839 config.cave_depth = (int)cave_depth;
840 config.cave_roughness = cave_roughness;
841 config.cave_chance = cave_chance;
842 config.water_level = (int)water_level;
843 config.exponent = exponent;
844 config.linear = linear;
845
846 // Add some default frequency settings for better island generation
847 config.frequencies.clear();
848 config.frequencies.push_back({1.0, 1.0}); // Base frequency
849 config.frequencies.push_back({2.0, 0.5}); // Higher frequency, lower weight
850 config.frequencies.push_back({4.0, 0.25}); // Even higher, even lower weight
851 config.frequencies.push_back({8.0, 0.125}); // Finest detail
852
853 // Enhanced island generation parameters
854 config.euclidean = true; // Use euclidean distance for smoother island shapes
855 config.island_distance_exponent = 2.0; // Smooth falloff
856
857 // Use the working terrain layers from the layout design tab
858 config.terrain_layers = working_terrain_layers;
859
860 // Update the sand biome layer's enabled state based on checkbox
861 for (auto& layer : config.terrain_layers) {
862 if (layer.name == "Sand") {
863 layer.enabled = config.sand_biome;
864 }
865 }
866
867 // Cave configuration
868 int caveSelection = cave_brush_choice->GetSelection();
869 if (caveSelection >= 0 && caveSelection < static_cast<int>(available_brushes.size())) {
870 config.cave_brush_name = available_brushes[caveSelection];
871 }
872 config.cave_item_id = cave_item_id_spin->GetValue();
873
874 // Water configuration
875 int waterSelection = water_brush_choice->GetSelection();
876 if (waterSelection >= 0 && waterSelection < static_cast<int>(available_brushes.size())) {
877 config.water_brush_name = available_brushes[waterSelection];
878 }
879 config.water_item_id = water_item_id_spin->GetValue();
880
881 return config;
882}
883
884// Terrain layer management event handlers
885void OTMapGenDialog::OnTerrainLayerSelect(wxListEvent& event) {
886 UpdateLayerControls();
887}
888
889void OTMapGenDialog::OnTerrainLayerAdd(wxCommandEvent& event) {
890 TerrainLayer newLayer;
891 newLayer.name = "New Layer";
892 newLayer.brush_name = "grass";
893 newLayer.item_id = 4526;
894 newLayer.height_min = 0.0;
895 newLayer.height_max = 1.0;
896 newLayer.moisture_min = -1.0;
897 newLayer.moisture_max = 1.0;
898 newLayer.noise_scale = 1.0;
899 newLayer.coverage = 1.0;
900 newLayer.use_borders = true;
901 newLayer.enabled = true;
902 newLayer.z_order = 1000;
903
904 working_terrain_layers.push_back(newLayer);
905 PopulateTerrainLayerList();
906
907 // Select the new layer
908 terrain_layer_list->SetItemState(working_terrain_layers.size() - 1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
909 UpdateLayerControls();
910}
911
912void OTMapGenDialog::OnTerrainLayerRemove(wxCommandEvent& event) {
913 long selected = terrain_layer_list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
914 if (selected >= 0 && selected < static_cast<long>(working_terrain_layers.size())) {
915 working_terrain_layers.erase(working_terrain_layers.begin() + selected);
916 PopulateTerrainLayerList();
917 UpdateLayerControls();
918 }
919}
920
921void OTMapGenDialog::OnTerrainLayerMoveUp(wxCommandEvent& event) {
922 long selected = terrain_layer_list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
923 if (selected > 0 && selected < static_cast<long>(working_terrain_layers.size())) {
924 std::swap(working_terrain_layers[selected], working_terrain_layers[selected - 1]);
925 PopulateTerrainLayerList();
926 terrain_layer_list->SetItemState(selected - 1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
927 UpdateLayerControls();
928 }
929}
930
931void OTMapGenDialog::OnTerrainLayerMoveDown(wxCommandEvent& event) {
932 long selected = terrain_layer_list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
933 if (selected >= 0 && selected < static_cast<long>(working_terrain_layers.size()) - 1) {
934 std::swap(working_terrain_layers[selected], working_terrain_layers[selected + 1]);
935 PopulateTerrainLayerList();
936 terrain_layer_list->SetItemState(selected + 1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
937 UpdateLayerControls();
938 }
939}
940
941void OTMapGenDialog::OnTerrainLayerEdit(wxCommandEvent& event) {
942 // Save current layer properties back to the selected layer
943 TerrainLayer* layer = GetSelectedLayer();
944 if (layer) {
945 layer->name = layer_name_text->GetValue().ToStdString();
946
947 int brushSelection = layer_brush_choice->GetSelection();
948 if (brushSelection >= 0 && brushSelection < static_cast<int>(available_brushes.size())) {
949 layer->brush_name = available_brushes[brushSelection];
950 }
951
952 layer->item_id = layer_item_id_spin->GetValue();
953
954 double height_min, height_max, moisture_min, moisture_max, noise_scale, coverage;
955 if (height_min_text->GetValue().ToDouble(&height_min)) layer->height_min = height_min;
956 if (height_max_text->GetValue().ToDouble(&height_max)) layer->height_max = height_max;
957 if (moisture_min_text->GetValue().ToDouble(&moisture_min)) layer->moisture_min = moisture_min;
958 if (moisture_max_text->GetValue().ToDouble(&moisture_max)) layer->moisture_max = moisture_max;
959 if (noise_scale_text->GetValue().ToDouble(&noise_scale)) layer->noise_scale = noise_scale;
960 if (coverage_text->GetValue().ToDouble(&coverage)) layer->coverage = coverage;
961
962 layer->use_borders = use_borders_checkbox->GetValue();
963 layer->enabled = layer_enabled_checkbox->GetValue();
964 layer->z_order = z_order_spin->GetValue();
965
966 PopulateTerrainLayerList();
967 }
968}
969
970void OTMapGenDialog::OnBrushChoice(wxCommandEvent& event) {
971 // Auto-update item ID when brush is changed
972 wxChoice* choice = dynamic_cast<wxChoice*>(event.GetEventObject());
973 if (choice) {
974 int selection = choice->GetSelection();
975 if (selection >= 0 && selection < static_cast<int>(available_brushes.size())) {
976 wxString brushName = available_brushes[selection];
977
978 // Try to get the primary item ID from the brush (placeholder - needs implementation)
979 // For now, use some default mappings based on brush name
980 uint16_t itemId = 100; // Default
981 if (brushName == "grass") itemId = 4526;
982 else if (brushName == "sea") itemId = 4608;
983 else if (brushName == "sand") itemId = 231;
984 else if (brushName == "mountain") itemId = 919;
985 else if (brushName == "cave") itemId = 351;
986 else if (brushName == "snow") itemId = 670;
987 else if (brushName == "stone") itemId = 1284;
988
989 if (choice->GetId() == ID_LAYER_BRUSH_CHOICE) {
990 layer_item_id_spin->SetValue(itemId);
991 } else if (choice->GetId() == ID_CAVE_BRUSH_CHOICE) {
992 cave_item_id_spin->SetValue(itemId);
993 } else if (choice->GetId() == ID_WATER_BRUSH_CHOICE) {
994 water_item_id_spin->SetValue(itemId);
995 }
996 }
997 }
998}
999
1000void OTMapGenDialog::OnItemIdChange(wxCommandEvent& event) {
1001 // Item ID changed - could trigger preview update
1002}
1003
1004// Helper functions for terrain layer management
1005void OTMapGenDialog::PopulateTerrainLayerList() {
1006 terrain_layer_list->DeleteAllItems();
1007
1008 for (size_t i = 0; i < working_terrain_layers.size(); ++i) {
1009 const TerrainLayer& layer = working_terrain_layers[i];
1010
1011 long index = terrain_layer_list->InsertItem(i, layer.name);
1012 terrain_layer_list->SetItem(index, 1, layer.brush_name);
1013 terrain_layer_list->SetItem(index, 2, wxString::Format("%d", layer.item_id));
1014 terrain_layer_list->SetItem(index, 3, wxString::Format("%.1f-%.1f", layer.height_min, layer.height_max));
1015 terrain_layer_list->SetItem(index, 4, layer.enabled ? "Yes" : "No");
1016 }
1017}
1018
1019void OTMapGenDialog::PopulateBrushChoices() {
1020 // Populate with common brush names from grounds.xml
1021 // This should be replaced with actual brush parsing from the XML files
1022 available_brushes.clear();
1023 available_brushes.push_back("grass");
1024 available_brushes.push_back("sea");
1025 available_brushes.push_back("sand");
1026 available_brushes.push_back("mountain");
1027 available_brushes.push_back("cave");
1028 available_brushes.push_back("snow");
1029 available_brushes.push_back("stone floor");
1030 available_brushes.push_back("wooden floor");
1031 available_brushes.push_back("lawn");
1032 available_brushes.push_back("ice");
1033
1034 // Populate all brush choice controls
1035 layer_brush_choice->Clear();
1036 cave_brush_choice->Clear();
1037 water_brush_choice->Clear();
1038
1039 for (const std::string& brush : available_brushes) {
1040 layer_brush_choice->Append(brush);
1041 cave_brush_choice->Append(brush);
1042 water_brush_choice->Append(brush);
1043 }
1044
1045 // Set default selections
1046 cave_brush_choice->SetStringSelection("cave");
1047 water_brush_choice->SetStringSelection("sea");
1048}
1049
1050void OTMapGenDialog::UpdateLayerControls() {
1051 TerrainLayer* layer = GetSelectedLayer();
1052 bool hasSelection = (layer != nullptr);
1053
1054 // Enable/disable controls based on selection
1055 layer_properties_panel->Enable(hasSelection);
1056 remove_layer_button->Enable(hasSelection);
1057 edit_layer_button->Enable(hasSelection);
1058
1059 long selected = terrain_layer_list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1060 move_up_button->Enable(hasSelection && selected > 0);
1061 move_down_button->Enable(hasSelection && selected < static_cast<long>(working_terrain_layers.size()) - 1);
1062
1063 if (hasSelection) {
1064 SetSelectedLayer(*layer);
1065 } else {
1066 // Clear controls
1067 layer_name_text->SetValue("");
1068 layer_brush_choice->SetSelection(-1);
1069 layer_item_id_spin->SetValue(100);
1070 height_min_text->SetValue("0.0");
1071 height_max_text->SetValue("1.0");
1072 moisture_min_text->SetValue("-1.0");
1073 moisture_max_text->SetValue("1.0");
1074 noise_scale_text->SetValue("1.0");
1075 coverage_text->SetValue("1.0");
1076 use_borders_checkbox->SetValue(false);
1077 layer_enabled_checkbox->SetValue(false);
1078 z_order_spin->SetValue(1000);
1079 }
1080}
1081
1082TerrainLayer* OTMapGenDialog::GetSelectedLayer() {
1083 long selected = terrain_layer_list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1084 if (selected >= 0 && selected < static_cast<long>(working_terrain_layers.size())) {
1085 return &working_terrain_layers[selected];
1086 }
1087 return nullptr;
1088}
1089
1090void OTMapGenDialog::SetSelectedLayer(const TerrainLayer& layer) {
1091 layer_name_text->SetValue(layer.name);
1092
1093 // Find and select the brush
1094 for (size_t i = 0; i < available_brushes.size(); ++i) {
1095 if (available_brushes[i] == layer.brush_name) {
1096 layer_brush_choice->SetSelection(i);
1097 break;
1098 }
1099 }
1100
1101 layer_item_id_spin->SetValue(layer.item_id);
1102 height_min_text->SetValue(wxString::Format("%.2f", layer.height_min));
1103 height_max_text->SetValue(wxString::Format("%.2f", layer.height_max));
1104 moisture_min_text->SetValue(wxString::Format("%.2f", layer.moisture_min));
1105 moisture_max_text->SetValue(wxString::Format("%.2f", layer.moisture_max));
1106 noise_scale_text->SetValue(wxString::Format("%.2f", layer.noise_scale));
1107 coverage_text->SetValue(wxString::Format("%.2f", layer.coverage));
1108 use_borders_checkbox->SetValue(layer.use_borders);
1109 layer_enabled_checkbox->SetValue(layer.enabled);
1110 z_order_spin->SetValue(layer.z_order);
1111}
1112
1113void OTMapGenDialog::GetTilePreviewColor(uint16_t tileId, unsigned char& r, unsigned char& g, unsigned char& b) {
1114 // First check if this tile ID matches any of our configured terrain layers
1115 for (const auto& layer : working_terrain_layers) {
1116 if (layer.item_id == tileId && layer.enabled) {
1117 // Use layer-specific colors based on layer name
1118 if (layer.name == "Water" || layer.brush_name == "sea") {
1119 r = 0; g = 100; b = 255; // Blue for water
1120 return;
1121 } else if (layer.name == "Grass" || layer.brush_name == "grass") {
1122 r = 50; g = 200; b = 50; // Green for grass
1123 return;
1124 } else if (layer.name == "Sand" || layer.brush_name == "sand") {
1125 r = 255; g = 255; b = 100; // Yellow for sand
1126 return;
1127 } else if (layer.name == "Mountain" || layer.brush_name == "mountain") {
1128 r = 139; g = 69; b = 19; // Brown for mountains
1129 return;
1130 } else if (layer.brush_name == "snow") {
1131 r = 255; g = 255; b = 255; // White for snow
1132 return;
1133 } else if (layer.brush_name == "cave") {
1134 r = 64; g = 64; b = 64; // Dark gray for caves
1135 return;
1136 } else if (layer.brush_name.find("stone") != std::string::npos) {
1137 r = 128; g = 128; b = 128; // Gray for stone
1138 return;
1139 } else if (layer.brush_name == "ice") {
1140 r = 200; g = 200; b = 255; // Light blue for ice
1141 return;
1142 } else if (layer.brush_name.find("wood") != std::string::npos) {
1143 r = 139; g = 69; b = 19; // Brown for wood
1144 return;
1145 }
1146 }
1147 }
1148
1149 // Check cave and water configuration
1150 if (tileId == cave_item_id_spin->GetValue()) {
1151 r = 64; g = 64; b = 64; // Dark gray for caves
1152 return;
1153 }
1154
1155 if (tileId == water_item_id_spin->GetValue()) {
1156 r = 0; g = 100; b = 255; // Blue for water
1157 return;
1158 }
1159
1160 // Fallback: Convert tile ID to preview colors using common item IDs
1161 switch (tileId) {
1162 case 4608: case 4609: case 4610: case 4611: case 4612: case 4613:
1163 r = 0; g = 100; b = 255; // Blue for water
1164 break;
1165 case 4526: case 4527: case 4528: case 4529: case 4530:
1166 r = 50; g = 200; b = 50; // Green for grass
1167 break;
1168 case 231:
1169 r = 255; g = 255; b = 100; // Yellow for sand
1170 break;
1171 case 1284: case 431:
1172 r = 128; g = 128; b = 128; // Gray for stone
1173 break;
1174 case 4597:
1175 r = 100; g = 100; b = 100; // Dark gray for gravel
1176 break;
1177 case 670: case 671:
1178 r = 255; g = 255; b = 255; // White for snow
1179 break;
1180 case 919: case 4468: case 4469:
1181 r = 139; g = 69; b = 19; // Brown for mountains
1182 break;
1183 case 351: case 352: case 353:
1184 r = 64; g = 64; b = 64; // Dark gray for caves
1185 break;
1186 case 106: case 108: case 109:
1187 r = 0; g = 150; b = 0; // Dark green for lawn
1188 break;
1189 case 405: case 448:
1190 r = 139; g = 69; b = 19; // Brown for wooden floors
1191 break;
1192 default:
1193 r = 64; g = 64; b = 64; // Default dark gray
1194 break;
1195 }
1196}
Top