4 #include <BLIB/Cameras/2D/Camera2D.hpp>
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"};
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"};
46 unsigned int computePatchSize(
unsigned int total,
unsigned int maxPatch) {
47 if (total < maxPatch) {
return total; }
49 unsigned int patch = maxPatch;
50 while (total % patch > 0 && patch > 0) { --patch; }
61 EditMap::EditMap(
const PositionCb& cb,
const PositionCb& mcb,
const ActionCb& actionCb,
72 , controlsEnabled(false)
73 , currentEventFillColor(0, 0, 0, 100)
75 , renderOverlay(RenderOverlay::None)
78 , setRenderTarget(false) {
81 static const auto callCb = [
this, &s](
const PositionCb& cb) {
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);
90 bl::rc::RenderTarget* rt = com->
getTarget();
92 const glm::vec2 wpos = rt->transformToWorldSpace({pixels.x, pixels.y});
98 const sf::Vector2i tiles(std::floor(pixels.x * PixelRatio),
99 std::floor(pixels.y * PixelRatio));
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));
108 getSignal(bl::gui::Event::LeftClicked).willAlwaysCall([
this](
const bl::gui::Event&, Element*) {
109 if (controlsEnabled) { callCb(clickCb); }
112 getSignal(bl::gui::Event::MouseMoved).willAlwaysCall([
this](
const bl::gui::Event&, Element*) {
116 setOutlineThickness(0.f);
120 if (townSquareBatch.exists()) {
121 townSquareBatch.destroy();
136 if (
save(savefile)) {
137 saveHead = historyHead;
146 const std::string& tileset,
unsigned int width,
unsigned int height) {
152 levels.front().init(width, height, 2, 2, 1);
158 bool EditMap::doLoad(
const std::string& file) {
160 if (!MapManager::initializeExisting(
getMapFile(file),
static_cast<Map&
>(*
this)))
return false;
163 if (nextItemId <= item.mapId) { nextItemId = item.mapId + 1; }
165 return editorActivate();
168 bool EditMap::editorActivate() {
173 systems->
engine().ecs().destroyAllEntitiesWithFlags(bl::ecs::Flags::WorldObject);
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});
182 Map::prepareRender();
183 if (getComponent()) { getComponent()->onElementUpdated(); }
200 BL_LOG_WARN <<
"Failed to spawn character: " << spawn.file;
206 bl::event::Dispatcher::dispatch<core::event::MapEntered>({*
this});
209 levelFilter.resize(
levels.size(),
true);
211 layerFilter.resize(
levels.size());
212 for (
unsigned int i = 0; i <
levels.size(); ++i) {
213 layerFilter[i].resize(
levels[i].layerCount(),
true);
223 void EditMap::update(
float dt) {
225 if (
scene && !camera) {
228 bl::rc::RenderTarget* rt = com->
getTarget();
230 bl::cam::Camera2D* cam = rt->setCamera<bl::cam::Camera2D>(
232 glm::vec2{getAcquisition().width, getAcquisition().height});
233 camera = cam->setController<EditCameraController>(
this);
234 camera->enabled = controlsEnabled;
240 if (!setRenderTarget) {
243 bl::rc::RenderTarget* rt = com->getTarget();
246 setRenderTarget =
true;
251 const sf::Color newEventColor = nextEventFillColor(dt);
252 if (currentEventFillColor != newEventColor) {
253 currentEventFillColor = newEventColor;
254 for (
auto& rect : eventsOverlay) { rect.setFillColor(newEventColor); }
257 if (exportState.exportComplete) {
258 exportState.exportComplete =
false;
261 setRenderOverlay(exportState.prevRenderOverlay, exportState.prevOverlayLevel);
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]);
276 if (exportState.entitiesHidden) {
282 camera->zoomAmount = exportState.prevZoom;
283 camera->getCamera().setCenter(exportState.prevCenter);
284 controlsEnabled = exportState.prevEnabled;
285 bl::event::Dispatcher::dispatch<event::MapRenderCompleted>({});
288 exportState.renderTexture.release();
294 if (camera) { camera->enabled = controlsEnabled; }
298 levelFilter[level] = v;
301 std::advance(it, level);
304 unsigned int layer = 0;
305 for (
auto zone : rl.zones) {
306 const bool hide = !v || !layerFilter[level][layer];
308 updateLayerVisibility(level, layer, hide);
314 layerFilter[level][layer] = v;
315 updateLayerVisibility(level, layer, !v || !levelFilter[level]);
318 void EditMap::updateLayerVisibility(
unsigned int level,
unsigned int layer,
bool hide) {
320 std::advance(it, level);
323 auto* zone = rl.zones[layer];
324 zone->tileSprites.setHidden(hide);
325 zone->tileAnims.setHidden(hide);
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); }
340 if (renderOverlay != ro || overlayLevel != l) {
347 for (
auto& spawn : spawnSprites) {
348 spawn.second.arrow.setHidden(hideSpawns);
349 spawn.second.label.setHidden(hideSpawns);
352 unsigned int level = 0;
353 for (
auto& catchLayer : catchTileOverlay) {
355 overlayLevel != level);
360 for (
auto& colLayer : collisionTileOverlay) {
362 overlayLevel != level);
366 levelTransitionsOverlay.value().batch.setHidden(renderOverlay !=
370 for (
auto& eventRect : eventsOverlay) { eventRect.setHidden(hideEvents); }
380 selectRect.getTransform().setPosition(s.left * PixelsPerTile, s.top * PixelsPerTile);
381 selectRect.setHidden(s.width == 0);
383 selectRect.setFillColor(sf::Color(180, 160, 20, 165));
384 selectRect.scaleToSize(glm::vec2(s.width * PixelsPerTile, s.height * PixelsPerTile));
387 selectRect.scaleToSize({PixelsPerTile, PixelsPerTile});
388 selectRect.setFillColor(sf::Color(20, 70, 220, 165));
394 unsigned int levelIndex,
395 unsigned int layerIndex) ->
bool {
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) {
403 setupTile(levelIndex, layerIndex, {x, y});
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);
417 for (
auto& layer : level.ysortLayers()) {
418 cleanLayer(layer, levelIndex, layerIndex);
421 for (
auto& layer : level.topLayers()) {
422 cleanLayer(layer, levelIndex, layerIndex);
430 EditMap::EditCameraController::EditCameraController(
EditMap* owner)
434 void EditMap::EditCameraController::update(
float dt) {
436 float PixelsPerSecond =
438 constexpr
float ZoomPerSecond = 1.f;
440 if (sf::Keyboard::isKeyPressed(sf::Keyboard::Space)) { PixelsPerSecond *= 5.f; }
442 if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up) ||
443 sf::Keyboard::isKeyPressed(sf::Keyboard::W)) {
444 camera().move({0.f, -PixelsPerSecond * dt});
446 if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right) ||
447 sf::Keyboard::isKeyPressed(sf::Keyboard::D)) {
448 camera().move({PixelsPerSecond * dt, 0.f});
450 if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down) ||
451 sf::Keyboard::isKeyPressed(sf::Keyboard::S)) {
452 camera().move({0.f, PixelsPerSecond * dt});
454 if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left) ||
455 sf::Keyboard::isKeyPressed(sf::Keyboard::A)) {
456 camera().move({-PixelsPerSecond * dt, 0.f});
458 if (sf::Keyboard::isKeyPressed(sf::Keyboard::C)) { zoom(-ZoomPerSecond * dt); }
459 if (sf::Keyboard::isKeyPressed(sf::Keyboard::V)) { zoom(ZoomPerSecond * dt); }
463 const float w = camera().getSize().x;
464 const float h = camera().getSize().y;
465 glm::vec2 position = camera().getCenter();
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; }
470 else { position.x = mapSize.x * 0.5f; }
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; }
475 else { position.y = mapSize.y * 0.5f; }
476 camera().setCenter(position);
479 const sf::FloatRect& acq = owner->getAcquisition();
480 camera().setSize(glm::vec2(acq.width, acq.height) * zoomAmount);
483 void EditMap::EditCameraController::reset(
const sf::Vector2i& t) {
485 camera().setCenter(mapSize * 0.5f);
490 void EditMap::EditCameraController::zoom(
float z) {
492 if (zoomAmount < 0.1f) { zoomAmount = 0.1f; }
495 void EditMap::EditCameraController::updateDepthPlanes() {
496 camera().setNearAndFarPlanes(-owner->getMinDepth(), 0.f);
499 sf::Vector2f EditMap::minimumRequisition()
const {
return {100.f, 100.f}; }
501 bl::gui::rdr::Component* EditMap::doPrepareRender(bl::gui::rdr::Renderer& renderer) {
502 return renderer.createComponent<EditMap>(*this);
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); }
512 if (!history.empty() && historyHead > 0) {
513 if (history[historyHead - 1]->
undo(*
this)) { syncCb(); }
520 if (!history.empty() && historyHead > 0)
return history[historyHead - 1]->description();
525 if (historyHead != history.size()) {
526 if (history[historyHead]->apply(*
this)) { syncCb(); }
533 if (historyHead != history.size())
return history[historyHead]->description();
537 void EditMap::addAction(
const Action::Ptr& a) {
538 if (historyHead < history.size()) {
539 history.erase(history.begin() + historyHead, history.end());
542 history.emplace_back(a);
543 if (history.back()->apply(*
this)) { syncCb(); }
544 historyHead = history.size();
639 bl::tmap::Direction dir) {
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) {
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) {
670 const sf::Vector2i& pos)
const {
671 const glm::i32vec2 gpos(pos.x, pos.y);
673 if (spawn.position.level == level) {
674 if (spawn.position.position == gpos) {
return &spawn; }
683 if (e != bl::ecs::InvalidEntity) {
687 const auto& pos = orig->
position.position;
688 BL_LOG_WARN <<
"Failed to get entity id at location: (" << pos.x <<
", " << pos.y <<
")";
700 const auto& pos = s->
position.position;
701 BL_LOG_WARN <<
"Failed to get entity id at location: (" << pos.x <<
", " << pos.y <<
")";
706 exportState.exportInProgress =
true;
707 exportState.exportComplete =
false;
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);
718 exportState.prevRenderOverlay = renderOverlay;
719 exportState.prevOverlayLevel = overlayLevel;
723 exportState.prevSelection = selection;
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);
747 if (exportState.entitiesHidden) {
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);
760 exportState.prevEnabled = controlsEnabled;
761 controlsEnabled =
false;
762 bl::event::Dispatcher::dispatch<event::MapRenderStarted>({});
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(); });
775 addAction(AddEventAction::create(event, eventsField.size()));
780 const sf::IntRect area(event.position, event.areaSize);
781 if (area.contains(tiles))
return &event;
788 while (&eventsField[i] != orig) { ++i; }
789 addAction(EditEventAction::create(*orig, val, i));
794 while (&eventsField[i] != e) { ++i; }
795 addAction(RemoveEventAction::create(*e, i));
798 void EditMap::addOrEditItem(
unsigned int level,
const sf::Vector2i& tiles,
core::item::Id item,
802 for (; i < itemsField.size(); ++i) {
803 if (itemsField[i].level == level) {
804 if (itemsField[i].position == tiles) {
810 if (!found) i = itemsField.size();
811 addAction(AddOrEditItemAction::create(
812 i, level, tiles, item, visible, found ? itemsField[i] :
core::map::Item(), !found));
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) {
824 void EditMap::removeItem(
unsigned int level,
const sf::Vector2i& tiles) {
826 for (
const auto& item : itemsField) {
827 if (item.level == level && item.position == tiles) {
828 addAction(RemoveItemAction::create(i, level, tiles, item));
835 void EditMap::setLight(
const sf::Vector2i& pos,
unsigned int rad) {
839 addAction(SetLightAction::create(pos, rad, orig));
842 void EditMap::removeLight(
const sf::Vector2i& pos) {
845 addAction(RemoveLightAction::create(lighting.getLight(h)));
849 void EditMap::addCatchRegion() { addAction(AddCatchRegionAction::create()); }
851 const std::vector<core::map::CatchRegion>& EditMap::catchRegions()
const {
852 return catchRegionsField;
856 addAction(EditCatchRegionAction::create(index, value, catchRegionsField[index]));
859 void EditMap::removeCatchRegion(std::uint8_t index) {
860 addAction(RemoveCatchRegionAction::create(index, catchRegionsField[index]));
863 void EditMap::setAmbientLight(std::uint8_t lower, std::uint8_t upper,
bool sun) {
864 addAction(SetAmbientLightAction::create(sun, upper, lower, lightingSystem()));
867 void EditMap::addTown() { addAction(AddTownAction::create()); }
870 addAction(EditTownAction::create(i, towns[i], town));
873 void EditMap::removeTown(std::uint8_t i) { addAction(RemoveTownAction::create(i, towns[i])); }
875 void EditMap::setTownTile(
const sf::Vector2i& pos, std::uint8_t
id) {
876 addAction(SetTownTileAction::create(pos,
id, townTiles(pos.x, pos.y)));
879 void EditMap::setTownTileArea(
const sf::IntRect& area, std::uint8_t
id) {
880 addAction(SetTownTileAreaAction::create(area,
id, *
this));
883 void EditMap::fillTownTiles(
const sf::Vector2i& pos, std::uint8_t
id) {
884 addAction(FillTownTileAction::create(pos,
id, *
this));
888 addAction(SetLevelTileAction::create(pos, lt, transitionField(pos.x, pos.y)));
892 addAction(SetLevelTileAreaAction::create(area, lt, *
this));
895 void EditMap::setupOverlay() {
896 BL_LOG_INFO <<
"Preparing map editor overlays...";
900 BL_LOG_INFO <<
"Generating town geometry...";
901 if (townSquareBatch.exists()) {
902 townSquareBatch.destroy();
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});
913 square.setOutlineColor(sf::Color::Black);
914 square.setOutlineThickness(-1.f);
915 square.getLocalTransform().setPosition(x * PixelsPerTile, y * PixelsPerTile);
919 townSquareBatch.component().setContainsTransparency(
true);
920 townSquareBatch.addToScene(scene, bl::rc::UpdateSpeed::Static);
921 townSquareBatch.setHidden(renderOverlay != RenderOverlay::Towns);
924 BL_LOG_INFO <<
"Generating spawn icons...";
925 spawnSprites.clear();
926 for (
auto& spawn : spawns) { addSpawnGfx(spawn.second); }
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);
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);
954 if (!collisionTilesTexture) { stitchOverlayTextures(); }
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);
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);
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);
993 ltOverlay.batch.component().setContainsTransparency(
true);
994 ltOverlay.batch.addToScene(scene, bl::rc::UpdateSpeed::Static);
995 ltOverlay.batch.setHidden(renderOverlay != RenderOverlay::LevelTransitions);
998 BL_LOG_INFO <<
"Generating events geometry...";
999 eventsOverlay.clear();
1000 for (
unsigned int i = 0; i < eventsField.size(); ++i) { addEventGfx(i); }
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);
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); }
1014 grid.create(systems->engine(), (size.x + size.y + 2) * 2);
1015 grid.setHidden(
true);
1017 for (
int x = 0; x <= size.x * 2; x += 2) {
1018 grid[x].color = Black;
1019 grid[x + 1].color = Black;
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;
1028 grid[y + 1 + o].pos =
1031 grid.component().setContainsTransparency(
true);
1032 grid.getTransform().setDepth(getMinDepth());
1034 grid.addToSceneWithCustomPipeline(
1035 scene, bl::rc::UpdateSpeed::Static, bl::rc::Config::PipelineIds::Lines2D);
1037 BL_LOG_INFO <<
"Map editor overlays initialized";
1043 systems->engine().renderer().texturePool().getOrLoadTexture(
"EditorResources/arrow.png");
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);
1056 gfx.label.create(systems->engine(),
1058 std::to_string(spawn.
id),
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);
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));
1080 EditMap::CatchTileLayerGraphics::~CatchTileLayerGraphics() {
1081 if (shapeBatch.exists()) { shapeBatch.destroy(); }
1084 EditMap::BatchSpriteOverlayLayer::~BatchSpriteOverlayLayer() {
1085 if (batch.exists()) { batch.destroy(); }
1088 void EditMap::updateCatchTileColor(
unsigned int level,
unsigned int x,
unsigned int y) {
1089 auto it = catchTileOverlay.begin();
1090 std::advance(it, level);
1092 it->tiles(x, y).commit();
1095 void EditMap::updateTownTileColor(
unsigned int x,
unsigned int y) {
1097 townSquares(x, y).commit();
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)];
1107 tile.updateSourceRect({src.x, src.y, Px, Px});
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)];
1116 tile.updateSourceRect({src.x, src.y, Px, Px});
1120 void EditMap::addEventGfx(
unsigned int i) {
1121 auto it = eventsOverlay.begin();
1122 std::advance(it, i);
1124 const auto&
event = eventsField[i];
1125 auto& rect = *eventsOverlay.emplace(it);
1126 rect.create(systems->engine(),
1129 rect.setFillColor(currentEventFillColor);
1132 rect.getTransform().setDepth(getMinDepth());
1133 rect.component().setContainsTransparency(
true);
1134 rect.addToScene(scene, bl::rc::UpdateSpeed::Static);
1135 rect.setHidden(renderOverlay != RenderOverlay::Events);
1138 void EditMap::removeEventGfx(
unsigned int i) {
1139 auto it = eventsOverlay.begin();
1140 std::advance(it, i);
1141 eventsOverlay.erase(it);
1144 sf::Color EditMap::nextEventFillColor(
float dt) {
1145 constexpr
float Period = 1.2f;
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);
1152 return currentEventFillColor;
1155 void EditMap::stitchOverlayTextures() {
1157 BL_LOG_INFO <<
"Stitching collision tile textures...";
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));
1165 collisionTilesTexture = systems->engine().renderer().texturePool().createTexture(
1166 colStitcher.value().getStitchedImage());
1169 BL_LOG_INFO <<
"Stitching level transition textures...";
1171 levelTransitionsTextureCoords.reserve(LevelTransitionTextures.size() + 1);
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));
1183 levelTransitionsTexture = systems->engine().renderer().texturePool().createTexture(
1184 levelTransitionsStitcher.value().getStitchedImage());
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();
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);
1197 const auto getBottomDepth = [
this, y, li, level, &tile]() {
1198 const unsigned int th = tileset->tileHeight(tile.id(), tile.isAnimation());
1201 return getDepthForPosition(level, y + size, li);
1204 switch (tile.renderObject.index()) {
1206 const float baseDepth = getDepthForPosition(level, y, li);
1207 const float depth = !isYsort ? baseDepth : (baseDepth + getBottomDepth()) * 0.5f;
1209 std::get<bl::gfx::BatchSpriteSimple>(tile.renderObject).getVertices()) {
1214 const float topDepth = getDepthForPosition(level, y, li);
1215 const float bottomDepth = getBottomDepth();
1218 std::get<bl::gfx::BatchSlideshowSimple>(tile.renderObject).getVertices()) {
1219 v.pos.z = isYsort ? (i < 2 ? topDepth : bottomDepth) : topDepth;
1223 std::get<std::shared_ptr<bl::gfx::Animation2D>>(tile.renderObject)
1226 ((getDepthForPosition(level, y, li) + getBottomDepth()) * 0.5f) :
1227 getDepthForPosition(level, y, li));
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();
1243 void EditMap::updateLevelDepths(
unsigned int level) {
1244 for (
unsigned int layer = 0; layer < levels[level].layerCount(); ++layer) {
1245 updateLayerDepths(level, layer);
1249 void EditMap::updateAllDepths() {
1250 if (camera) { camera->updateDepthPlanes(); }
1251 for (
unsigned int level = 0; level < levels.size(); ++level) { updateLevelDepths(level); }
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);
1262 updateLevelDepths(i1);
1263 updateLevelDepths(i2);
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);
1272 EditMap::ExportState::ExportState()
1273 : exportInProgress(false)
1274 , exportJob(nullptr)
1275 , exportComplete(false) {}
1277 void EditMap::exportRendering() {
1283 patch.create(exportState.size.x, exportState.size.y, sf::Color::Transparent);
1284 stitched.create(sp.x, sp.y, sf::Color::Transparent);
1287 const unsigned int rows = sp.x / exportState.size.x;
1288 const unsigned int cols = sp.y / exportState.size.y;
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;
1297 exportState.exportJob->wait();
1298 exportState.exportJob->copyImage(patch);
1301 exportState.exportJob->release();
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));
1311 bl::engine::FrameStage::Update2, [
this]() {
1312 exportState.exportJob =
1313 systems->
engine().renderer().textureExporter().exportTexture(
1314 exportState.renderTexture->getTexture());
1319 stitched.copy(patch, row * exportState.size.x, col * exportState.size.y);
1322 if (hasNext) { queueTask.wait(); }
1327 exportState.exportComplete =
true;
1330 stitched.saveToFile(exportState.outputPath);
1332 bl::dialog::tinyfd_messageBox(
1333 "Rendering Complete",
1334 std::string(
"Rendered map saved to: " + exportState.outputPath).c_str(),
Id
Represents an item in its simplist form.
Collision
The different types of collisions in maps.
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.
Adding this component to an entity will allow it to be rendered.
void setHidden(bool hide)
Set whether the entity is hidden or not.
static Id cast(unsigned int id)
Helper function to cast a raw id to an item Id.
Represents a class of catchable peoplemon.
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.
Basic struct representing a pickup-able item in Map.
Generic map layer class. Can be used for any type of layer.
Represents a renderable light in a Map.
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,...
std::vector< Item > itemsField
std::unordered_map< std::uint16_t, Spawn > spawns
std::vector< LayerSet > levels
std::string unloadScriptField
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 ....
Map()
Creates an empty Map.
std::string loadScriptField
Weather::Type weatherField
bl::resource::Ref< Tileset > tileset
void setupTile(unsigned int level, unsigned int layer, const sf::Vector2u &pos)
const sf::Vector2i & sizeTiles() const
Returns the size of the map in tiles.
std::vector< CharacterSpawn > characterField
bl::ctr::Vector2D< LevelTransition > transitionField
void triggerAnimation(const bl::tmap::Position &position)
bl::rc::lgt::Scene2DLighting & getSceneLighting()
Returns the scene lighting for this map. Only valid after enter() is called.
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.
system::Systems * systems
const std::string & name() const
Returns the name of the map.
sf::Vector2f sizePixels() const
Returns the size of the map in pixels.
std::list< RenderLevel > renderLevels
Basic struct representing a spawn in a Map.
bl::tmap::Position position
static constexpr IdType Blank
Special id for blank tiles.
static std::string getFullPath(const std::string &path)
Generates the full path to the given tileset file.
Represents a town, route, or region within a map. Maps may have many towns. Individual tiles are asso...
void activate(system::Systems &systems, Map &map)
Activates the weather system.
void set(Type type, bool immediate=false)
Sets the current weather type.
static const sf::VulkanFont & MenuFont()
static int PixelsPerTile()
static void warn(const std::string &script)
In debug mode logs a warning if the given script is detected to be legacy.
bool spawnItem(const map::Item &item, map::Map &map)
Spawns an item into the world.
bl::ecs::Entity spawnCharacter(const map::CharacterSpawn &spawn, map::Map &map)
Spawns a trainer or an npc from the given spawn information.
bl::ecs::Entity getEntity(const bl::tmap::Position &pos) const
Returns the entity at the given position or InvalidEntity if not found.
void setMainRenderTarget(bl::rc::RenderTarget &target)
Sets the main render target. Used by the editor.
Owns all primary systems and a reference to the engine.
const bl::engine::Engine & engine() const
Const accessor for the Engine.
Entity & entity()
Returns the entity system.
Render & render()
Returns the render system.
Position & position()
Returns a reference to the position system.
Wrapper over the core::Map class that is directly usable in a bl::gui::GUI.
const std::string & currentFile() const
Returns the current file the map is saving to.
void removeLayer(unsigned int level, unsigned int layer)
Deletes the given layer.
bool spawnIdUnused(unsigned int id) const
Tells whether or not the given id is in use.
bool editorLoad(const std::string &filename)
Loads the map contents from the given file.
std::function< void(const sf::Vector2f &pixels, const sf::Vector2i &tiles)> PositionCb
Called when the map is clicked.
std::function< void()> ActionCb
Called on various event types.
const char * redoDescription() const
Returns the description of what action will be redone if redo is called.
virtual ~EditMap()
Destroy the Edit Map object.
void setTile(unsigned int level, unsigned int layer, const sf::Vector2i &position, core::map::Tile::IdType id, bool isAnim)
Sets a single tile.
void showGrid(bool show)
Set whether or not to render a grid between tiles.
void setWeather(core::map::Weather::Type weather)
Set the weather in the map.
bool unsavedChanges() const
Returns whether or not the map has been updated since last being saved.
void setPlaylist(const std::string &playlist)
Sets the playlist of the map.
void appendTopLayer(unsigned int level)
Creates a new (empty) top layer.
bool editorSave()
Saves the map to the file it was loaded from or created with.
void setControlsEnabled(bool enabled)
Enables or disables the map camera and click controls.
void removeNpcSpawn(const core::map::CharacterSpawn *spawn)
Removes a character spawn.
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...
const std::string & getOnEnterScript() const
Get the OnEnter script.
void shiftLevel(unsigned int level, bool up)
Shifts the given level up or down.
void removeSpawn(unsigned int level, const sf::Vector2i &position)
Removes the spawn at the given position.
void appendYsortLayer(unsigned int level)
Creates a new (empty) y-sort layer.
void setOnEnterScript(const std::string &script)
Set the OnEnter script.
void editNpcSpawn(const core::map::CharacterSpawn *orig, const core::map::CharacterSpawn &spawn)
Edits an existing character spawn.
void setOnExitScript(const std::string &script)
Set the OnExit script.
static Ptr create(const PositionCb &clickCb, const PositionCb &moveCb, const ActionCb &actionCb, const ActionCb &syncCb, core::system::Systems &systems)
Creates a new EditMap.
void addNpcSpawn(const core::map::CharacterSpawn &spawn)
Adds a character spawn to the map.
void appendBottomLayer(unsigned int level)
Creates a new (empty) bottom layer.
void setLevelVisible(unsigned int level, bool visible)
Shows or hides the given level.
void setLayerVisible(unsigned int level, unsigned int layer, bool visible)
Shows or hides the given layer.
void setCatchArea(unsigned int level, const sf::IntRect &area, std::uint8_t id)
Sets a range of catch tiles to a single value.
void setCollisionArea(unsigned int level, const sf::IntRect &area, core::map::Collision id)
Sets a range of collision tiles to a single value.
void redo()
Reapplies the next action in the edit history.
void setName(const std::string &name)
Set the name of the map.
const char * undoDescription() const
Returns the description of what action will be undone if undo is called.
void rotateSpawn(unsigned int level, const sf::Vector2i &tile)
Rotates a player spawn.
std::shared_ptr< EditMap > Ptr
Pointer to an EditMap.
RenderOverlay
Optional render overlays depending on editor state.
@ 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.
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.
void staticRender(const RenderMapWindow ¶ms)
Renders the map contents.
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.
void setRenderOverlay(RenderOverlay overlay, unsigned int level)
Sets which overlay gets rendered on top of the map.
const core::map::CharacterSpawn * getNpcSpawn(unsigned int level, const sf::Vector2i &position) const
Get the Npc Spawn at the given position, if any.
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 ...
friend class rdr::EditMapComponent
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.
void fillCollision(unsigned int level, const sf::Vector2i &position, core::map::Collision id)
Performs a bucket fill of collisions from the given starting position.
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,...
void setCatch(unsigned int level, const sf::Vector2i &position, std::uint8_t id)
Sets a single catch tile.
void fillCatch(unsigned int level, const sf::Vector2i &position, std::uint8_t id)
Performs a bucket fill of catch tiles from the given position.
void appendLevel()
Creates a new level.
const std::string & getOnExitScript() const
Get the OnExit script.
void setCollision(unsigned int level, const sf::Vector2i &position, core::map::Collision id)
Sets a single collision tile.
void undo()
Undoes the previous action in the edit history.
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.
static sf::Color getColor(std::uint8_t index)
Returns the color to use for the given town.
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)
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)
static Action::Ptr create(unsigned int level, unsigned int layer, const sf::Vector2i &pos, core::map::Tile::IdType id, bool isAnim, EditMap &map)
static EditMap::Action::Ptr create(unsigned int level, const sf::Vector2i &pos, core::map::Collision value, const EditMap &map)
static Action::Ptr create(unsigned int level, const sf::Vector2i &pos, core::map::Collision col, EditMap &map)
static EditMap::Action::Ptr create(unsigned int level, const sf::IntRect &area, core::map::Collision value, const EditMap &map)
static EditMap::Action::Ptr create(unsigned int level, const sf::Vector2i &pos, std::uint8_t value, const EditMap &map)
static EditMap::Action::Ptr create(unsigned int level, const sf::IntRect &area, std::uint8_t value, const EditMap &map)
static Action::Ptr create(unsigned int level, const sf::Vector2i &pos, std::uint8_t id, EditMap &map)
static EditMap::Action::Ptr create(const std::string &playlist, const EditMap &editMap)
static EditMap::Action::Ptr create(const std::string &name, const EditMap &editMap)
static EditMap::Action::Ptr create(core::map::Weather::Type type, const EditMap &map)
static EditMap::Action::Ptr create(unsigned int level, Location location)
static EditMap::Action::Ptr create(unsigned int level, unsigned int layer, const EditMap &map)
static EditMap::Action::Ptr create(unsigned int level, unsigned int layer, bool up)
static EditMap::Action::Ptr create(unsigned int level, bool up)
static EditMap::Action::Ptr create()
static EditMap::Action::Ptr create(bool load, const std::string &s, const std::string &p)
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)