载入中...
搜索中...
未找到
PlacementSystem.cpp
浏览该文件的文档.
3#include "building/Ghost.h"
6
7#include <algorithm>
8#include <cmath>
9
10namespace eve::building {
11
12namespace {
13
14float wrapDeg(float deg) {
15 deg = std::fmod(deg, 360.f);
16 if (deg < 0.f) deg += 360.f;
17 return deg;
18}
19
20int cardinalQuarter(float rotationDeg) {
21 const float d = wrapDeg(rotationDeg);
22 int q = int(std::lround(d / 90.f)) % 4;
23 if (q < 0) q += 4;
24 return q;
25}
26
27} // namespace
28
30 static PlacementSystem instance;
31 return instance;
32}
33
34std::unordered_map<std::string, PlacementSystem::ValidateFn> &PlacementSystem::validateRules() {
35 return inst().validateRules_;
36}
37
38std::unordered_map<std::string, PlacementSystem::SnapFn> &PlacementSystem::snapRules() {
39 return inst().snapRules_;
40}
41
42std::unordered_map<std::string, PlacementSystem::ChangeHook> &PlacementSystem::changeHooks() {
43 return inst().changeHooks_;
44}
45
46std::unordered_map<std::string, PlacementSystem::SurfaceFn> &PlacementSystem::surfaces() {
47 return inst().surfaces_;
48}
49
50std::vector<BuildingChangeEvent> &PlacementSystem::eventQueue() {
51 return inst().eventQueue_;
52}
53
54int &PlacementSystem::instanceCounter() {
55 return inst().instanceCounter_;
56}
57
58bool &PlacementSystem::builtinsReady() {
59 return inst().builtinsReady_;
60}
61
63 if (name.empty() || !fn) return;
64 validateRules()[name] = std::move(fn);
65}
66
68 validateRules().erase(name);
69}
70
71bool PlacementSystem::hasValidateRule(const std::string &name) {
73 return validateRules().count(name) > 0;
74}
75
77 if (name.empty() || !fn) return;
78 snapRules()[name] = std::move(fn);
79}
80
81void PlacementSystem::unregisterSnapRule(const std::string &name) { snapRules().erase(name); }
82
83bool PlacementSystem::hasSnapRule(const std::string &name) {
85 return snapRules().count(name) > 0;
86}
87
89 if (name.empty() || !fn) return;
90 changeHooks()[name] = std::move(fn);
91}
92
93void PlacementSystem::unregisterChangeHook(const std::string &name) { changeHooks().erase(name); }
94
95bool PlacementSystem::hasChangeHook(const std::string &name) {
96 return changeHooks().count(name) > 0;
97}
98
100 if (name.empty() || !fn) return;
101 surfaces()[name] = std::move(fn);
102}
103
104void PlacementSystem::unregisterSurface(const std::string &name) { surfaces().erase(name); }
105
106bool PlacementSystem::hasSurface(const std::string &name) {
108 return surfaces().count(name) > 0;
109}
110
111bool PlacementSystem::surfaceHit(const PlacementWorld &world, const std::string &name, float x,
112 float y, PlacementHit *hit) {
114 auto &rules = surfaces();
115 auto it = rules.find(name.empty() ? "plane" : name);
116 if (it == rules.end() || !it->second) return false;
117 return it->second(world, x, y, hit);
118}
119
120std::vector<std::string> PlacementSystem::surfaceNames() {
122 std::vector<std::string> names;
123 names.reserve(surfaces().size());
124 for (const auto &kv : surfaces()) names.push_back(kv.first);
125 return names;
126}
127
128void PlacementSystem::setPlaneSurfaceHeight(float h) { planeSurfaceHeight() = h; }
129
130float PlacementSystem::getPlaneSurfaceHeight() { return planeSurfaceHeight(); }
131
132float &PlacementSystem::planeSurfaceHeight() {
133 return inst().planeSurfaceHeight_;
134}
135
137 if (builtinsReady()) return;
138 builtinsReady() = true;
139
140 registerSnapRule("grid", [](const PlacementWorld &world, float worldX, float worldY) {
141 SnapResult r;
142 int cx = 0, cy = 0;
143 grid::worldToCell(world.getGrid(), worldX, worldY, cx, cy, world.getWidth(),
144 world.getHeight());
145 r.cellX = cx;
146 r.cellY = cy;
147 world.cellToWorldPlane(cx, cy, r.worldX, r.worldY);
148 return r;
149 });
150
151 registerSnapRule("cell", [](const PlacementWorld &world, float worldX, float worldY) {
152 SnapResult r;
153 r.cellX = int(std::floor(worldX + 0.5f));
154 r.cellY = int(std::floor(worldY + 0.5f));
155 r.worldX = float(r.cellX);
156 r.worldY = float(r.cellY);
157 (void)world;
158 return r;
159 });
160
161 registerSnapRule("free", [](const PlacementWorld &world, float worldX, float worldY) {
162 SnapResult r;
163 r.worldX = worldX;
164 r.worldY = worldY;
165 r.cellX = world.worldToCellX(worldX);
166 r.cellY = world.worldToCellY(worldY);
167 return r;
168 });
169
170 // default: bounds + occupancy + terrain + adjacency
171 registerValidateRule("default", [](const PlacementWorld &world, const PlacementQuery &q,
172 std::string *reason) {
174 if (!def) {
175 if (reason) *reason = "unknown_building";
176 return false;
177 }
178 if (!checkBoundsAndOccupancy(world, *def, q, true, reason)) return false;
179 if (!checkTerrain(world, *def, q, reason)) return false;
180 if (!checkAdjacency(world, *def, q, reason)) return false;
181 return true;
182 });
183
184 registerValidateRule("boundsOnly", [](const PlacementWorld &world, const PlacementQuery &q,
185 std::string *reason) {
187 if (!def) {
188 if (reason) *reason = "unknown_building";
189 return false;
190 }
191 return checkBoundsAndOccupancy(world, *def, q, false, reason);
192 });
193
194 registerValidateRule("overlapOk", [](const PlacementWorld &world, const PlacementQuery &q,
195 std::string *reason) {
197 if (!def) {
198 if (reason) *reason = "unknown_building";
199 return false;
200 }
201 if (!checkBoundsAndOccupancy(world, *def, q, false, reason)) return false;
202 if (!checkTerrain(world, *def, q, reason)) return false;
203 if (!checkAdjacency(world, *def, q, reason)) return false;
204 return true;
205 });
206
207 registerSurface("plane", [](const PlacementWorld &world, float x, float y,
208 PlacementHit *hit) {
209 if (!hit) return false;
210 const float h = planeSurfaceHeight();
211 if (world.getGrid().plane == grid::GridPlane::XZ) {
212 hit->worldX = x;
213 hit->worldY = h;
214 hit->worldZ = y;
215 } else {
216 hit->worldX = x;
217 hit->worldY = y;
218 hit->worldZ = h;
219 }
220 return true;
221 });
222}
223
224int PlacementSystem::nextInstanceId() { return ++instanceCounter(); }
225
226void PlacementSystem::pushEvent(BuildingChangeEvent ev) { emit(std::move(ev)); }
227
228void PlacementSystem::pollEvents(std::vector<BuildingChangeEvent> &out) {
229 out = eventQueue();
230 eventQueue().clear();
231}
232
233void PlacementSystem::clearEvents() { eventQueue().clear(); }
234
235const std::vector<BuildingChangeEvent> &PlacementSystem::events() { return eventQueue(); }
236
237void PlacementSystem::emit(BuildingChangeEvent ev) {
238 eventQueue().push_back(ev);
239 for (auto &kv : changeHooks()) {
240 if (kv.second) kv.second(ev);
241 }
242}
243
244SnapResult PlacementSystem::snapWithMode(const PlacementWorld &world, const std::string &mode,
245 float worldX, float worldY) {
247 auto &rules = snapRules();
248 auto it = rules.find(mode.empty() ? "grid" : mode);
249 if (it == rules.end() || !it->second) {
250 it = rules.find("grid");
251 }
252 if (it != rules.end() && it->second) return it->second(world, worldX, worldY);
253 SnapResult r;
254 r.cellX = world.worldToCellX(worldX);
255 r.cellY = world.worldToCellY(worldY);
256 r.worldX = world.cellToWorldX(r.cellX);
257 r.worldY = world.cellToWorldY(r.cellY);
258 return r;
259}
260
261SnapResult PlacementSystem::snap(const PlacementWorld &world, const std::string &buildingId,
262 float worldX, float worldY) {
264 std::string mode = world.getSnapMode();
265 if (const BuildingDefinition *def = BuildingRegistry::find(buildingId)) {
266 if (!def->snapMode.empty()) mode = def->snapMode;
267 }
268 return snapWithMode(world, mode, worldX, worldY);
269}
270
271SnapResult PlacementSystem::snap3D(const PlacementWorld &world, const std::string &buildingId,
272 float worldX, float worldY, float worldZ) {
273 float px = worldX;
274 float py = worldY;
275 float elev = worldZ;
276 if (world.getGrid().plane == grid::GridPlane::XZ) {
277 // 世界坐标 (x, y=高度, z):网格平面第二轴取 z,垂直高度取 y。
278 py = worldZ;
279 elev = worldY;
280 }
281 SnapResult s = snap(world, buildingId, px, py);
282 s.elevation = elev;
283 return s;
284}
285
286float PlacementSystem::normalizeRotation(const std::string &buildingId, float rotationDeg) {
287 const BuildingDefinition *def = BuildingRegistry::find(buildingId);
288 const std::string mode = def ? def->rotationMode : "cardinal";
289 if (mode == "none") return 0.f;
290 if (mode == "free") return wrapDeg(rotationDeg);
291 if (mode == "hex") {
292 const float d = wrapDeg(rotationDeg);
293 const int steps = int(std::lround(d / 60.f)) % 6;
294 return float((steps < 0 ? steps + 6 : steps) * 60);
295 }
296 // cardinal(默认)
297 return float(cardinalQuarter(rotationDeg) * 90);
298}
299
300void PlacementSystem::effectiveFootprint(const BuildingDefinition &def, float rotationDeg, int *outW,
301 int *outH) {
302 int w = def.footprintW;
303 int h = def.footprintH;
304 if (def.rotationMode == "hex") {
305 const int steps = int(std::lround(wrapDeg(rotationDeg) / 60.f)) % 6;
307 w, h);
308 } else if (def.rotationMode == "cardinal" || def.rotationMode.empty()) {
309 const int q = cardinalQuarter(rotationDeg);
310 if (q == 1 || q == 3) std::swap(w, h);
311 }
312 if (outW) *outW = w;
313 if (outH) *outH = h;
314}
315
317 int originCellY, float rotationDeg,
318 const std::function<bool(int cx, int cy)> &fn) {
319 if (!fn) return false;
320 const bool hex = def.rotationMode == "hex";
321 const int steps =
322 hex ? int(std::lround(wrapDeg(rotationDeg) / 60.f)) % 6 : cardinalQuarter(rotationDeg);
323 bool ok = true;
325 [&](int lx, int ly) {
326 if (!fn(originCellX + lx, originCellY + ly)) ok = false;
327 });
328 return ok;
329}
330
331bool PlacementSystem::checkBoundsAndOccupancy(const PlacementWorld &world,
332 const BuildingDefinition &def,
333 const PlacementQuery &q, bool checkOccupancy,
334 std::string *reason) {
335 bool ok = true;
336 foreachFootprintCell(def, q.cellX, q.cellY, q.rotationDeg, [&](int cx, int cy) {
337 if (!world.inBounds(cx, cy)) {
338 if (reason) *reason = "out_of_bounds";
339 ok = false;
340 return false;
341 }
342 if (checkOccupancy) {
343 const int occ = world.getOccupantInChannel(def.channel, cx, cy);
344 if (occ != 0 && occ != q.excludeInstanceId) {
345 if (reason) *reason = "occupied";
346 ok = false;
347 return false;
348 }
349 }
350 return true;
351 });
352 return ok;
353}
354
355bool PlacementSystem::checkTerrain(const PlacementWorld &world, const BuildingDefinition &def,
356 const PlacementQuery &q, std::string *reason) {
357 if (def.requireTerrain.empty() && def.forbidTerrain.empty()) return true;
358 bool ok = true;
359 foreachFootprintCell(def, q.cellX, q.cellY, q.rotationDeg, [&](int cx, int cy) {
360 const int sem = world.getTerrain(cx, cy);
361 if (!def.forbidTerrain.empty()) {
362 if (std::find(def.forbidTerrain.begin(), def.forbidTerrain.end(), sem) !=
363 def.forbidTerrain.end()) {
364 if (reason) *reason = "terrain_forbidden";
365 ok = false;
366 return false;
367 }
368 }
369 if (!def.requireTerrain.empty()) {
370 if (std::find(def.requireTerrain.begin(), def.requireTerrain.end(), sem) ==
371 def.requireTerrain.end()) {
372 if (reason) *reason = "terrain_mismatch";
373 ok = false;
374 return false;
375 }
376 }
377 return true;
378 });
379 return ok;
380}
381
382bool PlacementSystem::checkAdjacency(const PlacementWorld &world, const BuildingDefinition &def,
383 const PlacementQuery &q, std::string *reason) {
384 const bool needTag = !def.requireAdjacentTag.empty();
385 const bool needTerrain = def.requireAdjacentTerrain >= 0;
386 if (!needTag && !needTerrain) return true;
387
388 // Collect footprint cells + expand by 1 (4-neigh).
389 std::vector<std::pair<int, int>> cells;
390 foreachFootprintCell(def, q.cellX, q.cellY, q.rotationDeg, [&](int cx, int cy) {
391 cells.emplace_back(cx, cy);
392 return true;
393 });
394
395 auto isFootprint = [&](int x, int y) {
396 return std::find(cells.begin(), cells.end(), std::make_pair(x, y)) != cells.end();
397 };
398
399 bool tagOk = !needTag;
400 bool terrainOk = !needTerrain;
401 static const int dx[4] = {1, -1, 0, 0};
402 static const int dy[4] = {0, 0, 1, -1};
403
404 for (const auto &c : cells) {
405 for (int i = 0; i < 4; ++i) {
406 const int nx = c.first + dx[i];
407 const int ny = c.second + dy[i];
408 if (isFootprint(nx, ny)) continue;
409 if (!world.inBounds(nx, ny)) continue;
410
411 if (needTag && !tagOk) {
412 const int occ = world.getAnyOccupant(nx, ny);
413 if (occ != 0 && occ != q.excludeInstanceId) {
414 auto it = world.buildings().find(occ);
415 if (it != world.buildings().end()) {
416 const PlacedBuilding &pb = it->second;
417 bool hit = pb.hasTag(def.requireAdjacentTag);
418 if (!hit) {
419 if (const BuildingDefinition *odef =
420 BuildingRegistry::find(pb.buildingId)) {
421 hit = odef->hasTag(def.requireAdjacentTag);
422 }
423 }
424 if (hit) tagOk = true;
425 }
426 }
427 }
428 if (needTerrain && !terrainOk) {
429 if (world.getTerrain(nx, ny) == def.requireAdjacentTerrain) terrainOk = true;
430 }
431 if (tagOk && terrainOk) return true;
432 }
433 }
434
435 if (needTag && !tagOk) {
436 if (reason) *reason = "adjacency_tag";
437 return false;
438 }
439 if (needTerrain && !terrainOk) {
440 if (reason) *reason = "adjacency_terrain";
441 return false;
442 }
443 return true;
444}
445
446bool PlacementSystem::runValidate(const PlacementWorld &world, const BuildingDefinition &def,
447 const PlacementQuery &q, std::string *reason) {
448 ensureBuiltins();
449 std::string rule = def.validateRule.empty() ? world.getValidateRule() : def.validateRule;
450 if (rule.empty()) rule = "default";
451 auto &rules = validateRules();
452 auto it = rules.find(rule);
453 if (it == rules.end() || !it->second) {
454 it = rules.find("default");
455 }
456 if (it == rules.end() || !it->second) {
457 if (reason) *reason = "validate_rejected";
458 return false;
459 }
460 return it->second(world, q, reason);
461}
462
463bool PlacementSystem::canPlaceElev(PlacementWorld *world, const std::string &buildingId, int cellX,
464 int cellY, float elevation, float rotationDeg,
465 int excludeInstanceId, std::string *reason) {
466 ensureBuiltins();
467 if (!world) {
468 if (reason) *reason = "no_world";
469 return false;
470 }
471 const BuildingDefinition *def = BuildingRegistry::find(buildingId);
472 if (!def) {
473 if (reason) *reason = "unknown_building";
474 return false;
475 }
477 q.buildingId = buildingId;
478 q.cellX = cellX;
479 q.cellY = cellY;
480 q.rotationDeg = normalizeRotation(buildingId, rotationDeg);
481 world->cellToWorldPlane(cellX, cellY, q.worldX, q.worldY);
482 q.elevation = elevation;
483 q.excludeInstanceId = excludeInstanceId;
484 return runValidate(*world, *def, q, reason);
485}
486
487bool PlacementSystem::canPlace(PlacementWorld *world, const std::string &buildingId, int cellX,
488 int cellY, float rotationDeg, int excludeInstanceId,
489 std::string *reason) {
490 return canPlaceElev(world, buildingId, cellX, cellY, 0.f, rotationDeg, excludeInstanceId,
491 reason);
492}
493
494void PlacementSystem::writeOccupancy(PlacementWorld &world, const BuildingDefinition &def,
495 const PlacedBuilding &placed, int instanceId) {
496 std::vector<int> &occ = world.channelOccupancy(def.channel);
497 foreachFootprintCell(def, placed.originCellX, placed.originCellY, placed.rotationDeg,
498 [&](int cx, int cy) {
499 if (world.inBounds(cx, cy)) {
500 const size_t idx =
501 size_t(cy) * size_t(world.getWidth()) + size_t(cx);
502 if (idx < occ.size()) occ[idx] = instanceId;
503 }
504 return true;
505 });
506}
507
508void PlacementSystem::clearOccupancy(PlacementWorld &world, int instanceId) {
509 // 默认通道("")占用存于 occupancy_,附加通道存于 allChannels_,两者都要清。
510 for (int &v : world.occupancy()) {
511 if (v == instanceId) v = 0;
512 }
513 for (auto &kv : world.allChannels()) {
514 for (int &v : kv.second) {
515 if (v == instanceId) v = 0;
516 }
517 }
518}
519
520int PlacementSystem::placeAt(PlacementWorld *world, const std::string &buildingId, int cellX,
521 int cellY, float rotationDeg) {
522 ensureBuiltins();
523 if (!world) return 0;
524 const BuildingDefinition *def = BuildingRegistry::find(buildingId);
525 if (!def) return 0;
526 const float rot = normalizeRotation(buildingId, rotationDeg);
527 if (!canPlace(world, buildingId, cellX, cellY, rot, 0, nullptr)) return 0;
528
530 pb.instanceId = nextInstanceId();
531 pb.buildingId = buildingId;
532 pb.originCellX = cellX;
533 pb.originCellY = cellY;
534 world->cellToWorldPlane(cellX, cellY, pb.worldX, pb.worldY);
535 pb.elevation = 0.f;
536 pb.rotationDeg = rot;
537 pb.channel = def->channel;
538 pb.tags = def->tags;
539
540 writeOccupancy(*world, *def, pb, pb.instanceId);
541 world->buildings()[pb.instanceId] = pb;
542 world->instanceOrder_.push_back(pb.instanceId);
543
545 ev.action = "place";
546 ev.worldId = world->getId();
547 ev.buildingId = buildingId;
548 ev.instanceId = pb.instanceId;
549 ev.cellX = cellX;
550 ev.cellY = cellY;
551 ev.rotationDeg = rot;
552 ev.worldX = pb.worldX;
553 ev.worldY = pb.worldY;
554 ev.elevation = pb.elevation;
555 ev.channel = pb.channel;
556 emit(std::move(ev));
557 return pb.instanceId;
558}
559
560int PlacementSystem::placeAtWorld(PlacementWorld *world, const std::string &buildingId,
561 float worldX, float worldY, float rotationDeg) {
562 ensureBuiltins();
563 if (!world) return 0;
564 const SnapResult s = snap(*world, buildingId, worldX, worldY);
565 const int id = placeAt(world, buildingId, s.cellX, s.cellY, rotationDeg);
566 if (id <= 0) return 0;
567 // Preserve free / custom snap world pose (cell placement alone snaps to cell origin).
568 auto it = world->buildings().find(id);
569 if (it != world->buildings().end()) {
570 it->second.worldX = s.worldX;
571 it->second.worldY = s.worldY;
572 }
573 return id;
574}
575
576int PlacementSystem::placeAtWorld3D(PlacementWorld *world, const std::string &buildingId,
577 float worldX, float worldY, float worldZ,
578 float rotationDeg) {
579 ensureBuiltins();
580 if (!world) return 0;
581 const SnapResult s = snap3D(*world, buildingId, worldX, worldY, worldZ);
582 const int id = placeAt(world, buildingId, s.cellX, s.cellY, rotationDeg);
583 if (id <= 0) return 0;
584 auto it = world->buildings().find(id);
585 if (it != world->buildings().end()) {
586 it->second.worldX = s.worldX;
587 it->second.worldY = s.worldY;
588 it->second.elevation = s.elevation;
589 }
590 return id;
591}
592
593int PlacementSystem::placeGhost(PlacementWorld *world, Ghost *ghost) {
594 if (!world || !ghost) return 0;
595 if (!ghost->validate(world)) return 0;
596 const int id = placeAt(world, ghost->getBuildingId(), ghost->getCellX(), ghost->getCellY(),
597 ghost->getRotationDeg());
598 if (id <= 0) return 0;
599 // 保留吸附后的平面位姿与高度(free/自定义 snap 与 3D 放置需要)。
600 auto it = world->buildings().find(id);
601 if (it != world->buildings().end()) {
602 it->second.worldX = ghost->getWorldX();
603 it->second.worldY = ghost->getWorldY();
604 it->second.elevation = ghost->getElevation();
605 }
606 return id;
607}
608
609bool PlacementSystem::removeBuilding(PlacementWorld *world, int instanceId) {
610 if (!world || instanceId <= 0) return false;
611 auto it = world->buildings().find(instanceId);
612 if (it == world->buildings().end()) return false;
613
615 ev.action = "remove";
616 ev.worldId = world->getId();
617 ev.buildingId = it->second.buildingId;
618 ev.instanceId = instanceId;
619 ev.cellX = it->second.originCellX;
620 ev.cellY = it->second.originCellY;
621 ev.rotationDeg = it->second.rotationDeg;
622 ev.worldX = it->second.worldX;
623 ev.worldY = it->second.worldY;
624 ev.elevation = it->second.elevation;
625 ev.channel = it->second.channel;
626
627 clearOccupancy(*world, instanceId);
628 world->buildings().erase(it);
629 auto &order = world->instanceOrder_;
630 order.erase(std::remove(order.begin(), order.end(), instanceId), order.end());
631 emit(std::move(ev));
632 return true;
633}
634
635bool PlacementSystem::moveBuilding(PlacementWorld *world, int instanceId, int cellX, int cellY,
636 float rotationDeg) {
637 if (!world || instanceId <= 0) return false;
638 auto it = world->buildings().find(instanceId);
639 if (it == world->buildings().end()) return false;
640
641 PlacedBuilding &pb = it->second;
642 const float rot =
643 (rotationDeg < 0.f) ? pb.rotationDeg : normalizeRotation(pb.buildingId, rotationDeg);
644 if (!canPlace(world, pb.buildingId, cellX, cellY, rot, instanceId, nullptr)) return false;
645
646 const BuildingDefinition *def = BuildingRegistry::find(pb.buildingId);
647 if (!def) return false;
648
650 ev.action = "move";
651 ev.worldId = world->getId();
652 ev.buildingId = pb.buildingId;
653 ev.instanceId = instanceId;
654 ev.otherCellX = pb.originCellX;
655 ev.otherCellY = pb.originCellY;
656 ev.cellX = cellX;
657 ev.cellY = cellY;
658 ev.rotationDeg = rot;
659 float px = 0.f, py = 0.f;
660 world->cellToWorldPlane(cellX, cellY, px, py);
661 ev.worldX = px;
662 ev.worldY = py;
663 ev.elevation = pb.elevation;
664 ev.channel = pb.channel;
665
666 clearOccupancy(*world, instanceId);
667 pb.originCellX = cellX;
668 pb.originCellY = cellY;
669 world->cellToWorldPlane(cellX, cellY, pb.worldX, pb.worldY);
670 pb.rotationDeg = rot;
671 writeOccupancy(*world, *def, pb, instanceId);
672 emit(std::move(ev));
673 return true;
674}
675
676void PlacementSystem::clearBuildings(PlacementWorld *world) {
677 if (!world) return;
678 // Collect ids first so remove events fire consistently.
679 std::vector<int> ids = world->instanceOrder_;
680 for (int id : ids) removeBuilding(world, id);
681 world->buildings().clear();
682 world->instanceOrder_.clear();
683 for (auto &kv : world->allChannels()) {
684 std::fill(kv.second.begin(), kv.second.end(), 0);
685 }
686}
687
688} // namespace eve::building
float cx
Definition CardTypes.cpp:31
float cy
Definition CardTypes.cpp:32
std::string id
int y
Definition Grass.cpp:135
int x
Definition Grass.cpp:135
int h
int w
std::vector< Colorf > px
uint32_t c
const char * name
Definition RockMesh.cpp:21
SettlementPipeline::Stage fn
int d
int v
uint32_t s
Definition Weather.cpp:28
static const BuildingDefinition * find(const std::string &id)
float getElevation() const
Definition Ghost.h:30
int getCellY() const
Definition Ghost.h:24
float getWorldY() const
Definition Ghost.h:28
int getCellX() const
Definition Ghost.h:23
bool validate(PlacementWorld *world)
对当前姿态做校验,写入 valid_/reason_。
Definition Ghost.cpp:80
float getWorldX() const
Definition Ghost.h:27
float getRotationDeg() const
Definition Ghost.h:33
std::string getBuildingId() const
Definition Ghost.h:20
std::function< bool(const PlacementWorld &world, const PlacementQuery &q, std::string *reason)> ValidateFn
static SnapResult snap(const PlacementWorld &world, const std::string &buildingId, float worldX, float worldY)
static void effectiveFootprint(const BuildingDefinition &def, float rotationDeg, int *outW, int *outH)
旋转后的占地宽高(cardinal 90/270 交换)。
std::function< bool(const PlacementWorld &world, float x, float y, PlacementHit *hit)> SurfaceFn
static std::vector< std::string > surfaceNames()
static void registerSurface(const std::string &name, SurfaceFn fn)
static SnapResult snap3D(const PlacementWorld &world, const std::string &buildingId, float worldX, float worldY, float worldZ)
static PlacementSystem & inst()
Process singleton holding all PlacementSystem state.
static const std::vector< BuildingChangeEvent > & events()
static bool surfaceHit(const PlacementWorld &world, const std::string &name, float x, float y, PlacementHit *hit)
std::function< SnapResult(const PlacementWorld &world, float worldX, float worldY)> SnapFn
static void unregisterChangeHook(const std::string &name)
static void setPlaneSurfaceHeight(float h)
static void ensureBuiltins()
确保内置规则已注册(模块首次使用时自动调用)。
static bool hasSurface(const std::string &name)
std::function< void(const BuildingChangeEvent &ev)> ChangeHook
static void pushEvent(BuildingChangeEvent ev)
static bool foreachFootprintCell(const BuildingDefinition &def, int originCellX, int originCellY, float rotationDeg, const std::function< bool(int cx, int cy)> &fn)
枚举占地格子(旋转后局部 → 世界格子)。返回 false 若定义未知。
static float normalizeRotation(const std::string &buildingId, float rotationDeg)
规范化旋转角(cardinal → 0/90/180/270;none → 0)。
static bool hasSnapRule(const std::string &name)
static void unregisterSurface(const std::string &name)
static bool hasChangeHook(const std::string &name)
static void registerSnapRule(const std::string &name, SnapFn fn)
static bool hasValidateRule(const std::string &name)
static void pollEvents(std::vector< BuildingChangeEvent > &out)
static void unregisterValidateRule(const std::string &name)
static void unregisterSnapRule(const std::string &name)
static void registerChangeHook(const std::string &name, ChangeHook fn)
static SnapResult snapWithMode(const PlacementWorld &world, const std::string &mode, float worldX, float worldY)
static void registerValidateRule(const std::string &name, ValidateFn fn)
格子型建筑放置世界(脚本可直接操作)。
int worldToCellX(float worldX) const
世界像素 ↔ 格子坐标。
int getWidth() const
尺寸 / 格子大小 / 原点。
const std::unordered_map< int, PlacedBuilding > & buildings() const
const grid::GridConfig & getGrid() const
std::string getSnapMode() const
放置策略:吸附模式 / 校验规则。
std::string getId() const
世界 id(用于变更事件定位)。
float cellToWorldX(int cellX) const
float cellToWorldY(int cellY) const
int worldToCellY(float worldY) const
void cellToWorldPlane(int cellX, int cellY, float &px, float &py) const
const std::unordered_map< std::string, std::vector< int > > & allChannels() const
int getOccupantInChannel(const std::string &channel, int cellX, int cellY) const
std::vector< int > & channelOccupancy(const std::string &channel)
建筑放置模块入口:定义 / 放置世界 / 鬼影 / 变更事件的脚本绑定点。 设计文档:docs/dev/建筑放置系统设计.md
Definition Building.cpp:7
void worldToCell(const GridConfig &cfg, float px, float py, int &cx, int &cy, int mapW, int mapH)
void foreachRotatedFootprint(int w, int h, const std::vector< uint8_t > &mask, int steps, bool hexMode, const std::function< void(int lx, int ly)> &fn)
void rotatedFootprintSize(int w, int h, const std::vector< uint8_t > &mask, int steps, bool hexMode, int &outW, int &outH)
一次成功放置变更的事件(供脚本 poll / C++ hook)。
std::string action
place / remove / move / rotate
建筑模板(进程级注册表中的定义)。
std::string channel
占地格子尺寸。
std::string rotationMode
none | cardinal | free。
std::vector< uint8_t > footprintMask
可选占地掩码,长度 footprintW*footprintH;空表示实心矩形。行主序、原点在最小 x/y。
std::vector< std::string > tags
已放置的建筑实例。
std::vector< std::string > tags
校验上下文:传给可插拔规则。
int excludeInstanceId
move 时排除自身占用