Peoplemon  0.1.0
Peoplemon 3 game source documentation
EditMap.cpp
Go to the documentation of this file.
2 
3 #include "MapActions.hpp"
4 #include <BLIB/Cameras/2D/Camera2D.hpp>
5 #include <Core/Items/Item.hpp>
6 #include <Core/Resources.hpp>
12 
13 namespace editor
14 {
15 namespace component
16 {
17 namespace
18 {
19 const std::initializer_list<std::string_view> CollisionTileTextures{
20  "EditorResources/Collisions/none.png",
21  "EditorResources/Collisions/all.png",
22  "EditorResources/Collisions/top.png",
23  "EditorResources/Collisions/right.png",
24  "EditorResources/Collisions/bottom.png",
25  "EditorResources/Collisions/left.png",
26  "EditorResources/Collisions/topRight.png",
27  "EditorResources/Collisions/bottomRight.png",
28  "EditorResources/Collisions/bottomLeft.png",
29  "EditorResources/Collisions/topLeft.png",
30  "EditorResources/Collisions/topBottom.png",
31  "EditorResources/Collisions/leftRight.png",
32  "EditorResources/Collisions/noTop.png",
33  "EditorResources/Collisions/noRight.png",
34  "EditorResources/Collisions/noBottom.png",
35  "EditorResources/Collisions/noLeft.png",
36  "EditorResources/Collisions/water.png",
37  "EditorResources/Collisions/fall.png",
38  "EditorResources/Collisions/ledge.png"};
39 
40 const std::initializer_list<std::string_view> LevelTransitionTextures{
41  "EditorResources/LevelTransitions/horUpRight.png",
42  "EditorResources/LevelTransitions/horUpLeft.png",
43  "EditorResources/LevelTransitions/vertUpUp.png",
44  "EditorResources/LevelTransitions/vertUpDown.png"};
45 
46 unsigned int computePatchSize(unsigned int total, unsigned int maxPatch) {
47  if (total < maxPatch) { return total; }
48 
49  unsigned int patch = maxPatch;
50  while (total % patch > 0 && patch > 0) { --patch; }
51  return patch;
52 }
53 } // namespace
54 
55 EditMap::Ptr EditMap::create(const PositionCb& clickCb, const PositionCb& moveCb,
56  const ActionCb& actionCb, const ActionCb& syncCb,
57  core::system::Systems& systems) {
58  return Ptr(new EditMap(clickCb, moveCb, actionCb, syncCb, systems));
59 }
60 
61 EditMap::EditMap(const PositionCb& cb, const PositionCb& mcb, const ActionCb& actionCb,
62  const ActionCb& syncCb, core::system::Systems& s)
63 : bl::gui::Element()
64 , Map()
65 , historyHead(0)
66 , saveHead(0)
67 , clickCb(cb)
68 , moveCb(mcb)
69 , actionCb(actionCb)
70 , syncCb(syncCb)
71 , camera(nullptr)
72 , controlsEnabled(false)
73 , currentEventFillColor(0, 0, 0, 100)
74 , eventColorTime(0.f)
75 , renderOverlay(RenderOverlay::None)
76 , overlayLevel(0)
77 , nextItemId(0)
78 , setRenderTarget(false) {
79  systems = &s;
80 
81  static const auto callCb = [this, &s](const PositionCb& cb) {
82  static const float PixelRatio = 1.f / static_cast<float>(core::Properties::PixelsPerTile());
83 
84  const sf::Vector2i mouse = sf::Mouse::getPosition(s.engine().window().getSfWindow());
85  sf::Vector2f pixels(mouse);
86  pixels -= sf::Vector2f(getAcquisition().left, getAcquisition().top);
87 
88  rdr::EditMapComponent* com = dynamic_cast<rdr::EditMapComponent*>(getComponent());
89  if (com) {
90  bl::rc::RenderTarget* rt = com->getTarget();
91  if (rt) {
92  const glm::vec2 wpos = rt->transformToWorldSpace({pixels.x, pixels.y});
93  pixels.x = wpos.x;
94  pixels.y = wpos.y;
95  }
96  }
97 
98  const sf::Vector2i tiles(std::floor(pixels.x * PixelRatio),
99  std::floor(pixels.y * PixelRatio));
100  cb(pixels, tiles);
101 
102  for (unsigned int level = 0; level < levels.size(); ++level) {
104  bl::tmap::Position(level, glm::i32vec2(tiles.x, tiles.y), bl::tmap::Direction::Up));
105  }
106  };
107 
108  getSignal(bl::gui::Event::LeftClicked).willAlwaysCall([this](const bl::gui::Event&, Element*) {
109  if (controlsEnabled) { callCb(clickCb); }
110  });
111 
112  getSignal(bl::gui::Event::MouseMoved).willAlwaysCall([this](const bl::gui::Event&, Element*) {
113  callCb(moveCb);
114  });
115 
116  setOutlineThickness(0.f);
117 }
118 
120  if (townSquareBatch.exists()) {
121  townSquareBatch.destroy();
122  townSquares.clear();
123  }
124 }
125 
126 bool EditMap::editorLoad(const std::string& file) {
127  if (!doLoad(file)) {
128  doLoad(savefile);
129  return false;
130  }
131  savefile = file;
132  return true;
133 }
134 
136  if (save(savefile)) {
137  saveHead = historyHead;
138  return tileset->save(tilesetField);
139  }
140  return false;
141 }
142 
143 const std::string& EditMap::currentFile() const { return savefile; }
144 
145 void EditMap::newMap(const std::string& filename, const std::string& name,
146  const std::string& tileset, unsigned int width, unsigned int height) {
147  clear();
148  savefile = filename;
149  nameField = name;
151  levels.resize(1);
152  levels.front().init(width, height, 2, 2, 1);
153  transitionField.setSize(width, height, core::map::LevelTransition::None);
154  editorActivate();
155  syncCb();
156 }
157 
158 bool EditMap::doLoad(const std::string& file) {
159  clear();
160  if (!MapManager::initializeExisting(getMapFile(file), static_cast<Map&>(*this))) return false;
161  nextItemId = 0;
162  for (const auto& item : itemsField) {
163  if (nextItemId <= item.mapId) { nextItemId = item.mapId + 1; }
164  }
165  return editorActivate();
166 }
167 
168 bool EditMap::editorActivate() {
169  history.clear();
170  historyHead = 0;
171  saveHead = 0;
172 
173  systems->engine().ecs().destroyAllEntitiesWithFlags(bl::ecs::Flags::WorldObject);
174 
175  size = {static_cast<int>(levels.front().collisionLayer().width()),
176  static_cast<int>(levels.front().collisionLayer().height())};
177  bl::event::Dispatcher::dispatch<core::event::MapSwitch>({*this});
178 
179  tileset = TilesetManager::load(core::map::Tileset::getFullPath(tilesetField));
180  if (!tileset) return false;
181  tileset->activate(systems->engine());
182  Map::prepareRender();
183  if (getComponent()) { getComponent()->onElementUpdated(); }
184  camera = nullptr;
185 
186  weather.activate(*systems, *this);
189 
192 
193  if (!activated) {
194  activated = true;
196  }
197 
198  for (const core::map::CharacterSpawn& spawn : characterField) {
199  if (systems->entity().spawnCharacter(spawn, *this) == bl::ecs::InvalidEntity) {
200  BL_LOG_WARN << "Failed to spawn character: " << spawn.file;
201  }
202  }
203 
204  for (const core::map::Item& item : itemsField) { systems->entity().spawnItem(item, *this); }
205 
206  bl::event::Dispatcher::dispatch<core::event::MapEntered>({*this});
207 
208  levelFilter.clear();
209  levelFilter.resize(levels.size(), true);
210  layerFilter.clear();
211  layerFilter.resize(levels.size());
212  for (unsigned int i = 0; i < levels.size(); ++i) {
213  layerFilter[i].resize(levels[i].layerCount(), true);
214  }
215 
216  actionCb();
217 
218  return true;
219 }
220 
221 bool EditMap::unsavedChanges() const { return saveHead != historyHead; }
222 
223 void EditMap::update(float dt) {
224  Map::update(dt);
225  if (scene && !camera) {
226  rdr::EditMapComponent* com = dynamic_cast<rdr::EditMapComponent*>(getComponent());
227  if (com) {
228  bl::rc::RenderTarget* rt = com->getTarget();
229  if (rt) {
230  bl::cam::Camera2D* cam = rt->setCamera<bl::cam::Camera2D>(
231  glm::vec2{sizePixels().x, sizePixels().y},
232  glm::vec2{getAcquisition().width, getAcquisition().height});
233  camera = cam->setController<EditCameraController>(this);
234  camera->enabled = controlsEnabled;
235  camera->reset(sizeTiles());
236  setupOverlay();
237  }
238  }
239  }
240  if (!setRenderTarget) {
241  rdr::EditMapComponent* com = dynamic_cast<rdr::EditMapComponent*>(getComponent());
242  if (com) {
243  bl::rc::RenderTarget* rt = com->getTarget();
244  if (rt) {
246  setRenderTarget = true;
247  }
248  }
249  }
250 
251  const sf::Color newEventColor = nextEventFillColor(dt);
252  if (currentEventFillColor != newEventColor) {
253  currentEventFillColor = newEventColor;
254  for (auto& rect : eventsOverlay) { rect.setFillColor(newEventColor); }
255  }
256 
257  if (exportState.exportComplete) {
258  exportState.exportComplete = false;
259 
260  // reset render overlay and selection
261  setRenderOverlay(exportState.prevRenderOverlay, exportState.prevOverlayLevel);
262  showSelection(exportState.prevSelection);
263 
264  // reset layer visibility
265  for (unsigned int level = 0; level < levels.size(); ++level) {
266  for (unsigned int layer = 0; layer < levels[level].layerCount(); ++layer) {
267  updateLayerVisibility(
268  level, layer, !levelFilter[level] || !layerFilter[level][layer]);
269  }
270  }
271 
272  // reset lighting
273  lighting.setAmbientLevel(exportState.prevAmbientLightLow, exportState.prevAmbientLightHigh);
274 
275  // reset entities
276  if (exportState.entitiesHidden) {
277  systems->engine().ecs().getAllComponents<core::component::Renderable>().forEach(
278  [](bl::ecs::Entity, core::component::Renderable& rc) { rc.setHidden(false); });
279  }
280 
281  // reset camera and controls
282  camera->zoomAmount = exportState.prevZoom;
283  camera->getCamera().setCenter(exportState.prevCenter);
284  controlsEnabled = exportState.prevEnabled;
285  bl::event::Dispatcher::dispatch<event::MapRenderCompleted>({});
286 
287  // release resources
288  exportState.renderTexture.release();
289  }
290 }
291 
293  controlsEnabled = e;
294  if (camera) { camera->enabled = controlsEnabled; }
295 }
296 
297 void EditMap::setLevelVisible(unsigned int level, bool v) {
298  levelFilter[level] = v;
299 
300  auto it = renderLevels.begin();
301  std::advance(it, level);
302  auto& rl = *it;
303 
304  unsigned int layer = 0;
305  for (auto zone : rl.zones) {
306  const bool hide = !v || !layerFilter[level][layer];
307 
308  updateLayerVisibility(level, layer, hide);
309  ++layer;
310  }
311 }
312 
313 void EditMap::setLayerVisible(unsigned int level, unsigned int layer, bool v) {
314  layerFilter[level][layer] = v;
315  updateLayerVisibility(level, layer, !v || !levelFilter[level]);
316 }
317 
318 void EditMap::updateLayerVisibility(unsigned int level, unsigned int layer, bool hide) {
319  auto it = renderLevels.begin();
320  std::advance(it, level);
321  auto& rl = *it;
322 
323  auto* zone = rl.zones[layer];
324  zone->tileSprites.setHidden(hide);
325  zone->tileAnims.setHidden(hide);
326 
327  auto& tileLayer = levels[level].getLayer(layer);
328  for (unsigned int x = 0; x < size.x; ++x) {
329  for (unsigned int y = 0; y < size.y; ++y) {
330  auto& tile = tileLayer.getRef(x, y);
331  auto* anim = std::get_if<std::shared_ptr<bl::gfx::Animation2D>>(&tile.renderObject);
332  if (anim) { (*anim)->setHidden(hide); }
333  }
334  }
335 }
336 
337 void EditMap::setRenderOverlay(RenderOverlay ro, unsigned int l) {
338  if (exportState.exportInProgress && ro != RenderOverlay::None) { return; }
339 
340  if (renderOverlay != ro || overlayLevel != l) {
341  renderOverlay = ro;
342  overlayLevel = l;
343 
344  townSquareBatch.setHidden(renderOverlay != RenderOverlay::Towns);
345 
346  const bool hideSpawns = renderOverlay != RenderOverlay::Spawns;
347  for (auto& spawn : spawnSprites) {
348  spawn.second.arrow.setHidden(hideSpawns);
349  spawn.second.label.setHidden(hideSpawns);
350  }
351 
352  unsigned int level = 0;
353  for (auto& catchLayer : catchTileOverlay) {
354  catchLayer.shapeBatch.setHidden(renderOverlay != RenderOverlay::CatchTiles ||
355  overlayLevel != level);
356  ++level;
357  }
358 
359  level = 0;
360  for (auto& colLayer : collisionTileOverlay) {
361  colLayer.batch.setHidden(renderOverlay != RenderOverlay::Collisions ||
362  overlayLevel != level);
363  ++level;
364  }
365 
366  levelTransitionsOverlay.value().batch.setHidden(renderOverlay !=
368 
369  const bool hideEvents = renderOverlay != RenderOverlay::Events;
370  for (auto& eventRect : eventsOverlay) { eventRect.setHidden(hideEvents); }
371  }
372 }
373 
374 void EditMap::showGrid(bool s) { grid.setHidden(!s); }
375 
376 void EditMap::showSelection(const sf::IntRect& s) {
377  selection = s;
378 
379  const float PixelsPerTile = core::Properties::PixelsPerTile();
380  selectRect.getTransform().setPosition(s.left * PixelsPerTile, s.top * PixelsPerTile);
381  selectRect.setHidden(s.width == 0);
382  if (s.width > 0) {
383  selectRect.setFillColor(sf::Color(180, 160, 20, 165));
384  selectRect.scaleToSize(glm::vec2(s.width * PixelsPerTile, s.height * PixelsPerTile));
385  }
386  else {
387  selectRect.scaleToSize({PixelsPerTile, PixelsPerTile});
388  selectRect.setFillColor(sf::Color(20, 70, 220, 165));
389  }
390 }
391 
393  const auto cleanLayer = [this, id, anim](core::map::TileLayer& layer,
394  unsigned int levelIndex,
395  unsigned int layerIndex) -> bool {
396  bool mod = false;
397  for (unsigned int x = 0; x < layer.width(); ++x) {
398  for (unsigned int y = 0; y < layer.height(); ++y) {
399  auto& tile = layer.getRef(x, y);
400  if (tile.id() == id && tile.isAnimation() == anim) {
401  mod = true;
402  tile.set(core::map::Tile::Blank, false);
403  setupTile(levelIndex, layerIndex, {x, y});
404  }
405  }
406  }
407  return mod;
408  };
409 
410  unsigned int levelIndex = 0;
411  for (auto& level : levels) {
412  unsigned int layerIndex = 0;
413  for (auto& layer : level.bottomLayers()) {
414  cleanLayer(layer, levelIndex, layerIndex);
415  ++layerIndex;
416  }
417  for (auto& layer : level.ysortLayers()) {
418  cleanLayer(layer, levelIndex, layerIndex);
419  ++layerIndex;
420  }
421  for (auto& layer : level.topLayers()) {
422  cleanLayer(layer, levelIndex, layerIndex);
423  ++layerIndex;
424  }
425 
426  ++levelIndex;
427  }
428 }
429 
430 EditMap::EditCameraController::EditCameraController(EditMap* owner)
431 : enabled(false)
432 , owner(owner) {}
433 
434 void EditMap::EditCameraController::update(float dt) {
435  if (enabled) {
436  float PixelsPerSecond =
437  0.5f * zoomAmount * static_cast<float>(core::Properties::WindowWidth());
438  constexpr float ZoomPerSecond = 1.f;
439 
440  if (sf::Keyboard::isKeyPressed(sf::Keyboard::Space)) { PixelsPerSecond *= 5.f; }
441 
442  if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up) ||
443  sf::Keyboard::isKeyPressed(sf::Keyboard::W)) {
444  camera().move({0.f, -PixelsPerSecond * dt});
445  }
446  if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right) ||
447  sf::Keyboard::isKeyPressed(sf::Keyboard::D)) {
448  camera().move({PixelsPerSecond * dt, 0.f});
449  }
450  if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down) ||
451  sf::Keyboard::isKeyPressed(sf::Keyboard::S)) {
452  camera().move({0.f, PixelsPerSecond * dt});
453  }
454  if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left) ||
455  sf::Keyboard::isKeyPressed(sf::Keyboard::A)) {
456  camera().move({-PixelsPerSecond * dt, 0.f});
457  }
458  if (sf::Keyboard::isKeyPressed(sf::Keyboard::C)) { zoom(-ZoomPerSecond * dt); }
459  if (sf::Keyboard::isKeyPressed(sf::Keyboard::V)) { zoom(ZoomPerSecond * dt); }
460  }
461 
462  // always constrain
463  const float w = camera().getSize().x;
464  const float h = camera().getSize().y;
465  glm::vec2 position = camera().getCenter();
466  if (mapSize.x > w) {
467  if (position.x - w * 0.5f < 0.f) { position.x = w * 0.5f; }
468  if (position.x + w * 0.5f > mapSize.x) { position.x = mapSize.x - w * 0.5f; }
469  }
470  else { position.x = mapSize.x * 0.5f; }
471  if (mapSize.y > h) {
472  if (position.y - h * 0.5f < 0.f) { position.y = h * 0.5f; }
473  if (position.y + h * 0.5f > mapSize.y) { position.y = mapSize.y - h * 0.5f; }
474  }
475  else { position.y = mapSize.y * 0.5f; }
476  camera().setCenter(position);
477 
478  // always set size
479  const sf::FloatRect& acq = owner->getAcquisition();
480  camera().setSize(glm::vec2(acq.width, acq.height) * zoomAmount);
481 }
482 
483 void EditMap::EditCameraController::reset(const sf::Vector2i& t) {
484  mapSize = glm::vec2(t.x, t.y) * static_cast<float>(core::Properties::PixelsPerTile());
485  camera().setCenter(mapSize * 0.5f);
486  updateDepthPlanes();
487  zoomAmount = 1.5f;
488 }
489 
490 void EditMap::EditCameraController::zoom(float z) {
491  zoomAmount += z;
492  if (zoomAmount < 0.1f) { zoomAmount = 0.1f; }
493 }
494 
495 void EditMap::EditCameraController::updateDepthPlanes() {
496  camera().setNearAndFarPlanes(-owner->getMinDepth(), 0.f);
497 }
498 
499 sf::Vector2f EditMap::minimumRequisition() const { return {100.f, 100.f}; }
500 
501 bl::gui::rdr::Component* EditMap::doPrepareRender(bl::gui::rdr::Renderer& renderer) {
502  return renderer.createComponent<EditMap>(*this);
503 }
504 
505 bool EditMap::handleScroll(const bl::gui::Event& event) {
506  const bool c = getAcquisition().contains(event.mousePosition());
507  if (controlsEnabled && c) { camera->zoom(-event.scrollDelta() * 0.1f); }
508  return c;
509 }
510 
512  if (!history.empty() && historyHead > 0) {
513  if (history[historyHead - 1]->undo(*this)) { syncCb(); }
514  --historyHead;
515  actionCb();
516  }
517 }
518 
519 const char* EditMap::undoDescription() const {
520  if (!history.empty() && historyHead > 0) return history[historyHead - 1]->description();
521  return nullptr;
522 }
523 
525  if (historyHead != history.size()) {
526  if (history[historyHead]->apply(*this)) { syncCb(); }
527  ++historyHead;
528  actionCb();
529  }
530 }
531 
532 const char* EditMap::redoDescription() const {
533  if (historyHead != history.size()) return history[historyHead]->description();
534  return nullptr;
535 }
536 
537 void EditMap::addAction(const Action::Ptr& a) {
538  if (historyHead < history.size()) {
539  history.erase(history.begin() + historyHead, history.end());
540  }
541 
542  history.emplace_back(a);
543  if (history.back()->apply(*this)) { syncCb(); }
544  historyHead = history.size();
545  actionCb();
546 }
547 
548 void EditMap::setName(const std::string& n) { addAction(SetNameAction::create(n, *this)); }
549 
550 void EditMap::setPlaylist(const std::string& playlist) {
551  addAction(SetPlaylistAction::create(playlist, *this));
552 }
553 
555  addAction(SetWeatherAction::create(type, *this));
556 }
557 
558 void EditMap::setTile(unsigned int level, unsigned int layer, const sf::Vector2i& pos,
559  core::map::Tile::IdType id, bool isAnim) {
560  addAction(SetTileAction::create(level, layer, pos, isAnim, id, *this));
561 }
562 
563 void EditMap::setTileArea(unsigned int level, unsigned int layer, const sf::IntRect& area,
564  core::map::Tile::IdType value, bool isAnim) {
565  addAction(SetTileAreaAction::create(level, layer, area, isAnim, value, *this));
566 }
567 
568 void EditMap::fillTile(unsigned int level, unsigned int layer, const sf::Vector2i& pos,
569  core::map::Tile::IdType id, bool isAnim) {
570  addAction(FillTileAction::create(level, layer, pos, id, isAnim, *this));
571 }
572 
573 void EditMap::setCollision(unsigned int level, const sf::Vector2i& pos, core::map::Collision val) {
574  addAction(SetCollisionAction::create(level, pos, val, *this));
575 }
576 
577 void EditMap::setCollisionArea(unsigned int level, const sf::IntRect& area,
578  core::map::Collision val) {
579  addAction(SetCollisionAreaAction::create(level, area, val, *this));
580 }
581 
582 void EditMap::fillCollision(unsigned int level, const sf::Vector2i& pos, core::map::Collision col) {
583  addAction(FillCollisionAction::create(level, pos, col, *this));
584 }
585 
586 void EditMap::setCatch(unsigned int level, const sf::Vector2i& pos, std::uint8_t value) {
587  addAction(SetCatchAction::create(level, pos, value, *this));
588 }
589 
590 void EditMap::setCatchArea(unsigned int level, const sf::IntRect& area, std::uint8_t value) {
591  addAction(SetCatchAreaAction::create(level, area, value, *this));
592 }
593 
594 void EditMap::fillCatch(unsigned int level, const sf::Vector2i& pos, std::uint8_t id) {
595  addAction(FillCatchAction::create(level, pos, id, *this));
596 }
597 
598 void EditMap::appendBottomLayer(unsigned int level) {
600 }
601 
602 void EditMap::appendYsortLayer(unsigned int level) {
604 }
605 
606 void EditMap::appendTopLayer(unsigned int level) {
608 }
609 
610 void EditMap::removeLayer(unsigned int level, unsigned int layer) {
611  addAction(RemoveLayerAction::create(level, layer, *this));
612 }
613 
614 void EditMap::shiftLayer(unsigned int level, unsigned int layer, bool up) {
615  addAction(ShiftLayerAction::create(level, layer, up));
616 }
617 
618 void EditMap::shiftLevel(unsigned int level, bool up) {
619  addAction(ShiftLevelAction::create(level, up));
620 }
621 
623 
624 void EditMap::setOnEnterScript(const std::string& s) {
625  addAction(SetScriptAction::create(true, s, loadScriptField));
626 }
627 
628 const std::string& EditMap::getOnEnterScript() const { return loadScriptField; }
629 
630 void EditMap::setOnExitScript(const std::string& s) {
631  addAction(SetScriptAction::create(false, s, unloadScriptField));
632 }
633 
634 const std::string& EditMap::getOnExitScript() const { return unloadScriptField; }
635 
636 bool EditMap::spawnIdUnused(unsigned int i) const { return spawns.find(i) == spawns.end(); }
637 
638 void EditMap::addSpawn(unsigned int l, const sf::Vector2i& pos, unsigned int id,
639  bl::tmap::Direction dir) {
640  addAction(AddSpawnAction::create(l, pos, id, dir));
641 }
642 
643 void EditMap::rotateSpawn(unsigned int l, const sf::Vector2i& pos) {
644  const glm::i32vec2 gpos(pos.x, pos.y);
645  for (const auto& pair : spawns) {
646  if (pair.second.position.level == l) {
647  if (pair.second.position.position == gpos) {
648  addAction(RotateSpawnAction::create(pair.first));
649  }
650  }
651  }
652 }
653 
654 void EditMap::removeSpawn(unsigned int l, const sf::Vector2i& pos) {
655  const glm::i32vec2 gpos(pos.x, pos.y);
656  for (const auto& pair : spawns) {
657  if (pair.second.position.level == l) {
658  if (pair.second.position.position == gpos) {
659  addAction(RemoveSpawnAction::create(pair.first, pair.second));
660  }
661  }
662  }
663 }
664 
666  addAction(AddNpcSpawnAction::create(s, characterField.size()));
667 }
668 
670  const sf::Vector2i& pos) const {
671  const glm::i32vec2 gpos(pos.x, pos.y);
672  for (const auto& spawn : characterField) {
673  if (spawn.position.level == level) {
674  if (spawn.position.position == gpos) { return &spawn; }
675  }
676  }
677  return nullptr;
678 }
679 
681  const core::map::CharacterSpawn& val) {
682  const bl::ecs::Entity e = systems->position().getEntity(orig->position);
683  if (e != bl::ecs::InvalidEntity) {
684  addAction(EditNpcSpawnAction::create(orig - characterField.data(), *orig, val));
685  }
686  else {
687  const auto& pos = orig->position.position;
688  BL_LOG_WARN << "Failed to get entity id at location: (" << pos.x << ", " << pos.y << ")";
689  }
690 }
691 
693  unsigned int i = 0;
694  for (; i < characterField.size(); ++i) {
695  if (&characterField[i] == s) { break; }
696  }
697  const bl::ecs::Entity e = systems->position().getEntity(s->position);
698  if (e != bl::ecs::InvalidEntity) { addAction(RemoveNpcSpawnAction::create(*s, i)); }
699  else {
700  const auto& pos = s->position.position;
701  BL_LOG_WARN << "Failed to get entity id at location: (" << pos.x << ", " << pos.y << ")";
702  }
703 }
704 
706  exportState.exportInProgress = true;
707  exportState.exportComplete = false;
708  exportState.outputPath = params.outputPath();
709 
710  // unhide all layers
711  for (unsigned int level = 0; level < levels.size(); ++level) {
712  for (unsigned int layer = 0; layer < levels[level].layerCount(); ++layer) {
713  updateLayerVisibility(level, layer, false);
714  }
715  }
716 
717  // hide all overlays
718  exportState.prevRenderOverlay = renderOverlay;
719  exportState.prevOverlayLevel = overlayLevel;
721 
722  // hide selection
723  exportState.prevSelection = selection;
724  showSelection({0, 0, 0, 0});
725 
726  // set lighting
727  exportState.prevAmbientLightLow = lighting.getMinLightLevel();
728  exportState.prevAmbientLightHigh = lighting.getMaxLightLevel();
729  lighting.setAmbientLevel(params.lightLevel(), params.lightLevel());
730 
731  // create render texture
732  const sf::Vector2u sp(size * core::Properties::PixelsPerTile());
733  const unsigned int ms = systems->engine()
734  .renderer()
735  .vulkanState()
736  .physicalDeviceProperties.limits.maxImageDimension2D;
737  exportState.size = glm::u32vec2(computePatchSize(sp.x, ms), computePatchSize(sp.y, ms));
738  exportState.center = glm::u32vec2(exportState.size.x / 2, exportState.size.y / 2);
739  exportState.renderTexture = systems->engine().renderer().createRenderTexture(exportState.size);
740  exportState.renderTexture->pushScene(scene);
741  exportState.camera = exportState.renderTexture->setCamera<bl::cam::Camera2D>(
742  glm::vec2(exportState.center), glm::vec2(exportState.size));
743  exportState.camera->setNearAndFarPlanes(-getMinDepth(), 0.f);
744 
745  // hide all entities if required
746  exportState.entitiesHidden = !params.renderCharacters();
747  if (exportState.entitiesHidden) {
748  systems->engine().ecs().getAllComponents<core::component::Renderable>().forEach(
749  [](bl::ecs::Entity, core::component::Renderable& rc) { rc.setHidden(true); });
750  }
751 
752  // show entire map
753  const glm::vec2 spf(sp.x, sp.y);
754  exportState.prevCenter = camera->getCamera().getCenter();
755  exportState.prevZoom = camera->zoomAmount;
756  camera->getCamera().setCenter(spf * 0.5f);
757  camera->zoomAmount = std::max(spf.x / getAcquisition().width, spf.y / getAcquisition().height);
758 
759  // disable all controls
760  exportState.prevEnabled = controlsEnabled;
761  controlsEnabled = false;
762  bl::event::Dispatcher::dispatch<event::MapRenderStarted>({});
763 
764  // start initial export (deferred for descriptors to update)
765  systems->engine().renderer().vulkanState().cleanupManager.add([this]() {
766  exportState.exportJob = systems->engine().renderer().textureExporter().exportTexture(
767  exportState.renderTexture->getTexture());
768  systems->engine().renderer().vulkanState().cleanupManager.add([this]() {
769  systems->engine().longRunningThreadpool().queueTask([this]() { exportRendering(); });
770  });
771  });
772 }
773 
774 void EditMap::createEvent(const core::map::Event& event) {
775  addAction(AddEventAction::create(event, eventsField.size()));
776 }
777 
778 const core::map::Event* EditMap::getEvent(const sf::Vector2i& tiles) {
779  for (const core::map::Event& event : eventsField) {
780  const sf::IntRect area(event.position, event.areaSize);
781  if (area.contains(tiles)) return &event;
782  }
783  return nullptr;
784 }
785 
786 void EditMap::editEvent(const core::map::Event* orig, const core::map::Event& val) {
787  unsigned int i = 0;
788  while (&eventsField[i] != orig) { ++i; }
789  addAction(EditEventAction::create(*orig, val, i));
790 }
791 
792 void EditMap::removeEvent(const core::map::Event* e) {
793  unsigned int i = 0;
794  while (&eventsField[i] != e) { ++i; }
795  addAction(RemoveEventAction::create(*e, i));
796 }
797 
798 void EditMap::addOrEditItem(unsigned int level, const sf::Vector2i& tiles, core::item::Id item,
799  bool visible) {
800  unsigned int i = 0;
801  bool found = false;
802  for (; i < itemsField.size(); ++i) {
803  if (itemsField[i].level == level) {
804  if (itemsField[i].position == tiles) {
805  found = true;
806  break;
807  }
808  }
809  }
810  if (!found) i = itemsField.size();
811  addAction(AddOrEditItemAction::create(
812  i, level, tiles, item, visible, found ? itemsField[i] : core::map::Item(), !found));
813 }
814 
815 std::pair<core::item::Id, bool> EditMap::getItem(unsigned int level, const sf::Vector2i& tiles) {
816  for (const auto& item : itemsField) {
817  if (item.level == level && item.position == tiles) {
818  return {core::item::Item::cast(item.id), item.visible};
819  }
820  }
821  return {core::item::Id::Unknown, true};
822 }
823 
824 void EditMap::removeItem(unsigned int level, const sf::Vector2i& tiles) {
825  unsigned int i = 0;
826  for (const auto& item : itemsField) {
827  if (item.level == level && item.position == tiles) {
828  addAction(RemoveItemAction::create(i, level, tiles, item));
829  break;
830  }
831  ++i;
832  }
833 }
834 
835 void EditMap::setLight(const sf::Vector2i& pos, unsigned int rad) {
836  const core::map::LightingSystem::Handle h = lighting.getClosestLight(pos);
837  const core::map::Light orig =
838  h != core::map::LightingSystem::None ? lighting.getLight(h) : core::map::Light(rad, pos);
839  addAction(SetLightAction::create(pos, rad, orig));
840 }
841 
842 void EditMap::removeLight(const sf::Vector2i& pos) {
843  const core::map::LightingSystem::Handle h = lighting.getClosestLight(pos);
845  addAction(RemoveLightAction::create(lighting.getLight(h)));
846  }
847 }
848 
849 void EditMap::addCatchRegion() { addAction(AddCatchRegionAction::create()); }
850 
851 const std::vector<core::map::CatchRegion>& EditMap::catchRegions() const {
852  return catchRegionsField;
853 }
854 
855 void EditMap::editCatchRegion(std::uint8_t index, const core::map::CatchRegion& value) {
856  addAction(EditCatchRegionAction::create(index, value, catchRegionsField[index]));
857 }
858 
859 void EditMap::removeCatchRegion(std::uint8_t index) {
860  addAction(RemoveCatchRegionAction::create(index, catchRegionsField[index]));
861 }
862 
863 void EditMap::setAmbientLight(std::uint8_t lower, std::uint8_t upper, bool sun) {
864  addAction(SetAmbientLightAction::create(sun, upper, lower, lightingSystem()));
865 }
866 
867 void EditMap::addTown() { addAction(AddTownAction::create()); }
868 
869 void EditMap::editTown(std::uint8_t i, const core::map::Town& town) {
870  addAction(EditTownAction::create(i, towns[i], town));
871 }
872 
873 void EditMap::removeTown(std::uint8_t i) { addAction(RemoveTownAction::create(i, towns[i])); }
874 
875 void EditMap::setTownTile(const sf::Vector2i& pos, std::uint8_t id) {
876  addAction(SetTownTileAction::create(pos, id, townTiles(pos.x, pos.y)));
877 }
878 
879 void EditMap::setTownTileArea(const sf::IntRect& area, std::uint8_t id) {
880  addAction(SetTownTileAreaAction::create(area, id, *this));
881 }
882 
883 void EditMap::fillTownTiles(const sf::Vector2i& pos, std::uint8_t id) {
884  addAction(FillTownTileAction::create(pos, id, *this));
885 }
886 
887 void EditMap::setLevelTile(const sf::Vector2i& pos, core::map::LevelTransition lt) {
888  addAction(SetLevelTileAction::create(pos, lt, transitionField(pos.x, pos.y)));
889 }
890 
891 void EditMap::setLevelTileArea(const sf::IntRect& area, core::map::LevelTransition lt) {
892  addAction(SetLevelTileAreaAction::create(area, lt, *this));
893 }
894 
895 void EditMap::setupOverlay() {
896  BL_LOG_INFO << "Preparing map editor overlays...";
897  const float PixelsPerTile = core::Properties::PixelsPerTile();
898 
899  // towns
900  BL_LOG_INFO << "Generating town geometry...";
901  if (townSquareBatch.exists()) {
902  townSquareBatch.destroy();
903  townSquares.clear();
904  }
905  townSquareBatch.create(systems->engine(), size.x * size.y * 4);
906  townSquares.setSize(size.x, size.y);
907  townSquareBatch.getTransform().setDepth(getMinDepth());
908  for (int x = 0; x < size.x; ++x) {
909  for (int y = 0; y < size.y; ++y) {
910  auto& square = townSquares(x, y);
911  square.create(systems->engine(), townSquareBatch, {PixelsPerTile, PixelsPerTile});
912  square.setFillColor(page::Towns::getColor(townTiles(x, y)));
913  square.setOutlineColor(sf::Color::Black);
914  square.setOutlineThickness(-1.f);
915  square.getLocalTransform().setPosition(x * PixelsPerTile, y * PixelsPerTile);
916  square.commit();
917  }
918  }
919  townSquareBatch.component().setContainsTransparency(true);
920  townSquareBatch.addToScene(scene, bl::rc::UpdateSpeed::Static);
921  townSquareBatch.setHidden(renderOverlay != RenderOverlay::Towns);
922 
923  // spawns
924  BL_LOG_INFO << "Generating spawn icons...";
925  spawnSprites.clear();
926  for (auto& spawn : spawns) { addSpawnGfx(spawn.second); }
927 
928  // catch tiles
929  BL_LOG_INFO << "Generating catch tile geometry...";
930  catchTileOverlay.clear();
931  for (unsigned int level = 0; level < levels.size(); ++level) {
932  auto& overlay = catchTileOverlay.emplace_back();
933  overlay.shapeBatch.create(systems->engine(), size.x * size.y * 4);
934  overlay.shapeBatch.getTransform().setDepth(getMinDepth());
935  overlay.tiles.setSize(size.x, size.y);
936  for (unsigned int x = 0; x < size.x; ++x) {
937  for (unsigned int y = 0; y < size.y; ++y) {
938  auto& tile = overlay.tiles(x, y);
939  tile.create(systems->engine(), overlay.shapeBatch, {PixelsPerTile, PixelsPerTile});
940  tile.getLocalTransform().setPosition(x * PixelsPerTile, y * PixelsPerTile);
941  tile.setOutlineColor(sf::Color::Black);
942  tile.setOutlineThickness(-1.f);
943  tile.setFillColor(page::Catchables::getColor(levels[level].catchLayer().get(x, y)));
944  tile.commit();
945  }
946  }
947  overlay.shapeBatch.component().setContainsTransparency(true);
948  overlay.shapeBatch.addToScene(scene, bl::rc::UpdateSpeed::Static);
949  overlay.shapeBatch.setHidden(renderOverlay != RenderOverlay::CatchTiles ||
950  overlayLevel != level);
951  }
952 
953  // stitched textures
954  if (!collisionTilesTexture) { stitchOverlayTextures(); }
955 
956  // collisions
957  BL_LOG_INFO << "Generating collision tile geometry...";
958  collisionTileOverlay.clear();
959  for (unsigned int level = 0; level < levels.size(); ++level) {
960  auto& overlay = collisionTileOverlay.emplace_back();
961  overlay.batch.create(systems->engine(), collisionTilesTexture, size.x * size.y);
962  overlay.batch.getTransform().setDepth(getMinDepth());
963  overlay.tiles.setSize(size.x, size.y);
964  for (unsigned int x = 0; x < size.x; ++x) {
965  for (unsigned int y = 0; y < size.y; ++y) {
966  auto& tile = overlay.tiles(x, y);
967  tile.create(systems->engine(), overlay.batch, {0.f, 0.f, 32.f, 32.f});
968  tile.getLocalTransform().setPosition(x * PixelsPerTile, y * PixelsPerTile);
969  updateCollisionTileTexture(level, x, y);
970  }
971  }
972  overlay.batch.component().setContainsTransparency(true);
973  overlay.batch.addToScene(scene, bl::rc::UpdateSpeed::Static);
974  overlay.batch.setHidden(renderOverlay != RenderOverlay::Collisions ||
975  overlayLevel != level);
976  }
977 
978  // level transitions
979  BL_LOG_INFO << "Generating level transition geometry...";
980  levelTransitionsOverlay.reset();
981  auto& ltOverlay = levelTransitionsOverlay.emplace();
982  ltOverlay.batch.create(systems->engine(), levelTransitionsTexture, size.x * size.y);
983  ltOverlay.batch.getTransform().setDepth(getMinDepth());
984  ltOverlay.tiles.setSize(size.x, size.y);
985  for (unsigned int x = 0; x < size.x; ++x) {
986  for (unsigned int y = 0; y < size.y; ++y) {
987  auto& tile = ltOverlay.tiles(x, y);
988  tile.create(systems->engine(), ltOverlay.batch, {0.f, 0.f, 32.f, 32.f});
989  tile.getLocalTransform().setPosition(x * PixelsPerTile, y * PixelsPerTile);
990  updateLevelTransitionTexture(x, y);
991  }
992  }
993  ltOverlay.batch.component().setContainsTransparency(true);
994  ltOverlay.batch.addToScene(scene, bl::rc::UpdateSpeed::Static);
995  ltOverlay.batch.setHidden(renderOverlay != RenderOverlay::LevelTransitions);
996 
997  // events
998  BL_LOG_INFO << "Generating events geometry...";
999  eventsOverlay.clear();
1000  for (unsigned int i = 0; i < eventsField.size(); ++i) { addEventGfx(i); }
1001 
1002  // selection
1003  if (!selectRect.exists()) { selectRect.create(systems->engine(), {100.f, 100.f}); }
1004  selectRect.getTransform().setDepth(getMinDepth());
1005  selectRect.component().setContainsTransparency(true);
1006  selectRect.setHidden(true);
1007  selectRect.addToScene(scene, bl::rc::UpdateSpeed::Static);
1008 
1009  // grid
1010  BL_LOG_INFO << "Generating grid geometry...";
1011  constexpr glm::vec4 Black(0.f, 0.f, 0.f, 1.f);
1012  if (grid.exists()) { grid.resize((size.x + size.y + 2) * 2, false); }
1013  else {
1014  grid.create(systems->engine(), (size.x + size.y + 2) * 2);
1015  grid.setHidden(true);
1016  }
1017  for (int x = 0; x <= size.x * 2; x += 2) {
1018  grid[x].color = Black;
1019  grid[x + 1].color = Black;
1020  grid[x].pos = glm::vec3(x / 2 * core::Properties::PixelsPerTile(), 0.f, 0.f);
1021  grid[x + 1].pos = glm::vec3(x / 2 * core::Properties::PixelsPerTile(), sizePixels().y, 0.f);
1022  }
1023  const int o = size.x * 2;
1024  for (int y = 0; y <= size.y * 2; y += 2) {
1025  grid[y + o].color = Black;
1026  grid[y + 1 + o].color = Black;
1027  grid[y + o].pos = glm::vec3(0.f, y / 2 * core::Properties::PixelsPerTile(), 0.f);
1028  grid[y + 1 + o].pos =
1029  glm::vec3(sizePixels().x, y / 2 * core::Properties::PixelsPerTile(), 0.f);
1030  }
1031  grid.component().setContainsTransparency(true);
1032  grid.getTransform().setDepth(getMinDepth());
1033  grid.commit();
1034  grid.addToSceneWithCustomPipeline(
1035  scene, bl::rc::UpdateSpeed::Static, bl::rc::Config::PipelineIds::Lines2D);
1036 
1037  BL_LOG_INFO << "Map editor overlays initialized";
1038 }
1039 
1040 void EditMap::addSpawnGfx(const core::map::Spawn& spawn) {
1041  const float PixelsPerTile = core::Properties::PixelsPerTile();
1042  auto spawnArrow =
1043  systems->engine().renderer().texturePool().getOrLoadTexture("EditorResources/arrow.png");
1044 
1045  auto& gfx = spawnSprites.try_emplace(spawn.id).first->second;
1046  gfx.arrow.create(systems->engine(), spawnArrow);
1047  gfx.arrow.getTransform().setOrigin(spawnArrow->size() * 0.5f);
1048  gfx.arrow.getTransform().setPosition(spawn.position.getWorldPosition(PixelsPerTile) +
1049  glm::vec2(PixelsPerTile, PixelsPerTile) * 0.5f);
1050  gfx.arrow.getTransform().setRotation(90.f * static_cast<float>(spawn.position.direction));
1051  gfx.arrow.component().setContainsTransparency(true);
1052  gfx.arrow.getTransform().setDepth(getMinDepth() + 0.1f);
1053  gfx.arrow.setHidden(renderOverlay != RenderOverlay::Spawns);
1054  gfx.arrow.addToScene(scene, bl::rc::UpdateSpeed::Static);
1055 
1056  gfx.label.create(systems->engine(),
1058  std::to_string(spawn.id),
1059  24,
1060  sf::Color::Red);
1061  gfx.label.getSection().setOutlineColor(sf::Color::Black);
1062  gfx.label.getSection().setOutlineThickness(1.f);
1063  gfx.label.getTransform().setOrigin(gfx.label.getLocalSize() * 0.5f);
1064  gfx.label.setParent(gfx.arrow);
1065  gfx.label.getTransform().setDepth(-0.1f);
1066  gfx.label.setHidden(renderOverlay != RenderOverlay::Spawns);
1067  gfx.label.addToScene(scene, bl::rc::UpdateSpeed::Static);
1068  gfx.label.commit(); // need to call manually otherwise we render before commit occurs
1069 }
1070 
1071 void EditMap::updateSpawnRotation(std::uint16_t id) {
1072  const auto srcIt = spawns.find(id);
1073  const auto it = spawnSprites.find(id);
1074  if (srcIt != spawns.end() && it != spawnSprites.end()) {
1075  it->second.arrow.getTransform().setRotation(
1076  90.f * static_cast<float>(srcIt->second.position.direction));
1077  }
1078 }
1079 
1080 EditMap::CatchTileLayerGraphics::~CatchTileLayerGraphics() {
1081  if (shapeBatch.exists()) { shapeBatch.destroy(); }
1082 }
1083 
1084 EditMap::BatchSpriteOverlayLayer::~BatchSpriteOverlayLayer() {
1085  if (batch.exists()) { batch.destroy(); }
1086 }
1087 
1088 void EditMap::updateCatchTileColor(unsigned int level, unsigned int x, unsigned int y) {
1089  auto it = catchTileOverlay.begin();
1090  std::advance(it, level);
1091  it->tiles(x, y).setFillColor(page::Catchables::getColor(levels[level].catchLayer().get(x, y)));
1092  it->tiles(x, y).commit();
1093 }
1094 
1095 void EditMap::updateTownTileColor(unsigned int x, unsigned int y) {
1096  townSquares(x, y).setFillColor(page::Towns::getColor(townTiles(x, y)));
1097  townSquares(x, y).commit();
1098 }
1099 
1100 void EditMap::updateCollisionTileTexture(unsigned int level, unsigned int x, unsigned int y) {
1101  const auto col = levels[level].collisionLayer().get(x, y);
1102  auto it = collisionTileOverlay.begin();
1103  std::advance(it, level);
1104  auto& tile = it->tiles(x, y);
1105  const auto& src = collisionTextureCoords[static_cast<unsigned int>(col)];
1106  const float Px = core::Properties::PixelsPerTile();
1107  tile.updateSourceRect({src.x, src.y, Px, Px});
1108  tile.commit();
1109 }
1110 
1111 void EditMap::updateLevelTransitionTexture(unsigned int x, unsigned int y) {
1112  const auto lt = transitionField(x, y);
1113  auto& tile = levelTransitionsOverlay.value().tiles(x, y);
1114  const auto& src = levelTransitionsTextureCoords[static_cast<unsigned int>(lt)];
1115  const float Px = core::Properties::PixelsPerTile();
1116  tile.updateSourceRect({src.x, src.y, Px, Px});
1117  tile.commit();
1118 }
1119 
1120 void EditMap::addEventGfx(unsigned int i) {
1121  auto it = eventsOverlay.begin();
1122  std::advance(it, i);
1123 
1124  const auto& event = eventsField[i];
1125  auto& rect = *eventsOverlay.emplace(it);
1126  rect.create(systems->engine(),
1127  glm::vec2(event.areaSize.x * core::Properties::PixelsPerTile(),
1128  event.areaSize.y * core::Properties::PixelsPerTile()));
1129  rect.setFillColor(currentEventFillColor);
1130  rect.getTransform().setPosition(event.position.x * core::Properties::PixelsPerTile(),
1131  event.position.y * core::Properties::PixelsPerTile());
1132  rect.getTransform().setDepth(getMinDepth());
1133  rect.component().setContainsTransparency(true);
1134  rect.addToScene(scene, bl::rc::UpdateSpeed::Static);
1135  rect.setHidden(renderOverlay != RenderOverlay::Events);
1136 }
1137 
1138 void EditMap::removeEventGfx(unsigned int i) {
1139  auto it = eventsOverlay.begin();
1140  std::advance(it, i);
1141  eventsOverlay.erase(it);
1142 }
1143 
1144 sf::Color EditMap::nextEventFillColor(float dt) {
1145  constexpr float Period = 1.2f;
1146 
1147  eventColorTime += dt;
1148  if (eventColorTime >= Period) {
1149  eventColorTime -= Period;
1150  return currentEventFillColor.a == 100 ? sf::Color(0, 0, 0, 165) : sf::Color(0, 0, 0, 100);
1151  }
1152  return currentEventFillColor;
1153 }
1154 
1155 void EditMap::stitchOverlayTextures() {
1156  // collision tiles
1157  BL_LOG_INFO << "Stitching collision tile textures...";
1158  colStitcher.emplace(core::Properties::PixelsPerTile() * 8);
1159  collisionTextureCoords.reserve(CollisionTileTextures.size());
1160  for (const auto& src : CollisionTileTextures) {
1161  auto img = bl::resource::ResourceManager<sf::Image>::load(src.data());
1162  const auto coord = colStitcher.value().addImage(*img);
1163  collisionTextureCoords.emplace_back(glm::vec2(coord));
1164  }
1165  collisionTilesTexture = systems->engine().renderer().texturePool().createTexture(
1166  colStitcher.value().getStitchedImage());
1167 
1168  // level transitions
1169  BL_LOG_INFO << "Stitching level transition textures...";
1170  levelTransitionsStitcher.emplace(core::Properties::PixelsPerTile() * 6);
1171  levelTransitionsTextureCoords.reserve(LevelTransitionTextures.size() + 1);
1172  sf::Image empty;
1173  empty.create(core::Properties::PixelsPerTile(),
1175  sf::Color::Transparent);
1176  levelTransitionsTextureCoords.emplace_back(
1177  glm::vec2(levelTransitionsStitcher.value().addImage(empty)));
1178  for (const auto& src : LevelTransitionTextures) {
1179  auto img = bl::resource::ResourceManager<sf::Image>::load(src.data());
1180  const auto coord = levelTransitionsStitcher.value().addImage(*img);
1181  levelTransitionsTextureCoords.emplace_back(glm::vec2(coord));
1182  }
1183  levelTransitionsTexture = systems->engine().renderer().texturePool().createTexture(
1184  levelTransitionsStitcher.value().getStitchedImage());
1185 }
1186 
1187 void EditMap::updateLayerDepths(unsigned int level, unsigned int li) {
1188  auto& layer = levels[level].getLayer(li);
1189  const bool isYsort =
1190  li >= levels[level].bottomLayers().size() &&
1191  li < levels[level].bottomLayers().size() + levels[level].ysortLayers().size();
1192 
1193  for (unsigned int x = 0; x < size.x; ++x) {
1194  for (unsigned int y = 0; y < size.y; ++y) {
1195  auto& tile = layer.getRef(x, y);
1196 
1197  const auto getBottomDepth = [this, y, li, level, &tile]() {
1198  const unsigned int th = tileset->tileHeight(tile.id(), tile.isAnimation());
1199  unsigned int size = th / core::Properties::PixelsPerTile();
1200  if (th % core::Properties::PixelsPerTile() != 0) { ++size; }
1201  return getDepthForPosition(level, y + size, li);
1202  };
1203 
1204  switch (tile.renderObject.index()) {
1205  case 1: {
1206  const float baseDepth = getDepthForPosition(level, y, li);
1207  const float depth = !isYsort ? baseDepth : (baseDepth + getBottomDepth()) * 0.5f;
1208  for (auto& v :
1209  std::get<bl::gfx::BatchSpriteSimple>(tile.renderObject).getVertices()) {
1210  v.pos.z = depth;
1211  }
1212  } break;
1213  case 2: {
1214  const float topDepth = getDepthForPosition(level, y, li);
1215  const float bottomDepth = getBottomDepth();
1216  unsigned int i = 0;
1217  for (auto& v :
1218  std::get<bl::gfx::BatchSlideshowSimple>(tile.renderObject).getVertices()) {
1219  v.pos.z = isYsort ? (i < 2 ? topDepth : bottomDepth) : topDepth;
1220  }
1221  } break;
1222  case 3:
1223  std::get<std::shared_ptr<bl::gfx::Animation2D>>(tile.renderObject)
1224  ->getTransform()
1225  .setDepth(isYsort ?
1226  ((getDepthForPosition(level, y, li) + getBottomDepth()) * 0.5f) :
1227  getDepthForPosition(level, y, li));
1228  break;
1229 
1230  case 0:
1231  default:
1232  break;
1233  }
1234  }
1235  }
1236 
1237  auto it = renderLevels.begin();
1238  std::advance(it, level);
1239  it->zones[li]->tileSprites.component().getBuffer().commit();
1240  it->zones[li]->tileAnims.component().indexBuffer.commit();
1241 }
1242 
1243 void EditMap::updateLevelDepths(unsigned int level) {
1244  for (unsigned int layer = 0; layer < levels[level].layerCount(); ++layer) {
1245  updateLayerDepths(level, layer);
1246  }
1247 }
1248 
1249 void EditMap::updateAllDepths() {
1250  if (camera) { camera->updateDepthPlanes(); }
1251  for (unsigned int level = 0; level < levels.size(); ++level) { updateLevelDepths(level); }
1252 }
1253 
1254 void EditMap::swapRenderLevels(unsigned int i1, unsigned int i2) {
1255  const bool isGt = i1 > i2;
1256  const auto it1 = std::next(renderLevels.begin(), i1);
1257  const auto it2 = std::next(renderLevels.begin(), i2);
1258  const auto s1 = isGt ? it1 : it2;
1259  const auto s2 = isGt ? it2 : it1;
1260  renderLevels.splice(s2, renderLevels, s1);
1261 
1262  updateLevelDepths(i1);
1263  updateLevelDepths(i2);
1264 }
1265 
1266 void EditMap::swapRenderLayers(unsigned int level, unsigned int l1, unsigned int l2) {
1267  std::next(renderLevels.begin(), level)->swapLayers(l1, l2);
1268  updateLayerDepths(level, l1);
1269  updateLayerDepths(level, l2);
1270 }
1271 
1272 EditMap::ExportState::ExportState()
1273 : exportInProgress(false)
1274 , exportJob(nullptr)
1275 , exportComplete(false) {}
1276 
1277 void EditMap::exportRendering() {
1278  const sf::Vector2u sp(size * core::Properties::PixelsPerTile());
1279 
1280  // create images while main thread renders
1281  sf::Image patch;
1282  sf::Image stitched;
1283  patch.create(exportState.size.x, exportState.size.y, sf::Color::Transparent);
1284  stitched.create(sp.x, sp.y, sf::Color::Transparent);
1285 
1286  // determine how many rows & cols to render
1287  const unsigned int rows = sp.x / exportState.size.x;
1288  const unsigned int cols = sp.y / exportState.size.y;
1289 
1290  // render each patch
1291  bl::engine::Systems::TaskHandle queueTask;
1292  for (unsigned int row = 0; row < rows; ++row) {
1293  for (unsigned int col = 0; col < cols; ++col) {
1294  const bool hasNext = row < rows || col < cols;
1295 
1296  // wait for patch render to complete and copy to local memory
1297  exportState.exportJob->wait();
1298  exportState.exportJob->copyImage(patch);
1299 
1300  // release and queue next export if there is one
1301  exportState.exportJob->release();
1302  if (hasNext) {
1303  // update camera center
1304  const bool sameRow = col < cols - 1;
1305  const unsigned int x = (sameRow ? row : (row + 1)) * exportState.size.x;
1306  const unsigned int y = (sameRow ? (col + 1) : 0) * exportState.size.y;
1307  exportState.camera->setCenter(glm::vec2(x, y));
1308 
1309  // queue next export in sync with engine
1310  queueTask = systems->engine().systems().addFrameTask(
1311  bl::engine::FrameStage::Update2, [this]() {
1312  exportState.exportJob =
1313  systems->engine().renderer().textureExporter().exportTexture(
1314  exportState.renderTexture->getTexture());
1315  });
1316  }
1317 
1318  // copy result into stitched image
1319  stitched.copy(patch, row * exportState.size.x, col * exportState.size.y);
1320 
1321  // wait for export queue if there is one
1322  if (hasNext) { queueTask.wait(); }
1323  }
1324  }
1325 
1326  // signal completion
1327  exportState.exportComplete = true;
1328 
1329  // save result
1330  stitched.saveToFile(exportState.outputPath);
1331 
1332  bl::dialog::tinyfd_messageBox(
1333  "Rendering Complete",
1334  std::string("Rendered map saved to: " + exportState.outputPath).c_str(),
1335  "ok",
1336  "info",
1337  1);
1338 }
1339 
1340 } // namespace component
1341 } // namespace editor
Id
Represents an item in its simplist form.
Definition: Id.hpp:24
Collision
The different types of collisions in maps.
Definition: Collision.hpp:16
LevelTransition
Represents a level transition in a map. Level transitions are applied when an entity moves either ont...
@ None
No level transition, entity stays on same level.
All classes and functionality used for implementing the game editor.
Definition: Tile.hpp:11
Adding this component to an entity will allow it to be rendered.
Definition: Renderable.hpp:28
void setHidden(bool hide)
Set whether the entity is hidden or not.
Definition: Renderable.cpp:144
static Id cast(unsigned int id)
Helper function to cast a raw id to an item Id.
Definition: Item.cpp:31
Represents a class of catchable peoplemon.
Definition: CatchRegion.hpp:18
Represents a character to be spawned into a map on load.
bl::tmap::Position position
Represents an event in a Map. A script that is run on a trigger within a given region.
Definition: Event.hpp:19
Basic struct representing a pickup-able item in Map.
Definition: Item.hpp:16
Generic map layer class. Can be used for any type of layer.
Definition: Layer.hpp:21
Represents a renderable light in a Map.
Definition: Light.hpp:16
std::uint16_t Handle
Handle representing a light in the map.
std::uint8_t getMaxLightLevel() const
Returns the maximum ambient light level of this map.
std::uint8_t getMinLightLevel() const
Returns the minimum ambient light level of this map.
void setAmbientLevel(std::uint8_t lowLightLevel, std::uint8_t highLightLevel)
The ambient light band. 0 is full darkness and 255 is full brightness.
static constexpr Handle None
Special handle indicating that no light is referenced.
void activate(bl::rc::lgt::Scene2DLighting &sceneLighting)
Adds all lights into the scene.
void subscribe()
Subscribes the lighting system to time events for ambient light level.
float getMinDepth() const
Returns the maximum depth possible for this map. Maximum is always 0.f. Minimum is negative,...
Definition: Map.cpp:775
std::vector< Item > itemsField
Definition: Map.hpp:307
std::unordered_map< std::uint16_t, Spawn > spawns
Definition: Map.hpp:305
LightingSystem lighting
Definition: Map.hpp:309
std::string nameField
Definition: Map.hpp:298
std::vector< LayerSet > levels
Definition: Map.hpp:303
std::string unloadScriptField
Definition: Map.hpp:300
bool save(const std::string &file)
Saves the map to the given file. The path should be relative to the maps directory and include the ....
Definition: Map.cpp:229
Weather weather
Definition: Map.hpp:320
Map()
Creates an empty Map.
Definition: Map.cpp:21
std::string loadScriptField
Definition: Map.hpp:299
Weather::Type weatherField
Definition: Map.hpp:302
sf::Vector2i size
Definition: Map.hpp:318
bl::resource::Ref< Tileset > tileset
Definition: Map.hpp:319
void setupTile(unsigned int level, unsigned int layer, const sf::Vector2u &pos)
Definition: Map.cpp:641
const sf::Vector2i & sizeTiles() const
Returns the size of the map in tiles.
Definition: Map.cpp:173
std::vector< CharacterSpawn > characterField
Definition: Map.hpp:306
std::string tilesetField
Definition: Map.hpp:304
void clear()
Definition: Map.cpp:523
bl::ctr::Vector2D< LevelTransition > transitionField
Definition: Map.hpp:311
void triggerAnimation(const bl::tmap::Position &position)
Definition: Map.cpp:439
bl::rc::lgt::Scene2DLighting & getSceneLighting()
Returns the scene lighting for this map. Only valid after enter() is called.
Definition: Map.hpp:285
static std::string getMapFile(const std::string &partialFile)
Returns the full path to the map file from the given partial file. Accounts for missing extension.
Definition: Map.cpp:191
bl::rc::SceneRef scene
Definition: Map.hpp:328
bool activated
Definition: Map.hpp:326
system::Systems * systems
Definition: Map.hpp:315
const std::string & name() const
Returns the name of the map.
Definition: Map.cpp:171
sf::Vector2f sizePixels() const
Returns the size of the map in pixels.
Definition: Map.cpp:175
std::list< RenderLevel > renderLevels
Definition: Map.hpp:329
Basic struct representing a spawn in a Map.
Definition: Spawn.hpp:19
std::uint16_t id
Definition: Spawn.hpp:20
bl::tmap::Position position
Definition: Spawn.hpp:21
static constexpr IdType Blank
Special id for blank tiles.
Definition: Tile.hpp:36
std::uint16_t IdType
Definition: Tile.hpp:33
static std::string getFullPath(const std::string &path)
Generates the full path to the given tileset file.
Definition: Tileset.cpp:190
Represents a town, route, or region within a map. Maps may have many towns. Individual tiles are asso...
Definition: Town.hpp:22
Type
The type of weather.
Definition: Weather.hpp:38
void activate(system::Systems &systems, Map &map)
Activates the weather system.
Definition: Weather.cpp:49
void set(Type type, bool immediate=false)
Sets the current weather type.
Definition: Weather.cpp:54
static int WindowWidth()
Definition: Properties.cpp:250
static const sf::VulkanFont & MenuFont()
Definition: Properties.cpp:363
static int PixelsPerTile()
Definition: Properties.cpp:279
static void warn(const std::string &script)
In debug mode logs a warning if the given script is detected to be legacy.
Definition: LegacyWarn.hpp:26
bool spawnItem(const map::Item &item, map::Map &map)
Spawns an item into the world.
Definition: Entity.cpp:98
bl::ecs::Entity spawnCharacter(const map::CharacterSpawn &spawn, map::Map &map)
Spawns a trainer or an npc from the given spawn information.
Definition: Entity.cpp:25
bl::ecs::Entity getEntity(const bl::tmap::Position &pos) const
Returns the entity at the given position or InvalidEntity if not found.
Definition: Position.cpp:96
void setMainRenderTarget(bl::rc::RenderTarget &target)
Sets the main render target. Used by the editor.
Definition: Render.cpp:105
Owns all primary systems and a reference to the engine.
Definition: Systems.hpp:47
const bl::engine::Engine & engine() const
Const accessor for the Engine.
Definition: Systems.cpp:35
Entity & entity()
Returns the entity system.
Definition: Systems.cpp:55
Render & render()
Returns the render system.
Definition: Systems.cpp:87
Position & position()
Returns a reference to the position system.
Definition: Systems.cpp:47
Wrapper over the core::Map class that is directly usable in a bl::gui::GUI.
Definition: EditMap.hpp:28
const std::string & currentFile() const
Returns the current file the map is saving to.
Definition: EditMap.cpp:143
void removeLayer(unsigned int level, unsigned int layer)
Deletes the given layer.
Definition: EditMap.cpp:610
bool spawnIdUnused(unsigned int id) const
Tells whether or not the given id is in use.
Definition: EditMap.cpp:636
bool editorLoad(const std::string &filename)
Loads the map contents from the given file.
Definition: EditMap.cpp:126
std::function< void(const sf::Vector2f &pixels, const sf::Vector2i &tiles)> PositionCb
Called when the map is clicked.
Definition: EditMap.hpp:34
std::function< void()> ActionCb
Called on various event types.
Definition: EditMap.hpp:37
const char * redoDescription() const
Returns the description of what action will be redone if redo is called.
Definition: EditMap.cpp:532
virtual ~EditMap()
Destroy the Edit Map object.
Definition: EditMap.cpp:119
void setTile(unsigned int level, unsigned int layer, const sf::Vector2i &position, core::map::Tile::IdType id, bool isAnim)
Sets a single tile.
Definition: EditMap.cpp:558
void showGrid(bool show)
Set whether or not to render a grid between tiles.
Definition: EditMap.cpp:374
void setWeather(core::map::Weather::Type weather)
Set the weather in the map.
Definition: EditMap.cpp:554
bool unsavedChanges() const
Returns whether or not the map has been updated since last being saved.
Definition: EditMap.cpp:221
void setPlaylist(const std::string &playlist)
Sets the playlist of the map.
Definition: EditMap.cpp:550
void appendTopLayer(unsigned int level)
Creates a new (empty) top layer.
Definition: EditMap.cpp:606
bool editorSave()
Saves the map to the file it was loaded from or created with.
Definition: EditMap.cpp:135
void setControlsEnabled(bool enabled)
Enables or disables the map camera and click controls.
Definition: EditMap.cpp:292
void removeNpcSpawn(const core::map::CharacterSpawn *spawn)
Removes a character spawn.
Definition: EditMap.cpp:692
void removeAllTiles(core::map::Tile::IdType id, bool isAnim)
Removes all tiles that reference the given id in the tileset. This should be called before removing a...
Definition: EditMap.cpp:392
const std::string & getOnEnterScript() const
Get the OnEnter script.
Definition: EditMap.cpp:628
void shiftLevel(unsigned int level, bool up)
Shifts the given level up or down.
Definition: EditMap.cpp:618
void removeSpawn(unsigned int level, const sf::Vector2i &position)
Removes the spawn at the given position.
Definition: EditMap.cpp:654
void appendYsortLayer(unsigned int level)
Creates a new (empty) y-sort layer.
Definition: EditMap.cpp:602
void setOnEnterScript(const std::string &script)
Set the OnEnter script.
Definition: EditMap.cpp:624
void editNpcSpawn(const core::map::CharacterSpawn *orig, const core::map::CharacterSpawn &spawn)
Edits an existing character spawn.
Definition: EditMap.cpp:680
void setOnExitScript(const std::string &script)
Set the OnExit script.
Definition: EditMap.cpp:630
static Ptr create(const PositionCb &clickCb, const PositionCb &moveCb, const ActionCb &actionCb, const ActionCb &syncCb, core::system::Systems &systems)
Creates a new EditMap.
Definition: EditMap.cpp:55
void addNpcSpawn(const core::map::CharacterSpawn &spawn)
Adds a character spawn to the map.
Definition: EditMap.cpp:665
void appendBottomLayer(unsigned int level)
Creates a new (empty) bottom layer.
Definition: EditMap.cpp:598
void setLevelVisible(unsigned int level, bool visible)
Shows or hides the given level.
Definition: EditMap.cpp:297
void setLayerVisible(unsigned int level, unsigned int layer, bool visible)
Shows or hides the given layer.
Definition: EditMap.cpp:313
void setCatchArea(unsigned int level, const sf::IntRect &area, std::uint8_t id)
Sets a range of catch tiles to a single value.
Definition: EditMap.cpp:590
void setCollisionArea(unsigned int level, const sf::IntRect &area, core::map::Collision id)
Sets a range of collision tiles to a single value.
Definition: EditMap.cpp:577
void redo()
Reapplies the next action in the edit history.
Definition: EditMap.cpp:524
void setName(const std::string &name)
Set the name of the map.
Definition: EditMap.cpp:548
const char * undoDescription() const
Returns the description of what action will be undone if undo is called.
Definition: EditMap.cpp:519
void rotateSpawn(unsigned int level, const sf::Vector2i &tile)
Rotates a player spawn.
Definition: EditMap.cpp:643
std::shared_ptr< EditMap > Ptr
Pointer to an EditMap.
Definition: EditMap.hpp:31
RenderOverlay
Optional render overlays depending on editor state.
Definition: EditMap.hpp:42
@ Collisions
Renders collisions for the current level.
@ Towns
Renders colored tiles to indicate towns/routes.
@ LevelTransitions
Renders level transitions.
@ CatchTiles
Renders catch tiles for the current level.
@ Events
Renders events in the map.
@ Spawns
Renders spawns in the map.
void addSpawn(unsigned int level, const sf::Vector2i &tile, unsigned int id, bl::tmap::Direction dir)
Adds a new player spawn to the map.
Definition: EditMap.cpp:638
void setTileArea(unsigned int level, unsigned int layer, const sf::IntRect &area, core::map::Tile::IdType id, bool isAnim)
Sets a range of tiles to a given value. Tries to avoid overcrowding for large tiles.
Definition: EditMap.cpp:563
void staticRender(const RenderMapWindow &params)
Renders the map contents.
Definition: EditMap.cpp:705
void fillTile(unsigned int level, unsigned int layer, const sf::Vector2i &position, core::map::Tile::IdType id, bool isAnim)
Performs a bucket fill of tiles starting from the given position.
Definition: EditMap.cpp:568
void setRenderOverlay(RenderOverlay overlay, unsigned int level)
Sets which overlay gets rendered on top of the map.
Definition: EditMap.cpp:337
const core::map::CharacterSpawn * getNpcSpawn(unsigned int level, const sf::Vector2i &position) const
Get the Npc Spawn at the given position, if any.
Definition: EditMap.cpp:669
void showSelection(const sf::IntRect &selection)
Sets the tile selection to render. Set width or height to 0 to hide. Set width or height to negative ...
Definition: EditMap.cpp:376
friend class rdr::EditMapComponent
Definition: EditMap.hpp:767
void newMap(const std::string &filename, const std::string &name, const std::string &tileset, unsigned int width, unsigned int height)
Clears the current map and creates a new map with the given parameters.
Definition: EditMap.cpp:145
void fillCollision(unsigned int level, const sf::Vector2i &position, core::map::Collision id)
Performs a bucket fill of collisions from the given starting position.
Definition: EditMap.cpp:582
void shiftLayer(unsigned int level, unsigned int layer, bool up)
Shifts a layer up or down in the render order. This will also move layers between bottom,...
Definition: EditMap.cpp:614
void setCatch(unsigned int level, const sf::Vector2i &position, std::uint8_t id)
Sets a single catch tile.
Definition: EditMap.cpp:586
void fillCatch(unsigned int level, const sf::Vector2i &position, std::uint8_t id)
Performs a bucket fill of catch tiles from the given position.
Definition: EditMap.cpp:594
void appendLevel()
Creates a new level.
Definition: EditMap.cpp:622
const std::string & getOnExitScript() const
Get the OnExit script.
Definition: EditMap.cpp:634
void setCollision(unsigned int level, const sf::Vector2i &position, core::map::Collision id)
Sets a single collision tile.
Definition: EditMap.cpp:573
void undo()
Undoes the previous action in the edit history.
Definition: EditMap.cpp:511
GUI renderer component for the map itself.
bl::rc::RenderTarget * getTarget()
Returns the render target the map is being rendered to.
Options window to help trigger map renderings.
bool renderCharacters() const
Returns whether or not to render the characters in the map.
const std::string & outputPath() const
Returns the filename to output to.
std::uint8_t lightLevel() const
Returns the light level to render the map at.
static sf::Color getColor(std::uint8_t index)
Returns the color to use for the given catch region.
Definition: Catchables.cpp:59
static sf::Color getColor(std::uint8_t index)
Returns the color to use for the given town.
Definition: Towns.cpp:123
static EditMap::Action::Ptr create(unsigned int level, unsigned int layer, const sf::Vector2i &pos, bool isAnim, core::map::Tile::IdType value, const EditMap &map)
Definition: MapActions.cpp:134
static EditMap::Action::Ptr create(unsigned int level, unsigned int layer, const sf::IntRect &area, bool isAnim, core::map::Tile::IdType value, const EditMap &map)
Definition: MapActions.cpp:195
static Action::Ptr create(unsigned int level, unsigned int layer, const sf::Vector2i &pos, core::map::Tile::IdType id, bool isAnim, EditMap &map)
Definition: MapActions.cpp:280
static EditMap::Action::Ptr create(unsigned int level, const sf::Vector2i &pos, core::map::Collision value, const EditMap &map)
Definition: MapActions.cpp:349
static Action::Ptr create(unsigned int level, const sf::Vector2i &pos, core::map::Collision col, EditMap &map)
Definition: MapActions.cpp:423
static EditMap::Action::Ptr create(unsigned int level, const sf::IntRect &area, core::map::Collision value, const EditMap &map)
Definition: MapActions.cpp:379
static EditMap::Action::Ptr create(unsigned int level, const sf::Vector2i &pos, std::uint8_t value, const EditMap &map)
Definition: MapActions.cpp:487
static EditMap::Action::Ptr create(unsigned int level, const sf::IntRect &area, std::uint8_t value, const EditMap &map)
Definition: MapActions.cpp:514
static Action::Ptr create(unsigned int level, const sf::Vector2i &pos, std::uint8_t id, EditMap &map)
Definition: MapActions.cpp:557
static EditMap::Action::Ptr create(const std::string &playlist, const EditMap &editMap)
Definition: MapActions.cpp:618
static EditMap::Action::Ptr create(const std::string &name, const EditMap &editMap)
Definition: MapActions.cpp:639
static EditMap::Action::Ptr create(core::map::Weather::Type type, const EditMap &map)
Definition: MapActions.cpp:659
static EditMap::Action::Ptr create(unsigned int level, Location location)
Definition: MapActions.cpp:683
static EditMap::Action::Ptr create(unsigned int level, unsigned int layer, const EditMap &map)
Definition: MapActions.cpp:748
static EditMap::Action::Ptr create(unsigned int level, unsigned int layer, bool up)
Definition: MapActions.cpp:814
static EditMap::Action::Ptr create(unsigned int level, bool up)
Definition: MapActions.cpp:862
static EditMap::Action::Ptr create()
Definition: MapActions.cpp:904
static EditMap::Action::Ptr create(bool load, const std::string &s, const std::string &p)
Definition: MapActions.cpp:929
static Action::Ptr create(unsigned int lvl, const sf::Vector2i &pos, unsigned int id, bl::tmap::Direction dir)
static Action::Ptr create(unsigned int id)
static Action::Ptr create(unsigned int id, const core::map::Spawn &spawn)
static Action::Ptr create(const core::map::CharacterSpawn &s, unsigned int i)
static Action::Ptr create(unsigned int i, const core::map::CharacterSpawn &orig, const core::map::CharacterSpawn &s)
static Action::Ptr create(const core::map::CharacterSpawn &orig, unsigned int i)