////////////////////////////////////////////////////////////////////// // This file is part of Remere's Map Editor ////////////////////////////////////////////////////////////////////// // Remere's Map Editor is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Remere's Map Editor is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . ////////////////////////////////////////////////////////////////////// #include "main.h" #include "otmapgen_dialog.h" #include "otmapgen.h" #include "application.h" #include "editor.h" #include "settings.h" #include "action.h" #include "iomap.h" #include "gui.h" #include #include #include #include #include wxBEGIN_EVENT_TABLE(OTMapGenDialog, wxDialog) EVT_BUTTON(ID_GENERATE, OTMapGenDialog::OnGenerate) EVT_BUTTON(ID_PREVIEW, OTMapGenDialog::OnPreview) EVT_BUTTON(wxID_CANCEL, OTMapGenDialog::OnCancel) EVT_BUTTON(ID_FLOOR_UP, OTMapGenDialog::OnFloorUp) EVT_BUTTON(ID_FLOOR_DOWN, OTMapGenDialog::OnFloorDown) EVT_BUTTON(ID_ZOOM_IN, OTMapGenDialog::OnZoomIn) EVT_BUTTON(ID_ZOOM_OUT, OTMapGenDialog::OnZoomOut) EVT_TEXT(ID_SEED_TEXT, OTMapGenDialog::OnSeedChange) EVT_SPINCTRL(ID_WIDTH_SPIN, OTMapGenDialog::OnParameterChange) EVT_SPINCTRL(ID_HEIGHT_SPIN, OTMapGenDialog::OnParameterChange) EVT_CHOICE(ID_VERSION_CHOICE, OTMapGenDialog::OnParameterChangeText) EVT_CHOICE(ID_MOUNTAIN_TYPE_CHOICE, OTMapGenDialog::OnMountainTypeChange) // Terrain layer management events EVT_LIST_ITEM_SELECTED(ID_TERRAIN_LAYER_LIST, OTMapGenDialog::OnTerrainLayerSelect) EVT_BUTTON(ID_ADD_LAYER, OTMapGenDialog::OnTerrainLayerAdd) EVT_BUTTON(ID_REMOVE_LAYER, OTMapGenDialog::OnTerrainLayerRemove) EVT_BUTTON(ID_MOVE_UP_LAYER, OTMapGenDialog::OnTerrainLayerMoveUp) EVT_BUTTON(ID_MOVE_DOWN_LAYER, OTMapGenDialog::OnTerrainLayerMoveDown) EVT_BUTTON(ID_EDIT_LAYER, OTMapGenDialog::OnTerrainLayerEdit) EVT_CHOICE(ID_LAYER_BRUSH_CHOICE, OTMapGenDialog::OnBrushChoice) EVT_COMMAND(ID_LAYER_ITEM_ID_SPIN, wxEVT_COMMAND_SPINCTRL_UPDATED, OTMapGenDialog::OnItemIdChange) EVT_CHOICE(ID_CAVE_BRUSH_CHOICE, OTMapGenDialog::OnBrushChoice) EVT_COMMAND(ID_CAVE_ITEM_ID_SPIN, wxEVT_COMMAND_SPINCTRL_UPDATED, OTMapGenDialog::OnItemIdChange) EVT_CHOICE(ID_WATER_BRUSH_CHOICE, OTMapGenDialog::OnBrushChoice) EVT_COMMAND(ID_WATER_ITEM_ID_SPIN, wxEVT_COMMAND_SPINCTRL_UPDATED, OTMapGenDialog::OnItemIdChange) wxEND_EVENT_TABLE() OTMapGenDialog::OTMapGenDialog(wxWindow* parent) : wxDialog(parent, wxID_ANY, "Procedural Map Generator", wxDefaultPosition, wxSize(1000, 700), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), current_preview(nullptr), current_preview_floor(7), // Start with ground level (floor 7) current_zoom(1.0), preview_offset_x(0), preview_offset_y(0) { // Create main sizer wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); // Create notebook for tabs wxNotebook* notebook = new wxNotebook(this, wxID_ANY); // === Main Settings Tab (merged basic + advanced) === wxPanel* main_panel = new wxPanel(notebook); wxBoxSizer* settings_main_sizer = new wxBoxSizer(wxHORIZONTAL); // Left side - All Settings wxBoxSizer* left_main_sizer = new wxBoxSizer(wxVERTICAL); // Basic parameters group (more compact) wxStaticBoxSizer* basic_params_sizer = new wxStaticBoxSizer(wxVERTICAL, main_panel, "Basic Parameters"); wxFlexGridSizer* basic_grid_sizer = new wxFlexGridSizer(3, 4, 5, 5); basic_grid_sizer->AddGrowableCol(1); basic_grid_sizer->AddGrowableCol(3); // Row 1: Seed and Width basic_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Seed:"), 0, wxALIGN_CENTER_VERTICAL); seed_text_ctrl = new wxTextCtrl(main_panel, ID_SEED_TEXT, wxString::Format("%lld", (long long)time(nullptr) * 1000)); seed_text_ctrl->SetToolTip("Enter any integer value (supports 64-bit seeds)"); basic_grid_sizer->Add(seed_text_ctrl, 1, wxEXPAND); basic_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Width:"), 0, wxALIGN_CENTER_VERTICAL); width_spin_ctrl = new wxSpinCtrl(main_panel, ID_WIDTH_SPIN, "256", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 64, 2048, 256); basic_grid_sizer->Add(width_spin_ctrl, 1, wxEXPAND); // Row 2: Version and Height basic_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Version:"), 0, wxALIGN_CENTER_VERTICAL); wxArrayString versions; versions.Add("10.98"); versions.Add("11.00"); versions.Add("12.00"); version_choice = new wxChoice(main_panel, ID_VERSION_CHOICE, wxDefaultPosition, wxDefaultSize, versions); version_choice->SetSelection(0); basic_grid_sizer->Add(version_choice, 1, wxEXPAND); basic_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Height:"), 0, wxALIGN_CENTER_VERTICAL); height_spin_ctrl = new wxSpinCtrl(main_panel, ID_HEIGHT_SPIN, "256", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 64, 2048, 256); basic_grid_sizer->Add(height_spin_ctrl, 1, wxEXPAND); // Row 3: Mountain Type and Water Level basic_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Mountain Type:"), 0, wxALIGN_CENTER_VERTICAL); wxArrayString mountain_types; mountain_types.Add("MOUNTAIN"); mountain_types.Add("SNOW"); mountain_types.Add("SAND"); mountain_type_choice = new wxChoice(main_panel, ID_MOUNTAIN_TYPE_CHOICE, wxDefaultPosition, wxDefaultSize, mountain_types); mountain_type_choice->SetSelection(0); basic_grid_sizer->Add(mountain_type_choice, 1, wxEXPAND); basic_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Water Level:"), 0, wxALIGN_CENTER_VERTICAL); water_level_text = new wxTextCtrl(main_panel, ID_WATER_LEVEL_TEXT, "7"); water_level_text->SetToolTip("Tibia Z-coordinate (0-15, 7 = ground level)"); basic_grid_sizer->Add(water_level_text, 1, wxEXPAND); basic_params_sizer->Add(basic_grid_sizer, 0, wxEXPAND | wxALL, 5); // Checkboxes in horizontal layout wxBoxSizer* checkbox_sizer = new wxBoxSizer(wxHORIZONTAL); terrain_only_checkbox = new wxCheckBox(main_panel, wxID_ANY, "Terrain Only"); sand_biome_checkbox = new wxCheckBox(main_panel, wxID_ANY, "Sand Biome"); sand_biome_checkbox->SetValue(true); smooth_coastline_checkbox = new wxCheckBox(main_panel, wxID_ANY, "Smooth Coastlines"); smooth_coastline_checkbox->SetValue(true); add_caves_checkbox = new wxCheckBox(main_panel, wxID_ANY, "Underground Caves"); add_caves_checkbox->SetValue(true); checkbox_sizer->Add(terrain_only_checkbox, 0, wxALL, 5); checkbox_sizer->Add(sand_biome_checkbox, 0, wxALL, 5); checkbox_sizer->Add(smooth_coastline_checkbox, 0, wxALL, 5); checkbox_sizer->Add(add_caves_checkbox, 0, wxALL, 5); basic_params_sizer->Add(checkbox_sizer, 0, wxEXPAND | wxALL, 5); left_main_sizer->Add(basic_params_sizer, 0, wxEXPAND | wxALL, 5); // Advanced parameters group (more compact) wxStaticBoxSizer* advanced_params_sizer = new wxStaticBoxSizer(wxVERTICAL, main_panel, "Noise & Generation Parameters"); wxFlexGridSizer* advanced_grid_sizer = new wxFlexGridSizer(4, 4, 5, 5); advanced_grid_sizer->AddGrowableCol(1); advanced_grid_sizer->AddGrowableCol(3); // Row 1: Noise Increment and Island Distance advanced_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Noise Increment:"), 0, wxALIGN_CENTER_VERTICAL); noise_increment_text = new wxTextCtrl(main_panel, ID_NOISE_INCREMENT_TEXT, "1.0"); noise_increment_text->SetToolTip("Range: 0.001 - 100.0 (higher = more detail)"); advanced_grid_sizer->Add(noise_increment_text, 1, wxEXPAND); advanced_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Island Distance:"), 0, wxALIGN_CENTER_VERTICAL); island_distance_text = new wxTextCtrl(main_panel, ID_ISLAND_DISTANCE_TEXT, "0.92"); island_distance_text->SetToolTip("Range: 0.001 - 100.0 (lower = more island effect)"); advanced_grid_sizer->Add(island_distance_text, 1, wxEXPAND); // Row 2: Exponent and Linear advanced_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Exponent:"), 0, wxALIGN_CENTER_VERTICAL); exponent_text = new wxTextCtrl(main_panel, ID_EXPONENT_TEXT, "1.4"); exponent_text->SetToolTip("Range: 0.001 - 100.0 (height curve shaping)"); advanced_grid_sizer->Add(exponent_text, 1, wxEXPAND); advanced_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Linear:"), 0, wxALIGN_CENTER_VERTICAL); linear_text = new wxTextCtrl(main_panel, ID_LINEAR_TEXT, "6.0"); linear_text->SetToolTip("Range: 0.001 - 100.0 (height multiplier)"); advanced_grid_sizer->Add(linear_text, 1, wxEXPAND); // Row 3: Cave Depth and Cave Roughness advanced_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Cave Depth:"), 0, wxALIGN_CENTER_VERTICAL); cave_depth_text = new wxTextCtrl(main_panel, ID_CAVE_DEPTH_TEXT, "20"); cave_depth_text->SetToolTip("Range: 1 - 100 (number of underground floors)"); advanced_grid_sizer->Add(cave_depth_text, 1, wxEXPAND); advanced_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Cave Roughness:"), 0, wxALIGN_CENTER_VERTICAL); cave_roughness_text = new wxTextCtrl(main_panel, ID_CAVE_ROUGHNESS_TEXT, "0.45"); cave_roughness_text->SetToolTip("Range: 0.001 - 100.0 (noise scale for caves)"); advanced_grid_sizer->Add(cave_roughness_text, 1, wxEXPAND); // Row 4: Cave Chance (spanning two columns) advanced_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, "Cave Chance:"), 0, wxALIGN_CENTER_VERTICAL); cave_chance_text = new wxTextCtrl(main_panel, ID_CAVE_CHANCE_TEXT, "0.09"); cave_chance_text->SetToolTip("Range: 0.001 - 1.0 (probability of cave generation)"); advanced_grid_sizer->Add(cave_chance_text, 1, wxEXPAND); // Add spacers for the remaining cells advanced_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, ""), 0); advanced_grid_sizer->Add(new wxStaticText(main_panel, wxID_ANY, ""), 0); advanced_params_sizer->Add(advanced_grid_sizer, 0, wxEXPAND | wxALL, 5); left_main_sizer->Add(advanced_params_sizer, 0, wxEXPAND | wxALL, 5); // Right side - Large Preview section wxBoxSizer* right_main_sizer = new wxBoxSizer(wxVERTICAL); wxStaticBoxSizer* preview_sizer = new wxStaticBoxSizer(wxVERTICAL, main_panel, "Map Preview"); preview_bitmap = new wxStaticBitmap(main_panel, wxID_ANY, wxBitmap(400, 400)); preview_bitmap->SetBackgroundColour(*wxBLACK); preview_bitmap->SetMinSize(wxSize(400, 400)); preview_sizer->Add(preview_bitmap, 1, wxEXPAND | wxALL, 5); // Floor navigation wxBoxSizer* floor_nav_sizer = new wxBoxSizer(wxHORIZONTAL); floor_down_button = new wxButton(main_panel, ID_FLOOR_DOWN, "Floor -"); floor_label = new wxStaticText(main_panel, wxID_ANY, "Floor: 7 (Ground)"); floor_up_button = new wxButton(main_panel, ID_FLOOR_UP, "Floor +"); floor_nav_sizer->Add(floor_down_button, 0, wxALL, 2); floor_nav_sizer->AddStretchSpacer(); floor_nav_sizer->Add(floor_label, 0, wxALIGN_CENTER_VERTICAL | wxALL, 2); floor_nav_sizer->AddStretchSpacer(); floor_nav_sizer->Add(floor_up_button, 0, wxALL, 2); preview_sizer->Add(floor_nav_sizer, 0, wxEXPAND | wxALL, 5); // Zoom navigation wxBoxSizer* zoom_nav_sizer = new wxBoxSizer(wxHORIZONTAL); zoom_out_button = new wxButton(main_panel, ID_ZOOM_OUT, "Zoom -"); zoom_label = new wxStaticText(main_panel, wxID_ANY, "Zoom: 100%"); zoom_in_button = new wxButton(main_panel, ID_ZOOM_IN, "Zoom +"); zoom_nav_sizer->Add(zoom_out_button, 0, wxALL, 2); zoom_nav_sizer->AddStretchSpacer(); zoom_nav_sizer->Add(zoom_label, 0, wxALIGN_CENTER_VERTICAL | wxALL, 2); zoom_nav_sizer->AddStretchSpacer(); zoom_nav_sizer->Add(zoom_in_button, 0, wxALL, 2); preview_sizer->Add(zoom_nav_sizer, 0, wxEXPAND | wxALL, 5); // Preview buttons wxBoxSizer* preview_buttons_sizer = new wxBoxSizer(wxHORIZONTAL); preview_button = new wxButton(main_panel, ID_PREVIEW, "Generate Preview"); wxButton* refresh_preview_button = new wxButton(main_panel, ID_PREVIEW, "Refresh"); preview_buttons_sizer->Add(preview_button, 1, wxEXPAND | wxALL, 2); preview_buttons_sizer->Add(refresh_preview_button, 0, wxALL, 2); preview_sizer->Add(preview_buttons_sizer, 0, wxEXPAND | wxALL, 5); right_main_sizer->Add(preview_sizer, 1, wxEXPAND | wxALL, 5); // Add left and right sides to main tab settings_main_sizer->Add(left_main_sizer, 0, wxEXPAND | wxALL, 5); settings_main_sizer->Add(right_main_sizer, 1, wxEXPAND | wxALL, 5); main_panel->SetSizer(settings_main_sizer); notebook->AddPage(main_panel, "Map Generation", true); // === Layout Design Tab === wxPanel* layout_panel = new wxPanel(notebook); wxBoxSizer* layout_sizer = new wxBoxSizer(wxVERTICAL); // Terrain Layers section wxStaticBoxSizer* terrain_layers_sizer = new wxStaticBoxSizer(wxHORIZONTAL, layout_panel, "Terrain Layers"); // Terrain layer list terrain_layer_list = new wxListCtrl(layout_panel, ID_TERRAIN_LAYER_LIST, wxDefaultPosition, wxSize(300, 200), wxLC_REPORT | wxLC_SINGLE_SEL); terrain_layer_list->InsertColumn(0, "Name", wxLIST_FORMAT_LEFT, 100); terrain_layer_list->InsertColumn(1, "Brush", wxLIST_FORMAT_LEFT, 100); terrain_layer_list->InsertColumn(2, "Item ID", wxLIST_FORMAT_LEFT, 60); terrain_layer_list->InsertColumn(3, "Height", wxLIST_FORMAT_LEFT, 80); terrain_layer_list->InsertColumn(4, "Enabled", wxLIST_FORMAT_LEFT, 60); terrain_layers_sizer->Add(terrain_layer_list, 1, wxEXPAND | wxALL, 5); // Layer control buttons wxBoxSizer* layer_buttons_sizer = new wxBoxSizer(wxVERTICAL); add_layer_button = new wxButton(layout_panel, ID_ADD_LAYER, "Add Layer"); remove_layer_button = new wxButton(layout_panel, ID_REMOVE_LAYER, "Remove Layer"); move_up_button = new wxButton(layout_panel, ID_MOVE_UP_LAYER, "Move Up"); move_down_button = new wxButton(layout_panel, ID_MOVE_DOWN_LAYER, "Move Down"); edit_layer_button = new wxButton(layout_panel, ID_EDIT_LAYER, "Edit Layer"); layer_buttons_sizer->Add(add_layer_button, 0, wxEXPAND | wxALL, 2); layer_buttons_sizer->Add(remove_layer_button, 0, wxEXPAND | wxALL, 2); layer_buttons_sizer->Add(move_up_button, 0, wxEXPAND | wxALL, 2); layer_buttons_sizer->Add(move_down_button, 0, wxEXPAND | wxALL, 2); layer_buttons_sizer->Add(edit_layer_button, 0, wxEXPAND | wxALL, 2); layer_buttons_sizer->AddStretchSpacer(); terrain_layers_sizer->Add(layer_buttons_sizer, 0, wxEXPAND | wxALL, 5); // Layer Properties section wxStaticBoxSizer* layer_props_sizer = new wxStaticBoxSizer(wxVERTICAL, layout_panel, "Layer Properties"); layer_properties_panel = new wxPanel(layout_panel); wxFlexGridSizer* props_grid_sizer = new wxFlexGridSizer(5, 4, 5, 10); props_grid_sizer->AddGrowableCol(1); props_grid_sizer->AddGrowableCol(3); // Row 1: Name and Brush props_grid_sizer->Add(new wxStaticText(layer_properties_panel, wxID_ANY, "Name:"), 0, wxALIGN_CENTER_VERTICAL); layer_name_text = new wxTextCtrl(layer_properties_panel, wxID_ANY, ""); props_grid_sizer->Add(layer_name_text, 1, wxEXPAND); props_grid_sizer->Add(new wxStaticText(layer_properties_panel, wxID_ANY, "Brush:"), 0, wxALIGN_CENTER_VERTICAL); layer_brush_choice = new wxChoice(layer_properties_panel, ID_LAYER_BRUSH_CHOICE); props_grid_sizer->Add(layer_brush_choice, 1, wxEXPAND); // Row 2: Item ID and Z-Order props_grid_sizer->Add(new wxStaticText(layer_properties_panel, wxID_ANY, "Item ID:"), 0, wxALIGN_CENTER_VERTICAL); layer_item_id_spin = new wxSpinCtrl(layer_properties_panel, ID_LAYER_ITEM_ID_SPIN, "100", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 100, 65535, 100); props_grid_sizer->Add(layer_item_id_spin, 1, wxEXPAND); props_grid_sizer->Add(new wxStaticText(layer_properties_panel, wxID_ANY, "Z-Order:"), 0, wxALIGN_CENTER_VERTICAL); z_order_spin = new wxSpinCtrl(layer_properties_panel, wxID_ANY, "1000", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 10000, 1000); props_grid_sizer->Add(z_order_spin, 1, wxEXPAND); // Row 3: Height range props_grid_sizer->Add(new wxStaticText(layer_properties_panel, wxID_ANY, "Height Min:"), 0, wxALIGN_CENTER_VERTICAL); height_min_text = new wxTextCtrl(layer_properties_panel, wxID_ANY, "0.0"); props_grid_sizer->Add(height_min_text, 1, wxEXPAND); props_grid_sizer->Add(new wxStaticText(layer_properties_panel, wxID_ANY, "Height Max:"), 0, wxALIGN_CENTER_VERTICAL); height_max_text = new wxTextCtrl(layer_properties_panel, wxID_ANY, "1.0"); props_grid_sizer->Add(height_max_text, 1, wxEXPAND); // Row 4: Moisture range props_grid_sizer->Add(new wxStaticText(layer_properties_panel, wxID_ANY, "Moisture Min:"), 0, wxALIGN_CENTER_VERTICAL); moisture_min_text = new wxTextCtrl(layer_properties_panel, wxID_ANY, "-1.0"); props_grid_sizer->Add(moisture_min_text, 1, wxEXPAND); props_grid_sizer->Add(new wxStaticText(layer_properties_panel, wxID_ANY, "Moisture Max:"), 0, wxALIGN_CENTER_VERTICAL); moisture_max_text = new wxTextCtrl(layer_properties_panel, wxID_ANY, "1.0"); props_grid_sizer->Add(moisture_max_text, 1, wxEXPAND); // Row 5: Noise scale and Coverage props_grid_sizer->Add(new wxStaticText(layer_properties_panel, wxID_ANY, "Noise Scale:"), 0, wxALIGN_CENTER_VERTICAL); noise_scale_text = new wxTextCtrl(layer_properties_panel, wxID_ANY, "1.0"); props_grid_sizer->Add(noise_scale_text, 1, wxEXPAND); props_grid_sizer->Add(new wxStaticText(layer_properties_panel, wxID_ANY, "Coverage:"), 0, wxALIGN_CENTER_VERTICAL); coverage_text = new wxTextCtrl(layer_properties_panel, wxID_ANY, "1.0"); props_grid_sizer->Add(coverage_text, 1, wxEXPAND); layer_properties_panel->SetSizer(props_grid_sizer); layer_props_sizer->Add(layer_properties_panel, 1, wxEXPAND | wxALL, 5); // Checkboxes for layer options wxBoxSizer* layer_options_sizer = new wxBoxSizer(wxHORIZONTAL); use_borders_checkbox = new wxCheckBox(layout_panel, wxID_ANY, "Use Borders"); use_borders_checkbox->SetValue(true); layer_enabled_checkbox = new wxCheckBox(layout_panel, wxID_ANY, "Layer Enabled"); layer_enabled_checkbox->SetValue(true); layer_options_sizer->Add(use_borders_checkbox, 0, wxALL, 5); layer_options_sizer->Add(layer_enabled_checkbox, 0, wxALL, 5); layer_props_sizer->Add(layer_options_sizer, 0, wxEXPAND | wxALL, 5); layout_sizer->Add(layer_props_sizer, 0, wxEXPAND | wxALL, 5); // Cave and Water Configuration section wxStaticBoxSizer* special_terrain_sizer = new wxStaticBoxSizer(wxHORIZONTAL, layout_panel, "Cave & Water Configuration"); // Cave configuration wxBoxSizer* cave_config_sizer = new wxBoxSizer(wxVERTICAL); cave_config_sizer->Add(new wxStaticText(layout_panel, wxID_ANY, "Cave Configuration"), 0, wxALL, 2); wxFlexGridSizer* cave_grid_sizer = new wxFlexGridSizer(2, 2, 5, 10); cave_grid_sizer->AddGrowableCol(1); cave_grid_sizer->Add(new wxStaticText(layout_panel, wxID_ANY, "Cave Brush:"), 0, wxALIGN_CENTER_VERTICAL); cave_brush_choice = new wxChoice(layout_panel, ID_CAVE_BRUSH_CHOICE); cave_grid_sizer->Add(cave_brush_choice, 1, wxEXPAND); cave_grid_sizer->Add(new wxStaticText(layout_panel, wxID_ANY, "Cave Item ID:"), 0, wxALIGN_CENTER_VERTICAL); cave_item_id_spin = new wxSpinCtrl(layout_panel, ID_CAVE_ITEM_ID_SPIN, "351", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 100, 65535, 351); cave_grid_sizer->Add(cave_item_id_spin, 1, wxEXPAND); cave_config_sizer->Add(cave_grid_sizer, 0, wxEXPAND | wxALL, 5); special_terrain_sizer->Add(cave_config_sizer, 1, wxEXPAND | wxALL, 5); // Water configuration wxBoxSizer* water_config_sizer = new wxBoxSizer(wxVERTICAL); water_config_sizer->Add(new wxStaticText(layout_panel, wxID_ANY, "Water Configuration"), 0, wxALL, 2); wxFlexGridSizer* water_grid_sizer = new wxFlexGridSizer(2, 2, 5, 10); water_grid_sizer->AddGrowableCol(1); water_grid_sizer->Add(new wxStaticText(layout_panel, wxID_ANY, "Water Brush:"), 0, wxALIGN_CENTER_VERTICAL); water_brush_choice = new wxChoice(layout_panel, ID_WATER_BRUSH_CHOICE); water_grid_sizer->Add(water_brush_choice, 1, wxEXPAND); water_grid_sizer->Add(new wxStaticText(layout_panel, wxID_ANY, "Water Item ID:"), 0, wxALIGN_CENTER_VERTICAL); water_item_id_spin = new wxSpinCtrl(layout_panel, ID_WATER_ITEM_ID_SPIN, "4608", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 100, 65535, 4608); water_grid_sizer->Add(water_item_id_spin, 1, wxEXPAND); water_config_sizer->Add(water_grid_sizer, 0, wxEXPAND | wxALL, 5); special_terrain_sizer->Add(water_config_sizer, 1, wxEXPAND | wxALL, 5); layout_sizer->Add(special_terrain_sizer, 0, wxEXPAND | wxALL, 5); layout_sizer->Add(terrain_layers_sizer, 1, wxEXPAND | wxALL, 5); layout_panel->SetSizer(layout_sizer); notebook->AddPage(layout_panel, "Layout Design", false); main_sizer->Add(notebook, 1, wxEXPAND | wxALL, 5); // Buttons wxBoxSizer* button_sizer = new wxBoxSizer(wxHORIZONTAL); generate_button = new wxButton(this, ID_GENERATE, "Generate Map"); cancel_button = new wxButton(this, wxID_CANCEL, "Cancel"); button_sizer->Add(generate_button, 0, wxALL, 5); button_sizer->AddStretchSpacer(); button_sizer->Add(cancel_button, 0, wxALL, 5); main_sizer->Add(button_sizer, 0, wxEXPAND | wxALL, 5); SetSizer(main_sizer); Center(); // Initialize terrain layers and brush choices PopulateBrushChoices(); // Initialize default terrain layers GenerationConfig defaultConfig; defaultConfig.initializeDefaultLayers(); working_terrain_layers = defaultConfig.terrain_layers; // Populate the terrain layer list PopulateTerrainLayerList(); // Initially disable layer property controls UpdateLayerControls(); // Generate initial random 64-bit seed srand(time(nullptr)); long long initial_seed = ((long long)rand() << 32) | rand(); seed_text_ctrl->SetValue(wxString::Format("%lld", initial_seed)); } OTMapGenDialog::~OTMapGenDialog() { if (current_preview) { delete current_preview; } } void OTMapGenDialog::OnGenerate(wxCommandEvent& event) { if (GenerateMap()) { EndModal(wxID_OK); } } void OTMapGenDialog::OnCancel(wxCommandEvent& event) { EndModal(wxID_CANCEL); } void OTMapGenDialog::OnPreview(wxCommandEvent& event) { UpdatePreview(); } void OTMapGenDialog::OnSeedChange(wxCommandEvent& event) { // Optional: Auto-update preview when seed changes } void OTMapGenDialog::OnParameterChange(wxSpinEvent& event) { // Optional: Auto-update preview when parameters change } void OTMapGenDialog::OnParameterChangeText(wxCommandEvent& event) { // Optional: Auto-update preview when parameters change } void OTMapGenDialog::OnMountainTypeChange(wxCommandEvent& event) { // Optional: Auto-update preview when mountain type changes } void OTMapGenDialog::OnFloorUp(wxCommandEvent& event) { if (current_preview_floor > 0) { current_preview_floor--; UpdateFloorLabel(); UpdatePreviewFloor(); } } void OTMapGenDialog::OnFloorDown(wxCommandEvent& event) { if (current_preview_floor < 7) { // Only go up to floor 7 current_preview_floor++; UpdateFloorLabel(); UpdatePreviewFloor(); } } void OTMapGenDialog::OnZoomIn(wxCommandEvent& event) { if (current_zoom < 4.0) { // Max zoom 400% current_zoom *= 1.25; // 25% increase UpdateZoomLabel(); UpdatePreviewFloor(); } } void OTMapGenDialog::OnZoomOut(wxCommandEvent& event) { if (current_zoom > 0.25) { // Min zoom 25% current_zoom /= 1.25; // 25% decrease UpdateZoomLabel(); UpdatePreviewFloor(); } } void OTMapGenDialog::UpdatePreview() { preview_button->SetLabel("Generating..."); preview_button->Enable(false); try { // Create configuration from dialog values GenerationConfig config = BuildGenerationConfig(); // Generate preview using C++ implementation OTMapGenerator generator; current_layers = generator.generateLayers(config); // Update the preview for the current floor UpdatePreviewFloor(); } catch (const std::exception& e) { wxMessageBox(wxString::Format("Failed to generate preview: %s", e.what()), "Preview Error", wxOK | wxICON_ERROR); } preview_button->SetLabel("Generate Preview"); preview_button->Enable(true); } void OTMapGenDialog::UpdatePreviewFloor() { if (current_layers.empty()) { return; // No data to preview } try { // Create configuration for scaling GenerationConfig config = BuildGenerationConfig(); // Use the SAME mapping as OTBM generation: // Preview floor 7 = layers[0] (ground level) // Preview floor 6 = layers[1] (above ground 1) // ... // Preview floor 0 = layers[7] (highest above ground) int layerIndex = 7 - current_preview_floor; // Clamp to valid range layerIndex = std::max(0, std::min(7, layerIndex)); if (layerIndex < static_cast(current_layers.size()) && !current_layers[layerIndex].empty()) { const auto& layerData = current_layers[layerIndex]; int preview_width = 400; // Updated to match the larger preview int preview_height = 400; // Updated to match the larger preview wxImage preview_image(preview_width, preview_height); // Calculate zoom-adjusted scale and center point double base_scale_x = (double)config.width / preview_width; double base_scale_y = (double)config.height / preview_height; // Apply zoom - higher zoom means smaller scale (show less area) double scale_x = base_scale_x / current_zoom; double scale_y = base_scale_y / current_zoom; // Calculate center offset for zoomed view int center_x = config.width / 2 + preview_offset_x; int center_y = config.height / 2 + preview_offset_y; for (int y = 0; y < preview_height; ++y) { for (int x = 0; x < preview_width; ++x) { // Calculate source coordinates with zoom and offset int src_x = center_x + (int)((x - preview_width/2) * scale_x); int src_y = center_y + (int)((y - preview_height/2) * scale_y); // Set default color (black for out of bounds) unsigned char r = 0, g = 0, b = 0; if (src_x >= 0 && src_x < config.width && src_y >= 0 && src_y < config.height) { // Calculate index in the 1D layer int tileIndex = src_y * config.width + src_x; if (tileIndex < static_cast(layerData.size())) { uint16_t tileId = layerData[tileIndex]; // Convert tile ID to color for preview GetTilePreviewColor(tileId, r, g, b); } } preview_image.SetRGB(x, y, r, g, b); } } if (current_preview) { delete current_preview; } current_preview = new wxBitmap(preview_image); preview_bitmap->SetBitmap(*current_preview); preview_bitmap->Refresh(); } } catch (const std::exception& e) { wxMessageBox(wxString::Format("Failed to update preview floor: %s", e.what()), "Preview Error", wxOK | wxICON_ERROR); } } void OTMapGenDialog::UpdateFloorLabel() { wxString label; if (current_preview_floor == 7) { label = "Floor: 7 (Ground)"; } else if (current_preview_floor < 7) { label = wxString::Format("Floor: %d (Above Ground %d)", current_preview_floor, 7 - current_preview_floor); } else { label = wxString::Format("Floor: %d", current_preview_floor); } floor_label->SetLabel(label); } void OTMapGenDialog::UpdateZoomLabel() { wxString label = wxString::Format("Zoom: %.0f%%", current_zoom * 100); zoom_label->SetLabel(label); } bool OTMapGenDialog::GenerateMap() { try { // Create configuration from dialog values GenerationConfig config = BuildGenerationConfig(); wxProgressDialog progress("Generating Map", "Please wait while the map is being generated...", 100, this, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT); progress.Pulse(); // Generate the map data using C++ implementation OTMapGenerator generator; auto layers = generator.generateLayers(config); if (layers.empty()) { wxMessageBox("Failed to generate map data.", "Generation Error", wxOK | wxICON_ERROR); return false; } // Create a temporary map to hold our generated content Map tempMap; tempMap.setWidth(config.width); tempMap.setHeight(config.height); tempMap.setName("Generated Map"); tempMap.setMapDescription("Procedurally generated map"); // Set map properties tempMap.setSpawnFilename(""); tempMap.setHouseFilename(""); // Place generated tiles into the temporary map // Floor mapping for Tibia coordinates: // - Floor 7 = ground level (surface) ← layers[0] should go here // - Floors 6-0 = above ground (6 is just above surface, 0 is highest) ← layers[1-7] go here // // We need to REVERSE the mapping when writing to OTBM: // - layers[0] → Tibia floor 7 (ground level) // - layers[1] → Tibia floor 6 (above ground level 1) // - layers[7] → Tibia floor 0 (highest above ground) for (int layerIndex = 0; layerIndex < static_cast(layers.size()) && layerIndex < 8; ++layerIndex) { // REVERSE the mapping: layers[0]→floor 7, layers[1]→floor 6, ..., layers[7]→floor 0 int tibiaZ = 7 - layerIndex; // This puts ground level (layers[0]) at floor 7 // Extract tiles from the 1D layer data const auto& layerData = layers[layerIndex]; int tileIndex = 0; for (int y = 0; y < config.height; ++y) { for (int x = 0; x < config.width; ++x) { if (tileIndex >= static_cast(layerData.size())) { break; // Safety check } uint16_t tileId = layerData[tileIndex++]; if (tileId != 0) { Position pos(x, y, tibiaZ); // Create tile in temp map using the safe method TileLocation* location = tempMap.createTileL(pos); Tile* tile = tempMap.allocator(location); // Create ground item Item* groundItem = Item::Create(tileId); if (groundItem) { tile->ground = groundItem; tempMap.setTile(pos, tile); } else { delete tile; // Clean up if item creation failed } } } } } // Add decorations if enabled (only on surface level - Tibia Z=7) if (!config.terrain_only && layers.size() >= 8) { std::mt19937 decoration_rng(std::hash{}(config.seed)); std::uniform_real_distribution dist(0.0, 1.0); // Use the ground level layer (layers[0] will become Tibia floor 7) const auto& surfaceLayer = layers[0]; int tileIndex = 0; for (int y = 0; y < config.height; ++y) { for (int x = 0; x < config.width; ++x) { if (tileIndex >= static_cast(surfaceLayer.size())) { break; // Safety check } uint16_t tileId = surfaceLayer[tileIndex++]; if (tileId == 4526 && dist(decoration_rng) < 0.03) { // Use grass item ID directly Position pos(x, y, 7); // Surface level in Tibia coordinates Tile* tile = tempMap.getTile(pos); if (tile) { // Choose random decoration uint16_t decorationId; double rand_val = dist(decoration_rng); if (rand_val < 0.6) { decorationId = 2700; // Tree ID } else if (rand_val < 0.8) { decorationId = 2785; // Bush ID } else { decorationId = 2782; // Flower ID } Item* decoration = Item::Create(decorationId); if (decoration) { tile->addItem(decoration); } } } } } } // Create a temporary OTBM file wxString tempDir = wxStandardPaths::Get().GetTempDir(); wxString tempFileName = wxString::Format("generated_map_%ld.otbm", wxGetLocalTime()); wxString tempFilePath = tempDir + wxFileName::GetPathSeparator() + tempFileName; // Save the temporary map as OTBM progress.SetLabel("Saving temporary map file..."); // Use the map's save functionality bool saveSuccess = false; try { IOMapOTBM mapLoader(tempMap.getVersion()); saveSuccess = mapLoader.saveMap(tempMap, wxFileName(tempFilePath)); } catch (...) { saveSuccess = false; } if (!saveSuccess) { wxMessageBox("Failed to save temporary map file.", "Save Error", wxOK | wxICON_ERROR); return false; } progress.SetLabel("Loading generated map..."); progress.Pulse(); // Load the temporary file into the editor bool loadSuccess = false; try { // Use the GUI's LoadMap function which handles everything properly loadSuccess = g_gui.LoadMap(wxFileName(tempFilePath)); } catch (...) { loadSuccess = false; } // Clean up the temporary file if (wxFileExists(tempFilePath)) { wxRemoveFile(tempFilePath); } if (loadSuccess) { wxMessageBox("Procedural map generated and loaded successfully!", "Success", wxOK | wxICON_INFORMATION); return true; } else { wxMessageBox("Failed to load the generated map.", "Load Error", wxOK | wxICON_ERROR); return false; } } catch (const std::exception& e) { wxMessageBox(wxString::Format("Map generation failed with error: %s", e.what()), "Generation Error", wxOK | wxICON_ERROR); } return false; } GenerationConfig OTMapGenDialog::BuildGenerationConfig() { GenerationConfig config; // Basic parameters config.seed = seed_text_ctrl->GetValue().ToStdString(); config.width = width_spin_ctrl->GetValue(); config.height = height_spin_ctrl->GetValue(); config.version = version_choice->GetStringSelection().ToStdString(); config.mountain_type = mountain_type_choice->GetStringSelection().ToStdString(); // Boolean flags config.terrain_only = terrain_only_checkbox->GetValue(); config.sand_biome = sand_biome_checkbox->GetValue(); config.smooth_coastline = smooth_coastline_checkbox->GetValue(); config.add_caves = add_caves_checkbox->GetValue(); // Advanced parameters - parse text inputs with enhanced validation and ranges double noise_increment = 1.0; double island_distance = 0.92; double cave_depth = 20.0; double cave_roughness = 0.45; double cave_chance = 0.09; double water_level = 7.0; double exponent = 1.4; double linear = 6.0; if (!noise_increment_text->GetValue().ToDouble(&noise_increment)) noise_increment = 1.0; if (!island_distance_text->GetValue().ToDouble(&island_distance)) island_distance = 0.92; if (!cave_depth_text->GetValue().ToDouble(&cave_depth)) cave_depth = 20.0; if (!cave_roughness_text->GetValue().ToDouble(&cave_roughness)) cave_roughness = 0.45; if (!cave_chance_text->GetValue().ToDouble(&cave_chance)) cave_chance = 0.09; if (!water_level_text->GetValue().ToDouble(&water_level)) water_level = 7.0; if (!exponent_text->GetValue().ToDouble(&exponent)) exponent = 1.4; if (!linear_text->GetValue().ToDouble(&linear)) linear = 6.0; // Apply enhanced bounds checking with expanded ranges noise_increment = std::max(0.001, std::min(100.0, noise_increment)); island_distance = std::max(0.001, std::min(100.0, island_distance)); cave_depth = std::max(1.0, std::min(100.0, cave_depth)); cave_roughness = std::max(0.001, std::min(100.0, cave_roughness)); cave_chance = std::max(0.001, std::min(1.0, cave_chance)); water_level = std::max(0.0, std::min(15.0, water_level)); exponent = std::max(0.001, std::min(100.0, exponent)); linear = std::max(0.001, std::min(100.0, linear)); config.noise_increment = noise_increment; config.island_distance_decrement = island_distance; config.cave_depth = (int)cave_depth; config.cave_roughness = cave_roughness; config.cave_chance = cave_chance; config.water_level = (int)water_level; config.exponent = exponent; config.linear = linear; // Add some default frequency settings for better island generation config.frequencies.clear(); config.frequencies.push_back({1.0, 1.0}); // Base frequency config.frequencies.push_back({2.0, 0.5}); // Higher frequency, lower weight config.frequencies.push_back({4.0, 0.25}); // Even higher, even lower weight config.frequencies.push_back({8.0, 0.125}); // Finest detail // Enhanced island generation parameters config.euclidean = true; // Use euclidean distance for smoother island shapes config.island_distance_exponent = 2.0; // Smooth falloff // Use the working terrain layers from the layout design tab config.terrain_layers = working_terrain_layers; // Update the sand biome layer's enabled state based on checkbox for (auto& layer : config.terrain_layers) { if (layer.name == "Sand") { layer.enabled = config.sand_biome; } } // Cave configuration int caveSelection = cave_brush_choice->GetSelection(); if (caveSelection >= 0 && caveSelection < static_cast(available_brushes.size())) { config.cave_brush_name = available_brushes[caveSelection]; } config.cave_item_id = cave_item_id_spin->GetValue(); // Water configuration int waterSelection = water_brush_choice->GetSelection(); if (waterSelection >= 0 && waterSelection < static_cast(available_brushes.size())) { config.water_brush_name = available_brushes[waterSelection]; } config.water_item_id = water_item_id_spin->GetValue(); return config; } // Terrain layer management event handlers void OTMapGenDialog::OnTerrainLayerSelect(wxListEvent& event) { UpdateLayerControls(); } void OTMapGenDialog::OnTerrainLayerAdd(wxCommandEvent& event) { TerrainLayer newLayer; newLayer.name = "New Layer"; newLayer.brush_name = "grass"; newLayer.item_id = 4526; newLayer.height_min = 0.0; newLayer.height_max = 1.0; newLayer.moisture_min = -1.0; newLayer.moisture_max = 1.0; newLayer.noise_scale = 1.0; newLayer.coverage = 1.0; newLayer.use_borders = true; newLayer.enabled = true; newLayer.z_order = 1000; working_terrain_layers.push_back(newLayer); PopulateTerrainLayerList(); // Select the new layer terrain_layer_list->SetItemState(working_terrain_layers.size() - 1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); UpdateLayerControls(); } void OTMapGenDialog::OnTerrainLayerRemove(wxCommandEvent& event) { long selected = terrain_layer_list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (selected >= 0 && selected < static_cast(working_terrain_layers.size())) { working_terrain_layers.erase(working_terrain_layers.begin() + selected); PopulateTerrainLayerList(); UpdateLayerControls(); } } void OTMapGenDialog::OnTerrainLayerMoveUp(wxCommandEvent& event) { long selected = terrain_layer_list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (selected > 0 && selected < static_cast(working_terrain_layers.size())) { std::swap(working_terrain_layers[selected], working_terrain_layers[selected - 1]); PopulateTerrainLayerList(); terrain_layer_list->SetItemState(selected - 1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); UpdateLayerControls(); } } void OTMapGenDialog::OnTerrainLayerMoveDown(wxCommandEvent& event) { long selected = terrain_layer_list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (selected >= 0 && selected < static_cast(working_terrain_layers.size()) - 1) { std::swap(working_terrain_layers[selected], working_terrain_layers[selected + 1]); PopulateTerrainLayerList(); terrain_layer_list->SetItemState(selected + 1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); UpdateLayerControls(); } } void OTMapGenDialog::OnTerrainLayerEdit(wxCommandEvent& event) { // Save current layer properties back to the selected layer TerrainLayer* layer = GetSelectedLayer(); if (layer) { layer->name = layer_name_text->GetValue().ToStdString(); int brushSelection = layer_brush_choice->GetSelection(); if (brushSelection >= 0 && brushSelection < static_cast(available_brushes.size())) { layer->brush_name = available_brushes[brushSelection]; } layer->item_id = layer_item_id_spin->GetValue(); double height_min, height_max, moisture_min, moisture_max, noise_scale, coverage; if (height_min_text->GetValue().ToDouble(&height_min)) layer->height_min = height_min; if (height_max_text->GetValue().ToDouble(&height_max)) layer->height_max = height_max; if (moisture_min_text->GetValue().ToDouble(&moisture_min)) layer->moisture_min = moisture_min; if (moisture_max_text->GetValue().ToDouble(&moisture_max)) layer->moisture_max = moisture_max; if (noise_scale_text->GetValue().ToDouble(&noise_scale)) layer->noise_scale = noise_scale; if (coverage_text->GetValue().ToDouble(&coverage)) layer->coverage = coverage; layer->use_borders = use_borders_checkbox->GetValue(); layer->enabled = layer_enabled_checkbox->GetValue(); layer->z_order = z_order_spin->GetValue(); PopulateTerrainLayerList(); } } void OTMapGenDialog::OnBrushChoice(wxCommandEvent& event) { // Auto-update item ID when brush is changed wxChoice* choice = dynamic_cast(event.GetEventObject()); if (choice) { int selection = choice->GetSelection(); if (selection >= 0 && selection < static_cast(available_brushes.size())) { wxString brushName = available_brushes[selection]; // Try to get the primary item ID from the brush (placeholder - needs implementation) // For now, use some default mappings based on brush name uint16_t itemId = 100; // Default if (brushName == "grass") itemId = 4526; else if (brushName == "sea") itemId = 4608; else if (brushName == "sand") itemId = 231; else if (brushName == "mountain") itemId = 919; else if (brushName == "cave") itemId = 351; else if (brushName == "snow") itemId = 670; else if (brushName == "stone") itemId = 1284; if (choice->GetId() == ID_LAYER_BRUSH_CHOICE) { layer_item_id_spin->SetValue(itemId); } else if (choice->GetId() == ID_CAVE_BRUSH_CHOICE) { cave_item_id_spin->SetValue(itemId); } else if (choice->GetId() == ID_WATER_BRUSH_CHOICE) { water_item_id_spin->SetValue(itemId); } } } } void OTMapGenDialog::OnItemIdChange(wxCommandEvent& event) { // Item ID changed - could trigger preview update } // Helper functions for terrain layer management void OTMapGenDialog::PopulateTerrainLayerList() { terrain_layer_list->DeleteAllItems(); for (size_t i = 0; i < working_terrain_layers.size(); ++i) { const TerrainLayer& layer = working_terrain_layers[i]; long index = terrain_layer_list->InsertItem(i, layer.name); terrain_layer_list->SetItem(index, 1, layer.brush_name); terrain_layer_list->SetItem(index, 2, wxString::Format("%d", layer.item_id)); terrain_layer_list->SetItem(index, 3, wxString::Format("%.1f-%.1f", layer.height_min, layer.height_max)); terrain_layer_list->SetItem(index, 4, layer.enabled ? "Yes" : "No"); } } void OTMapGenDialog::PopulateBrushChoices() { // Populate with common brush names from grounds.xml // This should be replaced with actual brush parsing from the XML files available_brushes.clear(); available_brushes.push_back("grass"); available_brushes.push_back("sea"); available_brushes.push_back("sand"); available_brushes.push_back("mountain"); available_brushes.push_back("cave"); available_brushes.push_back("snow"); available_brushes.push_back("stone floor"); available_brushes.push_back("wooden floor"); available_brushes.push_back("lawn"); available_brushes.push_back("ice"); // Populate all brush choice controls layer_brush_choice->Clear(); cave_brush_choice->Clear(); water_brush_choice->Clear(); for (const std::string& brush : available_brushes) { layer_brush_choice->Append(brush); cave_brush_choice->Append(brush); water_brush_choice->Append(brush); } // Set default selections cave_brush_choice->SetStringSelection("cave"); water_brush_choice->SetStringSelection("sea"); } void OTMapGenDialog::UpdateLayerControls() { TerrainLayer* layer = GetSelectedLayer(); bool hasSelection = (layer != nullptr); // Enable/disable controls based on selection layer_properties_panel->Enable(hasSelection); remove_layer_button->Enable(hasSelection); edit_layer_button->Enable(hasSelection); long selected = terrain_layer_list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); move_up_button->Enable(hasSelection && selected > 0); move_down_button->Enable(hasSelection && selected < static_cast(working_terrain_layers.size()) - 1); if (hasSelection) { SetSelectedLayer(*layer); } else { // Clear controls layer_name_text->SetValue(""); layer_brush_choice->SetSelection(-1); layer_item_id_spin->SetValue(100); height_min_text->SetValue("0.0"); height_max_text->SetValue("1.0"); moisture_min_text->SetValue("-1.0"); moisture_max_text->SetValue("1.0"); noise_scale_text->SetValue("1.0"); coverage_text->SetValue("1.0"); use_borders_checkbox->SetValue(false); layer_enabled_checkbox->SetValue(false); z_order_spin->SetValue(1000); } } TerrainLayer* OTMapGenDialog::GetSelectedLayer() { long selected = terrain_layer_list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (selected >= 0 && selected < static_cast(working_terrain_layers.size())) { return &working_terrain_layers[selected]; } return nullptr; } void OTMapGenDialog::SetSelectedLayer(const TerrainLayer& layer) { layer_name_text->SetValue(layer.name); // Find and select the brush for (size_t i = 0; i < available_brushes.size(); ++i) { if (available_brushes[i] == layer.brush_name) { layer_brush_choice->SetSelection(i); break; } } layer_item_id_spin->SetValue(layer.item_id); height_min_text->SetValue(wxString::Format("%.2f", layer.height_min)); height_max_text->SetValue(wxString::Format("%.2f", layer.height_max)); moisture_min_text->SetValue(wxString::Format("%.2f", layer.moisture_min)); moisture_max_text->SetValue(wxString::Format("%.2f", layer.moisture_max)); noise_scale_text->SetValue(wxString::Format("%.2f", layer.noise_scale)); coverage_text->SetValue(wxString::Format("%.2f", layer.coverage)); use_borders_checkbox->SetValue(layer.use_borders); layer_enabled_checkbox->SetValue(layer.enabled); z_order_spin->SetValue(layer.z_order); } void OTMapGenDialog::GetTilePreviewColor(uint16_t tileId, unsigned char& r, unsigned char& g, unsigned char& b) { // First check if this tile ID matches any of our configured terrain layers for (const auto& layer : working_terrain_layers) { if (layer.item_id == tileId && layer.enabled) { // Use layer-specific colors based on layer name if (layer.name == "Water" || layer.brush_name == "sea") { r = 0; g = 100; b = 255; // Blue for water return; } else if (layer.name == "Grass" || layer.brush_name == "grass") { r = 50; g = 200; b = 50; // Green for grass return; } else if (layer.name == "Sand" || layer.brush_name == "sand") { r = 255; g = 255; b = 100; // Yellow for sand return; } else if (layer.name == "Mountain" || layer.brush_name == "mountain") { r = 139; g = 69; b = 19; // Brown for mountains return; } else if (layer.brush_name == "snow") { r = 255; g = 255; b = 255; // White for snow return; } else if (layer.brush_name == "cave") { r = 64; g = 64; b = 64; // Dark gray for caves return; } else if (layer.brush_name.find("stone") != std::string::npos) { r = 128; g = 128; b = 128; // Gray for stone return; } else if (layer.brush_name == "ice") { r = 200; g = 200; b = 255; // Light blue for ice return; } else if (layer.brush_name.find("wood") != std::string::npos) { r = 139; g = 69; b = 19; // Brown for wood return; } } } // Check cave and water configuration if (tileId == cave_item_id_spin->GetValue()) { r = 64; g = 64; b = 64; // Dark gray for caves return; } if (tileId == water_item_id_spin->GetValue()) { r = 0; g = 100; b = 255; // Blue for water return; } // Fallback: Convert tile ID to preview colors using common item IDs switch (tileId) { case 4608: case 4609: case 4610: case 4611: case 4612: case 4613: r = 0; g = 100; b = 255; // Blue for water break; case 4526: case 4527: case 4528: case 4529: case 4530: r = 50; g = 200; b = 50; // Green for grass break; case 231: r = 255; g = 255; b = 100; // Yellow for sand break; case 1284: case 431: r = 128; g = 128; b = 128; // Gray for stone break; case 4597: r = 100; g = 100; b = 100; // Dark gray for gravel break; case 670: case 671: r = 255; g = 255; b = 255; // White for snow break; case 919: case 4468: case 4469: r = 139; g = 69; b = 19; // Brown for mountains break; case 351: case 352: case 353: r = 64; g = 64; b = 64; // Dark gray for caves break; case 106: case 108: case 109: r = 0; g = 150; b = 0; // Dark green for lawn break; case 405: case 448: r = 139; g = 69; b = 19; // Brown for wooden floors break; default: r = 64; g = 64; b = 64; // Default dark gray break; } }