| @@ -0,0 +1,85 @@ | |||
| 1 | + | ////////////////////////////////////////////////////////////////////// | |
| 2 | + | // This file is part of Remere's Map Editor | |
| 3 | + | ////////////////////////////////////////////////////////////////////// | |
| 4 | + | // Remere's Map Editor is free software: you can redistribute it and/or modify | |
| 5 | + | // it under the terms of the GNU General Public License as published by | |
| 6 | + | // the Free Software Foundation, either version 3 of the License, or | |
| 7 | + | // (at your option) any later version. | |
| 8 | + | // | |
| 9 | + | // Remere's Map Editor is distributed in the hope that it will be useful, | |
| 10 | + | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 11 | + | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 12 | + | // GNU General Public License for more details. | |
| 13 | + | // | |
| 14 | + | // You should have received a copy of the GNU General Public License | |
| 15 | + | // along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| 16 | + | ////////////////////////////////////////////////////////////////////// | |
| 17 | + | ||
| 18 | + | #ifndef RME_OTMM_IOMAP_H_ | |
| 19 | + | #define RME_OTMM_IOMAP_H_ | |
| 20 | + | ||
| 21 | + | #include "iomap.h" | |
| 22 | + | ||
| 23 | + | #pragma pack(1) | |
| 24 | + | ||
| 25 | + | enum OTMM_VERSION { | |
| 26 | + | OTMM_VERSION_1, | |
| 27 | + | }; | |
| 28 | + | ||
| 29 | + | enum OTMM_TileAttributes { | |
| 30 | + | OTMM_ATTR_TILE_FLAGS = 1, | |
| 31 | + | }; | |
| 32 | + | ||
| 33 | + | enum OTMM_ItemAttribute { | |
| 34 | + | OTMM_ATTR_DESCRIPTION = 1, | |
| 35 | + | OTMM_ATTR_ACTION_ID = 2, | |
| 36 | + | OTMM_ATTR_UNIQUE_ID = 3, | |
| 37 | + | OTMM_ATTR_TEXT = 4, | |
| 38 | + | OTMM_ATTR_DESC = 5, | |
| 39 | + | OTMM_ATTR_TELE_DEST = 6, | |
| 40 | + | OTMM_ATTR_ITEM = 7, | |
| 41 | + | OTMM_ATTR_SUBTYPE = 8, | |
| 42 | + | OTMM_ATTR_DEPOT_ID = 9, | |
| 43 | + | OTMM_ATTR_DOOR_ID = 10, | |
| 44 | + | OTMM_ATTR_DURATION = 11, | |
| 45 | + | }; | |
| 46 | + | ||
| 47 | + | enum OTMM_NodeTypes { | |
| 48 | + | OTMM_ROOT = 1, | |
| 49 | + | OTMM_MAP_DATA = 2, | |
| 50 | + | OTMM_TILE_DATA = 3, | |
| 51 | + | OTMM_TILE = 4, | |
| 52 | + | OTMM_HOUSETILE = 5, | |
| 53 | + | OTMM_ITEM = 6, | |
| 54 | + | OTMM_SPAWN_DATA = 7, | |
| 55 | + | OTMM_SPAWN_AREA = 8, | |
| 56 | + | OTMM_MONSTER = 9, | |
| 57 | + | OTMM_NPC = 10, | |
| 58 | + | OTMM_TOWN_DATA = 11, | |
| 59 | + | OTMM_TOWN = 12, | |
| 60 | + | OTMM_HOUSE_DATA = 13, | |
| 61 | + | OTMM_HOUSE = 14, | |
| 62 | + | OTMM_DESCRIPTION = 15, | |
| 63 | + | OTMM_EDITOR = 16, | |
| 64 | + | }; | |
| 65 | + | ||
| 66 | + | #pragma pack() | |
| 67 | + | ||
| 68 | + | class NodeFileReadHandle; | |
| 69 | + | class NodeFileWriteHandle; | |
| 70 | + | ||
| 71 | + | class IOMapOTMM : public IOMap { | |
| 72 | + | public: | |
| 73 | + | IOMapOTMM(); | |
| 74 | + | ~IOMapOTMM(); | |
| 75 | + | ||
| 76 | + | virtual MapVersion getVersionInfo(const FileName& identifier); | |
| 77 | + | ||
| 78 | + | virtual bool loadMap(Map& map, NodeFileReadHandle& handle, const FileName& identifier, bool showdialog); | |
| 79 | + | virtual bool loadMap(Map& map, const FileName& identifier, bool showdialog); | |
| 80 | + | ||
| 81 | + | virtual bool saveMap(Map& map, NodeFileWriteHandle& handle, const FileName& identifier, bool showdialog); | |
| 82 | + | virtual bool saveMap(Map& map, const FileName& identifier, bool showdialog); | |
| 83 | + | }; | |
| 84 | + | ||
| 85 | + | #endif |
| @@ -0,0 +1,803 @@ | |||
| 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 "island_generator_dialog.h" | |
| 20 | + | #include "gui.h" | |
| 21 | + | #include "artprovider.h" | |
| 22 | + | #include "items.h" | |
| 23 | + | #include "brush.h" | |
| 24 | + | #include "ground_brush.h" | |
| 25 | + | #include <random> | |
| 26 | + | #include <ctime> | |
| 27 | + | ||
| 28 | + | BEGIN_EVENT_TABLE(IslandGeneratorDialog, wxDialog) | |
| 29 | + | EVT_BUTTON(ID_ISLAND_GENERATE_SINGLE, IslandGeneratorDialog::OnGenerateClick) | |
| 30 | + | EVT_BUTTON(ID_ISLAND_GENERATE_MULTIPLE, IslandGeneratorDialog::OnGenerateMultiple) | |
| 31 | + | EVT_BUTTON(ID_ISLAND_CANCEL, IslandGeneratorDialog::OnCancelClick) | |
| 32 | + | EVT_BUTTON(ID_ISLAND_RANDOM_SEED, IslandGeneratorDialog::OnRandomizeSeed) | |
| 33 | + | EVT_CHOICE(ID_ISLAND_SHAPE_SELECT, IslandGeneratorDialog::OnShapeSelect) | |
| 34 | + | EVT_SPINCTRL(ID_ISLAND_SIZE_CHANGE, IslandGeneratorDialog::OnSizeChange) | |
| 35 | + | EVT_SPINCTRL(ID_ISLAND_ROUGHNESS_CHANGE, IslandGeneratorDialog::OnRoughnessChange) | |
| 36 | + | EVT_TEXT(ID_ISLAND_SEED_TEXT, IslandGeneratorDialog::OnSeedText) | |
| 37 | + | EVT_LISTBOX(ID_ISLAND_BORDER_SELECT, IslandGeneratorDialog::OnBorderSelect) | |
| 38 | + | EVT_BUTTON(ID_ISLAND_BORDER_PREVIEW, IslandGeneratorDialog::OnBorderPreview) | |
| 39 | + | END_EVENT_TABLE() | |
| 40 | + | ||
| 41 | + | IslandPreviewButton::IslandPreviewButton(wxWindow* parent) : | |
| 42 | + | DCButton(parent, wxID_ANY, wxDefaultPosition, DC_BTN_TOGGLE, RENDER_SIZE_32x32, 0), | |
| 43 | + | m_id(0) { | |
| 44 | + | //// | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | void IslandPreviewButton::SetItemId(uint16_t id) { | |
| 48 | + | if (m_id == id) { | |
| 49 | + | return; | |
| 50 | + | } | |
| 51 | + | ||
| 52 | + | m_id = id; | |
| 53 | + | ||
| 54 | + | if (m_id != 0) { | |
| 55 | + | const ItemType& it = g_items.getItemType(m_id); | |
| 56 | + | if (it.id != 0) { | |
| 57 | + | SetSprite(it.clientID); | |
| 58 | + | return; | |
| 59 | + | } | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | SetSprite(0); | |
| 63 | + | } | |
| 64 | + | ||
| 65 | + | // Since we don't have FastNoise, let's create a simple noise function | |
| 66 | + | class SimpleNoise { | |
| 67 | + | public: | |
| 68 | + | SimpleNoise() : seed(0) {} | |
| 69 | + | void SetSeed(int s) { seed = s; } | |
| 70 | + | ||
| 71 | + | float GetNoise(float x, float y) { | |
| 72 | + | int n = seed + static_cast<int>(x * 1997) + static_cast<int>(y * 17931); | |
| 73 | + | n = (n << 13) ^ n; | |
| 74 | + | return 1.0f - ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0f; | |
| 75 | + | } | |
| 76 | + | private: | |
| 77 | + | int seed; | |
| 78 | + | }; | |
| 79 | + | ||
| 80 | + | IslandGeneratorDialog::IslandGeneratorDialog(wxWindow* parent) : | |
| 81 | + | wxDialog(parent, wxID_ANY, "Island Generator", wxDefaultPosition, wxSize(1000, 700), | |
| 82 | + | wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { | |
| 83 | + | ||
| 84 | + | // Create scrolled window for content | |
| 85 | + | wxScrolledWindow* scrolled = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, | |
| 86 | + | wxDefaultSize, wxVSCROLL | wxHSCROLL); | |
| 87 | + | scrolled->SetScrollRate(5, 5); | |
| 88 | + | ||
| 89 | + | // Main sizer with left and right columns | |
| 90 | + | wxBoxSizer* main_sizer = new wxBoxSizer(wxHORIZONTAL); | |
| 91 | + | wxBoxSizer* left_column = new wxBoxSizer(wxVERTICAL); | |
| 92 | + | wxBoxSizer* right_column = new wxBoxSizer(wxVERTICAL); | |
| 93 | + | ||
| 94 | + | // Ground and Water selection row | |
| 95 | + | wxBoxSizer* type_row = new wxBoxSizer(wxHORIZONTAL); | |
| 96 | + | ||
| 97 | + | // Ground selection with range input | |
| 98 | + | wxStaticBoxSizer* ground_sizer = new wxStaticBoxSizer(wxVERTICAL, scrolled, "Ground Type"); | |
| 99 | + | ground_button = new IslandPreviewButton(scrolled); | |
| 100 | + | ground_button->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(IslandGeneratorDialog::OnGroundClick), NULL, this); | |
| 101 | + | ||
| 102 | + | wxBoxSizer* ground_input_row = new wxBoxSizer(wxHORIZONTAL); | |
| 103 | + | ground_input_row->Add(ground_button, 0, wxALL | wxALIGN_CENTER, 5); | |
| 104 | + | ||
| 105 | + | ground_range_input = new wxTextCtrl(scrolled, wxID_ANY, "", wxDefaultPosition, wxSize(100, -1)); | |
| 106 | + | ground_range_input->SetToolTip("Enter ground ID or range (e.g., 100-105,200)"); | |
| 107 | + | ground_range_input->Bind(wxEVT_TEXT, &IslandGeneratorDialog::OnIdInput, this); | |
| 108 | + | ground_input_row->Add(ground_range_input, 1, wxALL | wxALIGN_CENTER_VERTICAL, 5); | |
| 109 | + | ||
| 110 | + | ground_sizer->Add(ground_input_row, 0, wxEXPAND); | |
| 111 | + | type_row->Add(ground_sizer, 1, wxALL | wxEXPAND, 5); | |
| 112 | + | ||
| 113 | + | // Water selection with range input | |
| 114 | + | wxStaticBoxSizer* water_sizer = new wxStaticBoxSizer(wxVERTICAL, scrolled, "Water Type"); | |
| 115 | + | water_button = new IslandPreviewButton(scrolled); | |
| 116 | + | water_button->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(IslandGeneratorDialog::OnWaterClick), NULL, this); | |
| 117 | + | ||
| 118 | + | wxBoxSizer* water_input_row = new wxBoxSizer(wxHORIZONTAL); | |
| 119 | + | water_input_row->Add(water_button, 0, wxALL | wxALIGN_CENTER, 5); | |
| 120 | + | ||
| 121 | + | water_range_input = new wxTextCtrl(scrolled, wxID_ANY, "", wxDefaultPosition, wxSize(100, -1)); | |
| 122 | + | water_range_input->SetToolTip("Enter water ID or range (e.g., 100-105,200)"); | |
| 123 | + | water_range_input->Bind(wxEVT_TEXT, &IslandGeneratorDialog::OnIdInput, this); | |
| 124 | + | water_input_row->Add(water_range_input, 1, wxALL | wxALIGN_CENTER_VERTICAL, 5); | |
| 125 | + | ||
| 126 | + | water_sizer->Add(water_input_row, 0, wxEXPAND); | |
| 127 | + | type_row->Add(water_sizer, 1, wxALL | wxEXPAND, 5); | |
| 128 | + | ||
| 129 | + | left_column->Add(type_row, 0, wxALL | wxEXPAND, 5); | |
| 130 | + | ||
| 131 | + | // Position and Shape row | |
| 132 | + | wxBoxSizer* pos_shape_row = new wxBoxSizer(wxHORIZONTAL); | |
| 133 | + | ||
| 134 | + | // Position controls | |
| 135 | + | wxStaticBoxSizer* pos_sizer = new wxStaticBoxSizer(wxVERTICAL, scrolled, "Starting Position"); | |
| 136 | + | wxFlexGridSizer* pos_grid = new wxFlexGridSizer(3, 2, 5, 5); | |
| 137 | + | ||
| 138 | + | pos_grid->Add(new wxStaticText(scrolled, wxID_ANY, "X:"), 0, wxALIGN_CENTER_VERTICAL); | |
| 139 | + | pos_x_spin = new wxSpinCtrl(scrolled, wxID_ANY, "0", wxDefaultPosition, wxSize(100, -1), | |
| 140 | + | wxSP_ARROW_KEYS, 0, 65535, 0); | |
| 141 | + | pos_grid->Add(pos_x_spin); | |
| 142 | + | ||
| 143 | + | pos_grid->Add(new wxStaticText(scrolled, wxID_ANY, "Y:"), 0, wxALIGN_CENTER_VERTICAL); | |
| 144 | + | pos_y_spin = new wxSpinCtrl(scrolled, wxID_ANY, "0", wxDefaultPosition, wxSize(100, -1), | |
| 145 | + | wxSP_ARROW_KEYS, 0, 65535, 0); | |
| 146 | + | pos_grid->Add(pos_y_spin); | |
| 147 | + | ||
| 148 | + | pos_grid->Add(new wxStaticText(scrolled, wxID_ANY, "Z:"), 0, wxALIGN_CENTER_VERTICAL); | |
| 149 | + | pos_z_spin = new wxSpinCtrl(scrolled, wxID_ANY, "7", wxDefaultPosition, wxSize(100, -1), | |
| 150 | + | wxSP_ARROW_KEYS, 0, 15, 7); | |
| 151 | + | pos_grid->Add(pos_z_spin); | |
| 152 | + | ||
| 153 | + | pos_sizer->Add(pos_grid, 0, wxALL | wxEXPAND, 5); | |
| 154 | + | pos_shape_row->Add(pos_sizer, 1, wxALL | wxEXPAND, 5); | |
| 155 | + | ||
| 156 | + | // Shape selection | |
| 157 | + | wxStaticBoxSizer* shape_sizer = new wxStaticBoxSizer(wxVERTICAL, scrolled, "Island Shape"); | |
| 158 | + | shape_choice = new wxChoice(scrolled, wxID_ANY); | |
| 159 | + | shape_choice->Append("Circular"); | |
| 160 | + | shape_choice->Append("Square"); | |
| 161 | + | shape_choice->Append("Irregular"); | |
| 162 | + | shape_choice->SetSelection(0); | |
| 163 | + | shape_sizer->Add(shape_choice, 0, wxALL | wxEXPAND, 5); | |
| 164 | + | pos_shape_row->Add(shape_sizer, 1, wxALL | wxEXPAND, 5); | |
| 165 | + | ||
| 166 | + | left_column->Add(pos_shape_row, 0, wxALL | wxEXPAND, 5); | |
| 167 | + | ||
| 168 | + | // Size and Roughness row | |
| 169 | + | wxBoxSizer* size_rough_row = new wxBoxSizer(wxHORIZONTAL); | |
| 170 | + | ||
| 171 | + | // Size control | |
| 172 | + | wxStaticBoxSizer* size_sizer = new wxStaticBoxSizer(wxVERTICAL, scrolled, "Island Size"); | |
| 173 | + | size_spin = new wxSpinCtrl(scrolled, wxID_ANY, "50", wxDefaultPosition, wxDefaultSize, | |
| 174 | + | wxSP_ARROW_KEYS, 10, 1000, 50); | |
| 175 | + | size_sizer->Add(size_spin, 0, wxALL | wxEXPAND, 5); | |
| 176 | + | size_rough_row->Add(size_sizer, 1, wxALL | wxEXPAND, 5); | |
| 177 | + | ||
| 178 | + | // Roughness control | |
| 179 | + | wxStaticBoxSizer* roughness_sizer = new wxStaticBoxSizer(wxVERTICAL, scrolled, "Roughness"); | |
| 180 | + | roughness_spin = new wxSpinCtrl(scrolled, wxID_ANY, "50", wxDefaultPosition, wxDefaultSize, | |
| 181 | + | wxSP_ARROW_KEYS, 0, 100, 50); | |
| 182 | + | roughness_sizer->Add(roughness_spin, 0, wxALL | wxEXPAND, 5); | |
| 183 | + | size_rough_row->Add(roughness_sizer, 1, wxALL | wxEXPAND, 5); | |
| 184 | + | ||
| 185 | + | left_column->Add(size_rough_row, 0, wxALL | wxEXPAND, 5); | |
| 186 | + | ||
| 187 | + | // Multiple islands controls | |
| 188 | + | wxStaticBoxSizer* multi_sizer = new wxStaticBoxSizer(wxVERTICAL, scrolled, "Multiple Islands"); | |
| 189 | + | wxFlexGridSizer* multi_grid = new wxFlexGridSizer(2, 2, 5, 5); | |
| 190 | + | ||
| 191 | + | multi_grid->Add(new wxStaticText(scrolled, wxID_ANY, "Count:"), 0, wxALIGN_CENTER_VERTICAL); | |
| 192 | + | islands_count_spin = new wxSpinCtrl(scrolled, wxID_ANY, "4", wxDefaultPosition, wxDefaultSize, | |
| 193 | + | wxSP_ARROW_KEYS, 1, 100, 4); | |
| 194 | + | multi_grid->Add(islands_count_spin); | |
| 195 | + | ||
| 196 | + | multi_grid->Add(new wxStaticText(scrolled, wxID_ANY, "Spacing:"), 0, wxALIGN_CENTER_VERTICAL); | |
| 197 | + | islands_spacing_spin = new wxSpinCtrl(scrolled, wxID_ANY, "10", wxDefaultPosition, wxDefaultSize, | |
| 198 | + | wxSP_ARROW_KEYS, 1, 100, 10); | |
| 199 | + | multi_grid->Add(islands_spacing_spin); | |
| 200 | + | ||
| 201 | + | multi_sizer->Add(multi_grid, 0, wxALL | wxEXPAND, 5); | |
| 202 | + | left_column->Add(multi_sizer, 0, wxALL | wxEXPAND, 5); | |
| 203 | + | ||
| 204 | + | // Seed input row | |
| 205 | + | wxStaticBoxSizer* seed_sizer = new wxStaticBoxSizer(wxHORIZONTAL, scrolled, "Generation Seed"); | |
| 206 | + | seed_input = new wxTextCtrl(scrolled, wxID_ANY); | |
| 207 | + | random_seed_button = new wxButton(scrolled, wxID_ANY, "Random"); | |
| 208 | + | seed_sizer->Add(seed_input, 1, wxALL | wxEXPAND, 5); | |
| 209 | + | seed_sizer->Add(random_seed_button, 0, wxALL, 5); | |
| 210 | + | left_column->Add(seed_sizer, 0, wxALL | wxEXPAND, 5); | |
| 211 | + | ||
| 212 | + | // Automagic checkbox | |
| 213 | + | use_automagic = new wxCheckBox(scrolled, wxID_ANY, "Use Automagic for Borders"); | |
| 214 | + | use_automagic->SetValue(true); | |
| 215 | + | left_column->Add(use_automagic, 0, wxALL | wxEXPAND, 5); | |
| 216 | + | ||
| 217 | + | // Preview area | |
| 218 | + | wxStaticBoxSizer* preview_sizer = new wxStaticBoxSizer(wxVERTICAL, scrolled, "Preview"); | |
| 219 | + | preview_bitmap = new wxStaticBitmap(scrolled, wxID_ANY, | |
| 220 | + | wxBitmap(200, 200, wxBITMAP_SCREEN_DEPTH)); | |
| 221 | + | preview_sizer->Add(preview_bitmap, 0, wxALL | wxEXPAND, 5); | |
| 222 | + | left_column->Add(preview_sizer, 1, wxALL | wxEXPAND, 5); | |
| 223 | + | ||
| 224 | + | // Progress bar | |
| 225 | + | progress = new wxGauge(scrolled, wxID_ANY, 100, wxDefaultPosition, wxSize(-1, 20)); | |
| 226 | + | left_column->Add(progress, 0, wxALL | wxEXPAND, 5); | |
| 227 | + | ||
| 228 | + | // Buttons row | |
| 229 | + | wxBoxSizer* button_sizer = new wxBoxSizer(wxHORIZONTAL); | |
| 230 | + | generate_button = new wxButton(scrolled, ID_ISLAND_GENERATE_SINGLE, "Generate Single"); | |
| 231 | + | generate_multiple_button = new wxButton(scrolled, ID_ISLAND_GENERATE_MULTIPLE, "Generate Multiple"); | |
| 232 | + | cancel_button = new wxButton(scrolled, ID_ISLAND_CANCEL, "Cancel"); | |
| 233 | + | button_sizer->Add(generate_button, 1, wxALL, 5); | |
| 234 | + | button_sizer->Add(generate_multiple_button, 1, wxALL, 5); | |
| 235 | + | button_sizer->Add(cancel_button, 1, wxALL, 5); | |
| 236 | + | left_column->Add(button_sizer, 0, wxALL | wxEXPAND, 5); | |
| 237 | + | ||
| 238 | + | // Border List (right column) | |
| 239 | + | wxStaticBoxSizer* border_sizer = new wxStaticBoxSizer(wxVERTICAL, scrolled, "Border Style"); | |
| 240 | + | ||
| 241 | + | // Create border list | |
| 242 | + | border_list = new wxListBox(scrolled, ID_ISLAND_BORDER_SELECT, | |
| 243 | + | wxDefaultPosition, wxSize(300, 400), | |
| 244 | + | 0, nullptr, wxLB_SINGLE | wxLB_HSCROLL); | |
| 245 | + | border_sizer->Add(border_list, 1, wxEXPAND | wxALL, 5); | |
| 246 | + | ||
| 247 | + | // Border preview | |
| 248 | + | border_preview = new wxStaticBitmap(scrolled, ID_ISLAND_BORDER_PREVIEW, | |
| 249 | + | wxBitmap(200, 200, wxBITMAP_SCREEN_DEPTH)); | |
| 250 | + | border_sizer->Add(border_preview, 0, wxALL | wxCENTER, 5); | |
| 251 | + | ||
| 252 | + | right_column->Add(border_sizer, 1, wxEXPAND | wxALL, 5); | |
| 253 | + | ||
| 254 | + | // Add both columns to main sizer | |
| 255 | + | main_sizer->Add(left_column, 1, wxEXPAND | wxALL, 5); | |
| 256 | + | main_sizer->Add(right_column, 0, wxEXPAND | wxALL, 5); | |
| 257 | + | ||
| 258 | + | // Set sizer for scrolled window | |
| 259 | + | scrolled->SetSizer(main_sizer); | |
| 260 | + | ||
| 261 | + | // Dialog sizer | |
| 262 | + | wxBoxSizer* dialog_sizer = new wxBoxSizer(wxVERTICAL); | |
| 263 | + | dialog_sizer->Add(scrolled, 1, wxEXPAND | wxALL, 5); | |
| 264 | + | SetSizer(dialog_sizer); | |
| 265 | + | ||
| 266 | + | // Initialize values | |
| 267 | + | ground_id = 0; | |
| 268 | + | water_id = 0; | |
| 269 | + | island_size = 50; | |
| 270 | + | roughness = 50; | |
| 271 | + | selected_shape = "Circular"; | |
| 272 | + | seed = wxString::Format("%d", wxGetLocalTime()); | |
| 273 | + | seed_input->SetValue(seed); | |
| 274 | + | start_position = Position(0, 0, 7); | |
| 275 | + | selected_border_id = -1; | |
| 276 | + | ||
| 277 | + | // Load borders | |
| 278 | + | LoadBorderChoices(); | |
| 279 | + | ||
| 280 | + | // Initial state | |
| 281 | + | UpdateWidgets(); | |
| 282 | + | Centre(wxBOTH); | |
| 283 | + | ||
| 284 | + | // Make dialog non-modal | |
| 285 | + | Show(true); | |
| 286 | + | } | |
| 287 | + | ||
| 288 | + | IslandGeneratorDialog::~IslandGeneratorDialog() { | |
| 289 | + | ground_button->Disconnect(wxEVT_LEFT_DOWN, wxMouseEventHandler(IslandGeneratorDialog::OnGroundClick), NULL, this); | |
| 290 | + | water_button->Disconnect(wxEVT_LEFT_DOWN, wxMouseEventHandler(IslandGeneratorDialog::OnWaterClick), NULL, this); | |
| 291 | + | } | |
| 292 | + | ||
| 293 | + | void IslandGeneratorDialog::OnGroundClick(wxMouseEvent& event) { | |
| 294 | + | const Brush* brush = g_gui.GetCurrentBrush(); | |
| 295 | + | if (brush && brush->isGround()) { | |
| 296 | + | const GroundBrush* ground = dynamic_cast<const GroundBrush*>(brush); | |
| 297 | + | if (ground) { | |
| 298 | + | uint16_t id = ground->getID(); | |
| 299 | + | ground_button->SetItemId(id); | |
| 300 | + | ground_id = id; | |
| 301 | + | ground_range_input->SetValue(wxString::Format("%d", id)); | |
| 302 | + | UpdatePreview(); | |
| 303 | + | } | |
| 304 | + | } | |
| 305 | + | UpdateWidgets(); | |
| 306 | + | } | |
| 307 | + | ||
| 308 | + | void IslandGeneratorDialog::OnWaterClick(wxMouseEvent& event) { | |
| 309 | + | const Brush* brush = g_gui.GetCurrentBrush(); | |
| 310 | + | if (brush && brush->isGround()) { | |
| 311 | + | const GroundBrush* ground = dynamic_cast<const GroundBrush*>(brush); | |
| 312 | + | if (ground) { | |
| 313 | + | uint16_t id = ground->getID(); | |
| 314 | + | water_button->SetItemId(id); | |
| 315 | + | water_id = id; | |
| 316 | + | water_range_input->SetValue(wxString::Format("%d", id)); | |
| 317 | + | UpdatePreview(); | |
| 318 | + | } | |
| 319 | + | } | |
| 320 | + | UpdateWidgets(); | |
| 321 | + | } | |
| 322 | + | ||
| 323 | + | void IslandGeneratorDialog::OnGenerateClick(wxCommandEvent& event) { | |
| 324 | + | if (ground_id == 0 || water_id == 0) { | |
| 325 | + | wxMessageBox("Please select both ground and water types!", "Error", wxOK | wxICON_ERROR); | |
| 326 | + | return; | |
| 327 | + | } | |
| 328 | + | ||
| 329 | + | // Update start position from controls | |
| 330 | + | start_position.x = pos_x_spin->GetValue(); | |
| 331 | + | start_position.y = pos_y_spin->GetValue(); | |
| 332 | + | start_position.z = pos_z_spin->GetValue(); | |
| 333 | + | ||
| 334 | + | GenerateIsland(); | |
| 335 | + | } | |
| 336 | + | ||
| 337 | + | void IslandGeneratorDialog::OnGenerateMultiple(wxCommandEvent& event) { | |
| 338 | + | if (ground_id == 0 || water_id == 0) { | |
| 339 | + | wxMessageBox("Please select both ground and water types!", "Error", wxOK | wxICON_ERROR); | |
| 340 | + | return; | |
| 341 | + | } | |
| 342 | + | ||
| 343 | + | // Update start position from controls | |
| 344 | + | start_position.x = pos_x_spin->GetValue(); | |
| 345 | + | start_position.y = pos_y_spin->GetValue(); | |
| 346 | + | start_position.z = pos_z_spin->GetValue(); | |
| 347 | + | ||
| 348 | + | int count = islands_count_spin->GetValue(); | |
| 349 | + | int spacing = islands_spacing_spin->GetValue(); | |
| 350 | + | ||
| 351 | + | GenerateMultipleIslands(count, spacing); | |
| 352 | + | } | |
| 353 | + | ||
| 354 | + | void IslandGeneratorDialog::OnCancelClick(wxCommandEvent& event) { | |
| 355 | + | Close(); | |
| 356 | + | } | |
| 357 | + | ||
| 358 | + | void IslandGeneratorDialog::OnShapeSelect(wxCommandEvent& event) { | |
| 359 | + | selected_shape = event.GetString(); | |
| 360 | + | UpdatePreview(); | |
| 361 | + | } | |
| 362 | + | ||
| 363 | + | void IslandGeneratorDialog::OnSeedText(wxCommandEvent& event) { | |
| 364 | + | seed = event.GetString(); | |
| 365 | + | UpdatePreview(); | |
| 366 | + | } | |
| 367 | + | ||
| 368 | + | void IslandGeneratorDialog::OnSizeChange(wxSpinEvent& event) { | |
| 369 | + | island_size = event.GetValue(); | |
| 370 | + | UpdatePreview(); | |
| 371 | + | } | |
| 372 | + | ||
| 373 | + | void IslandGeneratorDialog::OnRoughnessChange(wxSpinEvent& event) { | |
| 374 | + | roughness = event.GetValue(); | |
| 375 | + | UpdatePreview(); | |
| 376 | + | } | |
| 377 | + | ||
| 378 | + | void IslandGeneratorDialog::OnRandomizeSeed(wxCommandEvent& event) { | |
| 379 | + | seed = wxString::Format("%d", wxGetLocalTime()); | |
| 380 | + | seed_input->SetValue(seed); | |
| 381 | + | UpdatePreview(); | |
| 382 | + | } | |
| 383 | + | ||
| 384 | + | void IslandGeneratorDialog::UpdateWidgets() { | |
| 385 | + | generate_button->Enable(ground_id != 0 && water_id != 0); | |
| 386 | + | } | |
| 387 | + | ||
| 388 | + | void IslandGeneratorDialog::UpdatePreview() { | |
| 389 | + | // TODO: Generate a small preview of the island shape | |
| 390 | + | // This will be implemented in the next step | |
| 391 | + | } | |
| 392 | + | ||
| 393 | + | void IslandGeneratorDialog::LoadBorderChoices() { | |
| 394 | + | border_list->Clear(); | |
| 395 | + | border_data.clear(); | |
| 396 | + | ||
| 397 | + | // Load borders.xml | |
| 398 | + | wxString dataDir = GetDataDirectoryForVersion(g_gui.GetCurrentVersion().getName()); | |
| 399 | + | if(dataDir.IsEmpty()) return; | |
| 400 | + | ||
| 401 | + | wxString bordersPath = g_gui.GetDataDirectory() + "/" + dataDir + "/borders.xml"; | |
| 402 | + | ||
| 403 | + | pugi::xml_document doc; | |
| 404 | + | if(doc.load_file(bordersPath.mb_str())) { | |
| 405 | + | for(pugi::xml_node borderNode = doc.child("materials").child("border"); | |
| 406 | + | borderNode; borderNode = borderNode.next_sibling("border")) { | |
| 407 | + | ||
| 408 | + | BorderData data; | |
| 409 | + | data.id = borderNode.attribute("id").as_int(); | |
| 410 | + | data.name = wxString::Format("Border %d", data.id); | |
| 411 | + | ||
| 412 | + | // Load all border items | |
| 413 | + | for(pugi::xml_node itemNode = borderNode.child("borderitem"); | |
| 414 | + | itemNode; itemNode = itemNode.next_sibling("borderitem")) { | |
| 415 | + | uint16_t itemId = itemNode.attribute("item").as_uint(); | |
| 416 | + | data.items.push_back(itemId); | |
| 417 | + | } | |
| 418 | + | ||
| 419 | + | // Create preview bitmap | |
| 420 | + | if(!data.items.empty()) { | |
| 421 | + | const ItemType& type = g_items.getItemType(data.items[0]); | |
| 422 | + | if(type.id != 0) { | |
| 423 | + | Sprite* sprite = g_gui.gfx.getSprite(type.clientID); | |
| 424 | + | if(sprite) { | |
| 425 | + | wxMemoryDC dc; | |
| 426 | + | data.preview = wxBitmap(32, 32, 32); | |
| 427 | + | dc.SelectObject(data.preview); | |
| 428 | + | sprite->DrawTo(&dc, SPRITE_SIZE_32x32, 0, 0, 32, 32); | |
| 429 | + | dc.SelectObject(wxNullBitmap); | |
| 430 | + | } | |
| 431 | + | } | |
| 432 | + | } | |
| 433 | + | ||
| 434 | + | border_data.push_back(data); | |
| 435 | + | border_list->Append(data.name); | |
| 436 | + | } | |
| 437 | + | } | |
| 438 | + | ||
| 439 | + | if(!border_data.empty()) { | |
| 440 | + | border_list->SetSelection(0); | |
| 441 | + | selected_border_id = border_data[0].id; | |
| 442 | + | UpdateBorderPreview(); | |
| 443 | + | } | |
| 444 | + | } | |
| 445 | + | ||
| 446 | + | void IslandGeneratorDialog::UpdateBorderPreview() { | |
| 447 | + | if(selected_border_id == -1) return; | |
| 448 | + | ||
| 449 | + | // Find selected border data | |
| 450 | + | for(const BorderData& data : border_data) { | |
| 451 | + | if(data.id == selected_border_id) { | |
| 452 | + | border_preview->SetBitmap(data.preview); | |
| 453 | + | break; | |
| 454 | + | } | |
| 455 | + | } | |
| 456 | + | } | |
| 457 | + | ||
| 458 | + | void IslandGeneratorDialog::OnBorderSelect(wxCommandEvent& event) { | |
| 459 | + | int idx = event.GetSelection(); | |
| 460 | + | if(idx >= 0 && idx < (int)border_data.size()) { | |
| 461 | + | selected_border_id = border_data[idx].id; | |
| 462 | + | UpdateBorderPreview(); | |
| 463 | + | } | |
| 464 | + | } | |
| 465 | + | ||
| 466 | + | void IslandGeneratorDialog::OnBorderPreview(wxCommandEvent& WXUNUSED(event)) { | |
| 467 | + | UpdateBorderPreview(); | |
| 468 | + | } | |
| 469 | + | ||
| 470 | + | void IslandGeneratorDialog::GenerateIsland() { | |
| 471 | + | Editor* editor = g_gui.GetCurrentEditor(); | |
| 472 | + | if (!editor) return; | |
| 473 | + | ||
| 474 | + | // Create action to hold all changes | |
| 475 | + | Action* action = editor->actionQueue->createAction(ACTION_DRAW); | |
| 476 | + | ||
| 477 | + | // Calculate island dimensions based on size (no more division by 2) | |
| 478 | + | int start_x = start_position.x; | |
| 479 | + | int start_y = start_position.y; | |
| 480 | + | int end_x = start_position.x + island_size; | |
| 481 | + | int end_y = start_position.y + island_size; | |
| 482 | + | ||
| 483 | + | // Initialize random number generator with seed | |
| 484 | + | std::mt19937 rng(std::hash<std::string>{}(nstr(seed))); | |
| 485 | + | ||
| 486 | + | // Create noise generator | |
| 487 | + | SimpleNoise noise; | |
| 488 | + | noise.SetSeed(rng()); | |
| 489 | + | ||
| 490 | + | // First pass: Generate base shape | |
| 491 | + | std::vector<std::vector<bool>> isGround(island_size + 2, std::vector<bool>(island_size + 2, false)); | |
| 492 | + | ||
| 493 | + | // Calculate center and radius for shape generation | |
| 494 | + | float center_x = start_x + island_size / 2.0f; | |
| 495 | + | float center_y = start_y + island_size / 2.0f; | |
| 496 | + | float max_radius = island_size / 2.0f; | |
| 497 | + | ||
| 498 | + | // First pass: Generate initial shape | |
| 499 | + | for (int y = 0; y <= island_size; ++y) { | |
| 500 | + | for (int x = 0; x <= island_size; ++x) { | |
| 501 | + | float dx = (x - island_size/2.0f) / max_radius; | |
| 502 | + | float dy = (y - island_size/2.0f) / max_radius; | |
| 503 | + | ||
| 504 | + | bool should_place = false; | |
| 505 | + | float base_noise = (noise.GetNoise(x * 0.05f, y * 0.05f) + 1.0f) * 0.5f; | |
| 506 | + | float detail_noise = (noise.GetNoise(x * 0.15f, y * 0.15f) + 1.0f) * 0.5f; | |
| 507 | + | float combined_noise = base_noise * 0.7f + detail_noise * 0.3f; | |
| 508 | + | ||
| 509 | + | if (selected_shape == "Circular") { | |
| 510 | + | float distance = std::sqrt(dx * dx + dy * dy); | |
| 511 | + | float threshold = 1.0f - (roughness / 200.0f) * combined_noise; | |
| 512 | + | should_place = distance <= threshold; | |
| 513 | + | } | |
| 514 | + | else if (selected_shape == "Square") { | |
| 515 | + | float distance = std::max(std::abs(dx), std::abs(dy)); | |
| 516 | + | float threshold = 1.0f - (roughness / 200.0f) * combined_noise; | |
| 517 | + | should_place = distance <= threshold; | |
| 518 | + | } | |
| 519 | + | else if (selected_shape == "Irregular") { | |
| 520 | + | float distance = std::sqrt(dx * dx + dy * dy); | |
| 521 | + | float noise_influence = roughness / 100.0f; | |
| 522 | + | float threshold = 1.0f - noise_influence * combined_noise; | |
| 523 | + | should_place = distance <= threshold; | |
| 524 | + | } | |
| 525 | + | ||
| 526 | + | if (should_place) { | |
| 527 | + | isGround[y][x] = true; | |
| 528 | + | } | |
| 529 | + | } | |
| 530 | + | } | |
| 531 | + | ||
| 532 | + | // Second pass: Remove isolated ground tiles and ensure connectivity | |
| 533 | + | std::vector<std::vector<bool>> connected = isGround; | |
| 534 | + | std::queue<std::pair<int, int>> floodQueue; | |
| 535 | + | ||
| 536 | + | // Start flood fill from center | |
| 537 | + | int center_tile_x = island_size / 2; | |
| 538 | + | int center_tile_y = island_size / 2; | |
| 539 | + | if (isGround[center_tile_y][center_tile_x]) { | |
| 540 | + | floodQueue.push({center_tile_x, center_tile_y}); | |
| 541 | + | std::vector<std::vector<bool>> visited(island_size + 2, std::vector<bool>(island_size + 2, false)); | |
| 542 | + | visited[center_tile_y][center_tile_x] = true; | |
| 543 | + | ||
| 544 | + | while (!floodQueue.empty()) { | |
| 545 | + | auto [x, y] = floodQueue.front(); | |
| 546 | + | floodQueue.pop(); | |
| 547 | + | ||
| 548 | + | // Check all 8 neighbors | |
| 549 | + | for (int dy = -1; dy <= 1; ++dy) { | |
| 550 | + | for (int dx = -1; dx <= 1; ++dx) { | |
| 551 | + | if (dx == 0 && dy == 0) continue; | |
| 552 | + | ||
| 553 | + | int nx = x + dx; | |
| 554 | + | int ny = y + dy; | |
| 555 | + | ||
| 556 | + | if (nx >= 0 && nx < island_size && ny >= 0 && ny < island_size && | |
| 557 | + | isGround[ny][nx] && !visited[ny][nx]) { | |
| 558 | + | visited[ny][nx] = true; | |
| 559 | + | floodQueue.push({nx, ny}); | |
| 560 | + | } | |
| 561 | + | } | |
| 562 | + | } | |
| 563 | + | } | |
| 564 | + | ||
| 565 | + | // Only keep connected ground tiles | |
| 566 | + | for (int y = 0; y <= island_size; ++y) { | |
| 567 | + | for (int x = 0; x <= island_size; ++x) { | |
| 568 | + | connected[y][x] = visited[y][x]; | |
| 569 | + | } | |
| 570 | + | } | |
| 571 | + | } | |
| 572 | + | ||
| 573 | + | // Generate the island using the processed shape | |
| 574 | + | progress->SetValue(0); | |
| 575 | + | int total_tiles = (end_x - start_x + 1) * (end_y - start_y + 1); | |
| 576 | + | int current_tile = 0; | |
| 577 | + | ||
| 578 | + | // Store ground tile positions for border placement | |
| 579 | + | std::vector<Position> groundTiles; | |
| 580 | + | ||
| 581 | + | for (int y = 0; y <= island_size; ++y) { | |
| 582 | + | for (int x = 0; x <= island_size; ++x) { | |
| 583 | + | // Update progress | |
| 584 | + | current_tile++; | |
| 585 | + | progress->SetValue((current_tile * 100) / total_tiles); | |
| 586 | + | ||
| 587 | + | int map_x = start_x + x; | |
| 588 | + | int map_y = start_y + y; | |
| 589 | + | ||
| 590 | + | // Create or get the tile | |
| 591 | + | Tile* tile = editor->map.getTile(map_x, map_y, start_position.z); | |
| 592 | + | if (!tile) { | |
| 593 | + | tile = editor->map.createTile(map_x, map_y, start_position.z); | |
| 594 | + | } | |
| 595 | + | ||
| 596 | + | // Create a copy for the action | |
| 597 | + | Tile* new_tile = tile->deepCopy(editor->map); | |
| 598 | + | ||
| 599 | + | if (connected[y][x]) { | |
| 600 | + | // Place ground | |
| 601 | + | if (ground_id > 0) { | |
| 602 | + | Item* ground_item = Item::Create(ground_id); | |
| 603 | + | new_tile->addItem(ground_item); | |
| 604 | + | } | |
| 605 | + | groundTiles.push_back(Position(map_x, map_y, start_position.z)); | |
| 606 | + | } else { | |
| 607 | + | // Place water | |
| 608 | + | if (water_id > 0) { | |
| 609 | + | Item* water_item = Item::Create(water_id); | |
| 610 | + | new_tile->addItem(water_item); | |
| 611 | + | } | |
| 612 | + | } | |
| 613 | + | ||
| 614 | + | action->addChange(newd Change(new_tile)); | |
| 615 | + | } | |
| 616 | + | } | |
| 617 | + | ||
| 618 | + | // Add the action to the editor | |
| 619 | + | editor->addAction(action); | |
| 620 | + | ||
| 621 | + | // Apply borders if automagic is enabled | |
| 622 | + | if (use_automagic->GetValue()) { | |
| 623 | + | if(selected_border_id != -1) { | |
| 624 | + | // Find selected border data | |
| 625 | + | for(const BorderData& data : border_data) { | |
| 626 | + | if(data.id == selected_border_id) { | |
| 627 | + | Action* borderAction = editor->actionQueue->createAction(ACTION_DRAW); | |
| 628 | + | ||
| 629 | + | // For each ground tile, check if it needs borders | |
| 630 | + | for(const Position& pos : groundTiles) { | |
| 631 | + | // Check all 8 surrounding tiles | |
| 632 | + | for(int dy = -1; dy <= 1; dy++) { | |
| 633 | + | for(int dx = -1; dx <= 1; dx++) { | |
| 634 | + | if(dx == 0 && dy == 0) continue; | |
| 635 | + | ||
| 636 | + | Position checkPos(pos.x + dx, pos.y + dy, pos.z); | |
| 637 | + | bool isNeighborGround = false; | |
| 638 | + | ||
| 639 | + | // Check if neighbor position is in our ground tiles list | |
| 640 | + | for(const Position& groundPos : groundTiles) { | |
| 641 | + | if(groundPos == checkPos) { | |
| 642 | + | isNeighborGround = true; | |
| 643 | + | break; | |
| 644 | + | } | |
| 645 | + | } | |
| 646 | + | ||
| 647 | + | if(!isNeighborGround) { | |
| 648 | + | // This is an edge that needs a border | |
| 649 | + | Tile* tile = editor->map.getTile(checkPos.x, checkPos.y, checkPos.z); | |
| 650 | + | if(!tile) { | |
| 651 | + | tile = editor->map.createTile(checkPos.x, checkPos.y, checkPos.z); | |
| 652 | + | } | |
| 653 | + | ||
| 654 | + | Tile* new_tile = tile->deepCopy(editor->map); | |
| 655 | + | ||
| 656 | + | // Add a border item | |
| 657 | + | if(!data.items.empty()) { | |
| 658 | + | // Select appropriate border piece based on position | |
| 659 | + | size_t index = ((dx + 1) * 3 + (dy + 1)) % data.items.size(); | |
| 660 | + | Item* border_item = Item::Create(data.items[index]); | |
| 661 | + | new_tile->addItem(border_item); | |
| 662 | + | } | |
| 663 | + | ||
| 664 | + | borderAction->addChange(newd Change(new_tile)); | |
| 665 | + | } | |
| 666 | + | } | |
| 667 | + | } | |
| 668 | + | } | |
| 669 | + | ||
| 670 | + | editor->addAction(borderAction); | |
| 671 | + | break; | |
| 672 | + | } | |
| 673 | + | } | |
| 674 | + | } else { | |
| 675 | + | editor->borderizeSelection(); | |
| 676 | + | } | |
| 677 | + | } | |
| 678 | + | ||
| 679 | + | // Refresh the view | |
| 680 | + | g_gui.RefreshView(); | |
| 681 | + | } | |
| 682 | + | ||
| 683 | + | void IslandGeneratorDialog::GenerateMultipleIslands(int count, int spacing) { | |
| 684 | + | Editor* editor = g_gui.GetCurrentEditor(); | |
| 685 | + | if (!editor) return; | |
| 686 | + | ||
| 687 | + | // Calculate grid dimensions | |
| 688 | + | size_t grid_size = static_cast<size_t>(std::ceil(std::sqrt(static_cast<double>(count)))); | |
| 689 | + | size_t spacing_tiles = static_cast<size_t>(spacing * TileSize); // Convert spacing to tiles | |
| 690 | + | size_t island_size_t = static_cast<size_t>(island_size); | |
| 691 | + | ||
| 692 | + | // Store original position | |
| 693 | + | Position original_pos = start_position; | |
| 694 | + | ||
| 695 | + | // Generate islands in a grid pattern | |
| 696 | + | size_t islands_created = 0; | |
| 697 | + | progress->SetValue(0); | |
| 698 | + | ||
| 699 | + | for (size_t row = 0; row < grid_size && islands_created < static_cast<size_t>(count); ++row) { | |
| 700 | + | for (size_t col = 0; col < grid_size && islands_created < static_cast<size_t>(count); ++col) { | |
| 701 | + | // Calculate new start position for this island | |
| 702 | + | start_position.x = original_pos.x + static_cast<int>(col * (island_size_t + spacing_tiles)); | |
| 703 | + | start_position.y = original_pos.y + static_cast<int>(row * (island_size_t + spacing_tiles)); | |
| 704 | + | ||
| 705 | + | // Update progress | |
| 706 | + | progress->SetValue(static_cast<int>((islands_created * 100) / static_cast<size_t>(count))); | |
| 707 | + | ||
| 708 | + | // Generate the island | |
| 709 | + | GenerateIsland(); | |
| 710 | + | ||
| 711 | + | islands_created++; | |
| 712 | + | } | |
| 713 | + | } | |
| 714 | + | ||
| 715 | + | // Restore original position | |
| 716 | + | start_position = original_pos; | |
| 717 | + | ||
| 718 | + | // Final progress update | |
| 719 | + | progress->SetValue(100); | |
| 720 | + | ||
| 721 | + | // Refresh view | |
| 722 | + | g_gui.RefreshView(); | |
| 723 | + | } | |
| 724 | + | ||
| 725 | + | wxString IslandGeneratorDialog::GetDataDirectoryForVersion(const wxString& versionStr) { | |
| 726 | + | pugi::xml_document clientsDoc; | |
| 727 | + | wxString clientsPath = g_gui.GetDataDirectory() + "/clients.xml"; | |
| 728 | + | if(clientsDoc.load_file(clientsPath.mb_str())) { | |
| 729 | + | for(pugi::xml_node clientNode = clientsDoc.child("client_config").child("clients").child("client"); | |
| 730 | + | clientNode; clientNode = clientNode.next_sibling("client")) { | |
| 731 | + | if(versionStr == clientNode.attribute("name").value()) { | |
| 732 | + | return wxString(clientNode.attribute("data_directory").value()); | |
| 733 | + | } | |
| 734 | + | } | |
| 735 | + | } | |
| 736 | + | return wxString(); | |
| 737 | + | } | |
| 738 | + | ||
| 739 | + | std::vector<std::pair<uint16_t, uint16_t>> IslandGeneratorDialog::ParseRangeString(const wxString& input) { | |
| 740 | + | std::vector<std::pair<uint16_t, uint16_t>> ranges; | |
| 741 | + | wxStringTokenizer tokenizer(input, ","); | |
| 742 | + | ||
| 743 | + | while (tokenizer.HasMoreTokens()) { | |
| 744 | + | wxString token = tokenizer.GetNextToken().Trim(); | |
| 745 | + | ||
| 746 | + | if (token.Contains("-")) { | |
| 747 | + | // Handle range (e.g., "100-105") | |
| 748 | + | long start, end; | |
| 749 | + | wxString startStr = token.Before('-').Trim(); | |
| 750 | + | wxString endStr = token.After('-').Trim(); | |
| 751 | + | ||
| 752 | + | if (startStr.ToLong(&start) && endStr.ToLong(&end) && | |
| 753 | + | start > 0 && end > 0 && start <= end && end <= 65535) { | |
| 754 | + | ranges.push_back({static_cast<uint16_t>(start), static_cast<uint16_t>(end)}); | |
| 755 | + | } | |
| 756 | + | } else { | |
| 757 | + | // Handle single number | |
| 758 | + | long id; | |
| 759 | + | if (token.ToLong(&id) && id > 0 && id <= 65535) { | |
| 760 | + | ranges.push_back({static_cast<uint16_t>(id), static_cast<uint16_t>(id)}); | |
| 761 | + | } | |
| 762 | + | } | |
| 763 | + | } | |
| 764 | + | ||
| 765 | + | return ranges; | |
| 766 | + | } | |
| 767 | + | ||
| 768 | + | void IslandGeneratorDialog::OnIdInput(wxCommandEvent& event) { | |
| 769 | + | wxTextCtrl* input = dynamic_cast<wxTextCtrl*>(event.GetEventObject()); | |
| 770 | + | if (!input) return; | |
| 771 | + | ||
| 772 | + | wxString value = input->GetValue().Trim(); | |
| 773 | + | if (value.IsEmpty()) { | |
| 774 | + | if (input == ground_range_input) { | |
| 775 | + | ground_id = 0; | |
| 776 | + | ground_button->SetItemId(0); | |
| 777 | + | } else if (input == water_range_input) { | |
| 778 | + | water_id = 0; | |
| 779 | + | water_button->SetItemId(0); | |
| 780 | + | } | |
| 781 | + | UpdateWidgets(); | |
| 782 | + | return; | |
| 783 | + | } | |
| 784 | + | ||
| 785 | + | auto ranges = ParseRangeString(value); | |
| 786 | + | if (!ranges.empty()) { | |
| 787 | + | // Use the first ID in the range | |
| 788 | + | uint16_t id = ranges[0].first; | |
| 789 | + | const ItemType& type = g_items.getItemType(id); | |
| 790 | + | ||
| 791 | + | if (type.id != 0) { | |
| 792 | + | if (input == ground_range_input) { | |
| 793 | + | ground_id = id; | |
| 794 | + | ground_button->SetItemId(id); | |
| 795 | + | } else if (input == water_range_input) { | |
| 796 | + | water_id = id; | |
| 797 | + | water_button->SetItemId(id); | |
| 798 | + | } | |
| 799 | + | } | |
| 800 | + | } | |
| 801 | + | ||
| 802 | + | UpdateWidgets(); | |
| 803 | + | } |
| @@ -0,0 +1,143 @@ | |||
| 1 | + | ////////////////////////////////////////////////////////////////////// | |
| 2 | + | // This file is part of Remere's Map Editor | |
| 3 | + | ////////////////////////////////////////////////////////////////////// | |
| 4 | + | // Remere's Map Editor is free software: you can redistribute it and/or modify | |
| 5 | + | // it under the terms of the GNU General Public License as published by | |
| 6 | + | // the Free Software Foundation, either version 3 of the License, or | |
| 7 | + | // (at your option) any later version. | |
| 8 | + | // | |
| 9 | + | // Remere's Map Editor is distributed in the hope that it will be useful, | |
| 10 | + | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 11 | + | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 12 | + | // GNU General Public License for more details. | |
| 13 | + | // | |
| 14 | + | // You should have received a copy of the GNU General Public License | |
| 15 | + | // along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| 16 | + | ////////////////////////////////////////////////////////////////////// | |
| 17 | + | ||
| 18 | + | #ifndef RME_ISLAND_GENERATOR_DIALOG_H_ | |
| 19 | + | #define RME_ISLAND_GENERATOR_DIALOG_H_ | |
| 20 | + | ||
| 21 | + | #include "main.h" | |
| 22 | + | #include "common_windows.h" | |
| 23 | + | #include "dcbutton.h" | |
| 24 | + | #include "editor.h" | |
| 25 | + | #include "brush.h" | |
| 26 | + | #include "ground_brush.h" | |
| 27 | + | ||
| 28 | + | // Event IDs | |
| 29 | + | enum { | |
| 30 | + | ID_ISLAND_GENERATE_SINGLE = 34000, | |
| 31 | + | ID_ISLAND_GENERATE_MULTIPLE, | |
| 32 | + | ID_ISLAND_CANCEL, | |
| 33 | + | ID_ISLAND_RANDOM_SEED, | |
| 34 | + | ID_ISLAND_SHAPE_SELECT, | |
| 35 | + | ID_ISLAND_SIZE_CHANGE, | |
| 36 | + | ID_ISLAND_ROUGHNESS_CHANGE, | |
| 37 | + | ID_ISLAND_SEED_TEXT, | |
| 38 | + | ID_ISLAND_BORDER_SELECT, | |
| 39 | + | ID_ISLAND_BORDER_PREVIEW | |
| 40 | + | }; | |
| 41 | + | ||
| 42 | + | class IslandPreviewButton : public DCButton { | |
| 43 | + | public: | |
| 44 | + | IslandPreviewButton(wxWindow* parent); | |
| 45 | + | ~IslandPreviewButton() { } | |
| 46 | + | ||
| 47 | + | uint16_t GetItemId() const { return m_id; } | |
| 48 | + | void SetItemId(uint16_t id); | |
| 49 | + | ||
| 50 | + | private: | |
| 51 | + | uint16_t m_id; | |
| 52 | + | }; | |
| 53 | + | ||
| 54 | + | class IslandGeneratorDialog : public wxDialog { | |
| 55 | + | public: | |
| 56 | + | IslandGeneratorDialog(wxWindow* parent); | |
| 57 | + | ~IslandGeneratorDialog(); | |
| 58 | + | ||
| 59 | + | void SetStartPosition(const Position& pos) { | |
| 60 | + | start_position = pos; | |
| 61 | + | pos_x_spin->SetValue(pos.x); | |
| 62 | + | pos_y_spin->SetValue(pos.y); | |
| 63 | + | pos_z_spin->SetValue(pos.z); | |
| 64 | + | } | |
| 65 | + | ||
| 66 | + | // UI Event Handlers | |
| 67 | + | void OnGroundClick(wxMouseEvent& event); | |
| 68 | + | void OnWaterClick(wxMouseEvent& event); | |
| 69 | + | void OnGenerateClick(wxCommandEvent& event); | |
| 70 | + | void OnCancelClick(wxCommandEvent& event); | |
| 71 | + | void OnShapeSelect(wxCommandEvent& event); | |
| 72 | + | void OnSeedText(wxCommandEvent& event); | |
| 73 | + | void OnSizeChange(wxSpinEvent& event); | |
| 74 | + | void OnRoughnessChange(wxSpinEvent& event); | |
| 75 | + | void OnRandomizeSeed(wxCommandEvent& event); | |
| 76 | + | void OnPreviewUpdate(wxCommandEvent& event); | |
| 77 | + | void OnGenerateMultiple(wxCommandEvent& event); | |
| 78 | + | void OnBorderSelect(wxCommandEvent& event); | |
| 79 | + | void OnBorderPreview(wxCommandEvent& event); | |
| 80 | + | ||
| 81 | + | private: | |
| 82 | + | void UpdateWidgets(); | |
| 83 | + | void UpdatePreview(); | |
| 84 | + | void GenerateIsland(); | |
| 85 | + | void GenerateMultipleIslands(int count, int spacing); | |
| 86 | + | wxString GetDataDirectoryForVersion(const wxString& versionStr); | |
| 87 | + | std::vector<std::pair<uint16_t, uint16_t>> ParseRangeString(const wxString& input); | |
| 88 | + | void OnIdInput(wxCommandEvent& event); | |
| 89 | + | void LoadBorderChoices(); | |
| 90 | + | void UpdateBorderPreview(); | |
| 91 | + | ||
| 92 | + | struct BorderData { | |
| 93 | + | wxString name; | |
| 94 | + | int id; | |
| 95 | + | std::vector<uint16_t> items; | |
| 96 | + | wxBitmap preview; | |
| 97 | + | }; | |
| 98 | + | ||
| 99 | + | // UI Controls | |
| 100 | + | IslandPreviewButton* ground_button; | |
| 101 | + | IslandPreviewButton* water_button; | |
| 102 | + | wxTextCtrl* ground_range_input; | |
| 103 | + | wxTextCtrl* water_range_input; | |
| 104 | + | wxChoice* shape_choice; | |
| 105 | + | wxSpinCtrl* size_spin; | |
| 106 | + | wxSpinCtrl* roughness_spin; | |
| 107 | + | wxTextCtrl* seed_input; | |
| 108 | + | wxButton* random_seed_button; | |
| 109 | + | wxButton* generate_button; | |
| 110 | + | wxButton* generate_multiple_button; | |
| 111 | + | wxButton* cancel_button; | |
| 112 | + | wxGauge* progress; | |
| 113 | + | wxStaticBitmap* preview_bitmap; | |
| 114 | + | wxCheckBox* use_automagic; | |
| 115 | + | ||
| 116 | + | // Border controls | |
| 117 | + | wxListBox* border_list; | |
| 118 | + | wxStaticBitmap* border_preview; | |
| 119 | + | std::vector<BorderData> border_data; | |
| 120 | + | int selected_border_id; | |
| 121 | + | ||
| 122 | + | // Position controls | |
| 123 | + | wxSpinCtrl* pos_x_spin; | |
| 124 | + | wxSpinCtrl* pos_y_spin; | |
| 125 | + | wxSpinCtrl* pos_z_spin; | |
| 126 | + | ||
| 127 | + | // Multiple islands controls | |
| 128 | + | wxSpinCtrl* islands_count_spin; | |
| 129 | + | wxSpinCtrl* islands_spacing_spin; | |
| 130 | + | ||
| 131 | + | // Generation parameters | |
| 132 | + | uint16_t ground_id; | |
| 133 | + | uint16_t water_id; | |
| 134 | + | int island_size; | |
| 135 | + | int roughness; | |
| 136 | + | wxString seed; | |
| 137 | + | wxString selected_shape; | |
| 138 | + | Position start_position; | |
| 139 | + | ||
| 140 | + | DECLARE_EVENT_TABLE() | |
| 141 | + | }; | |
| 142 | + | ||
| 143 | + | #endif // RME_ISLAND_GENERATOR_DIALOG_H_ |
| @@ -0,0 +1,562 @@ | |||
| 1 | + | ////////////////////////////////////////////////////////////////////// | |
| 2 | + | // This file is part of Remere's Map Editor | |
| 3 | + | ////////////////////////////////////////////////////////////////////// | |
| 4 | + | // Remere's Map Editor is free software: you can redistribute it and/or modify | |
| 5 | + | // it under the terms of the GNU General Public License as published by | |
| 6 | + | // the Free Software Foundation, either version 3 of the License, or | |
| 7 | + | // (at your option) any later version. | |
| 8 | + | // | |
| 9 | + | // Remere's Map Editor is distributed in the hope that it will be useful, | |
| 10 | + | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 11 | + | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 12 | + | // GNU General Public License for more details. | |
| 13 | + | // | |
| 14 | + | // You should have received a copy of the GNU General Public License | |
| 15 | + | // along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| 16 | + | ////////////////////////////////////////////////////////////////////// | |
| 17 | + | ||
| 18 | + | #include "main.h" | |
| 19 | + | ||
| 20 | + | #include "brush.h" | |
| 21 | + | #include "graphics.h" | |
| 22 | + | #include "gui.h" | |
| 23 | + | #include "tile.h" | |
| 24 | + | #include "complexitem.h" | |
| 25 | + | #include "iomap.h" | |
| 26 | + | #include "item.h" | |
| 27 | + | ||
| 28 | + | #include "ground_brush.h" | |
| 29 | + | #include "carpet_brush.h" | |
| 30 | + | #include "table_brush.h" | |
| 31 | + | #include "wall_brush.h" | |
| 32 | + | ||
| 33 | + | Item* Item::Create(uint16_t _type, uint16_t _subtype /*= 0xFFFF*/) { | |
| 34 | + | if (_type == 0) { | |
| 35 | + | return nullptr; | |
| 36 | + | } | |
| 37 | + | Item* newItem = nullptr; | |
| 38 | + | ||
| 39 | + | const ItemType& it = g_items[_type]; | |
| 40 | + | ||
| 41 | + | if (it.id != 0) { | |
| 42 | + | if (it.isDepot()) { | |
| 43 | + | newItem = newd Depot(_type); | |
| 44 | + | } else if (it.isContainer()) { | |
| 45 | + | newItem = newd Container(_type); | |
| 46 | + | } else if (it.isTeleport()) { | |
| 47 | + | newItem = newd Teleport(_type); | |
| 48 | + | } else if (it.isDoor()) { | |
| 49 | + | newItem = newd Door(_type); | |
| 50 | + | } else if (it.isPodium()) { | |
| 51 | + | newItem = newd Podium(_type); | |
| 52 | + | } else if (_subtype == 0xFFFF) { | |
| 53 | + | if (it.isFluidContainer()) { | |
| 54 | + | newItem = newd Item(_type, LIQUID_NONE); | |
| 55 | + | } else if (it.isSplash()) { | |
| 56 | + | newItem = newd Item(_type, LIQUID_WATER); | |
| 57 | + | } else if (it.charges > 0) { | |
| 58 | + | newItem = newd Item(_type, it.charges); | |
| 59 | + | } else { | |
| 60 | + | newItem = newd Item(_type, 1); | |
| 61 | + | } | |
| 62 | + | } else { | |
| 63 | + | newItem = newd Item(_type, _subtype); | |
| 64 | + | } | |
| 65 | + | } else { | |
| 66 | + | newItem = newd Item(_type, _subtype); | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | return newItem; | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | Item::Item(unsigned short _type, unsigned short _count) : | |
| 73 | + | id(_type), | |
| 74 | + | subtype(1), | |
| 75 | + | selected(false), | |
| 76 | + | frame(0) { | |
| 77 | + | if (hasSubtype()) { | |
| 78 | + | subtype = _count; | |
| 79 | + | } | |
| 80 | + | } | |
| 81 | + | ||
| 82 | + | Item::~Item() { | |
| 83 | + | //// | |
| 84 | + | } | |
| 85 | + | ||
| 86 | + | Item* Item::deepCopy() const { | |
| 87 | + | Item* copy = Create(id, subtype); | |
| 88 | + | if (copy) { | |
| 89 | + | copy->selected = selected; | |
| 90 | + | if (attributes) { | |
| 91 | + | copy->attributes = newd ItemAttributeMap(*attributes); | |
| 92 | + | } | |
| 93 | + | } | |
| 94 | + | return copy; | |
| 95 | + | } | |
| 96 | + | ||
| 97 | + | Item* transformItem(Item* old_item, uint16_t new_id, Tile* parent) { | |
| 98 | + | if (old_item == nullptr) { | |
| 99 | + | return nullptr; | |
| 100 | + | } | |
| 101 | + | ||
| 102 | + | old_item->setID(new_id); | |
| 103 | + | // Through the magic of deepCopy, this will now be a pointer to an item of the correct type. | |
| 104 | + | Item* new_item = old_item->deepCopy(); | |
| 105 | + | if (parent) { | |
| 106 | + | // Find the old item and remove it from the tile, insert this one instead! | |
| 107 | + | if (old_item == parent->ground) { | |
| 108 | + | delete old_item; | |
| 109 | + | parent->ground = new_item; | |
| 110 | + | return new_item; | |
| 111 | + | } | |
| 112 | + | ||
| 113 | + | std::queue<Container*> containers; | |
| 114 | + | for (ItemVector::iterator item_iter = parent->items.begin(); item_iter != parent->items.end(); ++item_iter) { | |
| 115 | + | if (*item_iter == old_item) { | |
| 116 | + | delete old_item; | |
| 117 | + | item_iter = parent->items.erase(item_iter); | |
| 118 | + | parent->items.insert(item_iter, new_item); | |
| 119 | + | return new_item; | |
| 120 | + | } | |
| 121 | + | ||
| 122 | + | Container* c = dynamic_cast<Container*>(*item_iter); | |
| 123 | + | if (c) { | |
| 124 | + | containers.push(c); | |
| 125 | + | } | |
| 126 | + | } | |
| 127 | + | ||
| 128 | + | while (containers.size() != 0) { | |
| 129 | + | Container* container = containers.front(); | |
| 130 | + | ItemVector& v = container->getVector(); | |
| 131 | + | for (ItemVector::iterator item_iter = v.begin(); item_iter != v.end(); ++item_iter) { | |
| 132 | + | Item* i = *item_iter; | |
| 133 | + | Container* c = dynamic_cast<Container*>(i); | |
| 134 | + | if (c) { | |
| 135 | + | containers.push(c); | |
| 136 | + | } | |
| 137 | + | ||
| 138 | + | if (i == old_item) { | |
| 139 | + | // Found it! | |
| 140 | + | item_iter = v.erase(item_iter); | |
| 141 | + | v.insert(item_iter, new_item); | |
| 142 | + | return new_item; | |
| 143 | + | } | |
| 144 | + | } | |
| 145 | + | containers.pop(); | |
| 146 | + | } | |
| 147 | + | } | |
| 148 | + | ||
| 149 | + | delete new_item; | |
| 150 | + | return nullptr; | |
| 151 | + | } | |
| 152 | + | ||
| 153 | + | uint32_t Item::memsize() const { | |
| 154 | + | uint32_t mem = sizeof(*this); | |
| 155 | + | return mem; | |
| 156 | + | } | |
| 157 | + | ||
| 158 | + | void Item::setID(uint16_t newid) { | |
| 159 | + | id = newid; | |
| 160 | + | } | |
| 161 | + | ||
| 162 | + | void Item::setSubtype(uint16_t n) { | |
| 163 | + | subtype = n; | |
| 164 | + | } | |
| 165 | + | ||
| 166 | + | bool Item::hasSubtype() const { | |
| 167 | + | const ItemType& it = g_items[id]; | |
| 168 | + | return (it.isFluidContainer() || it.isSplash() || isCharged() || it.stackable || it.charges != 0); | |
| 169 | + | } | |
| 170 | + | ||
| 171 | + | uint16_t Item::getSubtype() const { | |
| 172 | + | if (hasSubtype()) { | |
| 173 | + | return subtype; | |
| 174 | + | } | |
| 175 | + | return 0; | |
| 176 | + | } | |
| 177 | + | ||
| 178 | + | bool Item::hasProperty(enum ITEMPROPERTY prop) const { | |
| 179 | + | const ItemType& it = g_items[id]; | |
| 180 | + | switch (prop) { | |
| 181 | + | case BLOCKSOLID: | |
| 182 | + | if (it.unpassable) { | |
| 183 | + | return true; | |
| 184 | + | } | |
| 185 | + | break; | |
| 186 | + | ||
| 187 | + | case MOVEABLE: | |
| 188 | + | if (it.moveable && getUniqueID() == 0) { | |
| 189 | + | return true; | |
| 190 | + | } | |
| 191 | + | break; | |
| 192 | + | /* | |
| 193 | + | case HASHEIGHT: | |
| 194 | + | if(it.height != 0 ) | |
| 195 | + | return true; | |
| 196 | + | break; | |
| 197 | + | */ | |
| 198 | + | case BLOCKPROJECTILE: | |
| 199 | + | if (it.blockMissiles) { | |
| 200 | + | return true; | |
| 201 | + | } | |
| 202 | + | break; | |
| 203 | + | ||
| 204 | + | case BLOCKPATHFIND: | |
| 205 | + | if (it.blockPathfinder) { | |
| 206 | + | return true; | |
| 207 | + | } | |
| 208 | + | break; | |
| 209 | + | ||
| 210 | + | case HOOK_SOUTH: | |
| 211 | + | if (it.hookSouth) { | |
| 212 | + | return true; | |
| 213 | + | } | |
| 214 | + | break; | |
| 215 | + | ||
| 216 | + | case HOOK_EAST: | |
| 217 | + | if (it.hookEast) { | |
| 218 | + | return true; | |
| 219 | + | } | |
| 220 | + | break; | |
| 221 | + | ||
| 222 | + | case BLOCKINGANDNOTMOVEABLE: | |
| 223 | + | if (it.unpassable && (!it.moveable || getUniqueID() != 0)) { | |
| 224 | + | return true; | |
| 225 | + | } | |
| 226 | + | break; | |
| 227 | + | ||
| 228 | + | case HASLIGHT: | |
| 229 | + | if (hasLight()) { | |
| 230 | + | return true; | |
| 231 | + | } | |
| 232 | + | break; | |
| 233 | + | ||
| 234 | + | default: | |
| 235 | + | return false; | |
| 236 | + | } | |
| 237 | + | return false; | |
| 238 | + | } | |
| 239 | + | ||
| 240 | + | std::pair<int, int> Item::getDrawOffset() const { | |
| 241 | + | ItemType& it = g_items[id]; | |
| 242 | + | if (it.sprite != nullptr) { | |
| 243 | + | return it.sprite->getDrawOffset(); | |
| 244 | + | } | |
| 245 | + | return std::make_pair(0, 0); | |
| 246 | + | } | |
| 247 | + | ||
| 248 | + | bool Item::hasLight() const { | |
| 249 | + | const ItemType& type = g_items.getItemType(id); | |
| 250 | + | if (type.sprite) { | |
| 251 | + | return type.sprite->hasLight(); | |
| 252 | + | } | |
| 253 | + | return false; | |
| 254 | + | } | |
| 255 | + | ||
| 256 | + | SpriteLight Item::getLight() const { | |
| 257 | + | const ItemType& type = g_items.getItemType(id); | |
| 258 | + | if (type.sprite) { | |
| 259 | + | return type.sprite->getLight(); | |
| 260 | + | } | |
| 261 | + | return SpriteLight { 0, 0 }; | |
| 262 | + | } | |
| 263 | + | ||
| 264 | + | double Item::getWeight() const { | |
| 265 | + | ItemType& it = g_items[id]; | |
| 266 | + | if (it.stackable) { | |
| 267 | + | return it.weight * std::max(1, (int)subtype); | |
| 268 | + | } | |
| 269 | + | ||
| 270 | + | return it.weight; | |
| 271 | + | } | |
| 272 | + | ||
| 273 | + | void Item::setUniqueID(unsigned short n) { | |
| 274 | + | setAttribute("uid", n); | |
| 275 | + | } | |
| 276 | + | ||
| 277 | + | void Item::setActionID(unsigned short n) { | |
| 278 | + | setAttribute("aid", n); | |
| 279 | + | } | |
| 280 | + | ||
| 281 | + | void Item::setText(const std::string& str) { | |
| 282 | + | setAttribute("text", str); | |
| 283 | + | } | |
| 284 | + | ||
| 285 | + | void Item::setDescription(const std::string& str) { | |
| 286 | + | setAttribute("desc", str); | |
| 287 | + | } | |
| 288 | + | ||
| 289 | + | void Item::setTier(unsigned short n) { | |
| 290 | + | setAttribute("tier", n); | |
| 291 | + | } | |
| 292 | + | ||
| 293 | + | double Item::getWeight() { | |
| 294 | + | ItemType& it = g_items[id]; | |
| 295 | + | if (it.isStackable()) { | |
| 296 | + | return it.weight * subtype; | |
| 297 | + | } | |
| 298 | + | return it.weight; | |
| 299 | + | } | |
| 300 | + | ||
| 301 | + | bool Item::canHoldText() const { | |
| 302 | + | return isReadable() || canWriteText(); | |
| 303 | + | } | |
| 304 | + | ||
| 305 | + | bool Item::canHoldDescription() const { | |
| 306 | + | return g_items[id].allowDistRead; | |
| 307 | + | } | |
| 308 | + | ||
| 309 | + | uint8_t Item::getMiniMapColor() const { | |
| 310 | + | GameSprite* spr = g_items[id].sprite; | |
| 311 | + | if (spr) { | |
| 312 | + | return spr->getMiniMapColor(); | |
| 313 | + | } | |
| 314 | + | return 0; | |
| 315 | + | } | |
| 316 | + | ||
| 317 | + | GroundBrush* Item::getGroundBrush() const { | |
| 318 | + | ItemType& item_type = g_items.getItemType(id); | |
| 319 | + | if (item_type.isGroundTile() && item_type.brush && item_type.brush->isGround()) { | |
| 320 | + | return item_type.brush->asGround(); | |
| 321 | + | } | |
| 322 | + | return nullptr; | |
| 323 | + | } | |
| 324 | + | ||
| 325 | + | TableBrush* Item::getTableBrush() const { | |
| 326 | + | ItemType& item_type = g_items.getItemType(id); | |
| 327 | + | if (item_type.isTable && item_type.brush && item_type.brush->isTable()) { | |
| 328 | + | return item_type.brush->asTable(); | |
| 329 | + | } | |
| 330 | + | return nullptr; | |
| 331 | + | } | |
| 332 | + | ||
| 333 | + | CarpetBrush* Item::getCarpetBrush() const { | |
| 334 | + | ItemType& item_type = g_items.getItemType(id); | |
| 335 | + | if (item_type.isCarpet && item_type.brush && item_type.brush->isCarpet()) { | |
| 336 | + | return item_type.brush->asCarpet(); | |
| 337 | + | } | |
| 338 | + | return nullptr; | |
| 339 | + | } | |
| 340 | + | ||
| 341 | + | DoorBrush* Item::getDoorBrush() const { | |
| 342 | + | ItemType& item_type = g_items.getItemType(id); | |
| 343 | + | if (!item_type.isWall || !item_type.isBrushDoor || !item_type.brush || !item_type.brush->isWall()) { | |
| 344 | + | return nullptr; | |
| 345 | + | } | |
| 346 | + | ||
| 347 | + | DoorType door_type = item_type.brush->asWall()->getDoorTypeFromID(id); | |
| 348 | + | DoorBrush* door_brush = nullptr; | |
| 349 | + | // Quite a horrible dependency on a global here, meh. | |
| 350 | + | switch (door_type) { | |
| 351 | + | case WALL_DOOR_NORMAL: { | |
| 352 | + | door_brush = g_gui.normal_door_brush; | |
| 353 | + | break; | |
| 354 | + | } | |
| 355 | + | case WALL_DOOR_LOCKED: { | |
| 356 | + | door_brush = g_gui.locked_door_brush; | |
| 357 | + | break; | |
| 358 | + | } | |
| 359 | + | case WALL_DOOR_QUEST: { | |
| 360 | + | door_brush = g_gui.quest_door_brush; | |
| 361 | + | break; | |
| 362 | + | } | |
| 363 | + | case WALL_DOOR_MAGIC: { | |
| 364 | + | door_brush = g_gui.magic_door_brush; | |
| 365 | + | break; | |
| 366 | + | } | |
| 367 | + | case WALL_DOOR_NORMAL_ALT: { | |
| 368 | + | door_brush = g_gui.normal_door_alt_brush; | |
| 369 | + | break; | |
| 370 | + | } | |
| 371 | + | case WALL_ARCHWAY: { | |
| 372 | + | door_brush = g_gui.archway_door_brush; | |
| 373 | + | break; | |
| 374 | + | } | |
| 375 | + | case WALL_WINDOW: { | |
| 376 | + | door_brush = g_gui.window_door_brush; | |
| 377 | + | break; | |
| 378 | + | } | |
| 379 | + | case WALL_HATCH_WINDOW: { | |
| 380 | + | door_brush = g_gui.hatch_door_brush; | |
| 381 | + | break; | |
| 382 | + | } | |
| 383 | + | default: { | |
| 384 | + | break; | |
| 385 | + | } | |
| 386 | + | } | |
| 387 | + | return door_brush; | |
| 388 | + | } | |
| 389 | + | ||
| 390 | + | WallBrush* Item::getWallBrush() const { | |
| 391 | + | ItemType& item_type = g_items.getItemType(id); | |
| 392 | + | if (item_type.isWall && item_type.brush && item_type.brush->isWall()) { | |
| 393 | + | return item_type.brush->asWall(); | |
| 394 | + | } | |
| 395 | + | return nullptr; | |
| 396 | + | } | |
| 397 | + | ||
| 398 | + | BorderType Item::getWallAlignment() const { | |
| 399 | + | ItemType& it = g_items[id]; | |
| 400 | + | if (!it.isWall) { | |
| 401 | + | return BORDER_NONE; | |
| 402 | + | } | |
| 403 | + | return it.border_alignment; | |
| 404 | + | } | |
| 405 | + | ||
| 406 | + | BorderType Item::getBorderAlignment() const { | |
| 407 | + | ItemType& it = g_items[id]; | |
| 408 | + | return it.border_alignment; | |
| 409 | + | } | |
| 410 | + | ||
| 411 | + | void Item::animate() { | |
| 412 | + | ItemType& type = g_items[id]; | |
| 413 | + | GameSprite* sprite = type.sprite; | |
| 414 | + | if (!sprite || !sprite->animator) { | |
| 415 | + | return; | |
| 416 | + | } | |
| 417 | + | ||
| 418 | + | frame = sprite->animator->getFrame(); | |
| 419 | + | } | |
| 420 | + | ||
| 421 | + | // ============================================================================ | |
| 422 | + | // Static conversions | |
| 423 | + | ||
| 424 | + | std::string Item::LiquidID2Name(uint16_t id) { | |
| 425 | + | switch (id) { | |
| 426 | + | case LIQUID_NONE: | |
| 427 | + | return "None"; | |
| 428 | + | case LIQUID_WATER: | |
| 429 | + | return "Water"; | |
| 430 | + | case LIQUID_BLOOD: | |
| 431 | + | return "Blood"; | |
| 432 | + | case LIQUID_BEER: | |
| 433 | + | return "Beer"; | |
| 434 | + | case LIQUID_SLIME: | |
| 435 | + | return "Slime"; | |
| 436 | + | case LIQUID_LEMONADE: | |
| 437 | + | return "Lemonade"; | |
| 438 | + | case LIQUID_MILK: | |
| 439 | + | return "Milk"; | |
| 440 | + | case LIQUID_MANAFLUID: | |
| 441 | + | return "Manafluid"; | |
| 442 | + | case LIQUID_WATER2: | |
| 443 | + | return "Water"; | |
| 444 | + | case LIQUID_LIFEFLUID: | |
| 445 | + | return "Lifefluid"; | |
| 446 | + | case LIQUID_OIL: | |
| 447 | + | return "Oil"; | |
| 448 | + | case LIQUID_SLIME2: | |
| 449 | + | return "Slime"; | |
| 450 | + | case LIQUID_URINE: | |
| 451 | + | return "Urine"; | |
| 452 | + | case LIQUID_COCONUT_MILK: | |
| 453 | + | return "Coconut Milk"; | |
| 454 | + | case LIQUID_WINE: | |
| 455 | + | return "Wine"; | |
| 456 | + | case LIQUID_MUD: | |
| 457 | + | return "Mud"; | |
| 458 | + | case LIQUID_FRUIT_JUICE: | |
| 459 | + | return "Fruit Juice"; | |
| 460 | + | case LIQUID_LAVA: | |
| 461 | + | return "Lava"; | |
| 462 | + | case LIQUID_RUM: | |
| 463 | + | return "Rum"; | |
| 464 | + | case LIQUID_SWAMP: | |
| 465 | + | return "Swamp"; | |
| 466 | + | case LIQUID_INK: | |
| 467 | + | return "Ink"; | |
| 468 | + | case LIQUID_TEA: | |
| 469 | + | return "Tea"; | |
| 470 | + | case LIQUID_MEAD: | |
| 471 | + | return "Mead"; | |
| 472 | + | default: | |
| 473 | + | return "Unknown"; | |
| 474 | + | } | |
| 475 | + | } | |
| 476 | + | ||
| 477 | + | uint16_t Item::LiquidName2ID(std::string liquid) { | |
| 478 | + | to_lower_str(liquid); | |
| 479 | + | if (liquid == "none") { | |
| 480 | + | return LIQUID_NONE; | |
| 481 | + | } | |
| 482 | + | if (liquid == "water") { | |
| 483 | + | return LIQUID_WATER; | |
| 484 | + | } | |
| 485 | + | if (liquid == "blood") { | |
| 486 | + | return LIQUID_BLOOD; | |
| 487 | + | } | |
| 488 | + | if (liquid == "beer") { | |
| 489 | + | return LIQUID_BEER; | |
| 490 | + | } | |
| 491 | + | if (liquid == "slime") { | |
| 492 | + | return LIQUID_SLIME; | |
| 493 | + | } | |
| 494 | + | if (liquid == "lemonade") { | |
| 495 | + | return LIQUID_LEMONADE; | |
| 496 | + | } | |
| 497 | + | if (liquid == "milk") { | |
| 498 | + | return LIQUID_MILK; | |
| 499 | + | } | |
| 500 | + | if (liquid == "manafluid") { | |
| 501 | + | return LIQUID_MANAFLUID; | |
| 502 | + | } | |
| 503 | + | if (liquid == "lifefluid") { | |
| 504 | + | return LIQUID_LIFEFLUID; | |
| 505 | + | } | |
| 506 | + | if (liquid == "oil") { | |
| 507 | + | return LIQUID_OIL; | |
| 508 | + | } | |
| 509 | + | if (liquid == "urine") { | |
| 510 | + | return LIQUID_URINE; | |
| 511 | + | } | |
| 512 | + | if (liquid == "coconut milk") { | |
| 513 | + | return LIQUID_COCONUT_MILK; | |
| 514 | + | } | |
| 515 | + | if (liquid == "wine") { | |
| 516 | + | return LIQUID_WINE; | |
| 517 | + | } | |
| 518 | + | if (liquid == "mud") { | |
| 519 | + | return LIQUID_MUD; | |
| 520 | + | } | |
| 521 | + | if (liquid == "fruit juice") { | |
| 522 | + | return LIQUID_FRUIT_JUICE; | |
| 523 | + | } | |
| 524 | + | if (liquid == "lava") { | |
| 525 | + | return LIQUID_LAVA; | |
| 526 | + | } | |
| 527 | + | if (liquid == "rum") { | |
| 528 | + | return LIQUID_RUM; | |
| 529 | + | } | |
| 530 | + | if (liquid == "swamp") { | |
| 531 | + | return LIQUID_SWAMP; | |
| 532 | + | } | |
| 533 | + | if (liquid == "ink") { | |
| 534 | + | return LIQUID_INK; | |
| 535 | + | } | |
| 536 | + | if (liquid == "tea") { | |
| 537 | + | return LIQUID_TEA; | |
| 538 | + | } | |
| 539 | + | if (liquid == "mead") { | |
| 540 | + | return LIQUID_MEAD; | |
| 541 | + | } | |
| 542 | + | return LIQUID_NONE; | |
| 543 | + | } | |
| 544 | + | ||
| 545 | + | // ============================================================================ | |
| 546 | + | // XML Saving & loading | |
| 547 | + | ||
| 548 | + | Item* Item::Create(pugi::xml_node xml) { | |
| 549 | + | pugi::xml_attribute attribute; | |
| 550 | + | ||
| 551 | + | int16_t id = 0; | |
| 552 | + | if ((attribute = xml.attribute("id"))) { | |
| 553 | + | id = attribute.as_ushort(); | |
| 554 | + | } | |
| 555 | + | ||
| 556 | + | int16_t count = 1; | |
| 557 | + | if ((attribute = xml.attribute("count")) || (attribute = xml.attribute("subtype"))) { | |
| 558 | + | count = attribute.as_ushort(); | |
| 559 | + | } | |
| 560 | + | ||
| 561 | + | return Create(id, count); | |
| 562 | + | } |
| @@ -0,0 +1,537 @@ | |||
| 1 | + | ////////////////////////////////////////////////////////////////////// | |
| 2 | + | // This file is part of Remere's Map Editor | |
| 3 | + | ////////////////////////////////////////////////////////////////////// | |
| 4 | + | // Remere's Map Editor is free software: you can redistribute it and/or modify | |
| 5 | + | // it under the terms of the GNU General Public License as published by | |
| 6 | + | // the Free Software Foundation, either version 3 of the License, or | |
| 7 | + | // (at your option) any later version. | |
| 8 | + | // | |
| 9 | + | // Remere's Map Editor is distributed in the hope that it will be useful, | |
| 10 | + | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 11 | + | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 12 | + | // GNU General Public License for more details. | |
| 13 | + | // | |
| 14 | + | // You should have received a copy of the GNU General Public License | |
| 15 | + | // along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| 16 | + | ////////////////////////////////////////////////////////////////////// | |
| 17 | + | ||
| 18 | + | #ifndef RME_ITEM_H_ | |
| 19 | + | #define RME_ITEM_H_ | |
| 20 | + | ||
| 21 | + | #include "items.h" | |
| 22 | + | #include "iomap_otbm.h" | |
| 23 | + | // #include "iomap_otmm.h" | |
| 24 | + | #include "item_attributes.h" | |
| 25 | + | #include "doodad_brush.h" | |
| 26 | + | #include "raw_brush.h" | |
| 27 | + | ||
| 28 | + | class Creature; | |
| 29 | + | class Border; | |
| 30 | + | class Tile; | |
| 31 | + | ||
| 32 | + | struct SpriteLight; | |
| 33 | + | ||
| 34 | + | enum ITEMPROPERTY { | |
| 35 | + | BLOCKSOLID, | |
| 36 | + | HASHEIGHT, | |
| 37 | + | BLOCKPROJECTILE, | |
| 38 | + | BLOCKPATHFIND, | |
| 39 | + | PROTECTIONZONE, | |
| 40 | + | HOOK_SOUTH, | |
| 41 | + | HOOK_EAST, | |
| 42 | + | MOVEABLE, | |
| 43 | + | BLOCKINGANDNOTMOVEABLE, | |
| 44 | + | HASLIGHT | |
| 45 | + | }; | |
| 46 | + | ||
| 47 | + | enum SplashType { | |
| 48 | + | LIQUID_NONE = 0, | |
| 49 | + | LIQUID_WATER = 1, | |
| 50 | + | LIQUID_BLOOD = 2, | |
| 51 | + | LIQUID_BEER = 3, | |
| 52 | + | LIQUID_SLIME = 4, | |
| 53 | + | LIQUID_LEMONADE = 5, | |
| 54 | + | LIQUID_MILK = 6, | |
| 55 | + | LIQUID_MANAFLUID = 7, | |
| 56 | + | LIQUID_INK = 8, | |
| 57 | + | LIQUID_WATER2 = 9, | |
| 58 | + | LIQUID_LIFEFLUID = 10, | |
| 59 | + | LIQUID_OIL = 11, | |
| 60 | + | LIQUID_SLIME2 = 12, | |
| 61 | + | LIQUID_URINE = 13, | |
| 62 | + | LIQUID_COCONUT_MILK = 14, | |
| 63 | + | LIQUID_WINE = 15, | |
| 64 | + | LIQUID_MUD = 19, | |
| 65 | + | LIQUID_FRUIT_JUICE = 21, | |
| 66 | + | LIQUID_LAVA = 26, | |
| 67 | + | LIQUID_RUM = 27, | |
| 68 | + | LIQUID_SWAMP = 28, | |
| 69 | + | LIQUID_TEA = 35, | |
| 70 | + | LIQUID_MEAD = 43, | |
| 71 | + | ||
| 72 | + | LIQUID_FIRST = LIQUID_WATER, | |
| 73 | + | LIQUID_LAST = LIQUID_MEAD | |
| 74 | + | }; | |
| 75 | + | ||
| 76 | + | IMPLEMENT_INCREMENT_OP(SplashType) | |
| 77 | + | ||
| 78 | + | class Item : public ItemAttributes { | |
| 79 | + | public: | |
| 80 | + | // Factory member to create item of right type based on type | |
| 81 | + | static Item* Create(uint16_t _type, uint16_t _subtype = 0xFFFF); | |
| 82 | + | static Item* Create(pugi::xml_node); | |
| 83 | + | static Item* Create_OTBM(const IOMap& maphandle, BinaryNode* stream); | |
| 84 | + | // static Item* Create_OTMM(const IOMap& maphandle, BinaryNode* stream); | |
| 85 | + | ||
| 86 | + | protected: | |
| 87 | + | // Constructor for items | |
| 88 | + | Item(unsigned short _type, unsigned short _count); | |
| 89 | + | ||
| 90 | + | public: | |
| 91 | + | virtual ~Item(); | |
| 92 | + | ||
| 93 | + | // Deep copy thingy | |
| 94 | + | virtual Item* deepCopy() const; | |
| 95 | + | ||
| 96 | + | // Get memory footprint size | |
| 97 | + | uint32_t memsize() const; | |
| 98 | + | /* | |
| 99 | + | virtual Container* getContainer() {return nullptr;} | |
| 100 | + | virtual const Container* getContainer() const {return nullptr;} | |
| 101 | + | virtual Teleport* getTeleport() {return nullptr;} | |
| 102 | + | virtual const Teleport* getTeleport() const {return nullptr;} | |
| 103 | + | virtual TrashHolder* getTrashHolder() {return nullptr;} | |
| 104 | + | virtual const TrashHolder* getTrashHolder() const {return nullptr;} | |
| 105 | + | virtual Mailbox* getMailbox() {return nullptr;} | |
| 106 | + | virtual const Mailbox* getMailbox() const {return nullptr;} | |
| 107 | + | virtual Door* getDoor() {return nullptr;} | |
| 108 | + | virtual const Door* getDoor() const {return nullptr;} | |
| 109 | + | virtual MagicField* getMagicField() {return nullptr;} | |
| 110 | + | virtual const MagicField* getMagicField() const {return nullptr;} | |
| 111 | + | */ | |
| 112 | + | ||
| 113 | + | // OTBM map interface | |
| 114 | + | // Serialize and unserialize (for save/load) | |
| 115 | + | // Used internally | |
| 116 | + | virtual bool readItemAttribute_OTBM(const IOMap& maphandle, OTBM_ItemAttribute attr, BinaryNode* stream); | |
| 117 | + | virtual bool unserializeAttributes_OTBM(const IOMap& maphandle, BinaryNode* stream); | |
| 118 | + | virtual bool unserializeItemNode_OTBM(const IOMap& maphandle, BinaryNode* node); | |
| 119 | + | ||
| 120 | + | // Will return a node containing this item | |
| 121 | + | virtual bool serializeItemNode_OTBM(const IOMap& maphandle, NodeFileWriteHandle& f) const; | |
| 122 | + | // Will write this item to the stream supplied in the argument | |
| 123 | + | virtual void serializeItemCompact_OTBM(const IOMap& maphandle, NodeFileWriteHandle& f) const; | |
| 124 | + | virtual void serializeItemAttributes_OTBM(const IOMap& maphandle, NodeFileWriteHandle& f) const; | |
| 125 | + | ||
| 126 | + | // OTMM map interface | |
| 127 | + | /* | |
| 128 | + | // Serialize and unserialize (for save/load) | |
| 129 | + | // Used internally | |
| 130 | + | virtual bool readItemAttribute_OTMM(const IOMap& maphandle, OTMM_ItemAttribute attr, BinaryNode* stream); | |
| 131 | + | virtual bool unserializeAttributes_OTMM(const IOMap& maphandle, BinaryNode* stream); | |
| 132 | + | virtual bool unserializeItemNode_OTMM(const IOMap& maphandle, BinaryNode* node); | |
| 133 | + | ||
| 134 | + | // Will return a node containing this item | |
| 135 | + | virtual bool serializeItemNode_OTMM(const IOMap& maphandle, NodeFileWriteHandle& f) const; | |
| 136 | + | // Will write this item to the stream supplied in the argument | |
| 137 | + | virtual void serializeItemCompact_OTMM(const IOMap& maphandle, NodeFileWriteHandle& f) const; | |
| 138 | + | virtual void serializeItemAttributes_OTMM(const IOMap& maphandle, NodeFileWriteHandle& f) const; | |
| 139 | + | */ | |
| 140 | + | ||
| 141 | + | // Static conversions | |
| 142 | + | static std::string LiquidID2Name(uint16_t id); | |
| 143 | + | static uint16_t LiquidName2ID(std::string id); | |
| 144 | + | ||
| 145 | + | // IDs | |
| 146 | + | uint16_t getID() const { | |
| 147 | + | return id; | |
| 148 | + | } | |
| 149 | + | uint16_t getClientID() const { | |
| 150 | + | return g_items[id].clientID; | |
| 151 | + | } | |
| 152 | + | // NOTE: This is very volatile, do NOT use this unless you know exactly what you're doing | |
| 153 | + | // which you probably don't so avoid it like the plague! | |
| 154 | + | void setID(uint16_t id); | |
| 155 | + | ||
| 156 | + | bool typeExists() const { | |
| 157 | + | return g_items.typeExists(id); | |
| 158 | + | } | |
| 159 | + | ||
| 160 | + | // Usual attributes | |
| 161 | + | virtual double getWeight() const; | |
| 162 | + | int getAttack() const { | |
| 163 | + | return g_items[id].attack; | |
| 164 | + | } | |
| 165 | + | int getArmor() const { | |
| 166 | + | return g_items[id].armor; | |
| 167 | + | } | |
| 168 | + | int getDefense() const { | |
| 169 | + | return g_items[id].defense; | |
| 170 | + | } | |
| 171 | + | uint16_t getSlotPosition() const { | |
| 172 | + | return g_items[id].slot_position; | |
| 173 | + | } | |
| 174 | + | uint8_t getWeaponType() const { | |
| 175 | + | return g_items[id].weapon_type; | |
| 176 | + | } | |
| 177 | + | uint8_t getClassification() const { | |
| 178 | + | return g_items[id].classification; | |
| 179 | + | } // 12.81 | |
| 180 | + | ||
| 181 | + | // Item g_settings | |
| 182 | + | bool canHoldText() const; | |
| 183 | + | bool canHoldDescription() const; | |
| 184 | + | bool isReadable() const { | |
| 185 | + | return g_items[id].canReadText; | |
| 186 | + | } | |
| 187 | + | bool canWriteText() const { | |
| 188 | + | return g_items[id].canWriteText; | |
| 189 | + | } | |
| 190 | + | uint32_t getMaxWriteLength() const { | |
| 191 | + | return g_items[id].maxTextLen; | |
| 192 | + | } | |
| 193 | + | Brush* getBrush() const { | |
| 194 | + | return g_items[id].brush; | |
| 195 | + | } | |
| 196 | + | GroundBrush* getGroundBrush() const; | |
| 197 | + | WallBrush* getWallBrush() const; | |
| 198 | + | DoorBrush* getDoorBrush() const; | |
| 199 | + | TableBrush* getTableBrush() const; | |
| 200 | + | CarpetBrush* getCarpetBrush() const; | |
| 201 | + | Brush* getDoodadBrush() const { | |
| 202 | + | return g_items[id].doodad_brush; | |
| 203 | + | } // This is not necessarily a doodad brush | |
| 204 | + | RAWBrush* getRAWBrush() const { | |
| 205 | + | return g_items[id].raw_brush; | |
| 206 | + | } | |
| 207 | + | bool hasCollectionBrush() const { | |
| 208 | + | return g_items[id].collection_brush; | |
| 209 | + | } | |
| 210 | + | uint16_t getGroundEquivalent() const { | |
| 211 | + | return g_items[id].ground_equivalent; | |
| 212 | + | } | |
| 213 | + | uint16_t hasBorderEquivalent() const { | |
| 214 | + | return g_items[id].has_equivalent; | |
| 215 | + | } | |
| 216 | + | uint32_t getBorderGroup() const { | |
| 217 | + | return g_items[id].border_group; | |
| 218 | + | } | |
| 219 | + | ||
| 220 | + | // Drawing related | |
| 221 | + | uint8_t getMiniMapColor() const; | |
| 222 | + | int getHeight() const; | |
| 223 | + | std::pair<int, int> getDrawOffset() const; | |
| 224 | + | ||
| 225 | + | bool hasLight() const; | |
| 226 | + | SpriteLight getLight() const; | |
| 227 | + | ||
| 228 | + | // Item types | |
| 229 | + | bool hasProperty(enum ITEMPROPERTY prop) const; | |
| 230 | + | bool isBlocking() const { | |
| 231 | + | return g_items[id].unpassable; | |
| 232 | + | } | |
| 233 | + | bool isStackable() const { | |
| 234 | + | return g_items[id].stackable; | |
| 235 | + | } | |
| 236 | + | bool isClientCharged() const { | |
| 237 | + | return g_items[id].isClientCharged(); | |
| 238 | + | } | |
| 239 | + | bool isExtraCharged() const { | |
| 240 | + | return g_items[id].isExtraCharged(); | |
| 241 | + | } | |
| 242 | + | bool isCharged() const { | |
| 243 | + | return isClientCharged() || isExtraCharged(); | |
| 244 | + | } | |
| 245 | + | bool isFluidContainer() const { | |
| 246 | + | return (g_items[id].isFluidContainer()); | |
| 247 | + | } | |
| 248 | + | bool isAlwaysOnBottom() const { | |
| 249 | + | return g_items[id].alwaysOnBottom; | |
| 250 | + | } | |
| 251 | + | int getTopOrder() const { | |
| 252 | + | return g_items[id].alwaysOnTopOrder; | |
| 253 | + | } | |
| 254 | + | bool isGroundTile() const { | |
| 255 | + | return g_items[id].isGroundTile(); | |
| 256 | + | } | |
| 257 | + | bool isSplash() const { | |
| 258 | + | return g_items[id].isSplash(); | |
| 259 | + | } | |
| 260 | + | bool isMagicField() const { | |
| 261 | + | return g_items[id].isMagicField(); | |
| 262 | + | } | |
| 263 | + | bool isNotMoveable() const { | |
| 264 | + | return !g_items[id].moveable; | |
| 265 | + | } | |
| 266 | + | bool isMoveable() const { | |
| 267 | + | return g_items[id].moveable; | |
| 268 | + | } | |
| 269 | + | bool isPickupable() const { | |
| 270 | + | return g_items[id].pickupable; | |
| 271 | + | } | |
| 272 | + | // bool isWeapon() const {return (g_items[id].weaponType != WEAPON_NONE && g_items[id].weaponType != WEAPON_AMMO);} | |
| 273 | + | // bool isUseable() const {return g_items[id].useable;} | |
| 274 | + | bool isHangable() const { | |
| 275 | + | return g_items[id].isHangable; | |
| 276 | + | } | |
| 277 | + | bool isRoteable() const { | |
| 278 | + | const ItemType& it = g_items[id]; | |
| 279 | + | return it.rotable && it.rotateTo; | |
| 280 | + | } | |
| 281 | + | void doRotate() { | |
| 282 | + | if (isRoteable()) { | |
| 283 | + | id = g_items[id].rotateTo; | |
| 284 | + | } | |
| 285 | + | } | |
| 286 | + | bool hasCharges() const { | |
| 287 | + | return g_items[id].charges != 0; | |
| 288 | + | } | |
| 289 | + | bool isBorder() const { | |
| 290 | + | return g_items[id].isBorder; | |
| 291 | + | } | |
| 292 | + | bool isOptionalBorder() const { | |
| 293 | + | return g_items[id].isOptionalBorder; | |
| 294 | + | } | |
| 295 | + | bool isWall() const { | |
| 296 | + | return g_items[id].isWall; | |
| 297 | + | } | |
| 298 | + | bool isDoor() const { | |
| 299 | + | return g_items[id].isDoor(); | |
| 300 | + | } | |
| 301 | + | bool isOpen() const { | |
| 302 | + | return g_items[id].isOpen; | |
| 303 | + | } | |
| 304 | + | bool isBrushDoor() const { | |
| 305 | + | return g_items[id].isBrushDoor; | |
| 306 | + | } | |
| 307 | + | bool isTable() const { | |
| 308 | + | return g_items[id].isTable; | |
| 309 | + | } | |
| 310 | + | bool isCarpet() const { | |
| 311 | + | return g_items[id].isCarpet; | |
| 312 | + | } | |
| 313 | + | bool isMetaItem() const { | |
| 314 | + | return g_items[id].isMetaItem(); | |
| 315 | + | } | |
| 316 | + | ||
| 317 | + | // Slot-based Item Types | |
| 318 | + | bool isWeapon() const { | |
| 319 | + | uint8_t weaponType = g_items[id].weapon_type; | |
| 320 | + | return weaponType != WEAPON_NONE && weaponType != WEAPON_AMMO; | |
| 321 | + | } | |
| 322 | + | bool isAmmunition() const { | |
| 323 | + | return g_items[id].weapon_type == WEAPON_AMMO; | |
| 324 | + | } | |
| 325 | + | bool isWearableEquipment() const { // Determine if the item is wearable piece of armor | |
| 326 | + | uint16_t slotPosition = g_items[id].slot_position; | |
| 327 | + | return slotPosition & SLOTP_HEAD || slotPosition & SLOTP_NECKLACE || | |
| 328 | + | // slotPosition & SLOTP_BACKPACK || // handled as container in properties window | |
| 329 | + | slotPosition & SLOTP_ARMOR || slotPosition & SLOTP_LEGS || slotPosition & SLOTP_FEET || slotPosition & SLOTP_RING || (slotPosition & SLOTP_AMMO && !isAmmunition()); // light sources that give stats | |
| 330 | + | } | |
| 331 | + | ||
| 332 | + | // Wall alignment (vertical, horizontal, pole, corner) | |
| 333 | + | BorderType getWallAlignment() const; | |
| 334 | + | // Border aligment (south, west etc.) | |
| 335 | + | BorderType getBorderAlignment() const; | |
| 336 | + | ||
| 337 | + | // Get the border type for z-ordering | |
| 338 | + | BorderType getBorderType() const; | |
| 339 | + | ||
| 340 | + | // Set the z-ordering location value for the item | |
| 341 | + | void setLocation(int z_level); | |
| 342 | + | int getLocation() const; | |
| 343 | + | ||
| 344 | + | // Get the name! | |
| 345 | + | const std::string getName() const { | |
| 346 | + | return g_items[id].name; | |
| 347 | + | } | |
| 348 | + | const std::string getFullName() const { | |
| 349 | + | return g_items[id].name + g_items[id].editorsuffix; | |
| 350 | + | } | |
| 351 | + | ||
| 352 | + | // Selection | |
| 353 | + | bool isSelected() const { | |
| 354 | + | return selected; | |
| 355 | + | } | |
| 356 | + | void select() { | |
| 357 | + | selected = true; | |
| 358 | + | } | |
| 359 | + | void deselect() { | |
| 360 | + | selected = false; | |
| 361 | + | } | |
| 362 | + | void toggleSelection() { | |
| 363 | + | selected = !selected; | |
| 364 | + | } | |
| 365 | + | ||
| 366 | + | // Item properties! | |
| 367 | + | virtual bool isComplex() const { | |
| 368 | + | return attributes && attributes->size(); | |
| 369 | + | } // If this item requires full save (not compact) | |
| 370 | + | ||
| 371 | + | // Weight | |
| 372 | + | bool hasWeight() { | |
| 373 | + | return isPickupable(); | |
| 374 | + | } | |
| 375 | + | virtual double getWeight(); | |
| 376 | + | ||
| 377 | + | // Subtype (count, fluid, charges) | |
| 378 | + | int getCount() const; | |
| 379 | + | uint16_t getSubtype() const; | |
| 380 | + | void setSubtype(uint16_t n); | |
| 381 | + | bool hasSubtype() const; | |
| 382 | + | ||
| 383 | + | // Unique ID | |
| 384 | + | void setUniqueID(uint16_t n); | |
| 385 | + | uint16_t getUniqueID() const; | |
| 386 | + | ||
| 387 | + | // Action ID | |
| 388 | + | void setActionID(uint16_t n); | |
| 389 | + | uint16_t getActionID() const; | |
| 390 | + | ||
| 391 | + | // Tier (12.81) | |
| 392 | + | void setTier(uint16_t n); | |
| 393 | + | uint16_t getTier() const; | |
| 394 | + | ||
| 395 | + | // Text | |
| 396 | + | void setText(const std::string& str); | |
| 397 | + | std::string getText() const; | |
| 398 | + | ||
| 399 | + | // Description | |
| 400 | + | void setDescription(const std::string& str); | |
| 401 | + | std::string getDescription() const; | |
| 402 | + | ||
| 403 | + | void animate(); | |
| 404 | + | int getFrame() const { | |
| 405 | + | return frame; | |
| 406 | + | } | |
| 407 | + | ||
| 408 | + | // Item locking | |
| 409 | + | bool isLocked() const {return locked;} | |
| 410 | + | void setLocked(bool b) {locked = b;} | |
| 411 | + | ||
| 412 | + | ||
| 413 | + | ||
| 414 | + | // Check if item blocks creatures (for border generation) | |
| 415 | + | bool isBlockingCreature() const { | |
| 416 | + | return g_items[id].unpassable; | |
| 417 | + | } | |
| 418 | + | ||
| 419 | + | // Check if item is a stair based on name | |
| 420 | + | bool isStairs() const { | |
| 421 | + | // Check by ID first - specific known floor transition tiles | |
| 422 | + | if (id == 459 || // Classic floor change | |
| 423 | + | id == 924 || // Ramp-style stair | |
| 424 | + | id == 1364 || // Northern wooden stairs | |
| 425 | + | id == 1369 || // Southern wooden stairs | |
| 426 | + | id == 1386) { // Wooden ladder/stair | |
| 427 | + | return true; | |
| 428 | + | } | |
| 429 | + | ||
| 430 | + | // Then check by ItemType property (if available) | |
| 431 | + | if (g_items[id].floorChangeDown || | |
| 432 | + | g_items[id].floorChangeNorth || | |
| 433 | + | g_items[id].floorChangeSouth || | |
| 434 | + | g_items[id].floorChangeEast || | |
| 435 | + | g_items[id].floorChangeWest) { | |
| 436 | + | return true; | |
| 437 | + | } | |
| 438 | + | ||
| 439 | + | // Finally check by name | |
| 440 | + | const std::string& name = getName(); | |
| 441 | + | std::string lowerName = name; | |
| 442 | + | std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); | |
| 443 | + | return lowerName.find("stair") != std::string::npos || | |
| 444 | + | lowerName.find("spiral") != std::string::npos || | |
| 445 | + | lowerName.find("ramp") != std::string::npos || | |
| 446 | + | lowerName.find("floor change") != std::string::npos || | |
| 447 | + | lowerName.find("level change") != std::string::npos; | |
| 448 | + | } | |
| 449 | + | ||
| 450 | + | // Check if item is a ladder based on name | |
| 451 | + | bool isLadder() const { | |
| 452 | + | // Check for known ladder IDs | |
| 453 | + | if (id == 1386 || // Wooden ladder/stair | |
| 454 | + | id == 3687 || // Wooden ladder | |
| 455 | + | id == 5543) { // Ship ladder | |
| 456 | + | return true; | |
| 457 | + | } | |
| 458 | + | ||
| 459 | + | // Check by floor change properties | |
| 460 | + | if (g_items[id].floorChangeDown || g_items[id].floorChange) { | |
| 461 | + | return true; | |
| 462 | + | } | |
| 463 | + | ||
| 464 | + | // Check by name | |
| 465 | + | const std::string& name = getName(); | |
| 466 | + | std::string lowerName = name; | |
| 467 | + | std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); | |
| 468 | + | return lowerName.find("ladder") != std::string::npos; | |
| 469 | + | } | |
| 470 | + | ||
| 471 | + | protected: | |
| 472 | + | uint16_t id; // the same id as in ItemType | |
| 473 | + | // Subtype is either fluid type, count, subtype or charges | |
| 474 | + | uint16_t subtype; | |
| 475 | + | bool selected; | |
| 476 | + | int frame; | |
| 477 | + | bool locked; | |
| 478 | + | ||
| 479 | + | private: | |
| 480 | + | Item& operator=(const Item& i); // Can't copy | |
| 481 | + | Item(const Item& i); // Can't copy-construct | |
| 482 | + | Item& operator==(const Item& i); // Can't compare | |
| 483 | + | }; | |
| 484 | + | ||
| 485 | + | typedef std::vector<Item*> ItemVector; | |
| 486 | + | typedef std::list<Item*> ItemList; | |
| 487 | + | ||
| 488 | + | Item* transformItem(Item* old_item, uint16_t new_id, Tile* parent = nullptr); | |
| 489 | + | ||
| 490 | + | inline int Item::getCount() const { | |
| 491 | + | if (isStackable() || isExtraCharged() || isClientCharged()) { | |
| 492 | + | return subtype; | |
| 493 | + | } | |
| 494 | + | return 1; | |
| 495 | + | } | |
| 496 | + | ||
| 497 | + | inline uint16_t Item::getUniqueID() const { | |
| 498 | + | const int32_t* a = getIntegerAttribute("uid"); | |
| 499 | + | if (a) { | |
| 500 | + | return *a; | |
| 501 | + | } | |
| 502 | + | return 0; | |
| 503 | + | } | |
| 504 | + | ||
| 505 | + | inline uint16_t Item::getActionID() const { | |
| 506 | + | const int32_t* a = getIntegerAttribute("aid"); | |
| 507 | + | if (a) { | |
| 508 | + | return *a; | |
| 509 | + | } | |
| 510 | + | return 0; | |
| 511 | + | } | |
| 512 | + | ||
| 513 | + | inline uint16_t Item::getTier() const { | |
| 514 | + | const int32_t* a = getIntegerAttribute("tier"); | |
| 515 | + | if (a) { | |
| 516 | + | return *a; | |
| 517 | + | } | |
| 518 | + | return 0; | |
| 519 | + | } | |
| 520 | + | ||
| 521 | + | inline std::string Item::getText() const { | |
| 522 | + | const std::string* a = getStringAttribute("text"); | |
| 523 | + | if (a) { | |
| 524 | + | return *a; | |
| 525 | + | } | |
| 526 | + | return ""; | |
| 527 | + | } | |
| 528 | + | ||
| 529 | + | inline std::string Item::getDescription() const { | |
| 530 | + | const std::string* a = getStringAttribute("desc"); | |
| 531 | + | if (a) { | |
| 532 | + | return *a; | |
| 533 | + | } | |
| 534 | + | return ""; | |
| 535 | + | } | |
| 536 | + | ||
| 537 | + | #endif |