carpet_brush.cpp

main 312 lines · 9 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
20#include "carpet_brush.h"
21
22#include "basemap.h"
23#include "items.h"
24
25//=============================================================================
26// Carpet brush
27
28uint32_t CarpetBrush::carpet_types[256];
29
30CarpetBrush::CarpetBrush() :
31 look_id(0) {
32 ////
33}
34
35CarpetBrush::~CarpetBrush() {
36 ////
37}
38
39bool CarpetBrush::load(pugi::xml_node node, wxArrayString& warnings) {
40 pugi::xml_attribute attribute;
41 if ((attribute = node.attribute("lookid"))) {
42 look_id = attribute.as_ushort();
43 }
44
45 if ((attribute = node.attribute("server_lookid"))) {
46 look_id = g_items[attribute.as_ushort()].clientID;
47 }
48
49 for (pugi::xml_node childNode = node.first_child(); childNode; childNode = childNode.next_sibling()) {
50 if (as_lower_str(childNode.name()) != "carpet") {
51 continue;
52 }
53
54 uint32_t alignment;
55 if ((attribute = childNode.attribute("align"))) {
56 const std::string& alignString = attribute.as_string();
57 alignment = AutoBorder::edgeNameToID(alignString);
58 if (alignment == BORDER_NONE) {
59 if (alignString == "center") {
60 alignment = CARPET_CENTER;
61 } else {
62 warnings.push_back("Invalid alignment of carpet node\n");
63 continue;
64 }
65 }
66 } else {
67 warnings.push_back("Could not read alignment tag of carpet node\n");
68 continue;
69 }
70
71 bool use_local_id = true;
72 for (pugi::xml_node subChildNode = childNode.first_child(); subChildNode; subChildNode = subChildNode.next_sibling()) {
73 if (as_lower_str(subChildNode.name()) != "item") {
74 continue;
75 }
76
77 use_local_id = false;
78 if (!(attribute = subChildNode.attribute("id"))) {
79 warnings.push_back("Could not read id tag of item node\n");
80 continue;
81 }
82
83 int32_t id = attribute.as_int();
84 if (!(attribute = subChildNode.attribute("chance"))) {
85 warnings.push_back("Could not read chance tag of item node\n");
86 continue;
87 }
88
89 int32_t chance = attribute.as_int();
90
91 ItemType& it = g_items[id];
92 if (it.id == 0) {
93 warnings.push_back("There is no itemtype with id " + std::to_string(id));
94 continue;
95 } else if (it.brush && it.brush != this) {
96 warnings.push_back("Itemtype id " + std::to_string(id) + " already has a brush");
97 continue;
98 }
99
100 it.isCarpet = true;
101 it.brush = this;
102
103 auto& alignItem = carpet_items[alignment];
104 alignItem.total_chance += chance;
105
106 CarpetType t;
107 t.id = id;
108 t.chance = chance;
109
110 alignItem.items.push_back(t);
111 }
112
113 if (use_local_id) {
114 if (!(attribute = childNode.attribute("id"))) {
115 warnings.push_back("Could not read id tag of carpet node\n");
116 continue;
117 }
118
119 uint16_t id = attribute.as_ushort();
120
121 ItemType& it = g_items[id];
122 if (it.id == 0) {
123 warnings.push_back("There is no itemtype with id " + std::to_string(id));
124 return false;
125 } else if (it.brush && it.brush != this) {
126 warnings.push_back("Itemtype id " + std::to_string(id) + " already has a brush");
127 return false;
128 }
129
130 it.isCarpet = true;
131 it.brush = this;
132
133 auto& alignItem = carpet_items[alignment];
134 alignItem.total_chance = 1;
135
136 CarpetType carpetType;
137 carpetType.id = id;
138 carpetType.chance = 1;
139
140 alignItem.items.push_back(carpetType);
141 }
142 }
143 return true;
144}
145
146bool CarpetBrush::canDraw(BaseMap* map, const Position& position) const {
147 return true;
148}
149
150void CarpetBrush::draw(BaseMap* map, Tile* tile, void* parameter) {
151 // Only remove old carpets if layering is disabled
152 if (!g_settings.getBoolean(Config::LAYER_CARPETS)) {
153 undraw(map, tile);
154 }
155 tile->addItem(Item::Create(getRandomCarpet(CARPET_CENTER)));
156}
157
158void CarpetBrush::undraw(BaseMap* map, Tile* tile) {
159 auto& items = tile->items;
160 for (auto it = items.begin(); it != items.end();) {
161 Item* item = *it;
162 if (item->isCarpet()) {
163 CarpetBrush* carpetBrush = item->getCarpetBrush();
164 if (carpetBrush) {
165 delete item;
166 it = items.erase(it);
167 } else {
168 ++it;
169 }
170 } else {
171 ++it;
172 }
173 }
174}
175
176void CarpetBrush::doCarpets(BaseMap* map, Tile* tile) {
177 static const auto hasMatchingCarpetBrushAtTile = [](BaseMap* map, CarpetBrush* carpetBrush, uint32_t x, uint32_t y, uint32_t z) -> bool {
178 Tile* tile = map->getTile(x, y, z);
179 if (!tile) {
180 return false;
181 }
182
183 for (Item* item : tile->items) {
184 if (item->getCarpetBrush() == carpetBrush) {
185 return true;
186 }
187 }
188 return false;
189 };
190
191 ASSERT(tile);
192 if (!tile->hasCarpet()) {
193 return;
194 }
195
196 const Position& position = tile->getPosition();
197 uint32_t x = position.x;
198 uint32_t y = position.y;
199 uint32_t z = position.z;
200 /*
201 static const std::pair<int32_t, int32_t> positionOffset[8] = {
202 {-1, -1}, {0, -1}, {1, -1}, {-1, 0}, {1, 0}, {-1, 1}, {0, 1}, {1, 1}
203 };
204
205 const auto& offset = positionOffset[i];
206 if(neighbours[i] && hasMatchingCarpetBrushAtTile(map, carpetBrush, x + offset.first, y + offset.second, z)) {
207 //
208 }
209 */
210 for (Item* item : tile->items) {
211 ASSERT(item);
212
213 CarpetBrush* carpetBrush = item->getCarpetBrush();
214 if (!carpetBrush) {
215 continue;
216 }
217
218 bool neighbours[8] = { false };
219 if (x == 0) {
220 if (y == 0) {
221 neighbours[0] = false;
222 neighbours[1] = false;
223 neighbours[2] = false;
224 neighbours[3] = false;
225 neighbours[4] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x + 1, y, z);
226 neighbours[5] = false;
227 neighbours[6] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x, y + 1, z);
228 neighbours[7] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x + 1, y + 1, z);
229 } else {
230 neighbours[0] = false;
231 neighbours[1] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x, y - 1, z);
232 neighbours[2] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x + 1, y - 1, z);
233 neighbours[3] = false;
234 neighbours[4] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x + 1, y, z);
235 neighbours[5] = false;
236 neighbours[6] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x, y + 1, z);
237 neighbours[7] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x + 1, y + 1, z);
238 }
239 } else if (y == 0) {
240 neighbours[0] = false;
241 neighbours[1] = false;
242 neighbours[2] = false;
243 neighbours[3] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x - 1, y, z);
244 neighbours[4] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x + 1, y, z);
245 neighbours[5] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x - 1, y + 1, z);
246 neighbours[6] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x, y + 1, z);
247 neighbours[7] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x + 1, y + 1, z);
248 } else {
249 neighbours[0] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x - 1, y - 1, z);
250 neighbours[1] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x, y - 1, z);
251 neighbours[2] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x + 1, y - 1, z);
252 neighbours[3] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x - 1, y, z);
253 neighbours[4] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x + 1, y, z);
254 neighbours[5] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x - 1, y + 1, z);
255 neighbours[6] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x, y + 1, z);
256 neighbours[7] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x + 1, y + 1, z);
257 }
258
259 uint32_t tileData = 0;
260 for (uint32_t i = 0; i < 8; ++i) {
261 if (neighbours[i]) {
262 // Same table as this one, calculate what border
263 tileData |= static_cast<uint32_t>(1) << i;
264 }
265 }
266
267 // border type is always valid.
268 uint16_t id = carpetBrush->getRandomCarpet(static_cast<BorderType>(carpet_types[tileData]));
269 if (id != 0) {
270 item->setID(id);
271 }
272 }
273}
274
275uint16_t CarpetBrush::getRandomCarpet(BorderType alignment) {
276 static const auto findRandomCarpet = [](const CarpetNode& node) -> uint16_t {
277 int32_t chance = random(1, node.total_chance);
278 for (const CarpetType& carpetType : node.items) {
279 if (chance <= carpetType.chance) {
280 return carpetType.id;
281 }
282 chance -= carpetType.chance;
283 }
284 return 0;
285 };
286
287 CarpetNode node = carpet_items[alignment];
288 if (node.total_chance > 0) {
289 return findRandomCarpet(node);
290 }
291
292 node = carpet_items[CARPET_CENTER];
293 if (alignment != CARPET_CENTER && node.total_chance > 0) {
294 uint16_t id = findRandomCarpet(node);
295 if (id != 0) {
296 return id;
297 }
298 }
299
300 // Find an item to place on the tile, first center, then the rest.
301 for (int32_t i = 0; i < 12; ++i) {
302 node = carpet_items[i];
303 if (node.total_chance > 0) {
304 uint16_t id = findRandomCarpet(node);
305 if (id != 0) {
306 return id;
307 }
308 }
309 }
310 return 0;
311}
312
Top