code:
#pragma once

#include "SFML/Audio/Sound.hpp"

#include <map>
#include <string>

#include "Core/Graphics/Texture.hpp"

#include "Database/Map.hpp"

class ResourceManager {
public:
  ResourceManager(ResourceManager&) = delete;
  ResourceManager(ResourceManager&&) = delete;
  ResourceManager& operator=(ResourceManager&) = delete;
  ResourceManager& operator=(ResourceManager&&) = delete;
  ~ResourceManager();

  void setBasepath(const std::string_view basepath);
  sf::SoundBuffer loadBGM(std::string_view path) const;
  sf::SoundBuffer loadBGS(std::string_view path) const;
  sf::SoundBuffer loadSE(std::string_view path) const;
  sf::SoundBuffer loadME(std::string_view path) const;
  Texture loadImage(std::string_view path);
  Texture loadAnimationImage(std::string_view path);
  Texture loadBattlebacks1Image(std::string_view path);
  Texture loadBattlebacks2Image(std::string_view path);
  Texture loadCharacterImage(std::string_view path);
  Texture loadEnemyImage(std::string_view path);
  Texture loadFaceImage(std::string_view path);
  Texture loadParallaxImage(std::string_view path);
  Texture loadPictureImage(std::string_view path);
  Texture loadSVActorImage(std::string_view path);
  Texture loadSVEnemyImage(std::string_view path);
  Texture loadSystemImage(std::string_view path);
  Texture loadTilesetImage(std::string_view path);
  Texture loadTitle1Image(std::string_view path);
  Texture loadTitle2Image(std::string_view path);
  Texture loadEditorTexture(std::string_view path);
  Texture loadTileMarkers(int width, int height);

  static ResourceManager* instance() {
    static ResourceManager instance;
    return &instance;
  }

  [[nodiscard]] std::vector<std::string> getDirectoryContents(const std::string& directoryPath, std::string_view filter = {}) const;
  std::vector<std::string> getDirectories(const std::string& directoryPath) const;

private:
  ResourceManager() = default;
  static sf::SoundBuffer loadSound(std::filesystem::path path);
  Texture loadTexture(std::filesystem::path path);
  std::map<std::string, Texture> m_loadedTextures{}; // Path -> texture
  std::map<std::string, Texture> m_editorTextures{}; // Path -> texture
  std::filesystem::path m_basePath{};
  std::filesystem::path m_dataPath{};
  std::filesystem::path m_audioPath{};
  std::filesystem::path m_bgmPath{};
  std::filesystem::path m_bgsPath{};
  std::filesystem::path m_sePath{};
  std::filesystem::path m_mePath{};
  std::filesystem::path m_imgPath{};
  std::filesystem::path m_animationsPath{};
  std::filesystem::path m_battlebacks1Path{};
  std::filesystem::path m_battlebacks2Path{};
  std::filesystem::path m_charactersPath{};
  std::filesystem::path m_enemiesPath{};
  std::filesystem::path m_facesPath{};
  std::filesystem::path m_parallaxesPath{};
  std::filesystem::path m_picturesPath{};
  std::filesystem::path m_sv_actorsPath{};
  std::filesystem::path m_sv_enemiesPath{};
  std::filesystem::path m_systemPath{};
  std::filesystem::path m_tilesetsPath{};
  std::filesystem::path m_titles1Path{};
  std::filesystem::path m_titles2Path{};
};


code:
#pragma once

#include <filesystem>
#include <string_view>

namespace App {

class Resources {
public:
  [[nodiscard]] static std::filesystem::path resource_path(const std::filesystem::path& file_path);
  [[nodiscard]] static std::filesystem::path font_path(const std::string_view& font_file);
  [[nodiscard]] static std::filesystem::path image_path(const std::string_view& image_file);
};

} // namespace App

repo_name: Antidote/openrpgeditor

license: mit


code:
#include "Core/Script/Bindings.hpp"

#include "Core/MainWindow.hpp"
#include "Core/Script/ScriptEngine.hpp"

#include "Core/Script/ImGui/ImGuiCoreOperations.hpp"
#include "ImGui/ImGuiMath.hpp"
void ImGuiBindings();

void RegisterBindings() {
  auto engine = ScriptEngine::instance()->engine();
  MainWindow::RegisterBindings();
  ImGuiMathBindings(engine);
  ImGuiCoreOperationBindings(engine);
}


code:
#pragma once

void RegisterBindings();


code:
#include "Core/Script/ByteCodeStream.hpp"

#include <sha1.h>

#include <athena/Compression.hpp>

ByteCodeWriter::ByteCodeWriter(const std::string& filepath)
: FileWriter(filepath) {}
ByteCodeWriter::~ByteCodeWriter() {
  writeUint32Big('ASC\0');
  writeUint32Little(m_writer.length());
  auto data = m_writer.data();

  // std::unique_ptr<atUint8[]> compData(new atUint8[m_writer.length() * 2]);
  // auto compLen = m_writer.length() * 2;
  // compLen = athena::io::Compression::compressZlib(data, m_writer.length(), compData.get(), compLen);
  // if (compLen > 0 && compLen < m_writer.length()) {
  //   const auto realLen = ROUND_UP_32(compLen);
  //   writeUint32Little(realLen);
  //   std::unique_ptr<atUint8[]> realData(new atUint8[realLen]);
  //   athena::utility::fillRandom(realData.get(), realLen);
  //   memcpy(realData.get(), compData.get(), compLen);
  //   std::unique_ptr<atUint8[]> sha1(getSha1(realData.get(), realLen));
  //   FileWriter::writeUBytes(sha1.get(), 20);
  //   FileWriter::writeUBytes(realData.get(), realLen);
  //   return;
  // }

  writeUint32Little(0);
  const auto realLen = ROUND_UP_32(m_writer.length());
  std::unique_ptr<uint8_t[]> realData(new uint8_t[realLen]);
  athena::utility::fillRandom(realData.get(), realLen);
  memcpy(realData.get(), data, m_writer.length());
  std::unique_ptr<uint8_t[]> sha1(getSha1(realData.get(), realLen));
  FileWriter::writeUBytes(sha1.get(), 20);
  FileWriter::writeUBytes(realData.get(), realLen);
}

int ByteCodeWriter::Write(const void* ptr, const asUINT size) {
  if (!size)
    return asERROR;

  m_writer.writeUBytes(static_cast<const uint8_t*>(ptr), size);
  return asSUCCESS;
}

int ByteCodeWriter::Read(void* ptr, asUINT size) { return asNOT_SUPPORTED; }

ByteCodeReader::ByteCodeReader(const std::string& filepath)
: FileReader(filepath)
, m_reader(new uint8_t[1], 1, true) {
  auto magic = readUint32Big();
  if (magic != 'ASC\0') {
    setError();
    return;
  }

  auto uncompLen = readUint32Little();
  auto compLen = readUint32Little();
  auto sha1Hash = readUBytes(20);
  uint32_t realLen = 0;
  if (compLen != 0) {
    realLen = ROUND_UP_32(compLen);
  } else {
    realLen = ROUND_UP_32(uncompLen);
  }

  uint8_t* data = new uint8_t[realLen];
  FileReader::readUBytesToBuf(data, realLen);
  std::unique_ptr<uint8_t[]> calculatedHash(getSha1(data, realLen));

  if (memcmp(sha1Hash.get(), calculatedHash.get(), 20) != 0) {
    setError();
    return;
  }

  if (compLen != 0) {
    const auto byteCode(new uint8_t[uncompLen]);
    if (auto outSize = athena::io::Compression::decompressZlib(data, compLen, byteCode, uncompLen); outSize != uncompLen) {
      setError();
      return;
    }

    m_reader.setData(byteCode, uncompLen, true);
    delete[] data;
  } else {
    m_reader.setData(data, uncompLen, true);
  }
}

int ByteCodeReader::Write(const void* ptr, asUINT size) { return asNOT_SUPPORTED; }

int ByteCodeReader::Read(void* ptr, const asUINT size) {
  if (!size || m_hasError) {
    return asERROR;
  }

  m_reader.readUBytesToBuf(ptr, size);
  return asSUCCESS;
}


code:
#pragma once

#include <angelscript.h>
#include <athena/FileReader.hpp>
#include <athena/FileWriter.hpp>
#include <athena/MemoryReader.hpp>
#include <athena/MemoryWriter.hpp>

class ByteCodeWriter final : public athena::io::FileWriter, public asIBinaryStream {
public:
  explicit ByteCodeWriter(const std::string& filepath);
  ~ByteCodeWriter() override;
  int Read(void* ptr, asUINT size) override;
  int Write(const void* ptr, asUINT size) override;

private:
  athena::io::MemoryCopyWriter m_writer;
};

class ByteCodeReader final : public athena::io::FileReader, public asIBinaryStream {
public:
  explicit ByteCodeReader(const std::string& filepath);
  int Read(void* ptr, asUINT size) override;
  int Write(const void* ptr, asUINT size) override;

private:
  athena::io::MemoryReader m_reader;
};


code:
#include "Core/Script/ImGui/ImGuiCoreOperations.hpp"
#include <angelscript.h>
#include <cassert>
#include <imgui.h>
#include <imgui_internal.h>
#include <string>
bool ImGui_Begin_Wrapper(const std::string& id, const bool showCloseButton, bool* still_open, const ImGuiWindowFlags flags) {
  bool open = true;
  const bool ret = ImGui::Begin(id.c_str(), showCloseButton ? &open : nullptr, flags);
  if (still_open) {
    *still_open = open;
  }
  return ret;
}

bool ImGui_Begin_Child_Wrapper(const std::string& id, const ImVec2& size, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags) {
  return ImGui::BeginChild(id.c_str(), size, child_flags, window_flags);
}

void ImGui_Window_Pos_Named_Wrapper(const std::string& id, const ImVec2& pos, ImGuiCond cond) { ImGui::SetWindowPos(id.c_str(), pos, cond); }
void ImGui_Window_Size_Named_Wrapper(const std::string& id, const ImVec2& size, ImGuiCond cond) { ImGui::SetWindowSize(id.c_str(), size, cond); }
void ImGui_Window_Collapsed_Named_Wrapper(const std::string& id, bool collapsed, ImGuiCond cond) { ImGui::SetWindowCollapsed(id.c_str(), collapsed, cond); }
void ImGui_Window_Focus_Named_Wrapper(const std::string& id) { ImGui::SetWindowFocus(id.c_str()); }

void ImGui_Text_Wrapper(const std::string& text) { ImGui::TextUnformatted(text.c_str()); }
std::string ImGui_GetVersion() { return ImGui::GetVersion(); }

void ImGuiRegisterTypedefs(asIScriptEngine* engine) {
  engine->RegisterTypedef("ImGuiID", "uint");
  engine->RegisterTypedef("ImS8", "int8");
  engine->RegisterTypedef("ImU8", "uint8");
  engine->RegisterTypedef("ImS16", "int16");
  engine->RegisterTypedef("ImU16", "uint16");
  engine->RegisterTypedef("ImS32", "int");
  engine->RegisterTypedef("ImU32", "uint");
  engine->RegisterTypedef("ImS64", "int64");
  engine->RegisterTypedef("ImU64", "uint64");
  engine->RegisterTypedef("ImGuiCol", "int");
  engine->RegisterTypedef("ImGuiCond", "int");
  engine->RegisterTypedef("ImGuiDataType", "int");
  engine->RegisterTypedef("ImGuiMouseButton", "int");
  engine->RegisterTypedef("ImGuiMouseCursor", "int");
  engine->RegisterTypedef("ImGuiStyleVar", "int");
  engine->RegisterTypedef("ImGuiTableBgTarget", "int");
  engine->RegisterTypedef("ImDrawFlags", "int");
  engine->RegisterTypedef("ImDrawListFlags", "int");
  engine->RegisterTypedef("ImFontAtlasFlags", "int");
  engine->RegisterTypedef("ImGuiBackendFlags", "int");
  engine->RegisterTypedef("ImGuiChildFlags", "int");
  engine->RegisterTypedef("ImGuiColorEditFlags", "int");
  engine->RegisterTypedef("ImGuiConfigFlags", "int");
  engine->RegisterTypedef("ImGuiComboFlags", "int");
  engine->RegisterTypedef("ImGuiDockNodeFlags", "int");
  engine->RegisterTypedef("ImGuiDragDropFlags", "int");
  engine->RegisterTypedef("ImGuiFocusedFlags", "int");
  engine->RegisterTypedef("ImGuiHoveredFlags", "int");
  engine->RegisterTypedef("ImGuiInputFlags", "int");
  engine->RegisterTypedef("ImGuiInputTextFlags", "int");
  engine->RegisterTypedef("ImGuiItemFlags", "int");
  engine->RegisterTypedef("ImGuiKeyChord", "int");
  engine->RegisterTypedef("ImGuiPopupFlags", "int");
  engine->RegisterTypedef("ImGuiMultiSelectFlags", "int");
  engine->RegisterTypedef("ImGuiSelectableFlags", "int");
  engine->RegisterTypedef("ImGuiSliderFlags", "int");
  engine->RegisterTypedef("ImGuiTabBarFlags", "int");
  engine->RegisterTypedef("ImGuiTabItemFlags", "int");
  engine->RegisterTypedef("ImGuiTableFlags", "int");
  engine->RegisterTypedef("ImGuiTableColumnFlags", "int");
  engine->RegisterTypedef("ImGuiTableRowFlags", "int");
  engine->RegisterTypedef("ImGuiTreeNodeFlags", "int");
  engine->RegisterTypedef("ImGuiViewportFlags", "int");
  engine->RegisterTypedef("ImGuiWindowFlags", "int");
  engine->RegisterTypedef("ImTextureID", "uint64");
  engine->RegisterTypedef("ImWchar32", "uint");
  engine->RegisterTypedef("ImWchar16", "uint16");
#ifdef IMGUI_USE_WCHAR32
  engine->RegisterTypedef("ImWchar", "uint");
#else
  engine->RegisterTypedef("ImWchar", "uint16");
#endif
  engine->RegisterTypedef("ImGuiSelectionUserData", "int64");
}

void ImGuiCoreOperationBindings(asIScriptEngine* engine) {
  auto def = engine->GetDefaultNamespace();
  ImGuiRegisterTypedefs(engine);
  engine->SetDefaultNamespace("ImGui");
  int r = engine->RegisterGlobalFunction("string GetVersion()", asFUNCTION(ImGui_GetVersion), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("bool Begin(const string& in id, bool show_close_button = false, bool&out p_open = false, ImGuiWindowFlags window_flags = 0)", asFUNCTION(ImGui_Begin_Wrapper),
                                     asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void End()", asFUNCTION(ImGui::End), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("bool BeginChild(const string& in id, const ImVec2& in size = ImVec2(), ImGuiChildFlags child_flags = 0, ImGuiWindowFlags window_flags = 0)",
                                     asFUNCTION(ImGui_Begin_Child_Wrapper), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("bool BeginChild(ImGuiID id, const ImVec2& in size = ImVec2(), ImGuiChildFlags child_flags = 0, ImGuiWindowFlags window_flags = 0)",
                                     asFUNCTIONPR(ImGui::BeginChild, (ImGuiID, const ImVec2&, ImGuiChildFlags, ImGuiWindowFlags), bool), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void EndChild()", asFUNCTION(ImGui::EndChild), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("bool IsWindowAppearing()", asFUNCTION(ImGui::IsWindowAppearing), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("bool IsWindowCollapsed()", asFUNCTION(ImGui::IsWindowCollapsed), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("bool IsWindowFocused(ImGuiFocusedFlags flags=0)", asFUNCTION(ImGui::IsWindowFocused), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("bool IsWindowHovered(ImGuiHoveredFlags flags=0)", asFUNCTION(ImGui::IsWindowHovered), asCALL_CDECL);
  assert(r >= 0);
  // r = engine->RegisterGlobalFunction("ImDrawList& GetWindowDrawList()", asFUNCTION(ImGui::GetWindowDrawList), asCALL_CDECL);
  // assert(r >= 0);
  r = engine->RegisterGlobalFunction("float GetDpiScale()", asFUNCTION(ImGui::IsWindowAppearing), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetNextWindowPos(const ImVec2&in pos, ImGuiCond cond = 0, const ImVec2&in pivot = ImVec2())", asFunctionPtr(ImGui::SetNextWindowPos), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetNextWindowSize(const ImVec2&in size, ImGuiCond cond = 0)", asFunctionPtr(ImGui::SetNextWindowSize), asCALL_CDECL);
  assert(r >= 0);
  // TODO: Constraints callback?
  r = engine->RegisterGlobalFunction("void SetNextWindowSizeConstraints(const ImVec2&in size_min, const ImVec2&in size_max, ImGuiCond cond = 0)", asFunctionPtr(ImGui::SetNextWindowSizeConstraints),
                                     asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetNextWindowContentSize(const ImVec2&in size, ImGuiCond cond = 0)", asFunctionPtr(ImGui::SetNextWindowSizeConstraints), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetNextWindowCollapsed(bool collapsed, ImGuiCond cond = 0)", asFUNCTION(ImGui::SetNextWindowCollapsed), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetNextWindowFocus()", asFUNCTION(ImGui::SetNextWindowFocus), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetNextWindowScroll(const ImVec2& in scroll)", asFUNCTION(ImGui::SetNextWindowScroll), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetNextWindowBgAlpha(float alpha)", asFUNCTION(ImGui::SetNextWindowBgAlpha), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetNextWindowViewport(ImGuiID viewport_id)", asFUNCTION(ImGui::SetNextWindowViewport), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetWindowPos(const ImVec2&in pos, ImGuiCond cond = 0)", asFUNCTIONPR(ImGui::SetWindowPos, (const ImVec2&, ImGuiCond), void), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetWindowSize(const ImVec2&in size, ImGuiCond cond = 0)", asFUNCTIONPR(ImGui::SetWindowSize, (const ImVec2&, ImGuiCond), void), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetWindowCollapsed(bool collapsed, ImGuiCond cond = 0)", asFUNCTIONPR(ImGui::SetWindowCollapsed, (bool, ImGuiCond), void), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetWindowFocus()", asFUNCTIONPR(ImGui::SetWindowFocus, (), void), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetWindowFontScale(float scale)", asFUNCTION(ImGui::SetWindowFontScale), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetWindowPos(const string&in name, const ImVec2&in pos, ImGuiCond cond = 0)", asFUNCTION(ImGui_Window_Pos_Named_Wrapper), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetWindowSize(const string&in name, const ImVec2&in size, ImGuiCond cond = 0)", asFUNCTION(ImGui_Window_Size_Named_Wrapper), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetWindowCollapsed(const string&in name, bool collapsed, ImGuiCond cond = 0)", asFUNCTION(ImGui_Window_Collapsed_Named_Wrapper), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetWindowFocus(const string&in name)", asFUNCTION(ImGui_Window_Focus_Named_Wrapper), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("float GetScrollX()", asFUNCTION(ImGui::GetScrollX), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("float GetScrollY()", asFUNCTION(ImGui::GetScrollY), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetScrollX(float scroll_x)", asFUNCTIONPR(ImGui::SetScrollX, (float), void), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetScrollY(float scroll_y)", asFUNCTIONPR(ImGui::SetScrollY, (float), void), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("float GetScrollMaxX()", asFUNCTION(ImGui::GetScrollMaxX), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("float GetScrollMaxY()", asFUNCTION(ImGui::GetScrollMaxY), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetScrollHereX(float center_x_ratio = 0.5f)", asFUNCTIONPR(ImGui::SetScrollHereX, (float), void), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetScrollHereY(float center_y_ratio = 0.5f)", asFUNCTIONPR(ImGui::SetScrollHereY, (float), void), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetScrollFromPosX(float local_x, float center_x_ratio = 0.5f)", asFUNCTIONPR(ImGui::SetScrollFromPosX, (float, float), void), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void SetScrollFromPosY(float local_y, float center_y_ratio = 0.5f)", asFUNCTIONPR(ImGui::SetScrollFromPosY, (float, float), void), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("void Text(const string& in)", asFUNCTION(ImGui_Text_Wrapper), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("double GetTime()", asFUNCTION(ImGui::GetTime), asCALL_CDECL);
  assert(r >= 0);
  r = engine->RegisterGlobalFunction("int GetFrameCount()", asFUNCTION(ImGui::GetFrameCount), asCALL_CDECL);
  assert(r >= 0);
  engine->SetDefaultNamespace(def);
}


code:
#pragma once

class asIScriptEngine;
void ImGuiCoreOperationBindings(asIScriptEngine* engine);


code:
#include <angelscript.h>
#include <cassert>
#include <imgui.h>
#include <new>

void ImGui_Construct_ImVec2(ImVec2* vec) { new (vec) ImVec2(0.f, 0.f); }
void ImGui_Construct_ImVec2Args(const float x, const float y, ImVec2* vec) { new (vec) ImVec2(x, y); }
void ImGui_Destruct_ImVec2(ImVec2* vec) { vec->~ImVec2(); }
void ImGui_Construct_ImVec4(ImVec4* vec) { new (vec) ImVec4(); }
void ImGui_Construct_ImVec4Args(const float x, const float y, float z, float w, ImVec4* vec) { new (vec) ImVec4(x, y, z, w); }
void ImGui_Destruct_ImVec4(ImVec4* vec) { vec->~ImVec4(); }
template <typename T>
void ImGui_Op_Negate_ImVec_Generic(asIScriptGeneric* generic) {
  *static_cast<T*>(generic->GetAddressOfReturnLocation()) = -(*static_cast<T*>(generic->GetObject()));
}

void ImGuiVec2Bindings(asIScriptEngine* engine) {
  int r = engine->RegisterObjectType("ImVec2", sizeof(ImVec2), asOBJ_VALUE | asOBJ_APP_CLASS_DAK);
  assert(r >= 0);
  r = engine->RegisterObjectBehaviour("ImVec2", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ImGui_Construct_ImVec2), asCALL_CDECL_OBJLAST);
  assert(r >= 0);
  r = engine->RegisterObjectBehaviour("ImVec2", asBEHAVE_CONSTRUCT, "void f(float, float)", asFUNCTION(ImGui_Construct_ImVec2Args), asCALL_CDECL_OBJLAST);
  assert(r >= 0);
  r = engine->RegisterObjectBehaviour("ImVec2", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(ImGui_Destruct_ImVec2), asCALL_CDECL_OBJLAST);
  assert(r >= 0);
  r = engine->RegisterObjectMethod("ImVec2", "ImVec2 &opAssign(const ImVec2 &in)", asMETHODPR(ImVec2, operator=, (const ImVec2&), ImVec2&), asCALL_THISCALL);
  assert(r >= 0);
  r = engine->RegisterObjectMethod("ImVec2", "float& opIndex(uint index)", asMETHODPR(ImVec2, operator[], (size_t idx), float&), asCALL_THISCALL);
  assert(r >= 0);
  r = engine->RegisterObjectMethod("ImVec2", "ImVec2 opMul(const float)", asFUNCTIONPR(operator*, (const ImVec2&, const float), ImVec2), asCALL_CDECL_OBJFIRST);
  assert(r >= 0);
  r = engine->RegisterObjectMethod("ImVec2", "ImVec2 opMul(const ImVec2 &in)", asFUNCTIONPR(operator*, (const ImVec2&, const ImVec2&), ImVec2), asCALL_CDECL_OBJFIRST);
  assert(r >= 0);
  r = engine->RegisterObjectMethod("ImVec2", "ImVec2 opDiv(const float)", asFUNCTIONPR(operator/, (const ImVec2&, const float), ImVec2), asCALL_CDECL_OBJFIRST);
  assert(r >= 0);
  r = engine->RegisterObjectMethod("ImVec2", "ImVec2 opAdd(const ImVec2 &in)", asFUNCTIONPR(operator+, (const ImVec2&, const ImVec2&), ImVec2), asCALL_CDECL_OBJFIRST);
  assert(r >= 0);
  r = engine->RegisterObjectMethod("ImVec2", "ImVec2 opSub(const ImVec2 &in)", asFUNCTIONPR(operator-, (const ImVec2&, const ImVec2&), ImVec2), asCALL_CDECL_OBJFIRST);
  assert(r >= 0);
  r = engine->RegisterObjectMethod("ImVec2", "ImVec2 opDiv(const ImVec2 &in)", asFUNCTIONPR(operator/, (const ImVec2&, const ImVec2&), ImVec2), asCALL_CDECL_OBJFIRST);
  assert(r >= 0);
  r = engine->RegisterObjectMethod("ImVec2", "ImVec2 opNeg()", asFUNCTION(ImGui_Op_Negate_ImVec_Generic<ImVec2>), asCALL_GENERIC);
  assert(r >= 0);
  r = engine->RegisterObjectMethod("ImVec2", "ImVec2 &opMulAssign(const float)", asFUNCTIONPR(operator*=, (ImVec2&, const float), ImVec2&), asCALL_CDECL_OBJFIRST);
  assert(r >= 0);
  r = engine->RegisterObjectMethod("ImVec2", "ImVec2 &opDivAssign(const float)", asFUNCTIONPR(operator/=, (ImVec2&, const float), ImVec2&), asCALL_CDECL_OBJFIRST);
  assert(r >= 0);
  r = engine->RegisterObjectMethod("ImVec2", "ImVec2 &opAddAssign(const ImVec2 &in)", asFUNCTIONPR(operator+=, (ImVec2&, const ImVec2&), ImVec2&), asCALL_CDECL_OBJFIRST);
  assert(r >= 0);
  r = engine->RegisterObjectMethod("ImVec2", "ImVec2 &opSubAssign(const ImVec2 &in)", asFUNCTIONPR(operator-=, (ImVec2&, const ImVec2&), ImVec2&), asCALL_CDECL_OBJFIRST);
  assert(r >= 0);
  r = engine->RegisterObjectMethod("ImVec2", "ImVec2 &opMulAssign(const ImVec2 &in)", asFUNCTIONPR(operator*=, (ImVec2&, const ImVec2&), ImVec2&), asCALL_CDECL_OBJFIRST);
  assert(r >= 0);
  r = engine->RegisterObjectMethod("ImVec2", "ImVec2 &opDivAssign(const ImVec2 &in)", asFUNCTIONPR(operator*=, (ImVec2&, const ImVec2&), ImVec2&), asCALL_CDECL_OBJFIRST);
  assert(r >= 0);
  r = engine->RegisterObjectMethod("ImVec2", "ImVec2 opEquals(const ImVec2 &in)", asFUNCTIONPR(operator==, (const ImVec2&, const ImVec2&), bool), asCALL_CDECL_OBJFIRST);
  assert(r >= 0);
  r = engine->RegisterObjectProperty("ImVec2", "float x", asOFFSET(ImVec2, x));
  assert(r >= 0);
  r = engine->RegisterObjectProperty("ImVec2", "float y", asOFFSET(ImVec2, y));
  assert(r >= 0);
}

void ImGuiVec4Bindings(asIScriptEngine* engine) {
  int r = engine->RegisterObjectType("ImVec4", sizeof(ImVec4), asOBJ_VALUE | asOBJ_APP_CLASS_DAK);
  assert(r >= 0);
  r = engine->RegisterObjectBehaviour("ImVec4", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ImGui_Construct_ImVec4), asCALL_CDECL_OBJLAST);
  assert(r >= 0);
  r = engine->RegisterObjectBehaviour("ImVec4", asBEHAVE_CONSTRUCT, "void f(float, float, float, float)", asFUNCTION(ImGui_Construct_ImVec4Args), asCALL_CDECL_OBJLAST);
  assert(r >= 0);
  r = engine->RegisterObjectBehaviour("ImVec4", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(ImGui_Destruct_ImVec4), asCALL_CDECL_OBJLAST);
  assert(r >= 0);
  r = engine->RegisterObjectMethod("ImVec4", "ImVec4 &opAssign(const ImVec4 &in)", asMETHODPR(ImVec4, operator=, (const ImVec4&), ImVec4&), asCALL_THISCALL);
  assert(r >= 0);
  r = engine->RegisterObjectMethod("ImVec4", "ImVec4 opMul(const ImVec4 &in)", asFUNCTIONPR(operator*, (const ImVec4&, const ImVec4&), ImVec4), asCALL_CDECL_OBJFIRST);
  assert(r >= 0);
  r = engine->RegisterObjectMethod("ImVec4", "ImVec4 opSub(const ImVec4 &in)", asFUNCTIONPR(operator-, (const ImVec4&, const ImVec4&), ImVec4), asCALL_CDECL_OBJFIRST);
  assert(r >= 0);
  r = engine->RegisterObjectMethod("ImVec4", "ImVec4 opEquals(const ImVec4 &in)", asFUNCTIONPR(operator==, (const ImVec4&, const ImVec4&), bool), asCALL_CDECL_OBJFIRST);
  assert(r >= 0);
  r = engine->RegisterObjectProperty("ImVec4", "float x", asOFFSET(ImVec4, x));
  assert(r >= 0);
  r = engine->RegisterObjectProperty("ImVec4", "float y", asOFFSET(ImVec4, y));
  assert(r >= 0);
  r = engine->RegisterObjectProperty("ImVec4", "float z", asOFFSET(ImVec4, z));
  assert(r >= 0);
  r = engine->RegisterObjectProperty("ImVec4", "float w", asOFFSET(ImVec4, w));
  assert(r >= 0);
}
void ImGuiMathBindings(asIScriptEngine* engine) {
  ImGuiVec2Bindings(engine);
  ImGuiVec4Bindings(engine);
}


code:
#pragma once

class asIScriptEngine;
void ImGuiMathBindings(asIScriptEngine* engine);


code:
#include "Core/Script/ScriptEngine.hpp"

#include "Bindings.hpp"
#include "Core/Log.hpp"
#include "Core/Script/ByteCodeStream.hpp"
#include "scriptany/scriptany.h"
#include "scriptdictionary/scriptdictionary.h"

#include <angelscript.h>
#include <scriptbuilder/scriptbuilder.h>

#include <cassert>
#include <cstdarg>
#include <iostream>
#include <ranges>
#include <scriptarray/scriptarray.h>
#include <scripthandle/scripthandle.h>
#include <scriptjson/scriptjson.h>
#include <scriptmath/scriptmath.h>
#include <scriptmath/scriptmathcomplex.h>
#include <scriptstdstring/scriptstdstring.h>

#include <weakref/weakref.h>
ScriptEngine::~ScriptEngine() {
  for (const auto& context : m_contexts) {
    (void)context->Release();
  }

  if (m_engine) {
    m_engine->ShutDownAndRelease();
  }
}

std::vector<std::string> splitByDoubleColons(const std::string& str) {
  std::vector<std::string> result;
  std::string current;

  for (char c : str) {
    if (c == ':' && current.back() == ':') {
      result.push_back(current.substr(0, current.size() - 1)); // Remove last ':'
      current.clear();
    } else {
      current += c;
    }
  }

  if (!current.empty()) {
    result.push_back(current);
  }

  return result;
}

void PrintString(int level, std::string fmt, ...) {
  va_list args;
  int size = 100;
  std::string str;

  while (true) {
    str.resize(size);
    va_start(args, fmt);
    int n = vsnprintf(const_cast<char*>(str.c_str()), size, fmt.c_str(), args);
    va_end(args);
    if (n > -1 && n < size) {
      str.resize(n);
      break;
    }
    if (n > -1) {
      size = n + 1;
    } else {
      size *= 2;
    }
  }

  APP_INFO(str);
}

namespace {
template <class Stream>
void printEnumList(const asIScriptEngine* engine, Stream& stream) {
  for (int i = 0; i < engine->GetEnumCount(); i++) {
    const auto e = engine->GetEnumByIndex(i);
    if (not e)
      continue;
    const std::string_view ns = e->GetNamespace();
    if (not ns.empty())
      stream << std::format("namespace {} {{\n", ns);
    stream << std::format("enum {} {{\n", e->GetName());
    for (int j = 0; j < e->GetEnumValueCount(); ++j) {
      stream << std::format("\t{}", e->GetEnumValueByIndex(j, nullptr));
      if (j < e->GetEnumValueCount() - 1)
        stream << ",";
      stream << "\n";
    }
    stream << "}\n";
    if (not ns.empty())
      stream << "}\n";
  }
}

template <class Stream>
void printClassTypeList(const asIScriptEngine* engine, Stream& stream) {
  for (int i = 0; i < engine->GetObjectTypeCount(); i++) {
    const auto t = engine->GetObjectTypeByIndex(i);
    if (not t)
      continue;

    const std::string_view ns = t->GetNamespace();
    if (not ns.empty())
      stream << std::format("namespace {} {{\n", ns);

    stream << std::format("class {}", t->GetName());
    if (t->GetSubTypeCount() > 0) {
      stream << "<";
      for (int sub = 0; sub < t->GetSubTypeCount(); ++sub) {
        if (sub < t->GetSubTypeCount() - 1)
          stream << ", ";
        const auto st = t->GetSubType(sub);
        stream << st->GetName();
      }

      stream << ">";
    }

    stream << "{\n";
    for (int j = 0; j < t->GetBehaviourCount(); ++j) {
      asEBehaviours behaviours;
      const auto f = t->GetBehaviourByIndex(j, &behaviours);
      if (behaviours == asBEHAVE_CONSTRUCT || behaviours == asBEHAVE_DESTRUCT) {
        stream << std::format("\t{};\n", f->GetDeclaration(false, true, true));
      }
    }
    for (int j = 0; j < t->GetMethodCount(); ++j) {
      const auto m = t->GetMethodByIndex(j);
      stream << std::format("\t{};\n", m->GetDeclaration(false, true, true));
    }
    for (int j = 0; j < t->GetPropertyCount(); ++j) {
      stream << std::format("\t{};\n", t->GetPropertyDeclaration(j, true));
    }
    for (int j = 0; j < t->GetChildFuncdefCount(); ++j) {
      stream << std::format("\tfuncdef {};\n", t->GetChildFuncdef(j)->GetFuncdefSignature()->GetDeclaration(false));
    }
    stream << "}\n";
    if (not ns.empty())
      stream << "}\n";
  }
}

template <class Stream>
void printGlobalFunctionList(const asIScriptEngine* engine, Stream& stream) {
  for (int i = 0; i < engine->GetGlobalFunctionCount(); i++) {
    const auto f = engine->GetGlobalFunctionByIndex(i);
    if (not f)
      continue;
    const std::string_view ns = f->GetNamespace();
    if (not ns.empty())
      stream << std::format("namespace {} {{ ", ns);
    stream << std::format("{};", f->GetDeclaration(false, false, true));
    if (not ns.empty())
      stream << " }";
    stream << "\n";
  }
}

template <class Stream>
void printGlobalPropertyList(const asIScriptEngine* engine, Stream& stream) {
  for (int i = 0; i < engine->GetGlobalPropertyCount(); i++) {
    const char* name;
    const char* ns0;
    int type;
    engine->GetGlobalPropertyByIndex(i, &name, &ns0, &type, nullptr, nullptr, nullptr, nullptr);

    const std::string t = engine->GetTypeDeclaration(type, true);
    if (t.empty())
      continue;

    std::string_view ns = ns0;
    if (not ns.empty())
      stream << std::format("namespace {} {{ ", ns);

    stream << std::format("{} {};", t, name);
    if (not ns.empty())
      stream << " }";
    stream << "\n";
  }
}

template <class Stream>
void printGlobalTypedef(const asIScriptEngine* engine, Stream& stream) {
  for (int i = 0; i < engine->GetTypedefCount(); ++i) {
    const auto type = engine->GetTypedefByIndex(i);
    if (not type)
      continue;
    const std::string_view ns = type->GetNamespace();
    if (not ns.empty())
      stream << std::format("namespace {} {{\n", ns);
    stream << std::format("typedef {} {};\n", engine->GetTypeDeclaration(type->GetTypedefTypeId()), type->GetName());
    if (not ns.empty())
      stream << "}\n";
  }
}
} // namespace

/// @brief Generate 'as.predefined' file, which contains all defined symbols in C++. It is used by the language server.
void GenerateScriptPredefined(const asIScriptEngine* engine, const std::string& path) {
  assert(path.ends_with("as.predefined"));

  std::ofstream stream{path};

  printEnumList(engine, stream);

  printClassTypeList(engine, stream);

  printGlobalFunctionList(engine, stream);

  printGlobalPropertyList(engine, stream);

  printGlobalTypedef(engine, stream);
}
bool ScriptEngine::initialize() {

  m_engine = asCreateScriptEngine();
  if (!m_engine) {
    return false;
  }
  int r = m_engine->SetMessageCallback(asMETHOD(ScriptEngine, messageCallback), this, asCALL_THISCALL);
  assert(r >= 0);

  RegisterScriptArray(m_engine, true);
  RegisterStdString(m_engine);
  RegisterStdStringUtils(m_engine);
  RegisterScriptHandle(m_engine);
  RegisterScriptWeakRef(m_engine);
  RegisterScriptMath(m_engine);
  RegisterScriptMathComplex(m_engine);
  RegisterScriptDictionary(m_engine);
  RegisterScriptJson(m_engine);
  RegisterScriptAny(m_engine);

  r = m_engine->RegisterEnum("LogLevel");
  assert(r >= 0);
  r = m_engine->RegisterEnumValue("LogLevel", "Message", 0);
  assert(r >= 0);
  r = m_engine->RegisterEnumValue("LogLevel", "Info", 1);
  assert(r >= 0);
  r = m_engine->RegisterEnumValue("LogLevel", "Warning", 2);
  assert(r >= 0);
  r = m_engine->RegisterEnumValue("LogLevel", "Error", 3);
  assert(r >= 0);
  r = m_engine->RegisterEnumValue("LogLevel", "Fatal", 4);
  assert(r >= 0);

  r = m_engine->RegisterGlobalFunction("void print(LogLevel level, const string& in)", asFUNCTION(PrintString), asCALL_CDECL);
  assert(r >= 0);
  RegisterBindings();

  GenerateScriptPredefined(m_engine, "as.predefined");
  return r >= 0;
}

asIScriptModule* ScriptEngine::createModule(const std::string_view name, const std::filesystem::path& scriptPath) const {

  if (asIScriptModule* mod = m_engine->GetModule(name.data(), asGM_ONLY_IF_EXISTS)) {
    return mod;
  }

  CScriptBuilder builder;
  int ret = builder.StartNewModule(m_engine, name.data());
  if (ret < 0) {
    return nullptr;
  }

  if (!exists(scriptPath)) {
    return nullptr;
  }

  ret = builder.AddSectionFromFile(scriptPath.generic_string().c_str());
  if (ret < 0) {
    return nullptr;
  }

  ret = builder.BuildModule();

  if (ret < 0) {
    return nullptr;
  }

  return m_engine->GetModule(name.data(), asGM_ONLY_IF_EXISTS);
}

asIScriptModule* ScriptEngine::createModule(const std::string_view name, ByteCodeReader& reader) const {

  if (asIScriptModule* mod = m_engine->GetModule(name.data(), asGM_ONLY_IF_EXISTS)) {
    return mod;
  }

  asIScriptModule* mod = m_engine->GetModule(name.data(), asGM_CREATE_IF_NOT_EXISTS);
  mod->LoadByteCode(&reader);
  return mod;
}

void ScriptEngine::messageCallback(const asSMessageInfo& msg) {
  if (msg.type == asMSGTYPE_WARNING) {
    APP_WARN("{} ({}, {}) : {}", msg.section, msg.row, msg.col, msg.message);
  } else if (msg.type == asMSGTYPE_ERROR) {
    APP_ERROR("{} ({}, {}) : {}", msg.section, msg.row, msg.col, msg.message);
  } else if (msg.type == asMSGTYPE_INFORMATION) {
    APP_INFO("{} ({}, {}) : {}", msg.section, msg.row, msg.col, msg.message);
  }

  if (msg.type == asMSGTYPE_ERROR) {
    m_hasCompilerErrors = true;
  }
}

asIScriptContext* ScriptEngine::prepareContextFromPool(asIScriptFunction* func) {
  asIScriptContext* ctx = nullptr;
  if (!m_contexts.empty()) {
    ctx = *m_contexts.rbegin();
    m_contexts.pop_back();
  } else {
    ctx = m_engine->CreateContext();
  }

  int ret = ctx->Prepare(func);
  assert(ret >= 0);
  return ctx;
}

int ScriptEngine::executeCall(asIScriptContext* ctx) {
  int r = ctx->Execute();
  if (r != asEXECUTION_FINISHED) {
    if (r == asEXECUTION_EXCEPTION) {
      APP_ERROR("Exception: {}", ctx->GetExceptionString());
      APP_ERROR("Function: {} Line {}", ctx->GetExceptionFunction()->GetDeclaration(), ctx->GetExceptionLineNumber());
    }
  }

  return r;
}

void ScriptEngine::returnContextToPool(asIScriptContext* ctx) {
  m_contexts.push_back(ctx);
  ctx->Unprepare();
}

ScriptEngine* ScriptEngine::instance() {
  static ScriptEngine instance;
  return &instance;
}


code:
#pragma once

#include "angelscript.h"

#include <filesystem>
#include <memory>
#include <string>
#include <vector>

class ByteCodeReader;
class asIScriptEngine;
class asIScriptFunction;
class asIScriptContext;
struct asSMessageInfo;
class asITypeInfo;
class EditorPlugin;
class ScriptEngine {
public:
  ~ScriptEngine();
  [[nodiscard]] bool hasCompileErrors() const { return m_hasCompilerErrors; }

  bool initialize();

  asIScriptEngine* engine() const { return m_engine; }
  static ScriptEngine* instance();

  asIScriptModule* createModule(std::string_view name, const std::filesystem::path& scriptPath) const;
  asIScriptModule* createModule(std::string_view name, ByteCodeReader& reader) const;
  static int executeCall(asIScriptContext* ctx);
  asIScriptContext* prepareContextFromPool(asIScriptFunction* func);
  void returnContextToPool(asIScriptContext* ctx);

private:
  void messageCallback(const asSMessageInfo& msg);

  ScriptEngine() = default;
  ScriptEngine(const ScriptEngine&) = delete;
  ScriptEngine& operator=(const ScriptEngine&) = delete;
  ScriptEngine(ScriptEngine&&) = delete;
  ScriptEngine& operator=(ScriptEngine&&) = delete;

  bool m_hasCompilerErrors{false};
  asIScriptEngine* m_engine = nullptr;

  std::vector<asIScriptContext*> m_contexts;
};


code:
#include "Core/Settings.hpp"
#include <fstream>

Settings* Settings::m_instance = nullptr;

void to_json(nlohmann::ordered_json& j, const Settings::WindowRect& r) {
  j = {
      {"x", r.x}, {"y", r.y}, {"w", r.w}, {"h", r.h}, {"maximized", r.maximized},
  };
}

void from_json(const nlohmann::ordered_json& j, Settings::WindowRect& r) {
  r.x = j.value("x", r.x);
  r.y = j.value("y", r.y);
  r.w = j.value("w", r.w);
  r.h = j.value("h", r.h);
  r.maximized = j.value("maximized", r.maximized);
}

void to_json(nlohmann::ordered_json& j, const Settings::Plugin& p) {
  j = {
      {"enabled", p.enabled},
      {"compiled", p.compiled},
  };
}

void from_json(const nlohmann::ordered_json& j, Settings::Plugin& p) {
  p.enabled = j.value("enabled", p.enabled);
  p.compiled = j.value("compiled", p.compiled);
}

Settings::Settings() { m_instance = this; }
bool Settings::load(std::string_view path) {
  try {
    if (std::ifstream f(path.data()); f.is_open()) {
      return loadFromJson(nlohmann::ordered_json::parse(f));
    }
  } catch (...) {
    // scream into the void, we don't care
  }
  return false;
}

bool Settings::loadFromJson(const nlohmann::ordered_json& parser) {
  try {
    window = parser.value("window", window);
    mru = parser.value("mru", mru);
    plugins = parser.value("plugins", plugins);
    lastDirectory = parser.value("lastDirectory", lastDirectory);
    lastProject = parser.value("lastProject", lastProject);
    projectBaseDirectory = parser.value("projectBaseDirectory", projectBaseDirectory);
    lcfProjectDirectory = parser.value("lcfProjectDirectory", lcfProjectDirectory);
    rpgMakerMVLocation = parser.value("rpgMakerMVLocation", rpgMakerMVLocation);
    rpgMakerMZLocation = parser.value("rpgMakerMZLocation", rpgMakerMZLocation);
    rpgMakerMVVersion = parser.value("rpgMakerMVVersion", rpgMakerMVVersion);
    rpgMakerMZVersion = parser.value("rpgMakerMZVersion", rpgMakerMZVersion);
    locale = parser.value("locale", locale);
    fontSize = parser.value("fontSize", fontSize);
    monoFontSize = parser.value("monoFontSize", monoFontSize);
    maxMru = parser.value("maxMru", maxMru);
    currentNWJSVersion = parser.value("currentNWJSVersion", currentNWJSVersion);
    ranFirstBootWizard = parser.value("ranFirstBootWizard", ranFirstBootWizard);
    enableExperimentalFeatures = parser.value("enableExperimentalFeatures", enableExperimentalFeatures);
    enableDebugFeatures = parser.value("enableDebugFeatures", enableDebugFeatures);
    imguiState = parser.value("imguiState", imguiState);
    return true;
  } catch (...) {}

  return false;
}
void Settings::serialize(const std::string_view path) {
  if (std::ofstream f(path.data()); f.is_open()) {
    f << serializeToJson().dump(4);
  }
}

nlohmann::ordered_json Settings::serializeToJson() {
  return {
      {"window", window},                                         //
      {"mru", mru},                                               //
      {"plugins", plugins},                                       //
      {"lastDirectory", lastDirectory},                           //
      {"lastProject", lastProject},                               //
      {"projectBaseDirectory", projectBaseDirectory},             //
      {"lcfProjectDirectory", lcfProjectDirectory},               //
      {"rpgMakerMVLocation", rpgMakerMVLocation},                 //
      {"rpgMakerMZLocation", rpgMakerMZLocation},                 //
      {"rpgMakerMVVersion", rpgMakerMVVersion},                   //
      {"rpgMakerMZVersion", rpgMakerMZVersion},                   //
      {"locale", locale},                                         //
      {"fontSize", fontSize},                                     //
      {"monoFontSize", monoFontSize},                             //
      {"maxMru", maxMru},                                         //
      {"currentNWJSVersion", currentNWJSVersion},                 //
      {"ranFirstBootWizard", ranFirstBootWizard},                 //
      {"enableExperimentalFeatures", enableExperimentalFeatures}, //
      {"enableDebugFeatures", enableDebugFeatures},               //
      {"imguiState", imguiState},                                 //
  };
}


code:
#pragma once

#include "Database/Globals.hpp"
#include "nlohmann/json.hpp"

#include <deque>
#include <SDL3/SDL_video.h>

#define ORE_CHECK_DEBUG_BEGIN() if (Settings::instance()->enableDebugFeatures) {
#define ORE_CHECK_DEBUG_END() }

#define ORE_CHECK_EXPERIMENTAL_BEGIN() if (Settings::instance()->enableExperimentalFeatures) {
#define ORE_CHECK_EXPERIMENTAL_END() }

#define ORE_DISABLE_EXPERIMENTAL_BEGIN() ImGui::BeginDisabled(!Settings::instance()->enableExperimentalFeatures)

#define ORE_DISABLE_EXPERIMENTAL_END() ImGui::EndDisabled()

struct Settings {
  struct WindowRect {
    friend void to_json(nlohmann::ordered_json& j, const WindowRect& r);
    friend void from_json(const nlohmann::ordered_json& j, WindowRect& r);
    int x{SDL_WINDOWPOS_CENTERED};
    int y{SDL_WINDOWPOS_CENTERED};
    int w{1920};
    int h{1080};
    bool maximized{false};
  };

  struct Plugin {
    friend void to_json(nlohmann::ordered_json& j, const Plugin& r);
    friend void from_json(const nlohmann::ordered_json& j, Plugin& p);
    bool enabled{false};
    bool compiled{false};
  };

  Settings();
  bool load(std::string_view path);
  bool loadFromJson(const nlohmann::ordered_json& parser);
  void serialize(std::string_view path);
  nlohmann::ordered_json serializeToJson();
  WindowRect window{};
  std::deque<std::pair<std::string, std::string>> mru;
  std::map<std::string, Plugin> plugins; // Base directory -> settings
  std::string lastDirectory;
  std::string lastProject;
  std::string projectBaseDirectory;
  std::string lcfProjectDirectory;
  std::string rpgMakerMVLocation;
  std::string rpgMakerMZLocation;
  std::string locale;
  int rpgMakerMVVersion{-1};
  int rpgMakerMZVersion{-1};
  int fontSize{12};
  int monoFontSize{12};
  int maxMru{10};
  std::string currentNWJSVersion;
  bool ranFirstBootWizard{false};

  bool enableExperimentalFeatures{false};
  bool enableDebugFeatures
  // We want this enabled by default in debug builds
#ifndef NDEBUG
      {true};
#else
      {false};
#endif

  [[nodiscard]] static Settings* instance() { return m_instance; }

  std::string imguiState;

private:
  static Settings* m_instance;
};

void to_json(nlohmann::ordered_json& j, const Settings::WindowRect& r);
void from_json(const nlohmann::ordered_json& j, Settings::WindowRect& r);


code:
#include "Core/SettingsDialog.hpp"

#include "Core/Application.hpp"
#include "Core/Settings.hpp"
#include "imgui.h"

std::tuple<bool, bool> SettingsDialog::draw() {
  if (m_uiRefreshRequested) {
    App::APP->requestFontUpdate();
    m_uiRefreshRequested = false;
    m_wasUIRefreshRequested = true;
  }

  if (isOpen()) {
    if (m_tempSettings.empty()) {
      m_tempSettings = Settings::instance()->serializeToJson();
    }
    ImGui::OpenPopup(m_name.c_str());
  }

  const ImVec2 center = ImGui::GetMainViewport()->GetCenter();
  ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
  ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size / 3, ImGuiCond_Always);
  if (ImGui::BeginPopupModal(m_name.c_str(), &m_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings)) {
    ImGui::BeginGroup();
    {
      if (ImGui::BeginTabBar("##settings_tabbar")) {
        for (const auto& tab : m_tabs) {
          tab->draw();
        }
        ImGui::EndTabBar();
      }
    }
    ImGui::EndGroup();
    const auto textSize = ImGui::CalcTextSize("OKAPPLYCANCEL");
    ImGui::BeginGroup();
    ImGui::SetCursorPosY(ImGui::GetContentRegionMax().y - (textSize.y + (ImGui::GetStyle().FramePadding.y * 2) + ImGui::GetStyle().ItemSpacing.y));
    ImGui::Separator();
    ImGui::SetCursorPosX(ImGui::GetContentRegionMax().x - (textSize.x + (ImGui::GetStyle().FramePadding.x * 2) + (ImGui::GetStyle().ItemSpacing.x * 4)));
    {
      if (ImGui::Button("OK")) {
        m_confirmed = true;
        m_open = false;

        m_wasUIRefreshRequested = false;
        m_tempSettings.clear();
        ImGui::CloseCurrentPopup();
        if (m_hasChangedSettings) {
          App::APP->serializeSettings();
        }
        m_uiRefreshRequested = false;
        m_hasChangedSettings = false;
        m_closedHere = true;
      }
      ImGui::SameLine();
      ImGui::BeginDisabled(!m_hasChangedSettings);
      if (ImGui::Button("Apply")) {
        m_confirmed = false;
        m_open = true;

        m_tempSettings = Settings::instance()->serializeToJson();
        if (m_wasUIRefreshRequested) {
          App::APP->requestFontUpdate();
          m_wasUIRefreshRequested = false;
        }

        if (m_hasChangedSettings) {
          App::APP->serializeSettings();
          m_hasChangedSettings = false;
        }
      }
      ImGui::EndDisabled();
      ImGui::SameLine();
      if (ImGui::Button("Cancel")) {
        m_confirmed = false;
        m_open = false;
        m_hasChangedSettings = false;
        Settings::instance()->loadFromJson(m_tempSettings);
        m_uiRefreshRequested = true;
        m_tempSettings.clear();
        if (m_wasUIRefreshRequested) {
          App::APP->requestFontUpdate();
          m_wasUIRefreshRequested = false;
        }
        ImGui::CloseCurrentPopup();
        m_closedHere = true;
      }
    }
    ImGui::EndGroup();
    ImGui::EndPopup();

    if (!m_open && !m_closedHere) {
      Settings::instance()->loadFromJson(m_tempSettings);
      m_tempSettings.clear();
      if (m_wasUIRefreshRequested) {
        App::APP->requestFontUpdate();
        m_wasUIRefreshRequested = false;
      }
      m_closedHere = false;
    }
  }
  return {!m_open, m_confirmed};
}

void SettingsDialog::onValueChanged() { m_hasChangedSettings = true; }
void SettingsDialog::onUIRefreshRequested() { m_uiRefreshRequested = true; }


code:
#pragma once
#include "CommonUI/IDialogController.hpp"
#include "SettingsDialog/ISettingsTab.hpp"

#include <memory>
#include <nlohmann/json.hpp>
#include <vector>

class SettingsDialog final : public IDialogController {
public:
  SettingsDialog()
  : IDialogController("Settings") {}

  std::tuple<bool, bool> draw() override;

  void addTab(ISettingsTab* settingsTab) {
    if (!settingsTab) {
      return;
    }
    m_tabs.emplace_back(settingsTab);
    m_tabs.back()->onValueChanged.connect<&SettingsDialog::onValueChanged>(this);
    m_tabs.back()->onUIRefreshRequested.connect<&SettingsDialog::onUIRefreshRequested>(this);
  }

private:
  void onValueChanged();
  void onUIRefreshRequested();
  nlohmann::ordered_json m_tempSettings;
  std::vector<std::unique_ptr<ISettingsTab>> m_tabs;
  bool m_hasChangedSettings{false};
  bool m_uiRefreshRequested{false};
  bool m_wasUIRefreshRequested{false};
  bool m_closedHere{false};
};


code:
#include "Core/SettingsDialog/GeneralSettingsTab.hpp"

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

#include "Core/Application.hpp"

#include <imgui.h>
#include <SDL3/SDL_dialog.h>

#if defined(_WIN32) || defined(_WIN64)
#include <string.h>
#define strcasecmp _stricmp
#define strncasecmp _strnicmp
#else
#include <strings.h>
#endif

void GeneralSettingsTab::draw() {
  if (ImGui::BeginTabItem(trNOOP("General"))) {
    ImGui::TextUnformatted(trNOOP("Debug & Experimental Features"));
    // TL-NOTE: "I Know What I'm Doing" should *not* be localized
    ImGui::LabelOverLineEdit("##experimental_features_confirmation", trNOOP("Type \"I Know What I'm Doing\" to enable features."), m_confirmationText, sizeof(m_confirmationText), 0.f, nullptr,
                             ImGuiInputTextFlags_None);
    const bool confirmed = !!strncasecmp(m_confirmationText, "i know what i'm doing", sizeof(m_confirmationText));
    ImGui::BeginDisabled(confirmed && !Settings::instance()->enableExperimentalFeatures);
    ImGui::Checkbox(trNOOP("Experimental"), &Settings::instance()->enableExperimentalFeatures);
    ImGui::EndDisabled();
    ImGui::BeginDisabled(confirmed && !Settings::instance()->enableDebugFeatures);
    ImGui::Checkbox(trNOOP("Debug"), &Settings::instance()->enableDebugFeatures);
    ImGui::EndDisabled();
    ImGui::Separator();
    ImGui::TextUnformatted(trNOOP("Max Recent Projects"));
    ImGui::SliderInt("##mru_max", &Settings::instance()->maxMru, 1, 20);
    if (ImGui::IsItemDeactivatedAfterEdit()) {
      Settings::instance()->maxMru = std::clamp(Settings::instance()->maxMru, 1, 20);
      emit_signal(onValueChanged);
    }
    // TODO: Undo/Redo stack settings

    char location[4096];
    strncpy(location, Settings::instance()->projectBaseDirectory.c_str(), 4096);
    ImGui::LabelOverLineEdit("##project_location_line_edit", trNOOP("Project Base Directory Location"), location, sizeof(location), 0.f, trNOOP("Sets the directory where projects are stored"));
    if (ImGui::IsItemDeactivatedAfterEdit()) {
      Settings::instance()->projectBaseDirectory = location;
      emit_signal(onValueChanged);
    }
    ImGui::SameLine();
    ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetStyle().FramePadding.y);
    ImGui::BeginGroup();
    {
      ImGui::NewLine();
      ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetStyle().FramePadding.y);
      if (ImGui::Button(trNOOP("Choose..."))) {
        SDL_ShowOpenFolderDialog(
            [](void* userdata, const char* const* fileList, int filter) {
              if (!fileList || !*fileList || filter >= 1) {
                return;
              }
              const std::filesystem::path path{fileList[0]};
              Settings::instance()->projectBaseDirectory = absolute(path).generic_string();
              emit_signal(static_cast<ISettingsTab*>(userdata)->onValueChanged);
            },
            this, App::APP->getWindow()->getNativeWindow(), !Settings::instance()->projectBaseDirectory.empty() ? Settings::instance()->projectBaseDirectory.c_str() : nullptr, false);
      }
      ImGui::SetItemTooltip("%s", trNOOP("Select a directory to store RPG Maker game projects"));
    }
    ImGui::EndGroup();

    ImGui::EndTabItem();
  }
}


code:
#pragma once
#include "Core/SettingsDialog/ISettingsTab.hpp"

class GeneralSettingsTab final : public ISettingsTab {
public:
  void draw() override;

private:
  char m_confirmationText[22];
};


code:
#pragma once
#include "Utils/SignalSlot.hpp"

class SettingsDialog;
class ISettingsTab {
public:
  virtual ~ISettingsTab() = default;
  virtual void draw() = 0;

  rpgmutils::signal<void()> onValueChanged;
  rpgmutils::signal<void()> onUIRefreshRequested;
};


code:
#include "Core/SettingsDialog/RPGMakerLocationAndVersionTab.hpp"
#include "Core/Application.hpp"
#include "Core/ImGuiExt/ImGuiUtils.hpp"
#include "Core/Settings.hpp"

#include <Database/Versions.hpp>

#include "orei18n.hpp"

#include <imgui.h>
#include <SDL3/SDL_dialog.h>

void RPGMakerLocationAndVersionTab::draw() {
  if (ImGui::BeginTabItem(trNOOP("RPG Maker Location & Version"))) {
    char location[4096];
    strncpy(location, Settings::instance()->rpgMakerMVLocation.c_str(), 4096);
    if (ImGui::LabelOverLineEdit("##rpgmakermv_location_line_edit", trNOOP("RPG Maker MV Location"), location, sizeof(location), 0.f)) {
      Settings::instance()->rpgMakerMVLocation = location;
      emit_signal(onValueChanged);
    }
    ImGui::SameLine();
    ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetStyle().FramePadding.y);
    ImGui::BeginGroup();
    {
      ImGui::NewLine();
      ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetStyle().FramePadding.y);
      if (ImGui::Button(std::format("{}##rpgmakermv", trNOOP("Choose...")).c_str())) {
        SDL_ShowOpenFolderDialog(
            [](void* userdata, const char* const* fileList, int filter) {
              if (!fileList || !*fileList || filter >= 1) {
                return;
              }
              const std::filesystem::path path{fileList[0]};
              Settings::instance()->rpgMakerMVLocation = absolute(path).generic_string();
              emit_signal(static_cast<ISettingsTab*>(userdata)->onValueChanged);
            },
            this, App::APP->getWindow()->getNativeWindow(), !Settings::instance()->rpgMakerMVLocation.empty() ? Settings::instance()->rpgMakerMVLocation.c_str() : nullptr, false);
      }
    }
    ImGui::EndGroup();
    ImGui::BeginGroup();
    {
      ImGui::Text("RPG Maker MV Version");
      if (ImGui::BeginCombo("##rpgmakermv_version_combo", Settings::instance()->rpgMakerMVVersion != -1 ? KnownRPGMVVersions[Settings::instance()->rpgMakerMVVersion].data() : "Latest")) {
        if (ImGui::Selectable("Latest", Settings::instance()->rpgMakerMVVersion == -1)) {
          Settings::instance()->rpgMakerMVVersion = -1;
        }
        for (int i = 0; i < KnownRPGMVVersions.size(); ++i) {
          if (ImGui::Selectable(KnownRPGMVVersions[i].data(), i == Settings::instance()->rpgMakerMVVersion)) {
            Settings::instance()->rpgMakerMVVersion = i;
            emit_signal(onValueChanged);
          }
          if (Settings::instance()->rpgMakerMVVersion == i) {
            ImGui::SetItemDefaultFocus();
          }
        }
        ImGui::EndCombo();
      }
    }
    ImGui::EndGroup();
    ImGui::Separator();
    char location2[4096];
    strncpy(location2, Settings::instance()->rpgMakerMZLocation.c_str(), 4096);
    if (ImGui::LabelOverLineEdit("##rpgmakermz_location_line_edit", "RPG Maker MZ Location", location2, sizeof(location2), 0.f)) {
      Settings::instance()->rpgMakerMZLocation = location2;
    }
    ImGui::SameLine();
    ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetStyle().FramePadding.y);
    ImGui::BeginGroup();
    {
      ImGui::NewLine();
      ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetStyle().FramePadding.y);
      if (ImGui::Button(std::format("{}##rpgmakermz", trNOOP("Choose...")).c_str())) {
        SDL_ShowOpenFolderDialog(
            [](void* userdata, const char* const* fileList, int filter) {
              if (!fileList || !*fileList || filter >= 1) {
                return;
              }
              const std::filesystem::path path{fileList[0]};
              Settings::instance()->rpgMakerMZLocation = absolute(path).generic_string();
              emit_signal(static_cast<ISettingsTab*>(userdata)->onValueChanged);
            },
            this, App::APP->getWindow()->getNativeWindow(), !Settings::instance()->rpgMakerMZLocation.empty() ? Settings::instance()->rpgMakerMZLocation.c_str() : nullptr, false);
      }
    }
    ImGui::EndGroup();
    ImGui::BeginGroup();
    {
      ImGui::Text("RPG Maker MZ Version");
      if (ImGui::BeginCombo("##rpgmakermz_version_combo", Settings::instance()->rpgMakerMZVersion != -1 ? KnownRPGMZVersions[Settings::instance()->rpgMakerMZVersion].data() : "Latest")) {
        if (ImGui::Selectable("Latest", Settings::instance()->rpgMakerMZVersion == -1)) {
          Settings::instance()->rpgMakerMZVersion = -1;
        }
        for (int i = 0; i < KnownRPGMZVersions.size(); ++i) {
          if (ImGui::Selectable(KnownRPGMZVersions[i].data(), i == Settings::instance()->rpgMakerMZVersion)) {
            Settings::instance()->rpgMakerMZVersion = i;
            emit_signal(onValueChanged);
          }
          if (Settings::instance()->rpgMakerMZVersion == i) {
            ImGui::SetItemDefaultFocus();
          }
        }
        ImGui::EndCombo();
      }
    }
    ImGui::EndGroup();
    ImGui::EndTabItem();
  }
}


code:
#pragma once

#include "Core/SettingsDialog/ISettingsTab.hpp"

class RPGMakerLocationAndVersionTab final : public ISettingsTab {
public:
  void draw() override;
};


code:
#include "Core/SettingsDialog/UISettingsTab.hpp"
#include "Core/Application.hpp"
#include "Core/Settings.hpp"
#include <imgui.h>
#include <imgui_internal.h>

void UISettingsTab::draw() {
  if (ImGui::BeginTabItem("UI")) {
    ImGui::PushFont(App::APP->getMainFont(), Settings::instance()->fontSize);
    ImGui::Text("Font Size");
    ImGui::SliderInt("##ui_font_size", &Settings::instance()->fontSize, 8, 48);
    // Settings::instance()->fontSize = nextMultipleOf8(Settings::instance()->fontSize);
    if (ImGui::IsItemDeactivatedAfterEdit()) {
      App::APP->requestFontUpdate();
      m_fontSizeChanged = true;
      emit_signal(onValueChanged);
    }
    ImGui::PopFont();
    ImGui::PushFont(App::APP->getMonoFont(), Settings::instance()->monoFontSize);
    ImGui::Text("Monospace Font Size");
    ImGui::SliderInt("##ui_mono_font_size", &Settings::instance()->monoFontSize, 8, 48);
    // Settings::instance()->monoFontSize = nextMultipleOf8(Settings::instance()->monoFontSize);
    if (ImGui::IsItemDeactivatedAfterEdit()) {
      App::APP->requestFontUpdate();
      m_fontSizeChanged = true;
      emit_signal(onValueChanged);
    }
    ImGui::PopFont();
    if (m_fontSizeChanged) {
      m_fontSizeChanged = !App::APP->fontUpdateRequestPerformed();
      emit_signal(onValueChanged);
    }
    ImGui::EndTabItem();
  }
}


code:
#pragma once

#include "Core/SettingsDialog/ISettingsTab.hpp"

class UISettingsTab final : public ISettingsTab {
public:
  void draw() override;

private:
  bool m_fontSizeChanged{false};
};


code:
#include "Core/Sound.hpp"

#include "Log.hpp"

sf::Vector3f stereoPanPosition(float panAmount) {
  // make sure that panAmount is in the range [-1, 1] (-1 is left; 1 is right)
  panAmount = std::clamp(panAmount, -1.f, 1.f);
  return {panAmount, 0.f, panAmount < 0.f ? -panAmount - 1.f : panAmount - 1.f};
}

Sound::Sound(const sf::SoundBuffer& buffer) {
  m_numChannels = buffer.getChannelCount();
  if (m_numChannels > 2) {
    APP_DEBUG("Unsupported number of channels {}", m_numChannels);
  }

  if (m_numChannels == 2) {
    auto samples = buffer.getSamples();
    std::vector<sf::Int16> leftSamples;
    std::vector<sf::Int16> rightSamples;

    for (int i = 0; i < buffer.getSampleCount() / 2; ++i) {
      leftSamples.push_back(samples[i * 2]);
      rightSamples.push_back(samples[(i * 2) + 1]);
    }

    m_leftBuffer.loadFromSamples(leftSamples.data(), leftSamples.size(), 1, buffer.getSampleRate());
    m_leftSound.setBuffer(m_leftBuffer);

    m_rightBuffer.loadFromSamples(rightSamples.data(), rightSamples.size(), 1, buffer.getSampleRate());
    m_rightSound.setBuffer(m_rightBuffer);
    /* Retrieve and sync up parameters */
    m_volume = m_leftSound.getVolume();
    m_minDistance = m_leftSound.getMinDistance();
    m_attenuation = m_leftSound.getAttenuation();
    m_pitch = m_leftSound.getPitch();
    m_relativeToListener = false;
    m_leftSound.setRelativeToListener(m_relativeToListener);
    m_rightSound.setVolume(m_volume);
    m_rightSound.setMinDistance(m_minDistance);
    m_rightSound.setPitch(m_pitch);
    m_rightSound.setRelativeToListener(m_relativeToListener);
  } else {
    m_leftBuffer = buffer;
  }

  m_volume = m_leftSound.getVolume();
  m_minDistance = m_leftSound.getMinDistance();
  m_attenuation = m_leftSound.getAttenuation();
  m_pitch = m_leftSound.getPitch();
  m_relativeToListener = false;
  m_leftSound.setRelativeToListener(m_relativeToListener);

  if (m_numChannels == 2) {
    m_rightSound.setVolume(m_volume);
    m_rightSound.setMinDistance(m_minDistance);
    m_rightSound.setPitch(m_pitch);
    m_rightSound.setRelativeToListener(m_relativeToListener);
  }

  m_pan = 0.f;
  setPan(m_pan);
}

Sound::Sound(const Sound& other) {
  m_numChannels = other.m_numChannels;
  m_leftBuffer = other.m_leftBuffer;
  m_rightBuffer = other.m_rightBuffer;
  m_minDistance = other.m_minDistance;
  m_pan = other.m_pan;
  m_volume = other.m_volume;
  m_minDistance = other.m_minDistance;
  m_pitch = other.m_pitch;
  m_attenuation = other.m_attenuation;
  m_leftSound = other.m_leftSound;
  m_rightSound = other.m_rightSound;
  m_leftSound.setBuffer(m_leftBuffer);
  m_leftSound.setVolume(m_volume);
  m_leftSound.setMinDistance(m_minDistance);
  m_leftSound.setPitch(m_pitch);
  m_leftSound.setRelativeToListener(m_relativeToListener);
  m_rightSound.setBuffer(m_rightBuffer);
  m_rightSound.setVolume(m_volume);
  m_rightSound.setMinDistance(m_minDistance);
  m_rightSound.setPitch(m_pitch);
  m_rightSound.setRelativeToListener(m_relativeToListener);
}

Sound& Sound::operator=(const Sound& other) {
  m_numChannels = other.m_numChannels;
  m_leftBuffer = other.m_leftBuffer;
  m_rightBuffer = other.m_rightBuffer;
  m_minDistance = other.m_minDistance;
  m_pan = other.m_pan;
  m_volume = other.m_volume;
  m_minDistance = other.m_minDistance;
  m_pitch = other.m_pitch;
  m_attenuation = other.m_attenuation;
  m_leftSound = other.m_leftSound;
  m_rightSound = other.m_rightSound;
  m_leftSound.setBuffer(m_leftBuffer);
  m_leftSound.setVolume(m_volume);
  m_leftSound.setMinDistance(m_minDistance);
  m_leftSound.setPitch(m_pitch);
  m_leftSound.setRelativeToListener(m_relativeToListener);
  m_rightSound.setBuffer(m_rightBuffer);
  m_rightSound.setVolume(m_volume);
  m_rightSound.setMinDistance(m_minDistance);
  m_rightSound.setPitch(m_pitch);
  m_rightSound.setRelativeToListener(m_relativeToListener);
  return *this;
}

void Sound::play() {
  m_leftSound.play();
  if (m_numChannels == 2) {
    m_rightSound.play();
  }
}

void Sound::pause() {
  m_leftSound.pause();
  if (m_numChannels == 2) {
    m_rightSound.pause();
  }
}

void Sound::stop() {
  m_leftSound.stop();
  if (m_numChannels) {
    m_rightSound.stop();
  }
}

void Sound::setBuffers(const sf::SoundBuffer& leftBuffer, const sf::SoundBuffer& rightBuffer) {
  m_leftSound.setBuffer(leftBuffer);
  if (m_numChannels == 2) {
    m_rightSound.setBuffer(rightBuffer);
  }
}

void Sound::setLoop(const bool loop) {
  m_leftSound.setLoop(loop);
  if (m_numChannels == 2) {
    m_rightSound.setLoop(loop);
  }
}
void Sound::setPlayingOffset(const sf::Time offset) {
  m_leftSound.setPlayingOffset(offset);
  if (m_numChannels == 2) {
    m_rightSound.setPlayingOffset(offset);
  }
}

[[nodiscard]] const sf::SoundBuffer* Sound::getLeftBuffer() const { return m_leftSound.getBuffer(); }
[[nodiscard]] const sf::SoundBuffer* Sound::getRightBuffer() const { return m_rightSound.getBuffer(); }
[[nodiscard]] bool Sound::getLoop() const { return m_leftSound.getLoop() | m_rightSound.getLoop(); }
[[nodiscard]] sf::Time Sound::getPlayingOffset() const { return m_leftSound.getPlayingOffset(); }
[[nodiscard]] sf::Sound::Status Sound::getLeftStatus() const { return m_leftSound.getStatus(); }
[[nodiscard]] sf::Sound::Status Sound::getRightStatus() const { return m_rightSound.getStatus(); }

void Sound::setPan(const float amount) {
  m_leftSound.setPosition(stereoPanPosition(amount));
  if (m_numChannels) {
    m_rightSound.setPosition(stereoPanPosition(amount));
  }
  m_pan = amount;
}

void Sound::setVolume(const float volume) {
  m_leftSound.setVolume(volume);
  if (m_numChannels == 2) {
    m_rightSound.setVolume(volume);
  }
  m_volume = volume;
}

float Sound::getVolume() const { return m_volume; }

void Sound::setPitch(const float pitch) {
  m_leftSound.setPitch(pitch);
  if (m_numChannels == 2) {
    m_rightSound.setPitch(pitch);
  }
  m_pitch = pitch;
}

float Sound::getPitch() const { return m_pitch; }

void Sound::setLeftPosition(const float x, const float y, const float z) { m_leftSound.setPosition(x, y, z); }
void Sound::setLeftPosition(const sf::Vector3f& position) { m_leftSound.setPosition(position); }

sf::Vector3f Sound::getLeftPosition() const { return m_leftSound.getPosition(); }

void Sound::setRightPosition(float x, float y, float z) { m_rightSound.setPosition(x, y, z); }
void Sound::setRightPosition(const sf::Vector3f& position) { m_rightSound.setPosition(position); }

sf::Vector3f Sound::getRightPosition() const { return m_rightSound.getPosition(); }

void Sound::setRelativeToListener(const bool relative) {
  m_leftSound.setRelativeToListener(relative);
  if (m_numChannels == 2) {
    m_rightSound.setRelativeToListener(relative);
  }
  m_relativeToListener = relative;
}

bool Sound::isRelativeToListener() const { return m_relativeToListener; }

void Sound::setMinDistance(float distance) {
  m_leftSound.setMinDistance(distance);
  if (m_numChannels == 2) {
    m_rightSound.setMinDistance(distance);
  }
  m_minDistance = distance;
}

float Sound::getMinDistance() const { return m_minDistance; }

void Sound::setAttenuation(const float attenuation) {
  m_leftSound.setAttenuation(attenuation);
  if (m_numChannels == 2) {
    m_rightSound.setAttenuation(attenuation);
  }
  m_attenuation = attenuation;
}

float Sound::getAttenuation() const { return m_attenuation; }


code:
#pragma once
#include <SFML/Audio/Sound.hpp>
#include <SFML/Audio/SoundBuffer.hpp>

class Sound {
public:
  Sound() = default;
  explicit Sound(const sf::SoundBuffer& buffer);
  Sound(const Sound& other);
  Sound& operator=(const Sound& other);

  void play();
  void pause();
  void stop();
  void setBuffers(const sf::SoundBuffer& leftBuffer, const sf::SoundBuffer& rightBuffer);
  void setLoop(bool loop);
  void setPlayingOffset(sf::Time offset);
  [[nodiscard]] const sf::SoundBuffer* getLeftBuffer() const;
  [[nodiscard]] const sf::SoundBuffer* getRightBuffer() const;
  [[nodiscard]] bool getLoop() const;
  [[nodiscard]] sf::Time getPlayingOffset() const;
  [[nodiscard]] sf::Sound::Status getLeftStatus() const;
  [[nodiscard]] sf::Sound::Status getRightStatus() const;
  void setPan(float amount);
  void setVolume(float volume);
  [[nodiscard]] float getVolume() const;
  void setPitch(float pitch);
  [[nodiscard]] float getPitch() const;

  void setLeftPosition(float x, float y, float z);
  void setLeftPosition(const sf::Vector3f& position);
  [[nodiscard]] sf::Vector3f getLeftPosition() const;
  void setRightPosition(float x, float y, float z);
  void setRightPosition(const sf::Vector3f& position);
  [[nodiscard]] sf::Vector3f getRightPosition() const;

  void setRelativeToListener(bool relative);
  bool isRelativeToListener() const;
  void setMinDistance(float distance);
  [[nodiscard]] float getMinDistance() const;

  void setAttenuation(float attenuation);
  [[nodiscard]] float getAttenuation() const;

private:
  sf::SoundBuffer m_leftBuffer;
  sf::SoundBuffer m_rightBuffer;
  sf::Sound m_leftSound;
  sf::Sound m_rightSound;
  float m_volume{100.f};
  float m_pitch{1.f};
  float m_pan{0.f};
  float m_minDistance{1.f};
  float m_attenuation{1.f};
  bool m_relativeToListener{false};
  int m_numChannels = 2;
};


code:
#include "TemplateName.hpp"
#include "Core/Application.hpp"
#include "Database/Templates.hpp"

#include "imgui.h"

void TemplateName::draw() {
  ImGui::SetNextWindowSize(ImVec2{500, 130}, ImGuiCond_Once);
  if (ImGui::Begin("Template Properties", &m_open,
                   ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoResize)) {
    ImGui::Text("Template Name:");
    ImGui::SetNextItemWidth(400);
    ImGui::InputText("##orpg_templates_rename", &m_templateName);

    ImGui::BeginGroup();
    {
      if (ImGui::Button("OK")) {
        m_template->setName(m_templateName);
        m_hasChanges = true;
        ImGui::CloseCurrentPopup();
      }
    }
    ImGui::EndGroup();
  }
  ImGui::End();
};


code:
#pragma once
#include "Core/TemplateEditor/IDBTemplates.hpp"
#include "Database/Templates.hpp"

class TemplateName : public IDBTemplates {
public:
  TemplateName() = delete;
  explicit TemplateName(Template* templ, DatabaseEditor* parent)
  : IDBTemplates(parent)
  , m_template(templ) {
    m_templateName = m_template->name();
  }
  bool hasChanges() override { return m_hasChanges; }
  void draw() override;
  DatabaseEditor* m_parent = nullptr;

private:
  Template* m_template;
  std::string m_templateName;

  bool m_hasChanges{false};
  bool m_open{true};
};


code:
#include "Core/TemplateEditor/Dialog/TemplatesEvent.hpp"
#include "Core/Application.hpp"
#include "Database/Templates.hpp"

#include "imgui.h"

#include "IconsFontAwesome6.h"
void TemplatesEvent::draw() {
  static constexpr std::array<std::string_view, 4> kDirectionArrows = {{
      ICON_FA_ARROW_DOWN,
      ICON_FA_ARROW_LEFT,
      ICON_FA_ARROW_RIGHT,
      ICON_FA_ARROW_UP,
  }};
  ImGui::SetNextWindowSize(ImVec2{500, 400}, ImGuiCond_Once);
  if (ImGui::Begin("Template Properties", &m_open,
                   ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoResize)) {
    ImGui::Text("ID:");
    ImGui::SetNextItemWidth(200);

    if (ImGui::InputInt("##orpg_templates_event_properties_id", &m_id)) {
      if (m_id == 0) {
        m_id = 1; // Cannot pick index 0
      }
    }
    ImGui::Text("Name:");
    ImGui::InputText("##orpg_templates_event_properties_name", &m_eventName);
    ImGui::Text("Note:");
    ImGui::InputText("##orpg_templates_event_properties_note", &m_eventNote);
    ImGui::BeginChild("ArrowButtons", ImVec2{150, 150}, 0, ImGuiWindowFlags_NoBackground);
    ImGui::Text("Initial Direction:");
    ImGui::Columns(3, nullptr, false);
    const auto size = ImGui::CalcTextSize(ICON_FA_ARROW_LEFT);
    const auto width = size.x + (ImGui::GetStyle().FramePadding.x * 2) + (ImGui::GetStyle().ItemSpacing.x);
    ImGui::SetColumnWidth(0, width);
    ImGui::SetColumnWidth(1, width);
    ImGui::SetColumnWidth(2, width);
    for (int i = 0; i < 9; i++) {
      switch (i) {
      case 0:
        // if (ImGui::ArrowButton("##UpLeft", ImGuiDir_UpLeft)){}
        break;
      case 1:
        if (ImGui::ArrowButton("##Up", ImGuiDir_Up)) {
          m_direction = 8;
        }
        break;
      case 2:
        // if (ImGui::ArrowButton("##UpRight", ImGuiDir_UpRight)){}
        break;
      case 3:
        if (ImGui::ArrowButton("##Left", ImGuiDir_Left)) {
          m_direction = 4;
        }
        break;
      case 4:
        ImGui::SetCursorPos(ImVec2{ImGui::GetCursorPosX() + size.x / 2, ImGui::GetCursorPosY() + size.y / 2} - (ImGui::GetStyle().ItemSpacing / 2));
        ImGui::Text("%s", kDirectionArrows[(m_direction - 2) / 2].data());
        break;
      case 5:
        if (ImGui::ArrowButton("##Right", ImGuiDir_Right)) {
          m_direction = 6;
        }
        break;
      case 6:
        // if (ImGui::ArrowButton("##DownLeft", ImGuiDir_DownLeft)){}
        break;
      case 7:
        if (ImGui::ArrowButton("##Down", ImGuiDir_Down)) {
          m_direction = 2;
        }
        break;
      case 8:
        // if (ImGui::ArrowButton("##DownRight", ImGuiDir_DownRight)){}
        break;
      }
      ImGui::NextColumn();
    }
    ImGui::EndChild();

    ImGui::BeginGroup();
    {
      if (ImGui::Button("OK")) {
        // if (m_id < m_map->events.size()) {
        //   if (m_id != m_event->id) {
        //     // User requested that the event index be changed
        //     int oldIndex = m_event->id;
        //     int newIndex = m_map->events.at(m_id)->id;
        //     std::swap(m_map->events.at(oldIndex), m_map->events.at(newIndex));
        //     m_event->id = m_id;
        //   }
        // }
        m_event->setName(m_eventName);
        m_event->setNote(m_eventNote);

        m_event->setX(m_tileX);
        m_event->setY(m_tileY);
        for (auto& page : m_event->pages()) {
          page.image().setDirection(static_cast<Direction>(m_direction));
        }
        if (m_id != m_event->id()) {
          m_event->setId(m_id);
        }
        m_map->createEventFromTemplate(*m_event);
        m_hasChanges = true;
        ImGui::CloseCurrentPopup();
      }
      ImGui::SameLine();
      if (ImGui::Button("Cancel")) {
        m_hasChanges = true;
        ImGui::CloseCurrentPopup();
      }
    }
    ImGui::EndGroup();
    // ImGui::PopButtonRepeat();
    // ImGui::EndChild();
  }
  ImGui::End();
};


code:
#pragma once
#include "Core/TemplateEditor/IDBTemplates.hpp"
#include "Database/Event.hpp"
#include "Database/Map.hpp"

struct TemplatesEvent final : IDBTemplates {
  TemplatesEvent() = delete;
  explicit TemplatesEvent(Event* ev, Map* map, DatabaseEditor* parent, const int x, const int y)
  : IDBTemplates(parent)
  , m_event(ev)
  , m_map(map)
  , m_tileX(x)
  , m_tileY(y) {
    m_id = m_map->findOrMakeFreeId();
    m_eventName = m_event->name();
    m_eventNote = m_event->note();
    m_direction = static_cast<int>(m_event->page(0)->image().direction()); // We assume that the character index for one page defines the rest.
                                                                           // This is mostly useful for ARPG templates where all the event pages will be facing one direction.
    // for (auto& page : m_event->pages) {
    //   page.image.characterIndex = 1;
    // }
  }
  bool hasChanges() override { return m_hasChanges; }
  void draw() override;
  DatabaseEditor* m_parent = nullptr;

private:
  Event* m_event;
  Map* m_map;

  std::string m_eventName;
  std::string m_eventNote;
  int m_direction;
  int m_id{1};

  int m_tileX;
  int m_tileY;

  bool m_hasChanges{false};
  bool m_open{true};
};


code:
#pragma once
#include <vector>

class DatabaseEditor;
class IDBTemplates {
public:
  explicit IDBTemplates(DatabaseEditor* parent)
  : m_parent(parent) {}
  virtual ~IDBTemplates() = default;
  virtual std::vector<int> getData() { return {}; }
  virtual bool hasChanges() { return false; }
  virtual void draw() = 0;

protected:
  DatabaseEditor* m_parent;
};


code:
#include "Core/TemplateEditor/TemplatesCommand.hpp"
#include "Core/Application.hpp"
#include "Core/DatabaseEditor/IDBEditorTab.hpp"
#include "Database/Templates.hpp"

#include "imgui.h"

TemplatesCommand::TemplatesCommand(Template* templ, DatabaseEditor* parent)
: IDBTemplates(parent)
, m_parent(parent)
, m_currentTemplate(templ) {}

void TemplatesCommand::draw() {};


code:
#pragma once
#include "Core/TemplateEditor/IDBTemplates.hpp"
#include "Database/Templates.hpp"

class TemplatesCommand final : public IDBTemplates {
public:
  TemplatesCommand() = delete;
  explicit TemplatesCommand(Template* templ, DatabaseEditor* parent);
  void draw() override;
  DatabaseEditor* m_parent = nullptr;

private:
  Template* m_currentTemplate;
};


code:
#include "Core/TemplateEditor/TemplatesEventProperties.hpp"
#include "Core/Application.hpp"
#include "Database/Templates.hpp"

#include "imgui.h"

void TemplatesEventProperties::draw() {
  ImGui::BeginChild("#orpg_template_editor_event_properties");
  {}
  ImGui::EndChild();
};


code:
#pragma once
#include "Core/TemplateEditor/IDBTemplates.hpp"
#include "Database/Event.hpp"
#include "Database/Map.hpp"
#include "Database/Templates.hpp"

struct TemplatesEventProperties : IDBTemplates {
  TemplatesEventProperties() = delete;
  explicit TemplatesEventProperties(Template* templ, DatabaseEditor* parent)
  : IDBTemplates(parent)
  , m_currentTemplate(templ) {
    // m_id = m_event->id;
    // m_templateName = m_currentTemplate->name;
    //_templateName = m_currentTemplate->note;
    // m_eventNote = m_event->note;
    // m_characterIndex = m_event->pages.front().image.characterIndex; // We assume that the character index for one page defines the rest.
    //  This is mostly useful for ARPG templates where all the event pages will be facing one direction.
    // for (auto& page : m_event->pages) {
    //   page.image.characterIndex = 1;
    // }
  }
  bool hasChanges() override { return m_hasChanges; }
  void draw() override;
  DatabaseEditor* m_parent = nullptr;

private:
  Event* m_event;
  Map* m_map;

  std::string m_eventName;
  std::string m_eventNote;
  int m_characterIndex;
  int m_id;
  Template* m_currentTemplate;

  bool m_hasChanges{false};
  bool m_open{true};
};


code:
#include "Core/TemplateEditor/TemplatesTint.hpp"
#include "Core/Application.hpp"
#include "Database/Templates.hpp"

#include "imgui.h"

void TemplatesTint::draw() {
  ImGui::BeginChild("#orpg_template_editor_tint");
  {
    ImGui::SeparatorText("Color Tone");
    ImGui::BeginGroup();
    {
      ImGui::Text("Red:");
      ImGui::SetCursorPosY(ImGui::GetCursorPos().y + 8.f);
      ImGui::Text("Green:");
      ImGui::SetCursorPosY(ImGui::GetCursorPos().y + 8.f);
      ImGui::Text("Blue:");
      ImGui::SetCursorPosY(ImGui::GetCursorPos().y + 8.f);
      ImGui::Text("Gray:");
    }
    ImGui::EndGroup();
    ImGui::SameLine();
    ImGui::BeginGroup();
    {
      ImGui::SetNextItemWidth(150);
      ImGui::SliderInt("##tintscreen_red", &r, -255, 255, "", ImGuiSliderFlags_NoInput);
      ImGui::SameLine();
      ImGui::SetNextItemWidth(100);
      if (ImGui::InputInt("##tintscreen_int_red", &r, 1, 100)) {
        if (r < -255)
          r = -255;
        if (r > 255)
          r = 255;
      }

      ImGui::SetNextItemWidth(150);
      ImGui::SliderInt("##tintscreen_green", &g, -255, 255, "", ImGuiSliderFlags_NoInput);
      ImGui::SameLine();
      ImGui::SetNextItemWidth(100);
      if (ImGui::InputInt("##tintscreen_int_green", &g, 1, 100)) {
        if (g < -255)
          g = -255;
        if (g > 255)
          g = 255;
      }

      ImGui::SetNextItemWidth(150);
      ImGui::SliderInt("##tintscreen_blue", &b, -255, 255, "", ImGuiSliderFlags_NoInput);
      ImGui::SameLine();
      ImGui::SetNextItemWidth(100);
      if (ImGui::InputInt("##tintscreen_int_blue", &b, 1, 100)) {
        if (b < -255)
          b = -255;
        if (b > 255)
          b = 255;
      }

      ImGui::SetNextItemWidth(150);
      ImGui::SliderInt("##tintscreen_gray", &gray, 0, 255, "", ImGuiSliderFlags_NoInput);
      ImGui::SameLine();
      ImGui::SetNextItemWidth(100);
      if (ImGui::InputInt("##tintscreen_int_gray", &gray, 1, 100)) {
        if (gray < 1)
          gray = 1;
        if (gray > 255)
          gray = 255;
        // std::array<float, 3> hsv = rgbToHsv(r, g, b);
        // hsv[1] = gray / 100.f;
        // std::array<int, 3> rgb = hsvToRgb(hsv[0], hsv[1], hsv[2]);
        // r = rgb[0];
        // g = rgb[1];
        // b = rgb[2];
      }
    }
    ImGui::EndGroup();
    ImGui::SameLine();

    ImGui::ColorButton("##tintscreen_square", ImVec4{static_cast<float>(r * (1.0f / 255.0f)), static_cast<float>(g * (1.0f / 255.0f)), static_cast<float>(b * (1.0f / 255.0f)), 1}, 0,
                       ImVec2{100, 100});
  }
  ImGui::EndChild();
};


code:
#pragma once
#include "Core/TemplateEditor/IDBTemplates.hpp"
#include "Database/Templates.hpp"

class TemplatesTint final : public IDBTemplates {
public:
  TemplatesTint() = delete;
  explicit TemplatesTint(Template* templ, DatabaseEditor* parent)
  : IDBTemplates(parent)
  , m_currentTemplate(templ) {
    if (!m_currentTemplate->parameters().empty() && m_currentTemplate->parameters().size() == 4) {
      r = m_currentTemplate->parameters().at(0);
      g = m_currentTemplate->parameters().at(1);
      b = m_currentTemplate->parameters().at(2);
      gray = m_currentTemplate->parameters().at(3);
    }
  }
  void draw() override;
  DatabaseEditor* m_parent = nullptr;
  std::vector<int> getData() override { return std::vector({r, g, b, gray}); }
  bool hasChanges() override {
    return r != m_currentTemplate->parameters().at(0) || g != m_currentTemplate->parameters().at(1) || b != m_currentTemplate->parameters().at(2) || gray != m_currentTemplate->parameters().at(3);
  }

private:
  Template* m_currentTemplate;

  int r = 255;
  int g = 255;
  int b = 255;
  int gray = 255;
};


code:
#include "Core/Tilemap/ITileView.hpp"

#include "Core/Application.hpp"
#include "Core/Graphics/Texture.hpp"

RenderImage ITileView::createRenderTexture(const int width, const int height) { return {width, height}; }

void ITileView::clearRect(RenderImage& painter, const RectF& rect) {
  const auto oldComposition = painter.compositionMode();
  painter.setCompositionMode(RenderImage::CompositionMode::Clear);
  painter.eraseRect(rect);
  painter.setCompositionMode(oldComposition);
}


code:
#pragma once

#include <cmath>

#include "Core/Graphics/RenderImage.hpp"
#include "OREMath/Rect.hpp"

class ITileView {
public:
  ITileView(const int tileWidth, const int tileHeight)
  : m_tileWidth(tileWidth)
  , m_tileHeight(tileHeight) {}

  static RenderImage createRenderTexture(int width, int height);
  static void clearRect(RenderImage& painter, const RectF& rect);

  [[nodiscard]] float realTileWidth() const { return static_cast<float>(m_tileWidth); }
  [[nodiscard]] float realTileHeight() const { return static_cast<float>(m_tileHeight); }

  [[nodiscard]] bool isTileDirty(int x, int y) const { return m_dirtyRect.contains({x, y}); }
  void clearDirtyRect() { m_dirtyRect = Rect(); }
  void setDirtyRect(const int x, const int y, const int width, const int height) { m_dirtyRect |= Rect(x, y, width, height); }

  static Rect createRect(const Point& min, const Point& max) {
    Rect ret;
    int startX = min.x();
    if (max.x() <= min.x()) {
      startX = max.x();
    }

    int startY = min.y();
    if (max.y() <= min.y()) {
      startY = max.y();
    }

    ret.setTopLeft({startX, startY});

    int endX = min.x();
    if (min.x() <= max.x()) {
      endX = max.x();
    }

    int endY = min.y();
    if (min.y() <= max.y()) {
      endY = max.y();
    }

    ret.setBottomRight({endX, endY});

    return ret;
  }

  [[nodiscard]] Point pixelPointToTilePoint(const PointF& point) const {
    return {static_cast<int>(std::floor(point.x() / realTileWidth())), static_cast<int>(std::floor(point.y() / realTileHeight()))};
  }

private:
  int m_tileWidth{48};
  int m_tileHeight{48};
  bool m_tileSizeChanged{false};
  RectF m_viewport;
  Rect m_dirtyRect;
};


code:
#include "Core/Tilemap/TilePalette.hpp"
#include "Core/ResourceManager.hpp"
#include "Core/Tilemap/TileRenderHelper.hpp"

#include "Database/TileHelper.hpp"

void TilePalette::setTilesetNames(const std::array<std::string, 9> &tilesetNames) {
  m_tilesetNames = tilesetNames;
  for (int i = 0; i < 9; i++) {
    if (!m_tilesetNames[i].empty()) {
      m_textures[i] = ResourceManager::instance()->loadTilesetImage(m_tilesetNames[i]);
    } else {
      m_textures[i].invalidate();
    }
  }
  updateMaxRows();
  updateRenderTexture();
}

void TilePalette::updateRenderTexture() {
  const int width = paletteSize().width() * realTileWidth();
  const int height = paletteSize().height() * realTileHeight();

  m_painter.setSize(width, height);
  m_finalResult.setSize(width, height);
  m_checkerboardTexture.setSize(width, height);
}

std::array<int, 4> TilePalette::paletteTiles(int x, int y, const int page,
                                             const std::array<std::string, 9> &tilesetNames, const Tileset::Mode mode,
                                             const bool checkSpecial) {
  if (page == 0) {
    // Offsets in tiles
    constexpr std::array yOffs{
      2, 4, 4, 6, 16,
    };
    constexpr std::array idStarts{
      TileHelper::TILE_ID_A1, TileHelper::TILE_ID_A2, TileHelper::TILE_ID_A3, TileHelper::TILE_ID_A4,
      TileHelper::TILE_ID_A5,
    };

    for (int i = 0; i < 5; ++i) {
      if (!tilesetExists(tilesetNames, i)) {
        continue;
      }

      if (y < yOffs[i]) {
        const int col = (x + y * 8);
        const int firstId = (i == 4 ? 1 : 48) * col + idStarts[i];
        int secondId;
        if (checkSpecial) {
          if (TileHelper::isFloorTypeAutotile(firstId)) {
            secondId = firstId + 47;
          } else if (TileHelper::isWallTypeAutotile(firstId)) {
            secondId = firstId + 15;
          } else if (TileHelper::isWaterfallTypeAutotile(firstId)) {
            secondId = firstId + 3;
          } else {
            secondId = firstId;
          }
        } else {
          secondId = firstId;
        }

        if (TileHelper::isTileA1(secondId)) {
          if (col - 1 < 3) {
            return makeTileIdList(TileHelper::TILE_ID_A1, secondId, 0);
          }
        } else if (TileHelper::isTileA2(secondId)) {
          switch (mode) {
            case Tileset::Mode::World:
              if ((x & ~2) == 1) {
                return makeTileIdList(firstId - 48, secondId, 0);
              }
            case Tileset::Mode::Area:
              if (x > 3) {
                return makeTileIdList(-1, secondId, 0);
              }
              break;
            default:
              break;
          }
        }
        return makeTileIdList(secondId, 0, 0);
      }
      y -= yOffs[i];
    }
    return makeTileIdList(0, 0, 0);
  }

  const int tileId = x + ((page - 1) * 32 + y) * 8;
  return makeTileIdList(-1, -1, tileId);
}

std::array<int, 4> TilePalette::regionTiles(const Point &point) { return {getRegionNumber(point), -1, -1, -1}; }

std::array<int, 4> TilePalette::makeTileIdList(const int tileId1, const int tileId2, const int tileId3) {
  return {tileId1, tileId2, tileId3, -1};
}

void TilePalette::updateMaxRows() {
  if (!isMapMode()) {
    m_maxRows = isRegionMode() ? 32 : 0;
    return;
  }

  int maxRows = 32;

  if (m_pageIndex == 0) {
    maxRows = 0;
    static constexpr std::array yOffs{2, 4, 4, 6, 16};

    for (int i = 0; i < 5; ++i) {
      if (tilesetExists(m_tilesetNames, i)) {
        maxRows += yOffs[i];
      }
    }
  }

  if (m_maxRows == maxRows) {
    return;
  }

  m_maxRows = maxRows;
}

void TilePalette::eraseCursor() { setCursorRect({}); }

void TilePalette::setCursorRect(const Rect &rect) {
  if (m_cursorRect == rect) {
    return;
  }

  m_cursorRect = rect;

  updateCursorPixelRect();
}

void TilePalette::updateCursorPixelRect() {
  const Rect rect(static_cast<int>(realTileWidth() * m_cursorRect.left()),
                  static_cast<int>(realTileHeight() * m_cursorRect.top()),
                  static_cast<int>(m_cursorRect.width() * realTileWidth()),
                  static_cast<int>(m_cursorRect.height() * realTileHeight()));
  if (rect == m_cursorPixelRect) {
    return;
  }
  m_cursorPixelRect = rect;
}

void TilePalette::updatePick(const Point &point) {
  setCursorPage(m_pageIndex);
  setCursorRect(createRect(m_pickPoint, point) & paletteRect());
}

void TilePalette::setPenData(const Size &size, const std::vector<std::array<int, 4> > &data) {
  m_penData = data;
  m_penSize = size;
}

void TilePalette::onCursorDrag(const PointF &point) {
  if (!m_isPicking) {
    return;
  }

  updatePick(pixelPointToTilePoint(point));
}

void TilePalette::onCursorClicked(const PointF &point) {
  const Point p = pixelPointToTilePoint(point);
  if (isPointContained(p)) {
    startPick(p);
  }
}

void TilePalette::onCursorReleased() {
  if (!m_isPicking) {
    return;
  }
  endPick();
}

void TilePalette::endPick() {
  std::vector<std::array<int, 4> > penData;
  int startX = m_cursorRect.left();
  int endX = m_cursorRect.right();
  int startY = m_cursorRect.top();
  int endY = m_cursorRect.bottom();

  if (startY <= endY) {
    startX = m_cursorRect.left();
    endX = m_cursorRect.right();
    for (; startY <= endY; ++startY) {
      for (; startX <= endX; ++startX) {
        if (isRegionMode()) {
          penData.emplace_back(regionTiles({startX, startY}));
        } else {
          penData.emplace_back(paletteTiles(startX, startY, m_pageIndex, m_tilesetNames, m_tilesetMode, false));
        }
        endX = m_cursorRect.right();
      }
      startX = m_cursorRect.left();
      endY = m_cursorRect.bottom();
    }
    startY = m_cursorRect.top();
  }
  setPenData({(endX - startX) + 1, (endY - startY) + 1}, penData);
  m_isPicking = false;
}

void TilePalette::paintTiles() {
  if (!m_painter.lock()) {
    return;
  }

  for (int y = 0; y < m_maxRows; y++) {
    for (int x = 0; x < kMaxColumns; x++) {
      paintTile(m_painter, {x, y});
    }
  }

  m_painter.unlock();
  if (m_finalResult.lock()) {
    const int width = (paletteSize().width() * realTileWidth());
    const int height = (paletteSize().height() * realTileHeight());
    m_finalResult.drawImageRaw({0, 0, static_cast<float>(width), static_cast<float>(height)},
                               m_checkerboardTexture.get(), {0, 0, width, height});
    m_finalResult.drawImageRaw({0, 0, static_cast<float>(width), static_cast<float>(height)}, m_painter.get(),
                               {0, 0, width, height});
    m_finalResult.unlock();
  }
}

void TilePalette::paintTile(RenderImage &image, const Point &point) {
  const RectF rect = {(point.x() * realTileWidth()), (point.y() * realTileHeight()), realTileWidth(), realTileHeight()};
  m_renderHelper.clearRect(image, rect);
  if (!isPointContained(point)) {
    return;
  }
  if (isMapMode()) {
    for (const auto tiles = paletteTiles(point.x(), point.y(), m_pageIndex, m_tilesetNames, m_tilesetMode, true); const
         auto &tile: tiles) {
      m_renderHelper.drawTile(m_painter, rect, tile, m_textures, false);
    }
  } else {
    m_renderHelper.drawRegionTile(m_painter, rect, point.x() + point.y() * kMaxColumns);
  }
}


code:
#pragma once

#include "Core/Graphics/CheckerboardTexture.hpp"
#include "Core/Tilemap/ITileView.hpp"
#include "Database/Tileset.hpp"
#include "TileRenderHelper.hpp"

#include <array>
#include <string>
#include <vector>

class TilePalette : public ITileView {
public:
  static constexpr int kMaxColumns = 8;

  static std::array<int, 4> paletteTiles(int x, int y, int page, const std::array<std::string, 9> &tilesetNames,
                                         Tileset::Mode mode, bool checkSpecial);

  static std::array<int, 4> regionTiles(const Point &point);

  explicit TilePalette(const int tileWidth = 48, const int tileHeight = 48) : ITileView(tileWidth, tileHeight),
                                                                              m_renderHelper(tileWidth, tileHeight),
                                                                              m_checkerboardTexture(32, 32) {
    updateCursorPixelRect();
    updateMaxRows();
    updateRenderTexture();
  }

  void setMode(const Tileset::Mode mode) { m_tilesetMode = mode; }
  Tileset::Mode mode() const { return m_tilesetMode; }

  void setRegionMode() { m_isRegionMode = true; }
  void setMapMode() { m_isRegionMode = false; }
  bool isMapMode() const { return !m_isRegionMode; }
  bool isRegionMode() const { return m_isRegionMode; }

  int pageIndex() const { return m_pageIndex; }

  void setPageIndex(int page) {
    if (page == m_pageIndex) {
      return;
    }

    if (page <= -1) {
      setRegionMode();
    } else {
      setMapMode();
    }
    m_pageIndex = page;
    updateMaxRows();
    updateRenderTexture();
  }

  static bool tilesetExists(const std::array<std::string, 9> &tilesetNames, const int idx) {
    return !tilesetNames[idx].empty();
  }

  bool isPageValid(const int page) const {
    if (page == 0) {
      bool ret = false;
      for (int i = 0; i < 5; ++i) {
        ret |= tilesetExists(m_tilesetNames, i);
      }
      return ret;
    }

    return tilesetExists(m_tilesetNames, 4 + page);
  }

  void setTilesetNames(const std::array<std::string, 9> &tilesetNames);

  void paintTiles();

  int maxRows() const { return m_maxRows; }

  Size paletteSize() const { return {kMaxColumns, m_maxRows}; }
  Rect paletteRect() const { return {{}, paletteSize()}; }
  bool isPointContained(const Point &point) const { return paletteRect().contains(point); }

  Point imageSize() const {
    return {static_cast<int>(kMaxColumns * realTileWidth()), static_cast<int>(m_maxRows * realTileHeight())};
  }

  explicit operator ImTextureID() const { return m_finalResult.operator ImTextureID(); }

  void setPenData(const Size &size, const std::vector<std::array<int, 4> > &data);

  const std::vector<std::array<int, 4> > &penData() const { return m_penData; }
  const Size &penSize() const { return m_penSize; }

  static int getRegionNumber(const Point &point) { return point.x() + point.y() * 8; }

  void eraseCursor();

  void setCursorRect(const Rect &rect);

  Rect cursorRect() const { return m_cursorRect; }
  Rect cursorPixelRect() const { return m_cursorPixelRect; }
  void setCursorPage(const int cursorPage) { m_cursorPage = cursorPage; }

  void startPick(const Point &point) {
    m_pickPoint = point;
    updatePick(point);
    m_isPicking = true;
  }

  void endPick();

  void onCursorDrag(const PointF &point);

  void onCursorClicked(const PointF &point);

  void onCursorReleased();

  void invalidateCursor() { setCursorRect({}); }

private:
  void updateMaxRows();

  void updateRenderTexture();

  void updateCursorPixelRect();

  void updatePick(const Point &point);

  void paintTile(RenderImage &image, const Point &point);

  static std::array<int, 4> makeTileIdList(int tileId1, int tileId2, int tileId3);

  TileRenderHelper m_renderHelper;
  bool m_isRegionMode = false;
  int m_pageIndex = 0;
  int m_cursorPage = 0;
  int m_maxRows = 0;
  Tileset::Mode m_tilesetMode{};
  std::array<std::string, 9> m_tilesetNames;
  std::array<Texture, 9> m_textures;
  RenderImage m_painter;
  RenderImage m_finalResult;
  CheckerboardTexture m_checkerboardTexture;
  Rect m_cursorRect;
  Rect m_cursorPixelRect;
  std::vector<std::array<int, 4> > m_penData;
  Size m_penSize;
  bool m_isPicking = false;
  Point m_pickPoint;
};


code:
#pragma once

#include "Core/Graphics/Texture.hpp"
#include <array>

class RectF;
class RenderImage;
class TileRenderHelper {
public:
  TileRenderHelper(const int tileWidth, const int tileHeight)
  : m_tileWidth(tileWidth)
  , m_tileHeight(tileHeight) {}
  void drawTile(RenderImage& painter, const RectF& rect, int tileId, const std::array<Texture, 9>& images, bool isTable) const;
  void drawNormalTile(RenderImage& painter, const RectF& rect, int tileId, const std::array<Texture, 9>& images) const;
  void drawAutoTile(RenderImage& painter, const RectF& rect, int tileId, const std::array<Texture, 9>& images, bool isTable) const;
  void drawRegionTile(RenderImage& painter, const RectF& rect, int region);
  void clearRect(RenderImage& painter, const RectF& rect) const;

private:
  int m_tileWidth;
  int m_tileHeight;
};


code: #include "Core/Tilemap/TilemapView.hpp"


code:
#pragma once

#include "Core/Tilemap/ITileView.hpp"
#include "Core/Tilemap/TileRenderHelper.hpp"
#include <array>

#include <string>

class TilemapView : public ITileView {
public:
  TilemapView(const int tileWidth, const int tileHeight)
  : ITileView(tileWidth, tileHeight)
  , m_renderHelper(tileWidth, tileHeight) {}

  static bool tilesetExists(const std::array<std::string, 9>& tilesetNames, const int idx) { return !tilesetNames[idx].empty(); }

private:
  TileRenderHelper m_renderHelper;
  RenderImage m_lowerTiles;
  RenderImage m_upperTiles;
};


code:
#include "Core/EventEditor.hpp"

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

#include "Core/TroopsEventEditor/TroopsEVEditor.hpp"
#include "Core/TroopsEventEditor/TroopsEVPage.hpp"
#include "Database/EventCommands/EventDummy.hpp"
#include "Database/EventParser.hpp"
#include "Database/Troop.hpp"

#include <format>
#include <string>

#include "Database/Database.hpp"
#include "imgui.h"
#include "imgui_internal.h"

void TroopsEVEditor::fixupPages() {
  if (!m_troop) {
    m_pages.clear();
    return;
  }

  for (int i = 0; i < m_troop->pages().size(); ++i) {
    m_pages[i].setPage(&m_troop->pages()[i], i + 1);
    m_pages[i].setParent(this);
  }
}

bool TroopsEVEditor::draw() {
  if (m_troop) {
    ImGui::BeginGroup();
    {
      if (ImGui::Button(trNOOP("Copy\nEvent Page"))) {}
      ImGui::BeginDisabled(true);
      if (ImGui::Button(trNOOP("Paste\nEvent Page"))) {}
      ImGui::EndDisabled();
      ImGui::BeginDisabled(m_pages.size() <= 1);
      if (ImGui::Button(trNOOP("Delete\nEvent Page"))) {
        m_erased = true;
        m_erasedIdx = m_selectedPage;
      }
      ImGui::EndDisabled();
      if (ImGui::Button(trNOOP("Clear\nEvent Page"))) {
        if (m_selectedPage != -1) {
          m_pages[m_selectedPage].clearPage();
        }
      }
      ImGui::EndGroup();
    }

    ImGui::SameLine();
    if (ImGui::BeginChild("##orpg_troop_editor_page_content", ImVec2(0, 0), false)) {
      if (ImGui::BeginTabBar("##orpg_troop_editor_page", ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_TabListPopupButton | ImGuiTabBarFlags_FittingPolicyScroll)) {
        for (int i = 0; i < m_troop->pages().size(); ++i) {
          if (ImGui::BeginTabItem(std::to_string(i + 1).c_str())) { // Check if BeginTabItem is successful
            m_selectedPage = i;
            m_pages[m_selectedPage].draw(false, i);
            ImGui::EndTabItem();
          }
        }

        // Handle TabItemButton
        if (ImGui::TabItemButton(" + ")) {
          Troop::Page* page = &m_troop->pages().emplace_back();
          page->list().emplace_back(new EventDummy());
          m_pages.emplace_back(this, page, m_troop->id());
          fixupPages();
          m_selectedPage = m_pages.size() - 1;
        }

        ImGui::EndTabBar(); // Ensure EndTabBar is always called
      }
      ImGui::EndChild();
    }

    if (m_erased) {
      m_troop->pages().erase(m_troop->pages().begin() + m_erasedIdx);
      if (m_erasedIdx == m_selectedPage) {
        m_selectedPage = -1;
      }
      fixupPages();
      m_erased = false;
    }
  }
  return m_isOpen;
}


code:
#pragma once

#include "Core/TroopsEventEditor/TroopsEVEditor.hpp"
#include "Core/TroopsEventEditor/TroopsEVPage.hpp"
#include "Database/Troops.hpp"

#include <vector>

struct MainWindow;

struct TroopsEVEditor {
  explicit TroopsEVEditor(Troop* troop)
  : m_troop(troop) {
    if (m_troop) {
      for (auto& page : troop->pages()) {
        m_pages.emplace_back(this, &page, m_troop->id());
      }
      m_id = troop->id();
    }
  }
  void fixupPages();

  bool draw();

  Troop* event() { return m_troop; }
  const Troop* event() const { return m_troop; }

  void setTroop(Troop* troop) {
    m_troop = troop;
    fixupPages();
  }

  int id() const { return m_id; }

private:
  Troop* m_troop = nullptr;
  int m_id = 0;
  std::vector<TroopsEVPage> m_pages;
  int m_selectedPage = 0;
  bool m_isOpen = true;
  bool m_erased{false};
  int m_erasedIdx{-1};
};