doodad_brush.cpp

main 429 lines · 12.2 KB Raw
Alex Alex Commit Map Editor 18/10/2026 23:40
1//////////////////////////////////////////////////////////////////////
2// This file is part of Remere's Map Editor
3//////////////////////////////////////////////////////////////////////
4// Remere's Map Editor is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Remere's Map Editor is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//////////////////////////////////////////////////////////////////////
17
18#include "main.h"
19#include "gui.h"
20
21#include "doodad_brush.h"
22#include "basemap.h"
23
24#include <boost/lexical_cast.hpp>
25
26//=============================================================================
27// Doodad brush
28
29DoodadBrush::DoodadBrush() :
30 look_id(0),
31 thickness(0),
32 thickness_ceiling(0),
33 draggable(false),
34 on_blocking(false),
35 one_size(false),
36 do_new_borders(false),
37 on_duplicate(false),
38 clear_mapflags(0),
39 clear_statflags(0) {
40 ////
41}
42
43DoodadBrush::~DoodadBrush() {
44 for (std::vector<AlternativeBlock*>::iterator alt_iter = alternatives.begin(); alt_iter != alternatives.end(); ++alt_iter) {
45 delete *alt_iter;
46 }
47}
48
49DoodadBrush::AlternativeBlock::AlternativeBlock() :
50 composite_chance(0),
51 single_chance(0) {
52 ////
53}
54
55DoodadBrush::AlternativeBlock::~AlternativeBlock() {
56 for (std::vector<CompositeBlock>::iterator composite_iter = composite_items.begin(); composite_iter != composite_items.end(); ++composite_iter) {
57 CompositeTileList& tv = composite_iter->items;
58
59 for (CompositeTileList::iterator compt_iter = tv.begin(); compt_iter != tv.end(); ++compt_iter) {
60 ItemVector& items = compt_iter->second;
61 for (ItemVector::iterator iiter = items.begin(); iiter != items.end(); ++iiter) {
62 delete *iiter;
63 }
64 }
65 }
66
67 for (std::vector<SingleBlock>::iterator single_iter = single_items.begin(); single_iter != single_items.end(); ++single_iter) {
68 delete single_iter->item;
69 }
70}
71
72bool DoodadBrush::loadAlternative(pugi::xml_node node, wxArrayString& warnings, AlternativeBlock* which) {
73 AlternativeBlock* alternativeBlock;
74 if (which) {
75 alternativeBlock = which;
76 } else {
77 alternativeBlock = newd AlternativeBlock();
78 }
79
80 pugi::xml_attribute attribute;
81 for (pugi::xml_node childNode = node.first_child(); childNode; childNode = childNode.next_sibling()) {
82 const std::string& childName = as_lower_str(childNode.name());
83 if (childName == "item") {
84 if (!(attribute = childNode.attribute("chance"))) {
85 warnings.push_back("Can't read chance tag of doodad item node.");
86 continue;
87 }
88
89 Item* item = Item::Create(childNode);
90 if (!item) {
91 warnings.push_back("Can't create item from doodad item node.");
92 continue;
93 }
94
95 ItemType& it = g_items[item->getID()];
96 if (it.id != 0) {
97 it.doodad_brush = this;
98 }
99
100 SingleBlock sb;
101 sb.item = item;
102 sb.chance = attribute.as_int();
103
104 alternativeBlock->single_items.push_back(sb);
105 alternativeBlock->single_chance += sb.chance;
106 } else if (childName == "composite") {
107 if (!(attribute = childNode.attribute("chance"))) {
108 warnings.push_back("Can't read chance tag of doodad item node.");
109 continue;
110 }
111
112 alternativeBlock->composite_chance += attribute.as_int();
113
114 CompositeBlock cb;
115 cb.chance = alternativeBlock->composite_chance;
116
117 for (pugi::xml_node compositeNode = childNode.first_child(); compositeNode; compositeNode = compositeNode.next_sibling()) {
118 if (as_lower_str(compositeNode.name()) != "tile") {
119 continue;
120 }
121
122 if (!(attribute = compositeNode.attribute("x"))) {
123 warnings.push_back("Couldn't read positionX values of composite tile node.");
124 continue;
125 }
126
127 int32_t x = attribute.as_int();
128 if (!(attribute = compositeNode.attribute("y"))) {
129 warnings.push_back("Couldn't read positionY values of composite tile node.");
130 continue;
131 }
132
133 int32_t y = attribute.as_int();
134 int32_t z = compositeNode.attribute("z").as_int();
135 if (x < -0x7FFF || x > 0x7FFF) {
136 warnings.push_back("Invalid range of x value on composite tile node.");
137 continue;
138 } else if (y < -0x7FFF || y > 0x7FFF) {
139 warnings.push_back("Invalid range of y value on composite tile node.");
140 continue;
141 } else if (z < -0x7 || z > 0x7) {
142 warnings.push_back("Invalid range of z value on composite tile node.");
143 continue;
144 }
145
146 ItemVector items;
147 for (pugi::xml_node itemNode = compositeNode.first_child(); itemNode; itemNode = itemNode.next_sibling()) {
148 if (as_lower_str(itemNode.name()) != "item") {
149 continue;
150 }
151
152 Item* item = Item::Create(itemNode);
153 if (item) {
154 items.push_back(item);
155
156 ItemType& it = g_items[item->getID()];
157 if (it.id != 0) {
158 it.doodad_brush = this;
159 }
160 }
161 }
162
163 if (!items.empty()) {
164 cb.items.push_back(std::make_pair(Position(x, y, z), items));
165 }
166 }
167 alternativeBlock->composite_items.push_back(cb);
168 }
169 }
170
171 if (!which) {
172 alternatives.push_back(alternativeBlock);
173 }
174 return true;
175}
176
177bool DoodadBrush::load(pugi::xml_node node, wxArrayString& warnings) {
178 pugi::xml_attribute attribute;
179 if ((attribute = node.attribute("lookid"))) {
180 look_id = attribute.as_ushort();
181 }
182
183 if ((attribute = node.attribute("server_lookid"))) {
184 look_id = g_items[attribute.as_ushort()].clientID;
185 }
186
187 if ((attribute = node.attribute("on_blocking"))) {
188 on_blocking = attribute.as_bool();
189 }
190
191 if ((attribute = node.attribute("on_duplicate"))) {
192 on_duplicate = attribute.as_bool();
193 }
194
195 if ((attribute = node.attribute("redo_borders")) || (attribute = node.attribute("reborder"))) {
196 do_new_borders = attribute.as_bool();
197 }
198
199 if ((attribute = node.attribute("one_size"))) {
200 one_size = attribute.as_bool();
201 }
202
203 if ((attribute = node.attribute("draggable"))) {
204 draggable = attribute.as_bool();
205 }
206
207 if (node.attribute("remove_optional_border").as_bool()) {
208 if (!do_new_borders) {
209 warnings.push_back("remove_optional_border will not work without redo_borders\n");
210 }
211 clear_statflags |= TILESTATE_OP_BORDER;
212 }
213
214 const std::string& thicknessString = node.attribute("thickness").as_string();
215 if (!thicknessString.empty()) {
216 size_t slash = thicknessString.find('/');
217 if (slash != std::string::npos) {
218 thickness = boost::lexical_cast<int32_t>(thicknessString.substr(0, slash));
219 thickness_ceiling = std::max<int32_t>(thickness, boost::lexical_cast<int32_t>(thicknessString.substr(slash + 1)));
220 }
221 }
222
223 for (pugi::xml_node childNode = node.first_child(); childNode; childNode = childNode.next_sibling()) {
224 if (as_lower_str(childNode.name()) != "alternate") {
225 continue;
226 }
227 if (!loadAlternative(childNode, warnings)) {
228 return false;
229 }
230 }
231 loadAlternative(node, warnings, alternatives.empty() ? nullptr : alternatives.back());
232 return true;
233}
234
235bool DoodadBrush::AlternativeBlock::ownsItem(uint16_t id) const {
236 for (std::vector<SingleBlock>::const_iterator single_iter = single_items.begin(); single_iter != single_items.end(); ++single_iter) {
237 if (single_iter->item->getID() == id) {
238 return true;
239 }
240 }
241
242 for (std::vector<CompositeBlock>::const_iterator composite_iter = composite_items.begin(); composite_iter != composite_items.end(); ++composite_iter) {
243 const CompositeTileList& ctl = composite_iter->items;
244 for (CompositeTileList::const_iterator comp_iter = ctl.begin(); comp_iter != ctl.end(); ++comp_iter) {
245 const ItemVector& items = comp_iter->second;
246
247 for (ItemVector::const_iterator item_iter = items.begin(), item_end = items.end(); item_iter != item_end; ++item_iter) {
248 if ((*item_iter)->getID() == id) {
249 return true;
250 }
251 }
252 }
253 }
254 return false;
255}
256
257bool DoodadBrush::ownsItem(Item* item) const {
258 if (item->getDoodadBrush() == this) {
259 return true;
260 }
261 uint16_t id = item->getID();
262
263 for (std::vector<AlternativeBlock*>::const_iterator alt_iter = alternatives.begin(); alt_iter != alternatives.end(); ++alt_iter) {
264 if ((*alt_iter)->ownsItem(id)) {
265 return true;
266 }
267 }
268 return false;
269}
270
271void DoodadBrush::undraw(BaseMap* map, Tile* tile) {
272 // Remove all doodad-related
273 for (ItemVector::iterator item_iter = tile->items.begin(); item_iter != tile->items.end();) {
274 Item* item = *item_iter;
275 if (item->getDoodadBrush() != nullptr) {
276 if (item->isComplex() && g_settings.getInteger(Config::ERASER_LEAVE_UNIQUE)) {
277 ++item_iter;
278 } else if (g_settings.getInteger(Config::DOODAD_BRUSH_ERASE_LIKE)) {
279 // Only delete items of the same doodad brush
280 if (ownsItem(item)) {
281 delete item;
282 item_iter = tile->items.erase(item_iter);
283 } else {
284 ++item_iter;
285 }
286 } else {
287 delete item;
288 item_iter = tile->items.erase(item_iter);
289 }
290 } else {
291 ++item_iter;
292 }
293 }
294
295 if (tile->ground && tile->ground->getDoodadBrush() != nullptr) {
296 if (g_settings.getInteger(Config::DOODAD_BRUSH_ERASE_LIKE)) {
297 // Only delete items of the same doodad brush
298 if (ownsItem(tile->ground)) {
299 delete tile->ground;
300 tile->ground = nullptr;
301 }
302 } else {
303 delete tile->ground;
304 tile->ground = nullptr;
305 }
306 }
307}
308
309void DoodadBrush::draw(BaseMap* map, Tile* tile, void* parameter) {
310 int variation = 0;
311 if (parameter) {
312 variation = *reinterpret_cast<int*>(parameter);
313 }
314
315 if (alternatives.empty()) {
316 return;
317 }
318
319 variation %= alternatives.size();
320 const AlternativeBlock* ab_ptr = alternatives[variation];
321 ASSERT(ab_ptr);
322
323 int roll = random(1, ab_ptr->single_chance);
324 for (std::vector<SingleBlock>::const_iterator block_iter = ab_ptr->single_items.begin(); block_iter != ab_ptr->single_items.end(); ++block_iter) {
325 const SingleBlock& sb = *block_iter;
326 if (roll <= sb.chance) {
327 // Use this!
328 Item* new_item = sb.item->deepCopy();
329 if (new_item && g_gui.IsCurrentActionIDEnabled()) {
330 new_item->setActionID(g_gui.GetCurrentActionID());
331 }
332 tile->addItem(new_item);
333 break;
334 }
335 roll -= sb.chance;
336 }
337 if (clear_mapflags || clear_statflags) {
338 tile->setMapFlags(tile->getMapFlags() & (~clear_mapflags));
339 tile->setMapFlags(tile->getStatFlags() & (~clear_statflags));
340 if (clear_mapflags & TILESTATE_ZONE_BRUSH) {
341 tile->clearZoneId();
342 }
343 }
344}
345
346const CompositeTileList& DoodadBrush::getComposite(int variation) const {
347 static CompositeTileList empty;
348
349 if (alternatives.empty()) {
350 return empty;
351 }
352
353 variation %= alternatives.size();
354 const AlternativeBlock* ab_ptr = alternatives[variation];
355 ASSERT(ab_ptr);
356
357 int roll = random(1, ab_ptr->composite_chance);
358 for (std::vector<CompositeBlock>::const_iterator block_iter = ab_ptr->composite_items.begin(); block_iter != ab_ptr->composite_items.end(); ++block_iter) {
359 const CompositeBlock& cb = *block_iter;
360 if (roll <= cb.chance) {
361 return cb.items;
362 }
363 }
364 return empty;
365}
366
367bool DoodadBrush::isEmpty(int variation) const {
368 if (hasCompositeObjects(variation)) {
369 return false;
370 }
371 if (hasSingleObjects(variation)) {
372 return false;
373 }
374 if (thickness <= 0) {
375 return false;
376 }
377 return true;
378}
379
380int DoodadBrush::getCompositeChance(int ab) const {
381 if (alternatives.empty()) {
382 return 0;
383 }
384 ab %= alternatives.size();
385 const AlternativeBlock* ab_ptr = alternatives[ab];
386 ASSERT(ab_ptr);
387 return ab_ptr->composite_chance;
388}
389
390int DoodadBrush::getSingleChance(int ab) const {
391 if (alternatives.empty()) {
392 return 0;
393 }
394 ab %= alternatives.size();
395 const AlternativeBlock* ab_ptr = alternatives[ab];
396 ASSERT(ab_ptr);
397 return ab_ptr->single_chance;
398}
399
400int DoodadBrush::getTotalChance(int ab) const {
401 if (alternatives.empty()) {
402 return 0;
403 }
404 ab %= alternatives.size();
405 const AlternativeBlock* ab_ptr = alternatives[ab];
406 ASSERT(ab_ptr);
407 return ab_ptr->composite_chance + ab_ptr->single_chance;
408}
409
410bool DoodadBrush::hasSingleObjects(int ab) const {
411 if (alternatives.empty()) {
412 return false;
413 }
414 ab %= alternatives.size();
415 AlternativeBlock* ab_ptr = alternatives[ab];
416 ASSERT(ab_ptr);
417 return ab_ptr->single_chance > 0;
418}
419
420bool DoodadBrush::hasCompositeObjects(int ab) const {
421 if (alternatives.empty()) {
422 return false;
423 }
424 ab %= alternatives.size();
425 AlternativeBlock* ab_ptr = alternatives[ab];
426 ASSERT(ab_ptr);
427 return ab_ptr->composite_chance > 0;
428}
429
Top