code:
#pragma once

#include "Core/Graphics/CheckerboardTexture.hpp"
#include "Core/MapEditor/MapEvent.hpp"
#include "Core/MapEditor/MapRenderer.hpp"

#include "Core/CommonUI/ObjectPicker.hpp"
#include "Core/EventEditor.hpp"
#include "Database/MapInfos.hpp"
#include "Graphics/ParallaxTexture.hpp"
#include "MapEditor/MapCursor.hpp"
#include "MapEditor/MapProperties.hpp"
#include "OREMath/Rect.hpp"
#include "TemplateEditor/Dialog/TemplateName.hpp"
#include "TemplateEditor/Dialog/TemplatesEvent.hpp"

struct MainWindow;
struct Map;
struct MapInfo;

struct MapEditor {
  MapEditor(MainWindow* parent)
  : m_parent(parent)
  , m_tileCursor(this) {}

  void draw(bool closeRequested = false);

  void setMap(MapInfo* info);

  void clearLayers() {}

  void scale(float scale) {
    m_mapScale += scale;
    m_scaleChanged = true;
  }

  void setScale(float scale) {
    if (scale != m_mapScale) {
      m_scaleChanged = true;
    }
    m_mapScale = scale;
  }

  int tileCellX() const { return m_tileCursor.tileX(); }
  int tileCellY() const { return m_tileCursor.tileY(); }
  int tileAbsoluteX() const { return m_tileCursor.absoluteX(); }
  int tileAbsoluteY() const { return m_tileCursor.absoluteY(); }

  int tileSize() const;

  Event* selectedEvent() { return m_selectedEvent; }
  const Event* selectedEvent() const { return m_selectedEvent; }

  void setSelectedEvent(Event* event) {
    m_selectedEvent = event;
    if (event) {
      m_tileCursor.setPosition(event->x(), event->y());
      m_hasScrolled = false;
    }
  }

  bool scrolledToEvent() const { return m_hasScrolled; }

  Event* movingEvent() { return m_movingEvent; }
  const Event* movingEvent() const { return m_movingEvent; }
  void setMovingEvent(Event* event) { m_movingEvent = event; }

  MapRenderer& mapRenderer() { return m_mapRenderer; }
  const MapRenderer& mapRenderer() const { return m_mapRenderer; }

  bool prisonMode() const { return m_prisonMode; }
  void togglePrisonMode() { m_prisonMode ^= 1; }
  void setPrisonMode(const bool prisonMode) { m_prisonMode = prisonMode; }

  Map* map() { return m_mapInfo ? m_mapInfo->map() : nullptr; }
  const Map* map() const { return m_mapInfo ? m_mapInfo->map() : nullptr; }

  MainWindow* project() { return m_parent; }
  const MainWindow* project() const { return m_parent; }

  float zoom() const { return m_mapScale; }

  Rect mapRect() const {
    if (!map()) {
      return {};
    }

    return Rect{0, 0, map()->width(), map()->height()};
  }

  bool isMapPointValid(const Point& point) const { return mapRect().contains(point); }
  bool isRegionMode() const { return m_regionMode; }

  int readMapData(const Point& point, const int layer) const { return m_mapRenderer.tileId(point.x(), point.y(), layer); }

  void writeMapData(const Point& point, const int layer, const int tileId) {
    if (!map()) {
      return;
    }
    map()->setTileAt(tileId, point.x(), point.y(), layer);
  }

  double adjustX(double x);

  double adjustY(double y);

  double roundX(double x);

  double roundY(double y);

  double xWithDirection(double x, Direction d);

  double yWithDirection(double y, Direction d);

  double roundXWithDirection(double x, Direction d);

  double roundYWithDirection(double y, Direction d);

  bool isValid(const double x, const double y) {
    if (!map()) {
      return false;
    }

    return mapRect().contains(x, y);
  }

  bool isPassable(const double x, const double y, Direction d) const { return m_mapRenderer.isPassable(x, y, static_cast<int>(d)); }

  std::vector<Event*> eventsAtNoThrough(int x, int y) {
    if (!map()) {
      return {};
    }
    return map()->eventsAtNoThrough(x, y);
  }

  double deltaX(double x1, double x2) const {
    if (!map()) {
      return 0;
    }
    double result = x1 - x2;
    if (isLoopHorizontally() && std::abs(result) > map()->width() / 2) {
      if (result < 0) {
        result += map()->width();
      } else {
        result -= map()->width();
      }
    }

    return result;
  }

  double deltaY(const double y1, const double y2) const {
    if (!map()) {
      return 0;
    }
    double result = y1 - y2;
    if (isLoopVertically() && std::abs(result) > map()->height() / 2) {
      if (result < 0) {
        result += map()->height();
      } else {
        result -= map()->height();
      }
    }

    return result;
  }

  bool isLoopHorizontally() const {
    if (!map()) {
      return false;
    }

    return map()->scrollType() == ScrollType::Loop_Horizontally || map()->scrollType() == ScrollType::Loop_Both;
  }

  bool isLoopVertically() const {
    if (!map()) {
      return false;
    }
    return map()->scrollType() == ScrollType::Loop_Vertically || map()->scrollType() == ScrollType::Loop_Both;
  }

private:
  void drawParallax(ImGuiWindow* win);

  void drawGrid(ImGuiWindow* win);

  void handleEventDrag();

  void handleMouseInput(ImGuiWindow* win);

  void renderLayerRects(ImGuiWindow* win, const MapRenderer::MapLayer& layer);

  void renderLayerTex(ImGuiWindow* win, const MapRenderer::TileLayer& tLayer);

  void renderLayer(ImGuiWindow* win, const MapRenderer::MapLayer& layer);

  void handleKeyboardShortcuts();

  void updateAllAutotiles();

  void updateAutotilesInRect(const Rect& rect, int layer);

  void updateAutotile(const Point& point, int layer, int flags);

  void updateAutotilesAroundPoint(const Point& point, int layer);

  void updateShadowData(const Point& point);

  int updateFloorTypeAutotile(const Point& point, int layer, int flags) const;

  int updateWallTypeAutotile(const Point& point, int layer, int flags) const;

  int updateWaterfallTypeAutotile(const Point& point, int layer, int flags) const;

  int makeDirectionBit(const Point& nextPos, int tileId, int layer, int flags, bool skipBorder) const;

  bool isGroundTile(const Point& p, int layer) const;

  bool isShadowingTile(const Point& p, int layer) const;

  bool isSameKindTile(const Point& p, int layer, int tileId) const;

  bool isWallOrRoofTile(const Point& p, int layer) const;

  MainWindow* m_parent;
  MapInfo* m_mapInfo = nullptr;
  float m_mapScale = 1.f;
  bool m_scaleChanged = false;
  double m_initialScrollX = 0.0;
  double m_initialScrollY = 0.0;
  bool m_initialScrollSet = false;
  MapCursor m_tileCursor;

  int m_tileId{};
  ParallaxTexture m_parallaxTexture;
  MapRenderer m_mapRenderer;
  CheckerboardTexture m_checkeredBack;
  Event* m_movingEvent = nullptr;
  Event* m_selectedEvent = nullptr;
  bool m_hasScrolled = true;
  bool m_prisonMode{true};
  bool m_regionMode{false};
  bool m_templateSaving{false};

  std::optional<ObjectPicker<Template>> m_templatePicker;
  std::optional<TemplatesEvent> m_eventProperties;
  std::optional<TemplateName> m_templateNamePicker;
  std::optional<Event> m_templateEvent;

  int m_movingEventX = -1;
  int m_movingEventY = -1;
  int m_tempMapWidth = 0;
  int m_tempMapHeight = 0;

  void onParallaxNameModified(Map* info, const std::string& name);
};


code:
#include "Core/MapEditor/MapCursor.hpp"

#include "Core/ImGuiExt/ImGuiUtils.hpp"
#include "imgui_internal.h"

inline float roundUp(const float numToRound, const float multiple) {
  if (multiple == 0)
    return numToRound;

  const float remainder = fmod(numToRound, multiple);
  if (remainder == 0)
    return numToRound;

  return numToRound + multiple - remainder;
}

float MapCursor::alignCoord(const float value) const { return roundUp(value - fmod(value, m_tileSize * m_mapScale), m_tileSize * m_mapScale); }

void MapCursor::setCursorPositionFromTilePosition() {
  m_cursorPos.x = static_cast<float>(absoluteX()) * m_mapScale;
  m_cursorPos.y = static_cast<float>(absoluteY()) * m_mapScale;
  if (!(m_cursorPos.x < 0 || m_cursorPos.y < 0 || m_cursorPos.x >= ((m_mapWidth * m_tileSize) * m_mapScale) || m_cursorPos.y >= ((m_mapHeight * m_tileSize) * m_mapScale))) {
    if (m_cursorPos.x < 0) {
      m_cursorPos.x = 0;
    }
    if (m_cursorPos.x >= ((m_mapWidth * m_tileSize) * m_mapScale)) {
      m_cursorPos.x = ((m_mapWidth * m_tileSize) * m_mapScale);
    }

    if (m_cursorPos.y < 0) {
      m_cursorPos.y = 0;
    }

    if (m_cursorPos.y >= (m_mapHeight * m_tileSize) * m_mapScale) {
      m_cursorPos.y = ((m_mapHeight * m_tileSize) * m_mapScale);
    }
    m_cursorPos.x = alignCoord(m_cursorPos.x);
    m_cursorPos.y = alignCoord(m_cursorPos.y);
  }
}

void MapCursor::update(float mapScale, int mapWidth, int mapHeight, int tileSize, ImGuiWindow* win) {
  m_mapScale = mapScale;
  m_mapWidth = mapWidth;
  m_mapHeight = mapHeight;
  m_tileSize = tileSize;

  /* Keyboard mode is disabled until scrolling can be figured out */
  if (ImGui::IsWindowHovered() && ImGui::IsWindowFocused() &&
      (ImGui::IsKeyPressed(ImGuiKey_LeftArrow) || ImGui::IsKeyPressed(ImGuiKey_RightArrow) || ImGui::IsKeyPressed(ImGuiKey_UpArrow) || ImGui::IsKeyPressed(ImGuiKey_DownArrow))) {
    m_mode = MapCursorMode::Keyboard;

    if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) {
      m_tileX -= 1;
      if (m_tileX < 0) {
        m_tileX = 0;
      }
    }

    if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) {
      m_tileX += 1;
      if (m_tileX >= mapWidth) {
        m_tileX = mapWidth - 1;
      }
    }

    if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) {
      m_tileY -= 1;
      if (m_tileY < 0) {
        m_tileY = 0;
      }
    }

    if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) {
      m_tileY += 1;
      if (m_tileY >= mapHeight) {
        m_tileY = mapHeight - 1;
      }
    }
    setCursorPositionFromTilePosition();

    const float absX = m_cursorPos.x + win->ContentRegionRect.Min.x;
    const float absY = m_cursorPos.y + win->ContentRegionRect.Min.y;
    const auto deadZone = (m_tileSize * 3) * m_mapScale;
    ImGui::ScrollToRect(win, {absX - deadZone, absY - deadZone, absX + deadZone, absY + deadZone});
    m_visible = true;
  } else if (m_mode == MapCursorMode::Mouse) {
    // Is the user attempting to zoom in?
    if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsKeyDown(ImGuiKey_MouseWheelY)) {
      setCursorPositionFromTilePosition();
    } else {
      ImVec2 cursorPos = ImGui::GetIO().MousePos - win->ContentRegionRect.Min;

      if (!(cursorPos.x < 0 || cursorPos.y < 0 || cursorPos.x >= ((mapWidth * tileSize) * m_mapScale) || cursorPos.y >= ((m_mapHeight * tileSize) * m_mapScale))) {
        if (cursorPos.x < 0) {
          cursorPos.x = 0;
        }
        if (cursorPos.x >= ((m_mapWidth * m_tileSize) * m_mapScale)) {
          cursorPos.x = ((m_mapWidth * m_tileSize) * m_mapScale);
        }

        if (cursorPos.y < 0) {
          cursorPos.y = 0;
        }

        if (cursorPos.y >= (mapHeight * m_tileSize) * m_mapScale) {
          cursorPos.y = ((mapHeight * m_tileSize) * m_mapScale);
        }
        cursorPos.x = alignCoord(cursorPos.x);
        cursorPos.y = alignCoord(cursorPos.y);

        m_tileX = (static_cast<int>(cursorPos.x) + 1) / static_cast<int>(m_tileSize * m_mapScale);
        m_tileY = (static_cast<int>(cursorPos.y) + 1) / static_cast<int>(m_tileSize * m_mapScale);

        setCursorPositionFromTilePosition();
        m_visible = true;
      } else {
        m_visible = false;
      }
    }
  }
}

void MapCursor::draw(ImGuiWindow* win) {
  if (!m_visible) {
    return;
  }

  const auto tileSizef = static_cast<float>(m_tileSize);
  const auto outerThickness = ImGui::GetDPIScaledValue(2.0f);
  const auto innerThickness = ImGui::GetDPIScaledValue(1.5f);
  /* Contract the size a bit so that the cursor appears inside the tile cell */
  const ImVec2 cursorContraction = ImGui::GetDPIScaledSize(1.5, 1.5);
  /* Actually render the cursor, two unfilled rectangles: One a bit thicker for the outline, and one thinner for the white accent */
  const auto cursorPos = m_cursorPos + win->ContentRegionRect.Min;
  win->DrawList->AddRect(cursorPos + (ImVec2{0.f, 0.f} * m_mapScale) + cursorContraction, cursorPos + (ImVec2{tileSizef, tileSizef} * m_mapScale) - cursorContraction, 0xFF000000, 0.f, 0,
                         outerThickness);
  win->DrawList->AddRect(cursorPos + (ImVec2{0.f, 0.f} * m_mapScale) + cursorContraction, cursorPos + (ImVec2{tileSizef, tileSizef} * m_mapScale) - cursorContraction, 0xFFFFFFFF, 0.f, 0,
                         innerThickness);
}

void MapCursor::setFromAbsolute(const float x, const float y) {
  m_cursorPos.x = x;
  m_cursorPos.y = y;
  m_cursorPos.x = alignCoord(m_cursorPos.x);
  m_cursorPos.y = alignCoord(m_cursorPos.y);

  m_tileX = (static_cast<int>(m_cursorPos.x) + 1) / static_cast<int>(m_tileSize * m_mapScale);
  m_tileY = (static_cast<int>(m_cursorPos.y) + 1) / static_cast<int>(m_tileSize * m_mapScale);
}

repo_name: Antidote/openrpgeditor

license: mit


code:
#pragma once

#include "imgui.h"

struct ImGuiWindow;
struct MapEditor;

enum class MapCursorMode {
  Mouse,
  Keyboard,
};

struct MapCursor {
  MapCursor() = delete;

  MapCursor(MapEditor* parent)
  : m_parent(parent) {}

  void update(float mapScale, int mapWidth, int mapHeight, int tileSize, ImGuiWindow* win);

  void draw(ImGuiWindow* win);

  int tileX() const { return m_tileX; }
  int tileY() const { return m_tileY; }

  void setPosition(int x, int y) {
    m_tileX = x;
    m_tileY = y;
  }

  float alignCoord(float value) const;

  int absoluteX() const { return m_tileX * m_tileSize; }
  int absoluteY() const { return m_tileY * m_tileSize; }
  void setFromAbsolute(float x, float y);

  int tileSize() const { return m_tileSize; }
  void setTileSize(int tileSize) { m_tileSize = tileSize; }

  MapCursorMode mode() const { return m_mode; }
  void setMouseMode() { m_mode = MapCursorMode::Mouse; }
  void setKeyboardMode() {
    // Disabled due to issues with scrolling
    m_mode = MapCursorMode::Keyboard;
  }

  void setVisible(bool visible) { m_visible = visible; }
  bool visible() const { return m_visible; }

private:
  void setCursorPositionFromTilePosition();

  MapEditor* m_parent = nullptr;
  float m_mapScale{1.f};
  int m_mapHeight{0};
  int m_mapWidth{0};
  int m_tileX{0};
  int m_tileY{0};
  int m_tileSize{48};
  ImVec2 m_cursorPos;
  bool m_visible{true};
  MapCursorMode m_mode = MapCursorMode::Mouse;
};


code:
#include "Core/MapEditor/MapEvent.hpp"

#include "Core/MainWindow.hpp"
#include "Core/MapEditor.hpp"
#include "Core/ResourceManager.hpp"

#include "Core/Graphics/Texture.hpp"
#include "Utils/JavaMath.hpp"

#include "Database/EventCommands/MovementRoute/BlendMode.hpp"
#include "Database/EventCommands/MovementRoute/ChangeOpacity.hpp"
#include "Database/EventCommands/MovementRoute/Jump.hpp"
#include "Database/EventCommands/MovementRoute/PlaySE.hpp"
#include "Database/EventCommands/MovementRoute/Speed.hpp"
#include "Database/EventCommands/MovementRoute/Wait.hpp"
#include "imgui.h"
#include "imgui_internal.h"

#define _USE_MATH_DEFINES
#include "Core/ImGuiExt/ImGuiUtils.hpp"

#include <math.h>

uint32_t MapEvent::mSpriteId = 0;

MapEvent::MapEvent(Event* event)
: IEventRenderer(event) {
  setupPageSettings();
  m_x = m_realX = m_event->x();
  m_y = m_realY = m_event->y();
}

double oscillate(const double minValue, const double maxValue, const double period, const double currentTime) {
  const double rangeDelta = maxValue - minValue;
  const double phase = (sin(2 * M_PI * currentTime / period) + 1) / 2;
  return minValue + phase * rangeDelta;
}

void MapEvent::draw(const float mapScale, const bool isHovered, const bool selected, const bool halfAlpha, const bool updateOnly) {
  if (!m_mapEditor) {
    return;
  }
  if (!m_signalConnected) {
    m_event->modified().connect<&MapEvent::onModified>(this);
    m_signalConnected = true;
  }

  if ((!page()->walkAnime() && page()->moveType() != MoveType::Custom) || m_mapEditor->prisonMode()) {
    m_x = m_realX = m_event->x();
    m_y = m_realY = m_event->y();
    m_direction = page()->image().direction();
  }

  if (!m_mapEditor->prisonMode() && m_event->id() != 0) {
    m_lastFrameTime += 1.f * ImGui::GetIO().DeltaTime;
    /* Only process event logic once every frame at 60FPS */
    if (m_lastFrameTime >= (1.f / 60.f)) {
      if (isStopping()) {
        updateStop();
      }

      if (isJumping()) {
        updateJump();
      }

      if (isMoving()) {
        updateMove();
      }
      updateAnimation();
      m_lastFrameTime = 0.f;
    }
  }

  if (updateOnly) {
    return;
  }
  const bool hasCharacterSheet = !page()->image().characterName().empty();
  const bool isTile = page()->image().tileId() > 0;
  const int characterIndex = page()->image().characterIndex();
  const Direction direction = m_direction;
  const ImGuiWindow* win = ImGui::GetCurrentWindow();
  constexpr ImU32 NormalOutlineCol = 0xFF000000;
  constexpr ImU32 NormalBorderCol = 0xFFFFFFFF;

  ImU32 outlineCol = NormalOutlineCol;
  ImU32 borderCol = NormalBorderCol;

  ImU32 bgColor = 0x7f000000;
  ImU32 imageColor = 0xFFFFFFFF;
  if (selected) {
    const ImU8 r = static_cast<ImU8>(oscillate(0.5, 1.0, 2.0, ImGui::GetTime()) * 255.f);
    bgColor &= 0xFFFFFF00;
    bgColor |= r;
    imageColor = (0xFF << 24) | (r << 16) | (r << 8) | (r << 0);
  }

  if (halfAlpha) {
    outlineCol &= 0x00FFFFFF;
    outlineCol |= (127 << 24);
    borderCol &= 0x00FFFFFF;
    borderCol |= (127 << 24);
    bgColor &= 0x00FFFFFF;
    bgColor |= (63 << 24);
    imageColor &= 0x00FFFFFF;
    imageColor |= (127 << 24);
  }

  auto eventX = static_cast<float>(m_realX * m_mapEditor->tileSize()) * mapScale;
  auto eventY = static_cast<float>(m_realY * m_mapEditor->tileSize()) * mapScale;
  eventX += win->ContentRegionRect.Min.x;
  eventY += win->ContentRegionRect.Min.y;
  const auto eventS = m_mapEditor->tileSize() * mapScale;
  auto evMin = ImVec2{eventX, eventY};
  auto evMax = ImVec2{(eventX + eventS), (eventY + eventS)};

  const auto outerThickness = ImGui::GetDPIScaledValue(2.0f);
  const auto innerThickness = ImGui::GetDPIScaledValue(1.5f);
  const auto contraction = ImGui::GetDPIScaledSize(4, 4);

  if (mapScale > 0.5f) {
    if (m_mapEditor->prisonMode() || m_event->id() == 0 || (((!hasCharacterSheet || !m_characterSheet) && !isTile) && !m_mapEditor->prisonMode())) {
      win->DrawList->AddRectFilled(evMin + contraction, evMax - contraction, bgColor);
    }

    if (hasCharacterSheet && !isTile && m_characterSheet) {
      if (!m_mapEditor->prisonMode() && m_event->id() != 0) {
        auto [min, max] = m_characterSheet.getRectForCharacter(characterIndex, pattern(), direction);

        evMin.x -= ((static_cast<float>(m_characterSheet.characterWidth()) - m_mapEditor->tileSize()) / 2.f) * mapScale;
        evMax.x += ((static_cast<float>(m_characterSheet.characterWidth()) - m_mapEditor->tileSize()) / 2.f) * mapScale;
        evMin.y -= (static_cast<float>(m_characterSheet.characterHeight()) - m_mapEditor->tileSize()) * mapScale;
        win->DrawList->AddImage(m_characterSheet.texture(), evMin, evMax, min, max, imageColor);
      } else {
        auto [min, max] = m_characterSheet.getRectForCharacter(characterIndex, m_originalPattern, direction);

        if (m_characterSheet.characterWidth() == 72) {
          min.xr() *= m_characterSheet.texture().width();
          max.xr() *= m_characterSheet.texture().width();
          min.xr() += 16;
          max.xr() -= 16;
          min.xr() /= m_characterSheet.texture().width();
          max.xr() /= m_characterSheet.texture().width();
        }

        if (m_characterSheet.characterHeight() == 96) {
          min.yr() *= m_characterSheet.texture().height();
          max.yr() *= m_characterSheet.texture().height();
          min.yr() += 24;
          max.yr() -= 32;
          min.yr() /= m_characterSheet.texture().height();
          max.yr() /= m_characterSheet.texture().height();
        }
        win->DrawList->AddImage(m_characterSheet, evMin + contraction, evMax - contraction, min, max, imageColor);
      }
    } else if (m_mapEditor->map() && isTile) {
      const auto& [min, max] = m_characterSheet.getRectForTile(page()->image().tileId());
      if (m_mapEditor->prisonMode()) {
        win->DrawList->AddImage(m_characterSheet, evMin + contraction, evMax - contraction, min, max, imageColor);
      } else {
        win->DrawList->AddImage(m_characterSheet, evMin, evMax, min, max, imageColor);
      }
    }
  }
  if (m_mapEditor->prisonMode() || m_event->id() == 0 || (((!hasCharacterSheet || !m_characterSheet) && !isTile) && !m_mapEditor->prisonMode())) {
    win->DrawList->AddRect(evMin + contraction, evMax - contraction, outlineCol, 0, 0, outerThickness);
    win->DrawList->AddRect(evMin + contraction, evMax - contraction, borderCol, 0, 0, innerThickness);
  }
}

void MapEvent::setMapEditor(MapEditor* mapEditor) {
  m_mapEditor = mapEditor;
  m_prisonMode = !m_mapEditor || m_mapEditor->prisonMode();
}

void MapEvent::updateAnimation() {
  updateAnimationCount();
  if (m_animationCount >= animationWait()) {
    updatePattern();
    m_animationCount = 0;
  }
}

void MapEvent::updateAnimationCount() {
  if (isMoving() && hasWalkAnime()) {
    m_animationCount += 1.5f;
  } else if (hasStepAnime() || !isOriginalPattern()) {
    m_animationCount += 1.f;
  }
}

void MapEvent::updatePattern() {
  if (!hasStepAnime() && m_stopCount > 0) {
    resetPattern();
  } else {
    m_pattern = (m_pattern + 1) % maxPattern();
  }
}

float MapEvent::animationWait() const { return (9 - realMoveSpeed()) * 3; }

float MapEvent::realMoveSpeed() const { return m_moveSpeed + (isDashing() ? 1 : 0); }

bool MapEvent::isNearThePlayer() const {
  double sx = std::abs(deltaXFrom(m_mapEditor->tileCellX()));
  double sy = std::abs(deltaYFrom(m_mapEditor->tileCellY()));

  return (sx + sy) < 20;
}

double MapEvent::deltaXFrom(double x) const { return m_mapEditor->deltaX(m_x, x); }
double MapEvent::deltaYFrom(double y) const { return m_mapEditor->deltaY(m_y, y); }

void MapEvent::updateSelfMove() {
  if (/*!m_locked &&*/ isNearTheScreen() && checkStop(stopCountThreshold())) {
    switch (m_moveType) {
    case MoveType::Random:
      moveTypeRandom();
      break;
    case MoveType::Approach:
      moveTypeTowardPlayer();
      break;
    case MoveType::Custom:
      moveTypeCustom();
      break;
    default:
      break;
    }
  }
}

void MapEvent::moveTypeRandom() {
  switch (Math::randomInt(6)) {
  case 0:
  case 1:
    moveRandom();
    break;
  case 2:
  case 3:
  case 4:
    moveForward();
    break;
  case 5:
    resetStopCount();
    break;
  default:
    break;
  }
}

void MapEvent::moveTypeTowardPlayer() {
  if (isNearThePlayer()) {
    switch (Math::randomInt(6)) {
    case 0:
    case 1:
    case 2:
    case 3:
      moveTowardPlayer();
      break;
    case 4:
      moveRandom();
      break;
    case 5:
      moveForward();
      break;
    default:
      break;
    }
  } else {
    moveRandom();
  }
}

void MapEvent::moveTypeCustom() { updateMoveRoute(); }

void MapEvent::moveRandom() {
  if (const auto d = static_cast<Direction>(2 + Math::randomInt(4) * 2); canPass(m_x, m_y, d)) {
    moveStraight(d);
  }
}

void MapEvent::moveForward() { moveStraight(m_direction); }

void MapEvent::moveBackward() {
  const auto lastDirectionFix = directionFix();
  setDirectionFix(true);
  moveStraight(reverseDir(m_direction));
  setDirectionFix(lastDirectionFix);
}

void MapEvent::moveStraight(const Direction d) {
  setMovementSuccess(canPass(m_x, m_y, d));
  if (isMovementSucceeded()) {
    setDirection(d);
    m_x = m_mapEditor->roundXWithDirection(m_x, d);
    m_y = m_mapEditor->roundYWithDirection(m_y, d);
    m_realX = m_mapEditor->xWithDirection(m_x, reverseDir(d));
    m_realY = m_mapEditor->yWithDirection(m_y, reverseDir(d));
    increaseSteps();
  } else {
    setDirection(d);
    //  checkEventTriggerTouchFront(d);
  }
}

void MapEvent::moveDiagonally(const Direction horz, const Direction vert) {
  setMovementSuccess(canPassDiagonally(m_x, m_y, horz, vert));
  if (isMovementSucceeded()) {
    m_x = m_mapEditor->roundXWithDirection(m_x, horz);
    m_y = m_mapEditor->roundYWithDirection(m_y, vert);
    m_realX = m_mapEditor->xWithDirection(m_x, reverseDir(horz));
    m_realY = m_mapEditor->yWithDirection(m_y, reverseDir(vert));
    increaseSteps();
  }

  if (m_direction == reverseDir(horz)) {
    setDirection(horz);
  }
  if (m_direction == reverseDir(vert)) {
    setDirection(vert);
  }
}

void MapEvent::moveTowardCharacter(const double x, const double y) {
  const double sx = deltaXFrom(x);
  const double sy = deltaYFrom(y);

  if (std::abs(sx) > std::abs(sy)) {
    moveStraight(sx > 0 ? Direction::Left : Direction::Right);
    if (!isMovementSucceeded() && sy != 0) {
      moveStraight(sy > 0 ? Direction::Up : Direction::Down);
    }
  } else if (sy != 0) {
    moveStraight(sy > 0 ? Direction::Up : Direction::Down);
    if (!isMovementSucceeded() && sx != 0) {
      moveStraight(sx > 0 ? Direction::Left : Direction::Right);
    }
  }
}

void MapEvent::moveAwayFromCharacter(const double x, const double y) {
  const double sx = deltaXFrom(x);
  const double sy = deltaYFrom(y);

  if (std::abs(sx) > std::abs(sy)) {
    moveStraight(sx > 0 ? Direction::Right : Direction::Left);
    if (!isMovementSucceeded() && sy != 0) {
      moveStraight(sy > 0 ? Direction::Down : Direction::Up);
    }
  } else if (sy != 0) {
    moveStraight(sy > 0 ? Direction::Down : Direction::Up);
    if (!isMovementSucceeded() && sx != 0) {
      moveStraight(sx > 0 ? Direction::Right : Direction::Left);
    }
  }
}

void MapEvent::moveTowardPlayer() {
  printf("%s approaching player\n", m_event->name().c_str());
  moveTowardCharacter(m_mapEditor->tileCellX(), m_mapEditor->tileCellY());
}

void MapEvent::moveAwayFromPlayer() { moveAwayFromCharacter(m_mapEditor->tileCellX(), m_mapEditor->tileCellY()); }

void MapEvent::jump(const double x, const double y) {
  if (std::abs(x) > std::abs(y)) {
    if (x != 0) {
      setDirection(x < 0 ? Direction::Left : Direction::Right);
    }
  } else if (y != 0) {
    setDirection(y < 0 ? Direction::Up : Direction::Down);
  }

  m_x += x;
  m_y += y;
  m_jumpPeak = std::round(std::sqrt(x * x + y * y));
  m_jumpCount = m_jumpPeak * 2;

  resetStopCount();
  straighten();
}

void MapEvent::straighten() {
  if (hasWalkAnime() || hasStepAnime()) {
    m_pattern = 1;
  }
  m_animationCount = 0;
}

void MapEvent::turnRight90() {
  switch (m_direction) {
  case Direction::Down:
    setDirection(Direction::Left);
    break;
  case Direction::Left:
    setDirection(Direction::Up);
    break;
  case Direction::Right:
    setDirection(Direction::Down);
    break;
  case Direction::Up:
    setDirection(Direction::Right);
    break;
  default:
    break;
  }
}

void MapEvent::turnLeft90() {
  switch (m_direction) {
  case Direction::Down:
    setDirection(Direction::Right);
    break;
  case Direction::Left:
    setDirection(Direction::Down);
    break;
  case Direction::Right:
    setDirection(Direction::Up);
    break;
  case Direction::Up:
    setDirection(Direction::Left);
    break;
  default:
    break;
  }
}

void MapEvent::turn180() { setDirection(reverseDir(m_direction)); }

void MapEvent::turnLeftOrRight90() {
  switch (Math::randomInt(2)) {
  case 0:
    turnRight90();
    break;
  case 1:
    turnLeft90();
    break;
  default:
    break;
  }
}

void MapEvent::turnRandom() { setDirection(static_cast<Direction>(2 + Math::randomInt(4) * 2)); }

void MapEvent::turnTowardCharacter(const double x, const double y) {
  double sx = deltaXFrom(x);
  double sy = deltaYFrom(y);
  if (std::abs(sx) > std::abs(sy)) {
    setDirection(sx > 0 ? Direction::Right : Direction::Left);
  } else if (sy != 0) {
    setDirection(sy > 0 ? Direction::Down : Direction::Up);
  }
}

void MapEvent::turnTowardPlayer() { turnTowardCharacter(m_mapEditor->tileCellX(), m_mapEditor->tileCellY()); }

void MapEvent::turnAwayFromCharacter(const double x, const double y) {
  const double sx = deltaXFrom(x);
  const double sy = deltaYFrom(y);
  if (std::abs(sx) > std::abs(sy)) {
    setDirection(sx > 0 ? Direction::Left : Direction::Right);
  } else if (sy != 0) {
    setDirection(sy > 0 ? Direction::Up : Direction::Down);
  }
}

void MapEvent::turnAwayFromPlayer() { turnAwayFromCharacter(m_mapEditor->tileCellX(), m_mapEditor->tileCellY()); }

void MapEvent::restoreMoveRoute() {
  m_moveRoute = m_originalMoveRoute;
  m_moveRouteIndex = m_originalMoveRouteIndex;
  m_originalMoveRoute = nullptr;
}

bool MapEvent::canPass(const double x, const double y, const Direction d) {
  const double x2 = m_mapEditor->roundXWithDirection(x, d);
  const double y2 = m_mapEditor->roundYWithDirection(y, d);

  if (!m_mapEditor->isValid(x2, y2)) {
    return false;
  }

  if (isThrough() /* || isDebugThrough() */) {
    return true;
  }

  if (!isMapPassable(x, y, d)) {
    return false;
  }

  return !isCollidedWithCharacters(x2, y2);
}

bool MapEvent::canPassDiagonally(const double x, const double y, const Direction horz, const Direction vert) {
  const double x2 = m_mapEditor->roundXWithDirection(x, horz);
  const double y2 = m_mapEditor->roundYWithDirection(y, vert);

  if (canPass(x, y, vert) && canPass(x, y2, horz)) {
    return true;
  }

  if (canPass(x, y, horz) && canPass(x2, y, vert)) {
    return true;
  }

  return false;
}
void MapEvent::updateJump() {
  m_jumpCount--;
  m_realX = (m_realX * m_jumpCount + m_x) / (m_jumpCount + 1.0);
  m_realY = (m_realY * m_jumpCount + m_y) / (m_jumpCount + 1.0);
  // refreshBushDepth();
  if (m_jumpCount == 0) {
    m_realX = m_x = m_mapEditor->roundX(m_x);
    m_realY = m_y = m_mapEditor->roundY(m_y);
  }
}
void MapEvent::updateMove() {
  if (m_event->id() == 21) {
    // printf("BEFORE %s %i %.03f %.03f %.03f %.03f %s\n", m_event->name().c_str(), m_event->id(), m_x, m_y, m_realX, m_realY, magic_enum::enum_name(m_direction).data());
  }
  if (m_x < m_realX) {
    m_realX = std::max(m_realX - distancePerFrame(), m_x);
  }

  if (m_x > m_realX) {
    m_realX = std::min(m_realX + distancePerFrame(), m_x);
  }

  if (m_y < m_realY) {
    m_realY = std::max(m_realY - distancePerFrame(), m_y);
  }

  if (m_y > m_realY) {
    m_realY = std::min(m_realY + distancePerFrame(), m_y);
  }

  if (m_event->id() == 21) {
    // printf("AFTER  %s %i %.03f %.03f %.03f %.03f %s\n", m_event->name().c_str(), m_event->id(), m_x, m_y, m_realX, m_realY, magic_enum::enum_name(m_direction).data());
  }
  if (isMoving()) {
    // refreshBushDepth();
  }
}

void MapEvent::processMoveCommand(const std::shared_ptr<IEventCommand>& command) {
  switch (command->code()) {
  case EventCode::Event_Dummy:
    processRouteEnd();
    break;
  case EventCode::Move_Down:
    moveStraight(Direction::Down);
    break;
  case EventCode::Move_Left:
    moveStraight(Direction::Left);
    break;
  case EventCode::Move_Right:
    moveStraight(Direction::Right);
    break;
  case EventCode::Move_Up:
    moveStraight(Direction::Up);
    break;
  case EventCode::Move_Lower_Left:
    moveDiagonally(Direction::Left, Direction::Down);
    break;
  case EventCode::Move_Lower_Right:
    moveDiagonally(Direction::Right, Direction::Down);
    break;
  case EventCode::Move_Upper_Left:
    moveDiagonally(Direction::Left, Direction::Up);
    break;
  case EventCode::Move_Upper_Right:
    moveDiagonally(Direction::Right, Direction::Up);
    break;
  case EventCode::Move_at_Random:
    moveRandom();
    break;
  case EventCode::Move_toward_Player:
    moveTowardPlayer();
    break;
  case EventCode::Move_away_from_Player:
    moveAwayFromPlayer();
    break;
  case EventCode::_1_Step_Forward:
    moveForward();
    break;
  case EventCode::_1_Step_Backward:
    moveBackward();
    break;
  case EventCode::Jump: {
    const auto jumpCmd = std::dynamic_pointer_cast<MovementJumpCommand>(command);
    jump(jumpCmd->x, jumpCmd->y);
  } break;
  case EventCode::Wait_del_Movement: {
    const auto waitCmd = std::dynamic_pointer_cast<MovementWaitCommand>(command);
    m_waitCount = waitCmd->duration - 1;
  } break;
  case EventCode::Turn_Down:
    setDirection(Direction::Down);
    break;
  case EventCode::Turn_Left:
    setDirection(Direction::Left);
    break;
  case EventCode::Turn_Right:
    setDirection(Direction::Right);
    break;
  case EventCode::Turn_Up:
    setDirection(Direction::Up);
    break;
  case EventCode::Turn_90_deg_Right:
    turnRight90();
    break;
  case EventCode::Turn_90_deg_Left:
    turnLeft90();
    break;
  case EventCode::Turn_180_deg:
    turn180();
    break;
  case EventCode::Turn_90_deg_Left_or_Right:
    turnLeftOrRight90();
    break;
  case EventCode::Turn_at_Random:
    turnRandom();
    break;
  case EventCode::Turn_toward_Player:
    turnTowardPlayer();
    break;
  case EventCode::Turn_away_from_Player:
    turnAwayFromPlayer();
    break;
  case EventCode::Switch_ON:
    // TODO: Event/Variable state?
    break;
  case EventCode::Switch_OFF:
    break;
  case EventCode::Speed: {
    const auto speedCmd = std::dynamic_pointer_cast<MovementSpeedCommand>(command);
    setMoveSpeed(speedCmd->speed);
  } break;
  case EventCode::Frequency: {
    const auto freqCmd = std::dynamic_pointer_cast<MovementFrequencyCommand>(command);
    setMoveFrequency(freqCmd->frequency);
  } break;
  case EventCode::Walking_Animation_ON:
    setWalkAnime(true);
    break;
  case EventCode::Walking_Animation_OFF:
    setWalkAnime(false);
    break;
  case EventCode::Stepping_Animation_ON:
    setStepAnime(true);
    break;
  case EventCode::Stepping_Animation_OFF:
    setStepAnime(false);
    break;
  case EventCode::Direction_Fix_ON:
    setDirectionFix(true);
    break;
  case EventCode::Direction_Fix_OFF:
    setDirectionFix(false);
    break;
  case EventCode::Through_ON:
    setThrough(true);
    break;
  case EventCode::Through_OFF:
    setThrough(false);
    break;
  case EventCode::Transparent_ON:
    setTransparent(true);
    break;
  case EventCode::Transparent_OFF:
    setTransparent(false);
    break;
  case EventCode::Change_Opacity: {
    const auto opacityCmd = std::dynamic_pointer_cast<MovementChangeOpacityCommand>(command);
    setOpacity(opacityCmd->opacity);
  } break;
  case EventCode::Change_Blend_Mode: {
    const auto blendModeCmd = std::dynamic_pointer_cast<MovementChangeBlendModeCommand>(command);
    setBlendMode(blendModeCmd->mode);
  } break;
  case EventCode::Play_SE_del_Movement:
    // const auto seCmd = std::dynamic_pointer_cast<MovementPlaySECommand>(command);
    //  TODO: SE audio
    break;
  case EventCode::Script_del_Movement:
    // TODO: How to handle being unable to run Javascript?
    break;
  default:
    break;
  }
}

void MapEvent::processRouteEnd() {
  if (m_moveRoute->repeat()) {
    m_moveRouteIndex = 0;
  } else if (m_moveRouteForcing) {
    m_moveRouteForcing = false;
    restoreMoveRoute();
  }
}

bool MapEvent::isCollidedWithEvents(double x, double y) {
  auto events = m_mapEditor->eventsAtNoThrough(x, y);
  return std::ranges::any_of(events, [&](auto& e) { return e != m_event && e->renderer()->page()->priorityType() == EventPriority::Same_as_characters; });
}

bool MapEvent::isCollidedWithCharacters(const double x, const double y) { return isCollidedWithEvents(x, y) /*|| isCollidedWithVehicles(x, y)*/; }

Direction MapEvent::reverseDir(const Direction d) {
  switch (d) {
  case Direction::Up:
    return Direction::Down;
  case Direction::Down:
    return Direction::Up;
  case Direction::Left:
    return Direction::Right;
  case Direction::Right:
    return Direction::Left;
  default:
    return d;
  }
}

bool MapEvent::isMapPassable(const double x, const double y, const Direction d) const {
  const double x2 = m_mapEditor->roundXWithDirection(x, d);
  const double y2 = m_mapEditor->roundYWithDirection(y, d);
  const Direction d2 = reverseDir(d);
  return m_mapEditor->isPassable(x, y, d) && m_mapEditor->isPassable(x2, y2, d2);
}

void MapEvent::clearPageSettings() {
  m_moveType = MoveType::Fixed;
  m_trigger = EventTriggerType::Action_Button;
  setThrough(true);
}

void MapEvent::eventPointerInvalidated() {
  clearPageSettings();
  const auto page = m_page;
  setPage(-1);
  setPage(page);
}

IEventRenderer* IEventRenderer::create(Event* ev) { return new MapEvent(ev); }

double MapEvent::scrolledX() const { return m_mapEditor->adjustX(m_realX); }
double MapEvent::scrolledY() const { return m_mapEditor->adjustY(m_realY); }

bool MapEvent::isNearTheScreen() {
  double gw = ImGui::GetCurrentWindow()->ClipRect.Max.x;
  double gh = ImGui::GetCurrentWindow()->ClipRect.Max.y;
  double tw = m_mapEditor->tileSize();
  double th = m_mapEditor->tileSize();

  double px = scrolledX() * tw + tw / 2 - gw / 2;
  double py = scrolledY() * th + th / 2 - gh / 2;

  return px >= -gw && px <= gw && py >= -gh && py <= gh;
}


code:
#pragma once

#include "Core/Graphics/CharacterSheet.hpp"
#include "Database/Event.hpp"

struct MapEditor;

class MapEvent final : public IEventRenderer {
public:
  MapEvent() = delete;
  MapEvent(const MapEvent&) = delete;
  MapEvent(MapEvent&&) = delete;
  MapEvent& operator=(const MapEvent&) = delete;
  MapEvent& operator=(MapEvent&&) = delete;
  void draw(float mapScale, bool isHovered, bool selected, bool halfAlpha, bool updateOnly) override;
  void setupPageSettings() {
    if (page()) {
      m_characterSheet = CharacterSheet(page()->image().characterName(), page()->image().tileId() > 0, page()->image().tileId());
      if (m_originalDirection != page()->image().direction()) {
        m_originalDirection = page()->image().direction();
        m_prelockDirection = Direction::Retain;
        setDirectionFix(false);
        setDirection(page()->image().direction());
      }
      if (m_originalPattern != page()->image().pattern()) {
        m_originalPattern = page()->image().pattern();
        setPattern(page()->image().pattern());
      }
      setMoveFrequency(static_cast<int>(page()->moveFrequency()));
      m_originalMoveFrequency = m_moveFrequency;
      setMoveSpeed(static_cast<int>(page()->moveSpeed()));
      m_originalMoveSpeed = static_cast<int>(page()->moveSpeed());
      setPriorityType(page()->priorityType());
      setThrough(page()->through());
      setWalkAnime(page()->walkAnime());
      setStepAnime(page()->stepAnime());
      setDirectionFix(page()->directionFix());
      setMoveRoute(&page()->moveRoute());
      m_moveType = page()->moveType();
      m_trigger = page()->trigger();
      page()->image().modified().connect<&MapEvent::onModified>(this);
      page()->modified().connect<&MapEvent::onModified>(this);
    }
  }
  void setPage(const int idx) override {
    if (idx == m_page) {
      return;
    }

    if (page()) {
      page()->image().modified().disconnect<&MapEvent::onModified>(this);
      page()->modified().disconnect<&MapEvent::onModified>(this);
    }
    m_page = idx;
    setupPageSettings();
  }

  void setMapEditor(MapEditor* mapEditor);

  bool through() const { return m_through; }
  void setThrough(const bool through) { m_through = through; }
  bool isDashing() const { return m_isDashing; }
  void setDashing(const bool dashing) { m_isDashing = dashing; }
  bool transparent() const { return m_transparent; }
  void setTransparent(const bool transparent) { m_transparent = transparent; }
  bool isMoving() const { return m_x != m_realX || m_y != m_realY; }
  bool directionFix() const { return m_directionFix; }
  void setDirectionFix(const bool fix) { m_directionFix = fix; }
  bool hasWalkAnime() const { return m_walkAnime; }
  void setWalkAnime(const bool walkAnime) { m_walkAnime = walkAnime; }
  bool hasStepAnime() const { return m_stepAnime; }
  void setStepAnime(const bool step) { m_stepAnime = step; }
  bool isOriginalPattern() const { return m_pattern == m_originalPattern; }
  void setPattern(int pattern) { m_pattern = pattern; }
  int pattern() const { return m_pattern < 3 ? m_pattern : 1; }
  int opacity() const { return m_opacity; }
  void setOpacity(const int opacity) { m_opacity = opacity; }
  Blend blendMode() const { return m_blendMode; }
  void setBlendMode(const Blend blend) { m_blendMode = blend; }
  void setPriorityType(EventPriority priority) { m_priorityType = priority; }

  static constexpr int maxPattern() { return 4; }

  EventPage* page() const override { return m_event->page(m_page); }
  int pageIndex() const override { return m_page; }

  double x() const override { return m_realX; }
  double y() const override { return m_realY; }

  int screenZ() const override { return static_cast<int>(page()->priorityType()) * 2 + 1; }
  uint32_t spriteId() const override { return m_spriteId; }

  void increaseSteps() {
    // if (isOnLadder()) {
    //   setDirection(Direction::Up);
    // }

    resetStopCount();
    // refreshBushDepth();
  }

  void memorizeMoveRoute() {
    m_originalMoveRoute = m_moveRoute;
    m_originalMoveRouteIndex = m_moveRouteIndex;
  }

  bool isMoveRouteBruteForcing() const { return m_moveRouteForcing; }

  void setMoveRoute(MovementRoute* moveRoute) {
    m_moveRoute = moveRoute;
    m_moveRouteIndex = 0;
    m_moveRouteForcing = false;
  }

  void forceMoveRoute(MovementRoute* moveRoute) {
    if (!m_originalMoveRoute) {
      memorizeMoveRoute();
    }
    m_moveRoute = moveRoute;
    m_moveRouteIndex = 0;
    m_moveRouteForcing = true;
    m_waitCount = 0;
  }

  void updateJump();
  void updateMoveRoute() {
    if (m_waitCount > 0) {
      m_waitCount--;
    } else {
      setMovementSuccess(true);
      if (const auto cmd = m_moveRouteIndex < m_moveRoute->list().size() ? m_moveRoute->list()[m_moveRouteIndex] : nullptr) {
        processMoveCommand(cmd);
        advanceMoveRouteIndex();
      }
    }
  }

  void advanceMoveRouteIndex() {
    if (m_moveRoute && (isMovementSucceeded() || m_moveRoute->skippable())) {
      const auto numCommands = m_moveRoute->list().size() - 1;
      m_moveRouteIndex++;
      if (m_moveRoute->repeat() && m_moveRouteIndex >= numCommands) {
        m_moveRouteIndex = 0;
      }
    }
  }

  double jumpHeight() { return (m_jumpPeak * m_jumpPeak - std::pow(std::abs(m_jumpCount - m_jumpPeak), 2)) / 2; }

protected:
  friend class IEventRenderer* IEventRenderer::create(Event* ev);
  explicit MapEvent(Event* event);
  void eventPointerInvalidated() override;

  void updateAnimation();
  void updateAnimationCount();
  void updatePattern();
  float animationWait() const;
  float realMoveSpeed() const;
  void resetPattern() { setPattern(1); }

  void updateSelfMove();
  void moveTypeRandom();
  void moveTypeTowardPlayer();
  void moveTypeCustom();
  void resetStopCount() { m_stopCount = 0; }

  void moveRandom();
  void moveForward();
  void moveBackward();
  void moveStraight(Direction d);
  void moveDiagonally(Direction horz, Direction vert);
  void moveTowardPlayer();
  void moveAwayFromPlayer();
  void jump(double x, double y);

  void turnRight90();
  void turnLeft90();
  void turn180();
  void turnLeftOrRight90();
  void turnRandom();
  void moveTowardCharacter(double x, double y);
  void moveAwayFromCharacter(double x, double y);
  void turnTowardCharacter(double x, double y);
  void turnTowardPlayer();
  void turnAwayFromCharacter(double x, double y);
  void turnAwayFromPlayer();

  bool isNearThePlayer() const;

  void setMoveSpeed(double speed) { m_moveSpeed = speed; }
  void setMoveFrequency(double freq) { m_moveFrequency = freq; }

  bool canPass(double x, double y, Direction d);
  bool canPassDiagonally(double x, double y, Direction horz, Direction vert);

  bool isThrough() const override { return m_through; }
  bool isMapPassable(double x, double y, Direction d) const;

  bool isCollidedWithEvents(double x, double y);
  bool isCollidedWithCharacters(double x, double y);
  static Direction reverseDir(Direction d);
  void setMovementSuccess(const bool success) { m_movementSuccess = success; }
  bool isMovementSucceeded() const { return m_movementSuccess; }
  void setDirection(const Direction d) {
    if (!m_directionFix && d != Direction::Retain) {
      m_direction = d;
    }
    resetStopCount();
  }

  bool checkStop(const int threshold) const { return m_stopCount > threshold; }

  double stopCountThreshold() const { return 30 * (5 - moveFrequency()); }

  int moveFrequency() const { return m_moveFrequency; }

  bool isJumping() const { return m_jumpCount > 0; }
  bool isStopping() const { return !isMoving() && !isJumping(); }

  void updateStop() {
    m_stopCount++;

    updateSelfMove();
  }

  void updateMove();

  double distancePerFrame() const { return pow(2, realMoveSpeed()) / 256; }

  void processMoveCommand(const std::shared_ptr<IEventCommand>& command);
  void processRouteEnd();

  double deltaXFrom(double x) const;
  double deltaYFrom(double y) const;
  void restoreMoveRoute();
  void straighten();

  bool isNearTheScreen();

  double scrolledX() const;
  double scrolledY() const;

  void clearPageSettings();

private:
  MapEditor* m_mapEditor = nullptr;
  CharacterSheet m_characterSheet;
  uint32_t m_spriteId = mSpriteId++;
  int m_page{0};
  double m_x{0};
  double m_y{0};
  double m_realX{0};
  double m_realY{0};
  bool m_isDashing{false};
  double m_moveSpeed{4};
  double m_originalMoveSpeed{4};
  double m_moveFrequency{6};
  double m_originalMoveFrequency{6};
  int m_pattern{1};
  int m_originalPattern{1};
  float m_animationCount{0};
  int m_stopCount{0};
  bool m_through{false};
  bool m_movementSuccess{false};
  int m_waitCount{0};
  Direction m_direction{Direction::Retain};
  Direction m_originalDirection{Direction::Retain};
  Direction m_prelockDirection{Direction::Retain};
  static uint32_t mSpriteId;
  bool m_walkAnime{false};
  bool m_stepAnime{false};
  bool m_directionFix{false};
  bool m_transparent{false};
  int m_opacity{0};
  Blend m_blendMode{Blend::Normal};
  MoveType m_moveType;
  EventPriority m_priorityType{EventPriority::Same_as_characters};
  EventTriggerType m_trigger{EventTriggerType::Action_Button};
  MovementRoute* m_moveRoute = nullptr;
  int m_moveRouteIndex = -1;
  bool m_moveRouteForcing{false};

  MovementRoute* m_originalMoveRoute = nullptr;
  int m_originalMoveRouteIndex = -1;

  int m_jumpPeak = 0;
  int m_jumpCount = 0;
  bool m_prisonMode{false};

  float m_lastFrameTime = 0.f;
  void onModified(IModifiable*) { setupPageSettings(); }

  bool m_signalConnected{false};
};


code:
#include "Core/MapEditor/MapProperties.hpp"

#include "Core/ImGuiExt/ImGuiUtils.hpp"
#include "Database/Database.hpp"

#include <Database/MapInfo.hpp>

#include <imgui.h>
#include <imgui_internal.h>

Map* MapProperties::map() const { return m_mapInfo->map(); }

void MapProperties::resizeMap(const int width, const int height) const {
  if (map()) {
    map()->resize(width, height);
  }
}

std::tuple<bool, bool> MapProperties::draw() {
  if (!m_open || !m_mapInfo || !map()) {
    return {!m_open, m_confirmed};
  }

  if (ImGui::Begin(std::format("{}###mapproperties", trNOOP("Map Properties")).c_str(), &m_open)) {
    char buf[4096]{};
    ImGui::BeginGroupPanel();
    {
      ImGui::SeparatorText(trNOOP("General Settings"));
      ImGui::AlignTextToFramePadding();
      ImGui::BeginGroup();
      {
        ImGui::Text("%s", trFormat("Name - ID: {}", m_mapInfo->id()).c_str());
        ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x / 2);
        strncpy(buf, m_mapInfo->name().c_str(), 4096);
        if (ImGui::InputText("##map_name", buf, 4096)) {
          m_mapInfo->setName(buf);
        }
      }
      ImGui::EndGroup();
      ImGui::SameLine();
      ImGui::BeginGroup();
      {
        ImGui::Text("%s", trNOOP("Display Name"));
        ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
        strncpy(buf, map()->displayName().c_str(), 4096);
        if (ImGui::InputText("##map_display_name", buf, 4096)) {
          map()->setDisplayName(buf);
        }
      }
      ImGui::EndGroup();
      ImGui::AlignTextToFramePadding();
      ImGui::BeginGroup();
      {
        ImGui::Text("%s", trNOOP("Tileset"));
        strncpy(buf, m_mapInfo->name().c_str(), 4096);
        const auto& tilesetName = Database::instance()->tilesets.tileset(map()->tilesetId())->name();
        ImGui::PushID("##map_tileset_button");
        if (ImGui::Button((tilesetName.empty() ? "##map_tileset_button_empty" : tilesetName).c_str(), ImVec2{ImGui::GetContentRegionAvail().x / 2, 0})) {}
        ImGui::PopID();
      }
      ImGui::EndGroup();
      ImGui::SameLine();
      ImGui::BeginGroup();
      {
        ImGui::Text("%s", trNOOP("Width"));
        ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x / 2);
        ImGui::DragInt("##map_width", &m_tempWidth, 0, 0, 256);
      }
      ImGui::EndGroup();
      ImGui::SameLine();
      ImGui::BeginGroup();
      {
        ImGui::Text("%s", trNOOP("Height"));
        ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
        ImGui::DragInt("##map_height", &m_tempHeight, 0, 0, 256);
      }
      ImGui::EndGroup();
      ImGui::AlignTextToFramePadding();
      ImGui::BeginGroup();
      {
        ImGui::Text("%s", trNOOP("Scroll Type"));
        ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x / 2);
        if (ImGui::BeginCombo("##map_scroll_type", DecodeEnumName(magic_enum::enum_name(map()->scrollType())).c_str())) {
          for (const auto& e : magic_enum::enum_values<ScrollType>()) {
            if (ImGui::Selectable(DecodeEnumName(magic_enum::enum_name(e)).c_str(), e == map()->scrollType())) {
              map()->setScrollType(e);
            }
            if (e == map()->scrollType()) {
              ImGui::SetItemDefaultFocus();
            }
          }
          ImGui::EndCombo();
        }
      }
      ImGui::EndGroup();
      ImGui::SameLine();
      ImGui::BeginGroup();
      {
        ImGui::Text("%s", trNOOP("Enc. Steps"));
        ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x / 2);
        auto encounterStep = map()->encounterStep();
        if (ImGui::DragInt("##map_enc_steps", &encounterStep, 0, 0, 999)) {
          map()->setEncounterStep(encounterStep);
        }
      }
      ImGui::EndGroup();
    }
    ImGui::Separator();
    {
      ImGui::AlignTextToFramePadding();
      ImGui::BeginGroup();
      {
        bool autoplayBgm = map()->autoplayBgm();
        if (ImGui::Checkbox(trNOOP("Autoplay BGM"), &autoplayBgm)) {
          map()->setAutoplayBgm(autoplayBgm);
        }
        ImGui::PushItemFlag(ImGuiItemFlags_Disabled, !map()->autoplayBgm());
        {
          ImGui::PushID("##map_bgm_button");
          ImGui::SetNextItemWidth((ImGui::GetContentRegionAvail().x / 2));
          const std::string text = map()->bgm().name().empty() ? "##map_bgm_button_empty" : map()->bgm().name();
          if (ImGui::Button(text.c_str(), ImVec2{ImGui::GetContentRegionAvail().x / 2, 0})) {}
          ImGui::PopID();
        }
        ImGui::PopItemFlag();
      }
      ImGui::EndGroup();
      ImGui::SameLine();
      ImGui::BeginGroup();
      {
        bool autoplayBgs = map()->autoplayBgs();
        if (ImGui::Checkbox(trNOOP("Autoplay BGS"), &autoplayBgs)) {
          map()->setAutoplayBgs(autoplayBgs);
        }
        ImGui::PushItemFlag(ImGuiItemFlags_Disabled, !map()->autoplayBgs());
        {
          ImGui::PushID("##map_bgs_button");
          const std::string text = map()->bgs().name().empty() ? "##map_bgs_button_empty" : map()->bgs().name();
          if (ImGui::Button(text.c_str(), ImVec2{ImGui::GetContentRegionAvail().x, 0})) {}
          ImGui::PopID();
        }
        ImGui::PopItemFlag();
      }
      ImGui::EndGroup();
      ImGui::AlignTextToFramePadding();
      ImGui::BeginGroup();
      {
        bool specifyBattleback = map()->specifyBattleback();
        if (ImGui::Checkbox(trNOOP("Battleback"), &specifyBattleback)) {
          map()->setSpecifyBattleback(specifyBattleback);
        }
        ImGui::PushItemFlag(ImGuiItemFlags_Disabled, !map()->specifyBattleback());
        {
          ImGui::PushID("##map_battleback_button");
          // TODO: Combine battleBack1Name and battleBack2Name
          std::string text = map()->bgs().name().empty() ? "##map_battleback_button_empty" : map()->battleback1Name();
          if (ImGui::Button(text.c_str(), ImVec2{(ImGui::GetContentRegionMax().x / 2) - 15, 0})) {}
          ImGui::PopID();
        }
        ImGui::PopItemFlag();
      }
      ImGui::EndGroup();
      ImGui::SameLine();
      ImGui::BeginGroup();
      {
        bool disableDashing = map()->disableDashing();
        if (ImGui::Checkbox(trNOOP("Disable Dashing"), &disableDashing)) {
          map()->setDisableDashing(disableDashing);
        }
        ImGui::PushItemFlag(ImGuiItemFlags_Disabled, !map()->disableDashing());
        ImGui::PopItemFlag();
      }
      ImGui::EndGroup();
    }
    ImGui::Separator();
    {
      ImGui::AlignTextToFramePadding();
      ImGui::BeginGroup();
      {
        ImGui::Text("%s", trNOOP("Parallax Background"));
        ImGui::PushID("##map_parallax_button");
        std::string text = map()->parallaxName().empty() ? "##map_parallax_button_empty" : map()->parallaxName();
        if (ImGui::Button(text.c_str(), ImVec2{(ImGui::GetContentRegionMax().x / 2) - 15, 0})) {}
        ImGui::PopID();
      }
      ImGui::EndGroup();
      ImGui::AlignTextToFramePadding();
      ImGui::BeginGroup();
      {
        bool parallaxLoopX = map()->parallaxLoopX();
        if (ImGui::Checkbox(trNOOP("Loop Horizontally"), &parallaxLoopX)) {
          map()->setParallaxLoopX(parallaxLoopX);
        }
        ImGui::PushItemFlag(ImGuiItemFlags_Disabled, !map()->parallaxLoopX());
        {
          ImGui::SetNextItemWidth((ImGui::GetContentRegionMax().x / 2) - 15);
          int parallaxSx = map()->parallaxSx();
          if (ImGui::DragInt("##map_parallax_Sx", &parallaxSx, 0, 0, 999)) {
            map()->setParallaxSx(parallaxSx);
          }
        }
        ImGui::PopItemFlag();
      }
      ImGui::EndGroup();
      ImGui::SameLine();
      ImGui::BeginGroup();
      {
        bool parallaxLoopY = map()->parallaxLoopY();
        if (ImGui::Checkbox(trNOOP("Loop Vertically"), &parallaxLoopY)) {
          map()->setParallaxLoopY(parallaxLoopY);
        }
        ImGui::PushItemFlag(ImGuiItemFlags_Disabled, !map()->parallaxLoopY());
        {
          int parallaxSy = map()->parallaxSy();
          ImGui::SetNextItemWidth((ImGui::GetContentRegionMax().x / 2) - 15);
          if (ImGui::DragInt("##map_parallax_Sy", &parallaxSy, 0, 0, 999)) {
            map()->setParallaxSy(parallaxSy);
          }
        }
        ImGui::PopItemFlag();
      }
      ImGui::EndGroup();
      ImGui::AlignTextToFramePadding();
      ImGui::BeginGroup();
      {
        bool parallaxShow = map()->parallaxShow();
        if (ImGui::Checkbox(trNOOP("Show in Editor"), &parallaxShow)) {
          map()->setParallaxShow(parallaxShow);
        }
        ImGui::PushItemFlag(ImGuiItemFlags_Disabled, !map()->parallaxShow());
        ImGui::PopItemFlag();
      }
      ImGui::EndGroup();
      ImGui::BeginGroup();
      {
        ImGui::Text("%s", trNOOP("Note"));

        strncpy(buf, map()->note().c_str(), 4096);
        if (ImGui::InputTextMultiline("##map_note", buf, 4096, ImGui::GetContentRegionAvail())) {
          map()->setNote(buf);
        }
      }
      ImGui::EndGroup();
    }
    ImGui::EndGroupPanel();
  }
  ImGui::End();

  return {!m_open, m_confirmed};
}


code:
#pragma once
#include "Core/CommonUI/IDialogController.hpp"
#include "Database/Map.hpp"

class Map;
class MapInfo;
class MapProperties final : public IDialogController {
public:
  MapProperties()
  : IDialogController("Map Properties") {}

  void setMapInfo(MapInfo* mapInfo) {
    if (m_mapInfo == mapInfo) {
      return;
    }

    m_mapInfo = mapInfo;
    if (m_mapInfo && map()) {
      m_tempWidth = map()->width();
      m_tempHeight = map()->height();
    }
  }
  std::tuple<bool, bool> draw() override;

  [[nodiscard]] Map* map() const;

  void setOpen(const bool open) override {
    IDialogController::setOpen(open);
    if (m_mapInfo && map()) {
      m_tempWidth = map()->width();
      m_tempHeight = map()->height();
    }
  }

private:
  void resizeMap(int width, int height) const;
  MapInfo* m_mapInfo{nullptr};
  int m_tempWidth{0};
  int m_tempHeight{0};
};


code:
#pragma once

#include <algorithm>
#include <vector>

#include "Core/Graphics/Texture.hpp"

#include "Database/Map.hpp"
#include "Database/TileHelper.hpp"
#include "Database/Tilesets.hpp"

class Map;
class Tileset;
struct SDL_Texture;

struct TileRect {
  int tileId;
  float u;
  float v;
  float x;
  float y;
  int tileWidth;
  int tileHeight;
  int tileSheet;
  float animX;
  float animY;
};

class MapRenderer {
public:
  struct TileLayer {
    Texture tex;
    std::vector<TileRect> rects;
  };

  struct MapLayer {
    std::vector<TileLayer> tileLayers;
    void addRect(int setId, int tileId, float u, float v, float x, float y, int tileWidth, int tileHeight, float animX = 0.f, float animY = 0.f) {
      tileLayers[setId].rects.push_back({tileId, u, v, x, y, tileWidth, tileHeight, setId, animX, animY});
    }
  };

  MapRenderer();
  void setMap(const Map* map, const Tileset* tileset, int tileWidth = 48, int tileHeight = 48);
  [[nodiscard]] const Map* map() const { return m_map; }

  void update();

  [[nodiscard]] int tileId(int x, int y, int z) const;
  [[nodiscard]] bool isOverworld() const { return m_tileset && m_tileset->mode() == Tileset::Mode::World; }

  [[nodiscard]] std::vector<std::optional<int>> tilesetFlags() const {
    if (m_tileset) {
      return m_tileset->flags();
    }
    return {};
  }

  [[nodiscard]] std::vector<int> layeredTiles(const int x, const int y) const;
  [[nodiscard]] std::vector<int> allTiles(const int x, const int y) const;
  [[nodiscard]] int autoTileType(const int x, const int y, const int z) const;
  [[nodiscard]] bool checkPassage(const int x, const int y, const int bit) const;
  [[nodiscard]] bool isPassable(const int x, const int y, const int d) const;

  [[nodiscard]] int tileIdFromCoords(int x, int y, int z) { return (z * m_map->height() + y) * m_map->width() + x; }

  [[nodiscard]] bool isHigherTile(int tileId) { return m_tileset->flag(tileId) & 0x10; }

  [[nodiscard]] bool isTableTile(int tileId) { return TileHelper::isTileA2(tileId) && m_tileset->flag(tileId) & 0x80; }

  [[nodiscard]] bool isOverpassPosition(int mx, int my) { return false; }

  void paintTiles(int startX, int startY, int x, int y);

  const Tileset* tileset() const { return m_tileset; }

  std::array<MapLayer, 6> m_lowerLayers;
  std::array<MapLayer, 6> m_upperLayers;

private:
  bool m_frameUpdated = true;
  void drawTile(MapLayer& layer, int tileId, int dx, int dy);
  void drawAutoTile(MapLayer& layer, int tileId, int dx, int dy);
  void drawNormalTile(MapLayer& layer, int tileId, int dx, int dy) const;
  void drawTableEdge(MapLayer& layer, int tileId, int dx, int dy) const;
  void beginBlit(SDL_Texture* source);
  void blitImage(SDL_Texture* bitmap, int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh);
  void endBlit();
  void clearRect(SDL_Texture* clr, int x, int y, int w, int h);

  const Map* m_map = nullptr;
  const Tileset* m_tileset = nullptr;
  bool m_isValid = false;
  int m_tileWidth{48};
  int m_tileHeight{48};
};


code:
#if 0
      if (m_lowerLayer.empty()) {
        m_lowerLayer.clear();
        m_upperLayer.clear();
        for (int y = 0; y < m_map->height; y++) {
          for (int x = 0; x < m_map->width; x++) {
            for (int i = 0; i < 2; ++i) {
              static const int desiredLayers[2]{0, 2};
              int tileId = m_mapRenderer.tileId(x, y, i);
              if (m_mapRenderer.isHigherTile(tileId)) {
                m_mapRenderer.getTileRect(m_upperLayer, m_mapRenderer.tileId(x, y, desiredLayers[i]), x * 48, y * 48);
              } else {
                m_mapRenderer.getTileRect(m_lowerLayer, m_mapRenderer.tileId(x, y, desiredLayers[i]), x * 48, y * 48);
              }
            }
          }
        }
      }

      for (auto tile : m_lowerLayer) {
        const auto& tex = m_mapRenderer.m_tilesetTextures[tile.setNum];
        const float x0 = tile.x;
        const float x1 = tile.x + tile.tileWidth;
        const float y0 = tile.y;
        const float y1 = tile.y + tile.tileHeight;
        const float u0 = tile.u / tex.width();
        const float u1 = (tile.u + tile.tileWidth) / tex.width();
        const float v0 = tile.v / tex.height();
        const float v1 = (tile.v + tile.tileHeight) / tex.height();
        win->DrawList->AddImage(tex.get(), win->ContentRegionRect.Min + (ImVec2{x0, y0} * m_mapScale),
                                win->ContentRegionRect.Min + (ImVec2{x1, y1} * m_mapScale), ImVec2{u0, v0},
                                ImVec2{u1, v1});
      }
      for (auto tile : m_upperLayer) {
        const auto& tex = m_mapRenderer.m_tilesetTextures[tile.setNum];
        const float x0 = tile.x;
        const float x1 = tile.x + tile.tileWidth;
        const float y0 = tile.y;
        const float y1 = tile.y + tile.tileHeight;
        const float u0 = tile.u / tex.width();
        const float u1 = (tile.u + tile.tileWidth) / tex.width();
        const float v0 = tile.v / tex.height();
        const float v1 = (tile.v + tile.tileHeight) / tex.height();
        win->DrawList->AddImage(tex.get(), win->ContentRegionRect.Min + (ImVec2{x0, y0} * m_mapScale),
                                win->ContentRegionRect.Min + (ImVec2{x1, y1} * m_mapScale), ImVec2{u0, v0},
                                ImVec2{u1, v1});
      }
#endif

#if 0
  if (m_map) {
    const Tileset* tileset = m_parent->tileset(m_map->tilesetId);
    if (tileset && !tileset->name.empty() && !tileset->flags.empty()) {
      if (ImGui::Begin("Tile Debugger")) {
        ImGui::BeginGroup();
        {
          for (int z = 0; z < 6; ++z) {
            int tileCellIndex = (z * m_map->height + m_tileCellY) * m_map->width + m_tileCellX;
            if (tileCellIndex > m_map->data.size()) {
              continue;
            }
            ImGui::Text("Tile %i, layer %i, cell {%i, %i}, absolute {%i, %i}", tileCellIndex, z, m_tileCellX,
                        m_tileCellY, m_tileCellX * 48, m_tileCellY * 48);
            int tileId = m_map->data[tileCellIndex];
            if (tileId >= tileset->flags.size()) {
              continue;
            }
            int flags = tileset->flags[tileId];
            ImGui::Text(
                "Tile ID %i, Flags 0x%04X, IsAutoTile %i, IsA1Tile %i, IsA2Tile %i, IsA3Tile %i, IsA4Tile %i, IsA5Tile "
                "%i",
                tileId, flags, MapRenderer::isAutoTile(tileId), MapRenderer::isTileA1(tileId),
                MapRenderer::isTileA2(tileId), MapRenderer::isTileA3(tileId), MapRenderer::isTileA4(tileId),
                MapRenderer::isTileA5(tileId));
          }
        }
        ImGui::EndGroup();
        // ImGui::BeginGroup();
        // {
        //   ImGuiWindow* win = ImGui::GetCurrentWindow();
        //   for (int z = 0; z < 6; ++z) {
        //     int tileId = m_mapRenderer.tileId(m_tileCellX, m_tileCellY, z);
        //     if (MapRenderer::isAutoTile(tileId)) {
        //       auto tileRects = m_mapRenderer.getTileRect(tileId, m_tileCellX, m_tileCellY);
        //
        //       for (auto tile : tileRects) {
        //         Texture tex = m_resourceManager->loadTilesetImage(tileset->tilesetNames[tile.setNum]);
        //         if (tex) {
        //           win->DrawList->AddImage(
        //               tex.get(), win->ContentRegionRect.Min + ImVec2{tile.x * tile.tileWidth, tile.y *
        //               tile.tileHeight}, win->ContentRegionRect.Min + ImVec2{(tile.x * tile.tileWidth) +
        //               tile.tileWidth,
        //                                                   (tile.y * tile.tileHeight) + tile.tileHeight},
        //               {tile.u / tex.width(), tile.v / tex.height()},
        //               {(tile.u + tile.tileWidth) / tex.width(), (tile.v + tile.tileHeight) / tex.height()});
        //         }
        //       }
        //     }
        //   }
        // }
        // ImGui::EndGroup();
      }
    }
    ImGui::End();
  }

#endif

#if 0
  if (m_map) {
    printf("%zu bytes, %i w %i h\n", m_map->data.size(), m_map->width, m_map->height);
    for (int z = 0; z < 6; z++) {
      printf("-----------------Map Layer %.4i-----------------\n", z);
      for (int y = 0; y < m_map->height; y++) {
        for (int x = 0; x < m_map->width; x++) {
          int tileId = (z * m_map->height + y) * m_map->width + x;
          printf("%.4X ", m_map->data[tileId]);
        }
        printf("\n");
      }
    }
  }
#endif


code:
#include "Core/MapListView.hpp"

#include "Application.hpp"
#include "Core/MainWindow.hpp"
#include "Database/MapInfos.hpp"

#include "imgui.h"

void MapListView::drawContextMenu(MapInfo& in) {
  if (ImGui::BeginPopupContextItem()) {
    // Ensure we have the right-clicked map selected
    if (m_selectedMapId != in.id()) {
      m_selectedMapId = in.id();
      m_parent->setMap(in);
    }
    if (ImGui::BeginMenu("State")) {

      if (!Database::instance()->config.mapStateList.contains(m_selectedMapId)) {
        Database::instance()->config.mapStateList.insert(std::make_pair(m_selectedMapId, MapStateType::None));
      }
      bool isChecked = Database::instance()->config.mapStateList.at(m_selectedMapId) == MapStateType::WorkInProgress;
      bool isChecked_2 = Database::instance()->config.mapStateList.at(m_selectedMapId) == MapStateType::LowPriority;
      if (ImGui::Checkbox("Work in Progress##orpg_mapview_toggle_wip", &isChecked)) {
        if (isChecked) {
          if (Database::instance()->config.mapStateList.contains(m_selectedMapId)) {
            Database::instance()->config.mapStateList.at(m_selectedMapId) = MapStateType::WorkInProgress;
          } else {
            Database::instance()->config.mapStateList.insert(std::make_pair(m_selectedMapId, MapStateType::WorkInProgress));
          }
        } else {
          if (Database::instance()->config.mapStateList.contains(m_selectedMapId)) {
            Database::instance()->config.mapStateList.at(m_selectedMapId) = MapStateType::None;
          } else {
            Database::instance()->config.mapStateList.insert(std::make_pair(m_selectedMapId, MapStateType::None));
          }
        }
      }
      if (ImGui::Checkbox("Low Priority##orpg_mapview_toggle_lowprio", &isChecked_2)) {
        if (isChecked_2) {
          if (Database::instance()->config.mapStateList.contains(m_selectedMapId)) {
            Database::instance()->config.mapStateList.at(m_selectedMapId) = MapStateType::LowPriority;
          } else {
            Database::instance()->config.mapStateList.insert(std::make_pair(m_selectedMapId, MapStateType::LowPriority));
          }
        } else {
          if (Database::instance()->config.mapStateList.contains(m_selectedMapId)) {
            Database::instance()->config.mapStateList.at(m_selectedMapId) = MapStateType::None;
          } else {
            Database::instance()->config.mapStateList.insert(std::make_pair(m_selectedMapId, MapStateType::None));
          }
        }
      }
      ImGui::EndMenu();
    }
    if (ImGui::MenuItem("Edit...")) {
      m_parent->openMapProperties(&in);
    }
    if (ImGui::MenuItem("New...")) {
      // TODO: Add new map to directory based on current location. Maybe add it as a subdirectory of the current parent?
    }

    if (ImGui::Selectable("Load...")) {
      // TODO: Inserts a map from templates?
    }

    ImGui::Separator();

    if (ImGui::MenuItem("Copy")) {
      // TODO: Map -> Clipboard
    }

    if (ImGui::MenuItem("Paste")) {
      // TODO: Clipboard -> Map inserted
    }

    if (ImGui::MenuItem("Delete")) {
      // TODO: Deletes a map
    }

    ImGui::Separator();

    if (ImGui::MenuItem("Find...")) {
      // TODO: Finds a map based on a string input
    }

    if (ImGui::MenuItem("Find Next")) {
      // TODO: FInds the next map result based on a string input
    }

    if (ImGui::MenuItem("Find Previous")) {
      // TODO: FInds the previous map result based on a string input
    }

    ImGui::Separator();

    if (ImGui::MenuItem("Shift...")) {
      // TODO: Moves the map position in directory
    }

    if (ImGui::MenuItem("Generate Dungeon...")) {
      // ???
    }
    ImGui::EndPopup();
  }
}
void MapListView::recursiveDrawTree(MapInfo& in) {
  const std::string id = ("#orpg_map_" + std::to_string(in.id()) + in.name());
  bool isStyle{false};
  if (Database::instance()->config.mapStateList.contains(in.id())) {
    if (Database::instance()->config.mapStateList.at(in.id()) == MapStateType::WorkInProgress) {
      ImGui::PushStyleColor(ImGuiCol_Text, ImVec4{1.0f, 0.0f, 0.0f, 1.0f});
      isStyle = true;
    }
    if (Database::instance()->config.mapStateList.at(in.id()) == MapStateType::LowPriority) {
      ImGui::PushStyleColor(ImGuiCol_Text, ImVec4{1.0f, 0.5f, 0.0f, 1.0f});
      isStyle = true;
    }
  }
  std::string nameStr = in.id() == 0 ? Database::instance()->system.gameTitle() : in.name();
  if (nameStr.empty()) {
    nameStr = in.name();
  }
  const bool open = ImGui::TreeNodeEx(id.c_str(),
                                      (in.expanded() ? ImGuiTreeNodeFlags_DefaultOpen : 0) | (in.children().empty() ? ImGuiTreeNodeFlags_Leaf : 0) |
                                          (m_selectedMapId == in.id() ? ImGuiTreeNodeFlags_Selected : 0) | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick,
                                      "%s", nameStr.c_str());
  if (isStyle) {
    ImGui::PopStyleColor();
  }
  if (m_selectedMapId == in.id() && m_needsScroll) {
    ImGui::SetScrollHereY();
    m_needsScroll = false;
  }

  if ((ImGui::IsItemClicked(ImGuiMouseButton_Left) || ImGui::IsItemFocused()) && m_selectedMapId != in.id()) {
    m_selectedMapId = in.id();
    m_parent->setMap(*m_mapInfos->map(in.id()));
  }

  if (ImGui::BeginDragDropSource()) {
    if (in.id() != 0) {
      const auto payload = m_mapInfos->map(in.id());
      ImGui::SetDragDropPayload("##orpg_dnd_mapinfo", payload, sizeof(uintptr_t));
      ImGui::Text("%s", payload->name().c_str());
    }
    ImGui::EndDragDropSource();
  }

  if (ImGui::BeginDragDropTarget()) {
    if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("##orpg_dnd_mapinfo")) {
      if (const auto* src = static_cast<MapInfo*>(payload->Data)) {
        // Are we trying to put our parent into ourselves?
        if (in.parentId() != src->id()) {
          // Nope, we can just safely set the parent id without worry
          m_mapInfos->map(src->id())->setParentId(in.id());
        } else {
          // Yes, we have to swap parents, and assign the new parent
          m_mapInfos->map(in.id())->setParentId(m_mapInfos->map(src->id())->parentId());
          m_mapInfos->map(src->id())->setParentId(in.id());
        }
        /* Set ordering to force it at the end */
        if (!in.children().empty()) {
          m_mapInfos->map(src->id())->setOrder(in.children().back()->order() + 1);
        }
        m_mapTreeStale = true;
      }
    }
    ImGui::EndDragDropTarget();
  }
  drawContextMenu(in);

  if (open) {
    for (const auto& mapInfo : in.children()) {
      recursiveDrawTree(*mapInfo);
    }
  }

  if (ImGui::IsItemToggledOpen()) {
    in.setExpanded(!in.expanded());
  }
  if (open) {
    ImGui::TreePop();
  }
}
#define PRINT_MAP_ORDERING 0
#if PRINT_MAP_ORDERING
static bool orderingPrinted = false;
void recursivePrintOrdering(const MapInfo& in) {
  static int indent = 0;
  std::cout << std::string(indent, '\t') + std::format("id {} name {} order {}", in.id, in.name, in.order) << std::endl;
  indent++;
  for (auto& mapInfo : in.children()) {
    recursivePrintOrdering(*mapInfo);
  }
  indent--;
}
#endif

void MapListView::draw() {
  if (ImGui::Begin((tr("Maps") + "###maps").c_str(), nullptr, ImGuiWindowFlags_HorizontalScrollbar)) {
    if (ImGui::Checkbox(trNOOP("Display as list"), &m_listMode)) {
      m_needsScroll = true;
    }
    ImGui::BeginChild("##maplist");
    {
      if (m_mapInfos && !m_mapInfos->empty()) {
        if (!m_listMode) {
          recursiveDrawTree(m_mapInfos->root());
        } else {
          for (auto& mapInfo : m_mapInfos->mapInfos()) {
            if (!mapInfo || mapInfo->id() == 0) {
              continue;
            }

            if (ImGui::Selectable(std::format("{}##map{}", mapInfo->name(), mapInfo->id()).c_str(), m_selectedMapId == mapInfo->id())) {
              m_selectedMapId = mapInfo->id();
              m_parent->setMap(*mapInfo);
            }
            if (m_selectedMapId == mapInfo->id() && m_needsScroll) {
              ImGui::SetScrollHereY();
              m_needsScroll = false;
            }
            drawContextMenu(*mapInfo);
          }
        }
      }
    }
    ImGui::EndChild();
  }
  ImGui::End();

  if (m_mapInfos && m_mapTreeStale) {
    m_mapInfos->buildTree(true);
    m_mapInfos->rebuildOrdering();
    m_mapTreeStale = false;
#if PRINT_MAP_ORDERING
    orderingPrinted = false;
#endif
  }

#if PRINT_MAP_ORDERING
  if (m_mapInfos && !orderingPrinted) {
    recursivePrintOrdering(m_mapInfos->root());
    orderingPrinted = true;
  }
#endif
}

void MapListView::recursiveExpandParents(MapInfo& in) {
  if (!in.expanded() && m_selectedMapId != in.id()) {
    in.setExpanded(true);
  }

  if (in.parentId() != 0) {
    recursiveExpandParents(*m_mapInfos->map(in.parentId()));
  }
}

void MapListView::setCurrentMapId(const int id, const bool doExpand) {
  if (id == m_selectedMapId) {
    return;
  }

  m_selectedMapId = id;

  if (doExpand && m_selectedMapId >= 0) {
    recursiveExpandParents(*m_mapInfos->map(m_selectedMapId));
  }
  m_needsScroll = true;
}

MapInfo* MapListView::currentMapInfo() { return m_mapInfos->map(m_selectedMapId); }
const MapInfo* MapListView::currentMapInfo() const { return m_mapInfos->map(m_selectedMapId); }


code:
#pragma once

struct MainWindow;
struct MapInfos;
struct MapInfo;
struct MapListView {
  MapListView(MainWindow* parent)
  : m_parent(parent) {}

  void setMapInfos(MapInfos* mapInfos) { m_mapInfos = mapInfos; }

  void draw();

  MapInfo* currentMapInfo();
  const MapInfo* currentMapInfo() const;

  void reset() {
    m_mapInfos = nullptr;
    m_selectedMapId = -1;
  }

  void setCurrentMapId(int id, bool doExpand = false);

private:
  void recursiveExpandParents(MapInfo& in);
  void drawContextMenu(MapInfo& in);
  void recursiveDrawTree(MapInfo& in);
  MainWindow* m_parent = nullptr;
  MapInfos* m_mapInfos = nullptr;
  int m_selectedMapId = -1;
  bool m_mapTreeStale{false};
  bool m_needsScroll{false};
  bool m_listMode{false};
};


code:
#include "Core/NWJSVersionManager.hpp"
#include "Core/Settings.hpp"

#include "App/ProjectInfo.hpp"
#include "Database/EventCommands/ChangeVictoryME.hpp"
#include "imgui.h"
#include "imgui_internal.h"

#include <cstring>
#include <curl/curl.h>
#include <iostream>
#include <memory>

#include <SDL3/SDL.h>

#include <regex>
#include <string_view>

bool versionCompare(const std::string& v1, const std::string& v2) {
  std::istringstream iss1(v1);
  std::istringstream iss2(v2);
  std::string token1, token2;

  while (std::getline(iss1, token1, '.') && std::getline(iss2, token2, '.')) {
    if (token1[0] == 'v') {
      token1.erase(0, 1);
    }
    if (token2[0] == 'v') {
      token2.erase(0, 1);
    }
    int num1 = std::stoi(token1);
    int num2 = std::stoi(token2);

    if (num1 != num2) {
      return num1 > num2; // Change to '<' for ascending order
    }
  }

  // If one version has more parts than the other
  return std::distance(std::istream_iterator<std::string>(iss1), std::istream_iterator<std::string>()) > std::distance(std::istream_iterator<std::string>(iss2), std::istream_iterator<std::string>());
}

std::string extractVersionFromHtml(const std::string& html) {
  // Regex pattern to capture the version value without the trailing slash
  std::regex pattern(R"(href="([^/]+)/)");
  std::smatch matches;

  if (std::regex_search(html.begin(), html.end(), matches, pattern)) {
    return matches[1]; // Return the extracted version
  }
  return ""; // Return an empty string if no match is found
}

NWJSVersionManager::NWJSVersionManager(std::string_view nwjsPath)
: m_NWJSPath(nwjsPath) {
  m_versionDownloadHandle = m_downloadManager.addDownload(m_NWJSPath + "/index.html", (std::filesystem::temp_directory_path() / "nwjs_versions.html").generic_string());
}

bool NWJSVersionManager::getListing() {
  std::filesystem::path path = std::filesystem::temp_directory_path() / "nwjs_versions.html";
  if (!(m_versionDownloadHandle != -1 && m_downloadManager.transferComplete(m_versionDownloadHandle)) || !exists(path)) {
    return false;
  }

  std::ifstream file(path.generic_string(), std::ios::binary);

  if (!file.is_open()) {
    return false;
  }

  std::vector<std::string> lines;
  std::string line;
  while (std::getline(file, line)) {
    lines.push_back(line);
  }

  for (const auto& l : lines) {
    const auto& version = extractVersionFromHtml(l);
    if (version.empty() || version[0] != 'v') {
      continue;
    }

    m_versions.push_back(version);
  }

  /* Sort versions in descending order to the latest (largest version number) is the first in the list */
  std::ranges::sort(m_versions, versionCompare);
  remove(path);
  return !m_versions.empty();
}

void NWJSVersionManager::draw() {
  if (!m_isOpen) {
    return;
  }

  m_downloadManager.processDownloads();

  if (m_versions.empty()) {
    if (getListing() && !Settings::instance()->currentNWJSVersion.empty()) {
      for (int i = 0; i < m_versions.size(); ++i) {
        if (m_versions[i] == Settings::instance()->currentNWJSVersion) {
          m_selectedVersion = i;
        }
      }
    }
  }

  if (ImGui::Begin("NWJS Version Manager", &m_isOpen)) {
    if (m_versions.empty()) {
      ImGui::TextUnformatted((m_versionDownloadHandle == -1 || m_downloadManager.transferComplete(m_versionDownloadHandle)) ? trNOOP("Unable to get version list!")
                                                                                : trNOOP("Retrieving version listing..."));
    } else {
      ImGui::Text(trNOOP("Current Version: %s"), Settings::instance()->currentNWJSVersion.empty() ? trNOOP("None") : Settings::instance()->currentNWJSVersion.c_str());
      ImGui::Text(trNOOP("Latest Version: %s"), m_versions[0].c_str());
      ImGui::SeparatorText(trNOOP("Available Versions"));
      std::string version = m_selectedVersion < 0 ? trNOOP("None") : m_versions[m_selectedVersion];
      if (ImGui::BeginCombo("##NWJSVersion", version.c_str())) {
        if (ImGui::Selectable(trNOOP("None"), m_selectedVersion == -1)) {
          m_selectedVersion = -1;
          Settings::instance()->currentNWJSVersion.clear();
        }
        int i = 0;
        for (const auto& v : m_versions) {
          if (ImGui::Selectable(v.c_str(), i == m_selectedVersion)) {
            m_selectedVersion = i;
            Settings::instance()->currentNWJSVersion = v;
          }
          ++i;
        }
        ImGui::EndCombo();
      }
      ImGui::SameLine();
      ImGui::BeginDisabled(m_selectedVersion == -1);
      // TODO: Add ia32, x64, and arm64 checkboxes for each applicable platform
      // TODO: Figure out issues with DownloadManager
      if (ImGui::Button(trNOOP("Download"))) {
#if 0
        const char* confPath = SDL_GetPrefPath(App::COMPANY_NAMESPACE, App::APP_NAME);
        if (m_downloadWindows) {
          m_downloadManager.addDownload(std::format("{0}/{1}/nwjs-{1}-win-x64.zip", m_NWJSPath, m_versions[m_selectedVersion]),
                                        std::format("{0}/nwjs-{1}-win-x64.zip", confPath, m_versions[m_selectedVersion]));
          if (m_downloadSDK) {
            m_downloadManager.addDownload(std::format("{0}/{1}/nwjs-sdk-{1}-win-x64.zip", m_NWJSPath, m_versions[m_selectedVersion]),
                                          std::format("{0}/nwjs-sdk-{1}-win-x64.zip", confPath, m_versions[m_selectedVersion]));
          }
        }

        if (m_downloadMacOS) {
          m_downloadManager.addDownload(std::format("{0}/{1}/nwjs-{1}-osx-x64.zip", m_NWJSPath, m_versions[m_selectedVersion]),
                                        std::format("{0}/nwjs-{1}-osx-x64.zip", confPath, m_versions[m_selectedVersion]));
          m_downloadManager.addDownload(std::format("{0}/{1}/nwjs-{1}-osx-x64.zip", m_NWJSPath, m_versions[m_selectedVersion]),
                                        std::format("{0}/nwjs-{1}-osx-x64.zip", confPath, m_versions[m_selectedVersion]));
          if (m_downloadSDK) {
            m_downloadManager.addDownload(std::format("{0}/{1}/nwjs-sdk-{1}-osx-x64.zip", m_NWJSPath, m_versions[m_selectedVersion]),
                                          std::format("{0}/{1}/nwjs-sdk-{1}-osx-x64.zip", confPath, m_versions[m_selectedVersion]));
            m_downloadManager.addDownload(std::format("{0}/{1}/nwjs-sdk-{1}-osx-arm64.zip", m_NWJSPath, m_versions[m_selectedVersion]),
                                          std::format("{0}/nwjs-sdk-{1}-osx-arm64.zip", confPath, m_versions[m_selectedVersion]));
          }
        }

        if (m_downloadLinux) {
          m_downloadManager.addDownload(std::format("{0}/{1}/nwjs-{1}-linux-x64.tar.gz", m_NWJSPath, m_versions[m_selectedVersion]),
                                        std::format("{0}/nwjs-{1}-linux-x64.tar.gz", confPath, m_versions[m_selectedVersion]));
          if (m_downloadSDK) {
            m_downloadManager.addDownload(std::format("{0}/{1}/nwjs-sdk-{1}-linux-x64.tar.gz", m_NWJSPath, m_versions[m_selectedVersion]),
                                          std::format("{0}/nwjs-sdk-{1}-linux-x64.tar.gz", confPath, m_versions[m_selectedVersion]));
          }
        }
        SDL_free((void*)confPath);
#endif
      }
      ImGui::EndDisabled();
      ImGui::Checkbox(trNOOP("Windows"), &m_downloadWindows);
      ImGui::SameLine();
      ImGui::Checkbox(trNOOP("macOS"), &m_downloadMacOS);
      ImGui::SameLine();
      ImGui::Checkbox(trNOOP("Linux"), &m_downloadLinux);
      ImGui::SameLine();
      ImGui::Checkbox(trNOOP("SDK"), &m_downloadSDK);
    }
#if 0
    ImGui::BeginChild("##progress_list", ImGui::GetContentRegionAvail() - ImGui::GetStyle().FramePadding, ImGuiChildFlags_Borders, ImGuiWindowFlags_HorizontalScrollbar);
    {
      ImGui::BeginColumns("##progress_columns", 2);
      for (const auto& handle : m_downloadManager.handles()) {
        ImGui::TextUnformatted(handle.url().data());
        ImGui::NextColumn();
        printf("%ld, %ld, %g\n", handle.bytesTotal(), handle.bytesDownloaded(), handle.percent());

        ImGui::ProgressBar(handle.percent());
        ImGui::NextColumn();
      }
      ImGui::EndColumns();
    }
    ImGui::EndChild();
#endif
  }
  ImGui::End();
}


code:
#pragma once

#include "DownloadManager.hpp"

#include <string>
#include <vector>

class NWJSVersionManager {
public:
  explicit NWJSVersionManager(std::string_view nwjsPath);

  bool getListing();

  bool downloadSelectedVersion();

  void draw();

  [[nodiscard]] bool isOpen() const { return m_isOpen; }
  void open() { m_isOpen = true; }

private:
  DownloadManager m_downloadManager;

  int m_versionDownloadHandle = -1;
  std::string m_NWJSPath;
  std::vector<std::string> m_versions;
  int m_selectedVersion = -1;
  bool m_downloadWindows
#ifdef _WIN32
      {true};
#else
      {false};
#endif

  bool m_downloadMacOS
#ifdef __APPLE__
      {true};
#else
      {false};
#endif

  bool m_downloadLinux
#ifdef __linux__
      {true};
#else
      {false};
#endif

  bool m_downloadSDK{true};

  bool m_isOpen = false;
};


code:
#include "Core/ReferenceSearch.hpp"

#include "MainWindow.hpp"
ReferenceSearch::ReferenceSearch() {}
void ReferenceSearch::findAllReferences(int targetId, SearchType type) {
  m_results.clear();
  for (auto& mapInfo : Database::instance()->mapInfos.mapInfos()) {
    if (!mapInfo || mapInfo->id() == 0) {
      continue;
    }

    if (mapInfo->map()) {
      for (auto& event : mapInfo->map()->events()) {
        if (!event) {
          continue;
        }
        for (auto& [ref, cond] : event->isConditionalReference(targetId, type)) {
          if (!ref) {
            continue;
          }
          m_results.emplace_back(mapInfo->id(), event->id(), ref);
        }
      }
    }
  }
  searchAllListsByTarget(targetId, type);
  searchAllCommonByTarget(targetId, type);
  searchAllEffectsByTarget(targetId, type);
}
void ReferenceSearch::findAllReferences(const std::string& text, SearchType type) {
  m_results.clear();
  for (auto& result : m_results) {
    auto refEvent = result.getEvent();
    int pageIndex{0};
    for (auto& page : refEvent.pages()) {
      int stepIndex{0};
      for (auto& cmd : page.list()) {
        if (cmd->hasStringReference(text, type)) {
          m_list.emplace_back(result.getMapId(), result.getEventId(), cmd, pageIndex, stepIndex);
        }
        stepIndex++;
      }
      pageIndex++;
    }
  }
  searchAllListsByText(text, type);
  searchAllCommonByText(text, type);
}
void ReferenceSearch::searchAllListsByTarget(int targetId, SearchType type) {
  m_list.clear();
  for (auto& mapInfo : Database::instance()->mapInfos.mapInfos()) {
    if (!mapInfo || mapInfo->id() == 0) {
      continue;
    }
    if (mapInfo->map()) {
      for (auto& event : mapInfo->map()->events()) {
        if (event) {
          int pageIndex{0};
          for (auto& page : event.value().pages()) {
            int stepIndex{0};
            for (auto& cmd : page.list()) {
              if (cmd->hasReference(targetId, type)) {
                m_list.emplace_back(mapInfo->id(), event.value().id(), cmd, pageIndex, stepIndex);
              }
              stepIndex++;
            }
            pageIndex++;
          }
        }
      }
    }
  }
}
void ReferenceSearch::searchAllListsByText(const std::string& text, SearchType type) {
  m_list.clear();
  for (auto& mapInfo : Database::instance()->mapInfos.mapInfos()) {
    if (!mapInfo || mapInfo->id() == 0) {
      continue;
    }
    if (mapInfo->map()) {
      for (auto& event : mapInfo->map()->events()) {
        if (event) {
          int pageIndex{0};
          for (auto& page : event.value().pages()) {
            int stepIndex{0};
            for (auto& cmd : page.list()) {
              if (cmd->hasStringReference(text, type)) {
                m_list.emplace_back(mapInfo->id(), event.value().id(), cmd, pageIndex, stepIndex);
              }
              stepIndex++;
            }
            pageIndex++;
          }
        }
      }
    }
  }
}
void ReferenceSearch::searchAllCommonByTarget(int targetId, SearchType type) {
  m_common.clear();
  for (auto& common : Database::instance()->commonEvents.events()) {
    if (common.has_value()) {
      int index{0};
      for (auto& cmd : common.value().commands()) {
        if (cmd->hasReference(targetId, type)) {
          m_common.emplace_back(common.value().id(), cmd, index);
        }
        index++;
      }
      if (type == SearchType::Switch) {
        if (common.value().switchId() == targetId) {
          m_common.emplace_back(common.value().id(), -2);
        }
      }
    }
  }
}
void ReferenceSearch::searchAllCommonByText(const std::string& text, SearchType type) {
  m_common.clear();
  int index{0};
  for (auto& common : Database::instance()->commonEvents.events()) {
    if (common.has_value()) {
      for (auto& cmd : common.value().commands()) {
        if (cmd->hasStringReference(text, type)) {
          m_common.emplace_back(common.value().id(), cmd, index);
        }
        index++;
      }
    }
  }
}

void ReferenceSearch::searchAllEffectsByTarget(int targetId, SearchType type) {
  m_effects.clear();
  int index{0};
  if (type == SearchType::CommonEvent) {
    // Skills
    for (auto& skill : Database::instance()->skills.skills()) {
      for (auto& effect : skill.effects()) {
        if (effect.code() == EffectCode::Common_Event) {
          if (effect.value1() == targetId) {
            m_effects.emplace_back(SpecialDataParent::Skills, skill.id(), index);
          }
        }
        index++;
      }
    }
    // Items
    index = 0;
    for (auto& item : Database::instance()->items.items()) {
      for (auto& effect : item.effects()) {
        if (effect.code() == EffectCode::Common_Event) {
          if (effect.value1() == targetId) {
            m_effects.emplace_back(SpecialDataParent::Items, item.id(), index);
          }
        }
        index++;
      }
    }
  }
  if (type == SearchType::State) {
    for (auto& skill : Database::instance()->skills.skills()) {
      for (auto& effect : skill.effects()) {
        if (effect.code() == EffectCode::Add_State) {
          if (effect.value1() == targetId) {
            m_effects.emplace_back(SpecialDataParent::Skills, skill.id(), index);
          }
        }
        if (effect.code() == EffectCode::Remove_State) {
          if (effect.value1() == targetId) {
            m_effects.emplace_back(SpecialDataParent::Skills, skill.id(), index);
          }
        }
      }
      index++;
    }
    // Items
    index = 0;
    for (auto& item : Database::instance()->items.items()) {
      for (auto& effect : item.effects()) {
        if (effect.code() == EffectCode::Add_State) {
          if (effect.value1() == targetId) {
            m_effects.emplace_back(SpecialDataParent::Items, item.id(), index);
          }
        }
        if (effect.code() == EffectCode::Remove_State) {
          if (effect.value1() == targetId) {
            m_effects.emplace_back(SpecialDataParent::Items, item.id(), index);
          }
        }
        index++;
      }
    }
  }
  if (type == SearchType::Skill) {
    for (auto& skill : Database::instance()->skills.skills()) {
      for (auto& effect : skill.effects()) {
        index++;
        if (effect.code() == EffectCode::Learn_Skill) {
          if (effect.value1() == targetId) {
            m_effects.emplace_back(SpecialDataParent::Skills, skill.id(), index);
          }
        }
      }
    } // Items
    index = 0;
    for (auto& item : Database::instance()->items.items()) {
      for (auto& effect : item.effects()) {
        if (effect.code() == EffectCode::Learn_Skill) {
          if (effect.value1() == targetId) {
            m_effects.emplace_back(SpecialDataParent::Items, item.id(), index);
          }
        }
        index++;
      }
    }
  }
}
void ReferenceSearch::searchAllTraitsByTarget(int targetId, SearchType type) {
  m_traits.clear();
  // actors,classes,weapons,armors,traits,states =>

  // states, skills, weapon, armor, equip
  // TODO
}


code:
#pragma once
#include "Database/EventCommands/IEventCommand.hpp"
#include "Database/SearchResult.hpp"
#include <map>
#include <string>
#include <vector>

struct MainWindow;
class ReferenceSearch {
public:
  explicit ReferenceSearch();

  [[nodiscard]] const std::vector<SearchResult>& getEvents() const { return m_results; }
  [[nodiscard]] const std::vector<SearchResult>& getCommands() const { return m_list; }
  [[nodiscard]] const std::vector<SearchResult>& getCommons() const { return m_common; }
  [[nodiscard]] const std::vector<SearchResult>& getEffects() const { return m_effects; }
  [[nodiscard]] const std::vector<SearchResult>& getTraits() const { return m_traits; }
  void findAllReferences(int targetId, SearchType type);
  void findAllReferences(const std::string& text, SearchType type);
  void searchAllCommonByTarget(int targetId, SearchType type);
  void searchAllCommonByText(const std::string& text, SearchType type);
  void searchAllListsByTarget(int targetId, SearchType type);
  void searchAllListsByText(const std::string& text, SearchType type);
  void searchAllEffectsByTarget(int targetId, SearchType type);
  void searchAllTraitsByTarget(int targetId, SearchType type);
  int getCommonSize() { return m_common.size(); }
  int getListSize() { return m_list.size(); }
  int getEventSize() { return m_results.size(); }
  int totalSize() { return getCommonSize() + getListSize() + getEventSize(); }
  void clear() {
    m_results.clear();
    m_list.clear();
    m_common.clear();
  }

private:
  std::vector<SearchResult> m_results;
  std::vector<SearchResult> m_list;
  std::vector<SearchResult> m_common;
  std::vector<SearchResult> m_effects;
  std::vector<SearchResult> m_traits;
};


code:
#include "Core/ResourceManager.hpp"

#include "Core/Log.hpp"
#include "Core/Resources.hpp"
#include "Core/Utils.hpp"
#include "Resources.hpp"
#include "SDL3/SDL.h"
#include "SFML/Audio/SoundBuffer.hpp"
#include <exception>
#include <iostream>
#include <SDL3/SDL_hints.h>
namespace fs = std::filesystem;

void ResourceManager::setBasepath(const std::string_view basepath) {
  m_basePath = basepath;
  m_dataPath = m_basePath / "data/";
  m_audioPath = m_basePath / "audio/";
  m_bgmPath = m_basePath / "audio/bgm/";
  m_bgsPath = m_basePath / "audio/bgs/";
  m_sePath = m_basePath / "audio/se/";
  m_mePath = m_basePath / "audio/me/";
  m_imgPath = m_basePath / "img/";
  m_animationsPath = m_basePath / "img/animations/";
  m_battlebacks1Path = m_basePath / "img/battlebacks1/";
  m_battlebacks2Path = m_basePath / "img/battlebacks2/";
  m_charactersPath = m_basePath / "img/characters/";
  m_enemiesPath = m_basePath / "img/enemies/";
  m_facesPath = m_basePath / "img/faces/";
  m_parallaxesPath = m_basePath / "img/parallaxes/";
  m_picturesPath = m_basePath / "img/pictures/";
  m_sv_actorsPath = m_basePath / "img/sv_actors/";
  m_sv_enemiesPath = m_basePath / "img/sv_enemies/";
  m_systemPath = m_basePath / "img/system/";
  m_tilesetsPath = m_basePath / "img/tilesets/";
  m_titles1Path = m_basePath / "img/titles1/";
  m_titles2Path = m_basePath / "img/titles2/";

  for (const auto& texture : m_loadedTextures | std::views::values) {
    SDL_DestroyTexture(static_cast<SDL_Texture*>(texture.m_texture));
  }
  m_loadedTextures.clear();
}

ResourceManager::~ResourceManager() {
  for (const auto& texture : m_loadedTextures | std::views::values) {
    SDL_DestroyTexture(static_cast<SDL_Texture*>(texture.m_texture));
  }
  m_loadedTextures.clear();

  for (const auto& texture : m_editorTextures | std::views::values) {
    SDL_DestroyTexture(static_cast<SDL_Texture*>(texture.m_texture));
  }
  m_editorTextures.clear();
}

sf::SoundBuffer ResourceManager::loadSound(std::filesystem::path path) {
#ifndef _WIN32
  auto pathStr = path.generic_string();
  auto pos = pathStr.find('\\');
  while (pos != pathStr.npos) {
    pathStr[pos] = std::filesystem::path::preferred_separator;
    pos = pathStr.find('\\', pos);
  }
  path = pathStr;
#else
  path.make_preferred();
#endif
  if (!fs::is_regular_file(path)) {
    return {};
  }

  sf::SoundBuffer buffer;
  buffer.loadFromFile(path.generic_string());
  return buffer;
}
sf::SoundBuffer ResourceManager::loadBGM(const std::string_view path) const { return loadSound((m_bgmPath / path.data()).replace_extension(".ogg")); }

sf::SoundBuffer ResourceManager::loadBGS(const std::string_view path) const { return loadSound((m_bgsPath / path.data()).replace_extension(".ogg")); }

sf::SoundBuffer ResourceManager::loadSE(const std::string_view path) const { return loadSound((m_sePath / path.data()).replace_extension(".ogg")); }

sf::SoundBuffer ResourceManager::loadME(const std::string_view path) const { return loadSound((m_mePath / path.data()).replace_extension(".ogg")); }

Texture ResourceManager::loadTexture(std::filesystem::path path) {
#ifndef _WIN32
  auto pathStr = path.generic_string();
  auto pos = pathStr.find('\\');
  while (pos != pathStr.npos) {
    pathStr[pos] = std::filesystem::path::preferred_separator;
    pos = pathStr.find('\\', pos);
  }
  path = pathStr;
#else
  path.make_preferred();
#endif
  if (!fs::is_regular_file(path)) {
    return {};
  }

  if (m_loadedTextures.contains(path.generic_string())) {
    return m_loadedTextures[path.generic_string()];
  }

  const Texture ret(path.generic_string());
  m_loadedTextures[path.generic_string()] = ret;
  return ret;
}

Texture ResourceManager::loadImage(const std::string_view path) { return loadTexture((m_imgPath / path).replace_extension(".png")); }

Texture ResourceManager::loadAnimationImage(const std::string_view path) { return loadTexture((m_animationsPath / path).replace_extension(".png")); }

Texture ResourceManager::loadBattlebacks1Image(const std::string_view path) { return loadTexture((m_battlebacks1Path / path).replace_extension(".png")); }

Texture ResourceManager::loadBattlebacks2Image(const std::string_view path) { return loadTexture((m_battlebacks2Path / path).replace_extension(".png")); }

Texture ResourceManager::loadCharacterImage(const std::string_view path) { return loadTexture((m_charactersPath / path).replace_extension(".png")); }

Texture ResourceManager::loadEnemyImage(std::string_view path) { return loadTexture((m_enemiesPath / path).replace_extension(".png")); }

Texture ResourceManager::loadFaceImage(std::string_view path) { return loadTexture((m_facesPath / path).replace_extension(".png")); }

Texture ResourceManager::loadParallaxImage(std::string_view path) { return loadTexture((m_parallaxesPath / path).replace_extension(".png")); }

Texture ResourceManager::loadPictureImage(std::string_view path) { return loadTexture((m_picturesPath / path).replace_extension(".png")); }

Texture ResourceManager::loadSVActorImage(std::string_view path) { return loadTexture((m_sv_actorsPath / path).replace_extension(".png")); }
Texture ResourceManager::loadSVEnemyImage(std::string_view path) { return loadTexture((m_sv_enemiesPath / path).replace_extension(".png")); }
Texture ResourceManager::loadSystemImage(std::string_view path) { return loadTexture((m_systemPath / path).replace_extension(".png")); }
Texture ResourceManager::loadTilesetImage(std::string_view path) { return loadTexture((m_tilesetsPath / path).replace_extension(".png")); }

Texture ResourceManager::loadTitle1Image(std::string_view path) { return loadTexture((m_titles1Path / path).replace_extension(".png")); }

Texture ResourceManager::loadTitle2Image(std::string_view path) { return loadTexture((m_titles2Path / path).replace_extension(".png")); }

Texture ResourceManager::loadTileMarkers(int width, int height) {
  fs::path relativePath = std::format("tilemarkers_{}x{}.png", width, height);
  return loadEditorTexture(relativePath.generic_string());
}
Texture ResourceManager::loadEditorTexture(std::string_view path) {
  auto imagePath = App::Resources::image_path(path).generic_string();
  if (!fs::is_regular_file(imagePath)) {
    return {};
  }

  if (m_editorTextures.contains(imagePath)) {
    return m_editorTextures[imagePath];
  }

  Texture ret(imagePath);
  m_editorTextures[imagePath] = ret;
  ret.setFilter(Texture::Linear);
  return ret;
}

std::vector<std::string> ResourceManager::getDirectoryContents(const std::string& directoryPath, const std::string_view filter) const {
  std::vector<std::string> fileNames;
  for (const auto& entry : fs::directory_iterator(m_basePath / directoryPath)) {
    if (!filter.empty() && entry.path().extension().compare(filter) != 0) {
      continue;
    }

    auto filename = entry.path().filename();
    filename = filename.replace_extension();
    const auto path = filename.generic_string();
    fileNames.emplace_back(path);
  }
  return fileNames;
}
std::vector<std::string> ResourceManager::getDirectories(const std::string& directoryPath) const {
  std::vector<std::string> fileNames;
  for (const auto& entry : fs::directory_iterator(m_basePath / directoryPath)) {
    if (!entry.is_directory()) {
      continue;
    }
    const auto path = entry.path().filename().generic_string();
    fileNames.emplace_back(path);
  }
  return fileNames;
}