code:
#pragma once
#include "Database/Globals.hpp"
#include "IModifiable.hpp"

#include <nlohmann/json.hpp>
#include <string>

class Damage final : public IModifiable {
  friend void to_json(nlohmann::ordered_json& j, const Damage& damage);
  friend void from_json(const nlohmann::ordered_json& j, Damage& damage);

public:
  Damage() = default;
  Damage(const Damage& other);
  Damage& operator=(const Damage& other);
  Damage(Damage&& other) noexcept;
  Damage& operator=(Damage&& other) noexcept;

  bool critical() const;
  void setCritical(bool critical);

  int elementId() const;
  void setElementId(int elementId);

  const std::string& formula() const;
  void setFormula(const std::string& formula);

  DamageType type() const;
  void setType(DamageType type);

  int variance() const;
  void setVariance(int variance);

  void restoreOriginal() override;
  void acceptChanges() override;
  nlohmann::ordered_json serializeOldValues() const override;

  rpgmutils::signal<void(Damage*, bool)>& criticalModified();
  rpgmutils::signal<void(Damage*, int)>& elementIdModified();
  rpgmutils::signal<void(Damage*, const std::string&)>& formulaModified();
  rpgmutils::signal<void(Damage*, DamageType)>& typeModified();
  rpgmutils::signal<void(Damage*, int)>& varianceModified();

  bool operator==(const Damage& other) const {
    return m_critical == other.m_critical && m_elementId == other.m_elementId && m_formula == other.m_formula && m_type == other.m_type && m_variance == other.m_variance;
  }

private:
  bool m_critical;
  int m_elementId;
  std::string m_formula;
  DamageType m_type;
  int m_variance;

  std::optional<bool> m_oldcritical;
  std::optional<int> m_oldelementId;
  std::optional<std::string> m_oldformula;
  std::optional<DamageType> m_oldtype;
  std::optional<int> m_oldvariance;

  std::optional<rpgmutils::signal<void(Damage*, bool)>> m_criticalModified;
  std::optional<rpgmutils::signal<void(Damage*, int)>> m_elementIdModified;
  std::optional<rpgmutils::signal<void(Damage*, const std::string&)>> m_formulaModified;
  std::optional<rpgmutils::signal<void(Damage*, DamageType)>> m_typeModified;
  std::optional<rpgmutils::signal<void(Damage*, int)>> m_varianceModified;
};

void to_json(nlohmann::ordered_json& j, const Damage& damage);
void from_json(const nlohmann::ordered_json& j, Damage& damage);


code:
#include "Database/Database.hpp"

#include "Database/Serializable/FileQueue.hpp"

#include "Database/Serializable/ActorsSerializer.hpp"
#include "Database/Serializable/AnimationsSerializer.hpp"
#include "Database/Serializable/ArmorsSerializer.hpp"
#include "Database/Serializable/ClassesSerializer.hpp"
#include "Database/Serializable/CommonEventsSerializer.hpp"
#include "Database/Serializable/EnemiesSerializer.hpp"
#include "Database/Serializable/ItemsSerializer.hpp"
#include "Database/Serializable/SkillsSerializer.hpp"
#include "Database/Serializable/StatesSerializer.hpp"
#include "Database/Serializable/SystemSerializer.hpp"
#include "Database/Serializable/TilesetsSerializer.hpp"
#include "Database/Serializable/TroopsSerializer.hpp"
#include "Database/Serializable/WeaponsSerializer.hpp"
#include "Serializable/MapInfosSerializer.hpp"
#include "Serializable/MapSerializer.hpp"

Database* Database::m_instance = nullptr;

Database::Database(const std::string_view _projectBasePath, const std::string_view _projectFilePath, const std::string_view _projectVersion, const bool isMZ)
: projectVersion(_projectVersion)
, projectFilePath(_projectFilePath)
, basePath(_projectBasePath)
, m_isMZ(isMZ) {
  m_instance = this;
}

void Database::load() {
  config.load(basePath + "editor/config.json");
  transient.load(basePath + ".ore/transient.json");

  RPGM_INFO("Queue System definitions for load...");
  FileQueue::instance().enqueue(std::make_shared<SystemSerializer>("data/System.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    system = std::dynamic_pointer_cast<SystemSerializer>(serializer)->data();
    system.modified().connect<&Database::onAnyModifiableModified>(this);
    emit_signal(systemLoaded());
    RPGM_INFO("System definitions loaded");
  });
  RPGM_INFO("Queue MapInfo definitions for load...");
  FileQueue::instance().enqueue(std::make_shared<MapInfosSerializer>("data/MapInfos.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    mapInfos = std::dynamic_pointer_cast<MapInfosSerializer>(serializer)->data();
    mapInfos.buildTree(true);
    mapInfos.modified().connect<&Database::onAnyModifiableModified>(this);
    mapInfos.connectSignals();
    emit_signal(mapInfosLoaded());
    RPGM_INFO("MapInfo definitions loaded");
  });
  RPGM_INFO("Queue Actor definitions for load...");
  FileQueue::instance().enqueue(std::make_shared<ActorsSerializer>("data/Actors.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    actors = std::dynamic_pointer_cast<ActorsSerializer>(serializer)->data();
    actors.modified().connect<&Database::onAnyModifiableModified>(this);
    emit_signal(actorsLoaded());
    RPGM_INFO("Actor definitions loaded");
  });
  RPGM_INFO("Queue Class definitions for load...");
  FileQueue::instance().enqueue(std::make_shared<ClassesSerializer>("data/Classes.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    classes = std::dynamic_pointer_cast<ClassesSerializer>(serializer)->data();
    classes.modified().connect<&Database::onAnyModifiableModified>(this);
    emit_signal(classesLoaded());
    RPGM_INFO("Class definitions loaded");
  });
  RPGM_INFO("Queue Skill definitions for load...");
  FileQueue::instance().enqueue(std::make_shared<SkillsSerializer>("data/Skills.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    skills = std::dynamic_pointer_cast<SkillsSerializer>(serializer)->data();
    skills.modified().connect<&Database::onAnyModifiableModified>(this);
    emit_signal(skillsLoaded());
    RPGM_INFO("Skill definitions loaded");
  });
  RPGM_INFO("Queue Item definitions for load...");
  FileQueue::instance().enqueue(std::make_shared<ItemsSerializer>("data/Items.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    items = std::dynamic_pointer_cast<ItemsSerializer>(serializer)->data();
    items.modified().connect<&Database::onAnyModifiableModified>(this);
    emit_signal(itemsLoaded());
    RPGM_INFO("Item definitions loaded");
  });
  RPGM_INFO("Queue Weapon definitions for load...");
  FileQueue::instance().enqueue(std::make_shared<WeaponsSerializer>("data/Weapons.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    weapons = std::dynamic_pointer_cast<WeaponsSerializer>(serializer)->data();
    weapons.modified().connect<&Database::onAnyModifiableModified>(this);
    emit_signal(weaponsLoaded());
    RPGM_INFO("Weapon definitions loaded");
  });
  RPGM_INFO("Queue Armor definitions for load...");
  FileQueue::instance().enqueue(std::make_shared<ArmorsSerializer>("data/Armors.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    armors = std::dynamic_pointer_cast<ArmorsSerializer>(serializer)->data();
    armors.modified().connect<&Database::onAnyModifiableModified>(this);
    emit_signal(armorsLoaded());
    RPGM_INFO("Armor definitions loaded");
  });
  RPGM_INFO("Queue Enemy definitions for load...");
  FileQueue::instance().enqueue(std::make_shared<EnemiesSerializer>("data/Enemies.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    enemies = std::dynamic_pointer_cast<EnemiesSerializer>(serializer)->data();
    enemies.modified().connect<&Database::onAnyModifiableModified>(this);
    emit_signal(enemiesLoaded());
    RPGM_INFO("Enemy definitions loaded");
  });
  RPGM_INFO("Queue Troop definitions for load...");
  FileQueue::instance().enqueue(std::make_shared<TroopsSerializer>("data/Troops.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    troops = std::dynamic_pointer_cast<TroopsSerializer>(serializer)->data();
    troops.modified().connect<&Database::onAnyModifiableModified>(this);
    emit_signal(troopsLoaded());
    RPGM_INFO("Troop definitions loaded");
  });
  RPGM_INFO("Queue State definitions for load...");
  FileQueue::instance().enqueue(std::make_shared<StatesSerializer>("data/States.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    states = std::dynamic_pointer_cast<StatesSerializer>(serializer)->data();
    states.modified().connect<&Database::onAnyModifiableModified>(this);
    emit_signal(statesLoaded());
    RPGM_INFO("State definitions loaded");
  });
  RPGM_INFO("Queue Animation definitions for load...");
  FileQueue::instance().enqueue(std::make_shared<AnimationsSerializer>("data/Animations.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    animations = std::dynamic_pointer_cast<AnimationsSerializer>(serializer)->data();
    animations.modified().connect<&Database::onAnyModifiableModified>(this);
    emit_signal(animationsLoaded());
    RPGM_INFO("Animation definitions loaded");
  });
  RPGM_INFO("Queue Tileset definitions for load...");
  FileQueue::instance().enqueue(std::make_shared<TilesetsSerializer>("data/Tilesets.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    tilesets = std::dynamic_pointer_cast<TilesetsSerializer>(serializer)->data();
    tilesets.modified().connect<&Database::onAnyModifiableModified>(this);
    emit_signal(tilesetsLoaded());
    RPGM_INFO("Tileset definitions loaded");
  });
  RPGM_INFO("Queue CommonEvent definitions for load...");
  FileQueue::instance().enqueue(std::make_shared<CommonEventsSerializer>("data/CommonEvents.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    commonEvents = std::dynamic_pointer_cast<CommonEventsSerializer>(serializer)->data();
    commonEvents.modified().connect<&Database::onAnyModifiableModified>(this);
    emit_signal(commonEventsLoaded());
    RPGM_INFO("CommonEvent definitions loaded");
  });
  RPGM_INFO("Queue Map001 localization file...");
  locales = Locales();
  locales.loadMap(basePath + "locales/en/Map0001.json");
  // locales.onModified().connect<&Database::onAnyModifiableModified>(this);
  emit_signal(localesLoaded());

  RPGM_INFO("Loading Plugins...");
  plugins = Plugins::load(basePath + "js/plugins.js");
  // plugins.onModified().connect<&Database::onAnyModifiableModified>(this);
  emit_signal(pluginsLoaded());
  RPGM_INFO("Loading GameConstants");
  gameConstants = GameConstants::load(basePath + "/data/Constants.json");
  // gameConstants.onModified().connect<&Database::onAnyModifiableModified>(this);
  emit_signal(gameConstantsLoaded());
  RPGM_INFO("Loading Templates");
  templates = Templates::load(basePath + "/data/Templates.json");
  templates.modified().connect<&Database::onAnyModifiableModified>(this);
  emit_signal(templatesLoaded());

  RPGM_INFO("Loading Documentation");
  docs = Docs::load(basePath + "/editor/Documentation.json");
  docs.modified().connect<&Database::onAnyModifiableModified>(this);
  emit_signal(docsLoaded());
}

void Database::serializeProject() {
  serializeSettings();
  if (gameConstants.generateJS) {
    gameConstants.generateConstantsJS(basePath + "/js/Constants.js");
  }

  FileQueue::instance().enqueue(std::make_shared<CommonEventsSerializer>(commonEvents, "data/CommonEvents.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    commonEvents.acceptChanges();
    RPGM_INFO("CommonEvents saved");
  });
  FileQueue::instance().enqueue(std::make_shared<SystemSerializer>(system, "data/System.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    system.acceptChanges();
    RPGM_INFO("System saved");
  });
  FileQueue::instance().enqueue(std::make_shared<MapInfosSerializer>(mapInfos, "data/MapInfos.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    mapInfos.acceptChanges();
    RPGM_INFO("MapInfos saved");
  });
  FileQueue::instance().enqueue(std::make_shared<ActorsSerializer>(actors, "data/Actors.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    actors.acceptChanges();
    RPGM_INFO("Actors saved");
  });
  FileQueue::instance().enqueue(std::make_shared<ClassesSerializer>(classes, "data/Classes.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    classes.acceptChanges();
    RPGM_INFO("Classes saved");
  });
  FileQueue::instance().enqueue(std::make_shared<SkillsSerializer>(skills, "data/Skills.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    skills.acceptChanges();
    RPGM_INFO("Skills saved");
  });
  FileQueue::instance().enqueue(std::make_shared<ItemsSerializer>(items, "data/Items.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    items.acceptChanges();
    RPGM_INFO("Items saved");
  });
  FileQueue::instance().enqueue(std::make_shared<WeaponsSerializer>(weapons, "data/Weapons.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    weapons.acceptChanges();
    RPGM_INFO("Weapons saved");
  });
  FileQueue::instance().enqueue(std::make_shared<ArmorsSerializer>(armors, "data/Armors.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    armors.acceptChanges();
    RPGM_INFO("Armors saved");
  });
  FileQueue::instance().enqueue(std::make_shared<EnemiesSerializer>(enemies, "data/Enemies.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    enemies.acceptChanges();
    RPGM_INFO("Enemies saved");
  });
  FileQueue::instance().enqueue(std::make_shared<TroopsSerializer>(troops, "data/Troops.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    troops.acceptChanges();
    RPGM_INFO("Troops saved");
  });
  FileQueue::instance().enqueue(std::make_shared<StatesSerializer>(states, "data/States.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    states.acceptChanges();
    RPGM_INFO("States saved");
  });
  FileQueue::instance().enqueue(std::make_shared<AnimationsSerializer>(animations, "data/Animations.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    animations.acceptChanges();
    RPGM_INFO("Animations saved");
  });
  FileQueue::instance().enqueue(std::make_shared<TilesetsSerializer>(tilesets, "data/Tilesets.json"), [this](const std::shared_ptr<ISerializable>& serializer) {
    tilesets.acceptChanges();
    RPGM_INFO("Tilesets saved");
  });

  /* TODO: Implement Serializers */
  plugins.serialize(basePath + "/js/plugins.js");
  gameConstants.serialize(basePath + "/data/Constants.json");

  for (const auto& map : mapInfos.mapInfos()) {
    if (map->mapLoaded() && map->isModified()) {
      FileQueue::instance().enqueue(std::make_shared<MapSerializer>(map->map()->clone(), map->id(), std::format("data/Map{:03}.json", map->id())),
                                    [this](const std::shared_ptr<ISerializable>& serializer) {
                                      int mapId = std::dynamic_pointer_cast<MapSerializer>(serializer)->mapId();
                                      if (mapInfos.map(mapId)->mapLoaded()) {
                                        mapInfos.map(mapId)->acceptChanges();
                                      }
                                      RPGM_INFO("Map{:03} saved", mapId);
                                    });
    }
  }
}

repo_name: Antidote/openrpgeditor

license: mit


code:
#pragma once

#include "Database/Actors.hpp"
#include "Database/Animations.hpp"
#include "Database/Armors.hpp"
#include "Database/Classes.hpp"
#include "Database/CommonEvents.hpp"
#include "Database/Docs.hpp"
#include "Database/Enemies.hpp"
#include "Database/GameConstants.hpp"
#include "Database/Items.hpp"
#include "Database/Map.hpp"
#include "Database/MapInfos.hpp"
#include "Database/Plugins.hpp"
#include "Database/ProjectConfig.hpp"
#include "Database/Skills.hpp"
#include "Database/States.hpp"
#include "Database/System.hpp"
#include "Database/Templates.hpp"
#include "Database/Tilesets.hpp"
#include "Database/Troops.hpp"
#include "Database/Weapons.hpp"
#include "Locales.hpp"
#include "Utils/SignalSlot.hpp"

#include <string_view>
using namespace std::string_view_literals;

struct Database {
  static constexpr auto InvalidDataName = "?"sv;
  Database(std::string_view projectBasePath, std::string_view projectFilePath, std::string_view projectVersion, bool isMZ = false);
  ~Database() { m_instance = nullptr; }
  Database(Database&) = delete;
  Database(Database&&) = delete;
  void load();
  Database& operator=(Database&) = delete;
  Database& operator=(Database&&) = delete;

  Actors actors{};
  Classes classes{};
  Skills skills{};
  Items items{};
  Weapons weapons{};
  Armors armors{};
  Enemies enemies{};
  Troops troops{};
  States states{};
  Animations animations{};
  Tilesets tilesets{};
  CommonEvents commonEvents{};
  System system{};
  Plugins plugins{};
  MapInfos mapInfos{};
  GameConstants gameConstants{};
  Templates templates{};
  Locales locales{};
  Docs docs{};
  std::string projectVersion; // As stored in the .rpgproject file
  std::string projectFilePath;
  std::string basePath;
  ProjectConfig config;
  TransientConfig transient;

  void serializeProject();

  void serializeSettings() {
    config.serialize(basePath + "/editor/config.json");
    transient.serialize(basePath + "/.ore/transient.json");
  }

  static std::string framesText(const int frames) { return std::to_string(frames) + " " + (frames == 0 || frames > 1 ? trNOOP("frames") : trNOOP("frame")); }

  static std::string secondsText(const int seconds) { return std::to_string(seconds) + " " + (seconds > 1 ? trNOOP("seconds") : trNOOP("second")); }

  static std::string parentheses(const std::string& text) { return "(" + text + ")"; }

  static std::string imageText(const std::string& name, int index = -1) {
    if (!name.empty()) {
      if (index >= 0) {
        return std::format("{}({})", name, index);
      }
      return name;
    }

    return trNOOP("None");
  }

  static std::string dualImageText(const std::string& name1, const std::string& name2) {
    if (name1.empty() && name2.empty()) {
      return trNOOP("None");
    }

    std::string text = name1.empty() ? trNOOP("None") : name1;
    if (!name2.empty()) {
      text += " & " + name2;
    }

    return text;
  }

  static std::string audioText(const Audio& audio) {
    if (!audio.name().empty()) {
      return audio.name() + " " + parentheses(std::format("{}, {}, {}", audio.volume(), audio.pitch(), audio.pan()));
    }

    return trNOOP("None");
  }

  static std::string movieText(const std::string& name) {
    if (!name.empty()) {
      return name;
    }

    return trNOOP("None");
  }

  [[nodiscard]] std::string eventName(const int id) const {
    const auto map = mapInfos.currentMap();
    const auto object = map ? map->event(id) : nullptr;
    return object ? object->name() : InvalidDataName.data();
  }
  [[nodiscard]] std::string actorName(const int id) const {
    const auto object = actors.actor(id);
    return object ? object->name() : InvalidDataName.data();
  }

  [[nodiscard]] std::string className(const int id) const {
    const auto object = classes.classType(id);
    return object ? object->name() : InvalidDataName.data();
  }

  [[nodiscard]] std::string skillName(const int id) const {
    const auto object = skills.skill(id);
    return object ? object->name() : InvalidDataName.data();
  }

  [[nodiscard]] std::string itemName(const int id) const {
    const auto object = items.item(id);
    return object ? object->name() : InvalidDataName.data();
  }

  [[nodiscard]] std::string weaponName(const int id) const {
    const auto object = weapons.weapon(id);
    return object ? object->name() : InvalidDataName.data();
  }
  [[nodiscard]] std::string armorName(const int id) const {
    const auto object = armors.armor(id);
    return object ? object->name() : InvalidDataName.data();
  }

  [[nodiscard]] std::string enemyName(const int id) const {
    const auto object = enemies.enemy(id);
    return object ? object->name() : InvalidDataName.data();
  }

  [[nodiscard]] std::string troopName(const int id) const {
    const auto object = troops.troop(id);
    return object ? object->name() : InvalidDataName.data();
  }

  [[nodiscard]] std::string stateName(const int id) const {
    if (id == 0) {
      return trNOOP("Normal Attack");
    }

    const auto object = states.state(id);
    return object ? object->name() : InvalidDataName.data();
  }

  [[nodiscard]] std::string animationName(const int id) const {
    const auto object = animations.animation(id);
    return object ? object->name() : InvalidDataName.data();
  }

  [[nodiscard]] std::string tilesetName(const int id) const {
    const auto object = tilesets.tileset(id);
    return object ? object->name() : InvalidDataName.data();
  }

  [[nodiscard]] std::string commonEventName(const int id) const {
    const auto object = commonEvents.event(id);
    return object ? object->name() : InvalidDataName.data();
  }

  [[nodiscard]] std::string switchName(const int id) const {
    if (id < 0 || id >= system.switches().size()) {
      return InvalidDataName.data();
    }
    return system.switche(id);
  }

  [[nodiscard]] std::string variableName(const int id) const {
    if (id < 0 || id >= system.variables().size()) {
      return InvalidDataName.data();
    }
    return system.variable(id);
  }

  [[nodiscard]] std::string elementName(const int id) const {
    if (id < 0 || id >= system.elements().size()) {
      return InvalidDataName.data();
    }
    return system.element(id);
  }

  [[nodiscard]] std::string skillTypeName(const int id) const {
    if (id < 0 || id >= system.skillTypes().size()) {
      return InvalidDataName.data();
    }
    return system.skillType(id);
  }

  [[nodiscard]] std::string weaponTypeName(const int id) const {
    if (id < 0 || id >= system.weaponTypes().size()) {
      return InvalidDataName.data();
    }
    return system.weaponType(id);
  }

  [[nodiscard]] std::string armorTypeName(const int id) const {
    if (id < 0 || id >= system.armorTypes().size()) {
      return InvalidDataName.data();
    }
    return system.armorType(id);
  }

  [[nodiscard]] std::string equipTypeName(const int id) const {
    if (id < 0 || id >= system.equipTypes().size()) {
      return InvalidDataName.data();
    }
    return system.equipType(id);
  }

  [[nodiscard]] std::string troopMemberName(const int id, const int index) const {
    if (index < 0) {
      return trNOOP("Entire Troop");
    }
    const auto object = troops.troop(id);
    const auto member = object ? object->member(index) : nullptr;
    return member ? enemyName(member->enemyId()) : InvalidDataName.data();
  }

  [[nodiscard]] std::string mapName(const int id) const {
    const auto map = mapInfos.map(id);
    return map ? map->name() : InvalidDataName.data();
  }

  static std::string makeIdText(const int id, const int numDigits) {
    const std::string ret = std::to_string(id);
    const int digits = std::clamp<int>(numDigits - static_cast<int>(ret.length()), 0, 4);
    return std::string(digits, '0') + ret;
  }

  static std::string nameOrId(const std::string_view name, const int id, const int numDigits = 4) {
    if (!name.empty()) {
      return name.data();
    };
    return "#" + makeIdText(id, numDigits);
  }

  static std::string nameAndId(const std::string_view name, const int id, const int numDigits = 4) {
    if (!name.empty()) {
      return "#" + makeIdText(id, numDigits) + " " + name.data();
    }

    return "#" + makeIdText(id, numDigits);
  }

  [[nodiscard]] std::string eventNameOrId(const int id) const {
    if (id == -1) {
      return trNOOP("Player");
    }
    if (id == 0) {
      return trNOOP("This Event");
    }
    return nameOrId(eventName(id), id, 3);
  }
  [[nodiscard]] std::string actorNameOrId(const int id) const { return nameOrId(actorName(id), id); }
  [[nodiscard]] std::string actorNameAndId(const int id) const { return nameAndId(actorName(id), id); }
  [[nodiscard]] std::string classNameOrId(const int id) const { return nameOrId(className(id), id); }
  [[nodiscard]] std::string classNameAndId(const int id) const { return nameAndId(className(id), id); }
  [[nodiscard]] std::string skillNameOrId(const int id) const { return nameOrId(skillName(id), id); }
  [[nodiscard]] std::string skillNameAndId(const int id) const { return nameAndId(skillName(id), id); }
  [[nodiscard]] std::string itemNameOrId(const int id) const { return nameOrId(itemName(id), id); }
  [[nodiscard]] std::string itemNameAndId(const int id) const { return nameAndId(itemName(id), id); }
  [[nodiscard]] std::string weaponNameOrId(const int id) const { return nameOrId(weaponName(id), id); }
  [[nodiscard]] std::string weaponNameAndId(const int id) const { return nameAndId(weaponName(id), id); }
  [[nodiscard]] std::string armorNameOrId(const int id) const { return nameOrId(armorName(id), id); }
  [[nodiscard]] std::string armorNameAndId(const int id) const { return nameAndId(armorName(id), id); }
  [[nodiscard]] std::string enemyNameOrId(const int id) const { return nameOrId(enemyName(id), id); }
  [[nodiscard]] std::string enemyNameAndId(const int id) const { return nameAndId(enemyName(id), id); }
  [[nodiscard]] std::string troopNameOrId(const int id) const { return nameOrId(troopName(id), id); }
  [[nodiscard]] std::string troopNameAndId(const int id) const { return nameAndId(troopName(id), id); }

  [[nodiscard]] std::string stateNameOrId(const int id) const { return nameOrId(stateName(id), id); }
  [[nodiscard]] std::string stateNameAndId(const int id) const { return nameAndId(stateName(id), id); }
  [[nodiscard]] std::string animationNameOrId(const int id) const { return nameOrId(animationName(id), id); }
  [[nodiscard]] std::string animationNameAndId(const int id) const { return nameAndId(animationName(id), id); }
  [[nodiscard]] std::string tilesetNameOrId(const int id) const { return nameOrId(tilesetName(id), id); }
  [[nodiscard]] std::string tilesetNameAndId(const int id) const { return nameAndId(tilesetName(id), id); }
  [[nodiscard]] std::string commonEventNameOrId(const int id) const { return nameOrId(commonEventName(id), id); }
  [[nodiscard]] std::string commonEventNameAndId(const int id) const { return nameAndId(commonEventName(id), id); }
  [[nodiscard]] std::string switchNameOrId(const int id) const { return nameOrId(switchName(id), id); }
  [[nodiscard]] std::string switchNameAndId(const int id) const { return nameAndId(switchName(id), id); }
  [[nodiscard]] std::string variableNameOrId(const int id) const { return nameOrId(variableName(id), id); }
  [[nodiscard]] std::string variableNameAndId(const int id) const { return nameAndId(variableName(id), id); }
  [[nodiscard]] std::string mapNameOrId(const int id) const { return nameOrId(mapName(id), id); }
  [[nodiscard]] std::string mapNameAndId(const int id) const { return nameAndId(mapName(id), id); }
  [[nodiscard]] std::string skillTypeNameOrId(const int id) const { return nameOrId(skillTypeName(id), id, 2); }
  [[nodiscard]] std::string skillTypeNameAndId(const int id) const { return nameAndId(skillTypeName(id), id, 2); }
  [[nodiscard]] std::string weaponTypeNameOrId(const int id) const { return nameOrId(weaponTypeName(id), id, 2); }
  [[nodiscard]] std::string elementNameOrId(const int id) const { return nameOrId(elementName(id), id, 2); }
  [[nodiscard]] std::string elementNameAndId(const int id) const { return nameAndId(elementName(id), id, 2); }
  [[nodiscard]] std::string armorTypeOrId(const int id) const { return nameOrId(armorTypeName(id), id, 2); }
  [[nodiscard]] std::string armorTypeAndId(const int id) const { return nameAndId(armorTypeName(id), id, 2); }
  [[nodiscard]] std::string equipTypeNameOrId(const int id) const { return nameOrId(equipTypeName(id), id, 2); }
  [[nodiscard]] std::string equipTypeNameAndId(const int id) const { return nameAndId(equipTypeName(id), id, 2); }

  [[nodiscard]] std::pair<Actor*, Class*> featureObjects(const int actorId) const {
    const auto actor = actors.actor(actorId);
    if (!actor) {
      return {};
    }

    return std::make_pair(const_cast<Actor*>(actor), const_cast<Class*>(classes.classType(actor->classId())));
  }

  [[nodiscard]] std::vector<const Trait*> allTraits(const int actorId) const {
    const auto [act, cls] = featureObjects(actorId);
    std::vector<const Trait*> ret;
    if (act) {
      for (auto& trait : act->traits()) {
        ret.push_back(&trait);
      }
    }
    if (cls) {
      for (auto& trait : cls->traits()) {
        ret.push_back(&trait);
      }
    }

    return ret;
  }

  [[nodiscard]] std::vector<const Trait*> traits(const int actorId, const TraitCode code) const {
    const auto traits = allTraits(actorId);
    std::vector<const Trait*> ret;
    ret.reserve(traits.size());
    for (auto& trait : traits) {
      if (trait->code() == code) {
        ret.push_back(trait);
      }
    }
    ret.shrink_to_fit();
    return ret;
  }

  [[nodiscard]] std::vector<int> traitsSet(const int actorId, const TraitCode code) const {
    const auto tr = traits(actorId, code);
    std::vector<int> ret;
    ret.reserve(tr.size());
    for (auto& trait : tr) {
      if (trait->code() == code) {
        ret.push_back(trait->dataId());
      }
    }
    ret.shrink_to_fit();
    return ret;
  }

  [[nodiscard]] bool isEquipWeaponTypeOk(const int actorId, const int wTypeId) const {
    auto traits = traitsSet(actorId, TraitCode::Equip_Weapon);
    return std::ranges::find_if(traits, [&wTypeId](const auto& id) { return id == wTypeId; }) != traits.end();
  }

  [[nodiscard]] bool isEquipArmorTypeOk(const int actorId, const int aTypeId) const {
    auto traits = traitsSet(actorId, TraitCode::Equip_Armor);
    return std::ranges::find_if(traits, [&aTypeId](const auto& id) { return id == aTypeId; }) != traits.end();
  }

  [[nodiscard]] bool isEquipTypeSealed(const int actorId, const int aTypeId) const {
    auto traits = traitsSet(actorId, TraitCode::Seal_Equip);
    return std::ranges::find_if(traits, [&aTypeId](const auto& id) { return id == aTypeId; }) != traits.end();
  }

  [[nodiscard]] int slotType(const int actorId) const {

    if (const std::vector<int> set = traitsSet(actorId, TraitCode::Slot_Type); !set.empty()) {
      return *std::ranges::max_element(set);
    }
    return 0;
  }
  [[nodiscard]] bool isDualWield(const int actorId) const { return slotType(actorId) == 1; }

  [[nodiscard]] int slotIdToEquipId(const int actorId, const int slotId) const { return slotId == 1 && isDualWield(actorId) ? 1 : slotId + 1; }

  [[nodiscard]] std::vector<char> encryptionKeyAsBytes() const { return system.encryptionKeyAsBytes(); }

  static Database* instance() { return m_instance; }

  rpgmutils::signal<void()>& actorsLoaded() { return m_actorsLoaded; }
  rpgmutils::signal<void()>& classesLoaded() { return m_classesLoaded; }
  rpgmutils::signal<void()>& skillsLoaded() { return m_skillsLoaded; }
  rpgmutils::signal<void()>& itemsLoaded() { return m_itemsLoaded; }
  rpgmutils::signal<void()>& weaponsLoaded() { return m_weaponsLoaded; }
  rpgmutils::signal<void()>& armorsLoaded() { return m_armorsLoaded; }
  rpgmutils::signal<void()>& enemiesLoaded() { return m_enemiesLoaded; }
  rpgmutils::signal<void()>& troopsLoaded() { return m_troopsLoaded; }
  rpgmutils::signal<void()>& statesLoaded() { return m_statesLoaded; }
  rpgmutils::signal<void()>& animationsLoaded() { return m_animationsLoaded; }
  rpgmutils::signal<void()>& tilesetsLoaded() { return m_tilesetsLoaded; }
  rpgmutils::signal<void()>& commonEventsLoaded() { return m_commonEventsLoaded; }
  rpgmutils::signal<void()>& systemLoaded() { return m_systemLoaded; }
  rpgmutils::signal<void()>& pluginsLoaded() { return m_pluginsLoaded; }
  rpgmutils::signal<void()>& mapInfosLoaded() { return m_mapInfosLoaded; }
  rpgmutils::signal<void()>& gameConstantsLoaded() { return m_gameConstantsLoaded; }
  rpgmutils::signal<void()>& templatesLoaded() { return m_templatesLoaded; }
  rpgmutils::signal<void()>& docsLoaded() { return m_docsLoaded; }
  rpgmutils::signal<void()>& localesLoaded() { return m_localesLoaded; }
  rpgmutils::signal<void()>& databaseModified() { return m_databaseModified; }

  bool isMZ() const { return m_isMZ; }
  bool isMV() const { return !m_isMZ; }

  bool isModified() const {
    bool isModified = false;
    isModified |= actors.isModified();
    isModified |= classes.isModified();
    isModified |= skills.isModified();
    isModified |= items.isModified();
    isModified |= weapons.isModified();
    isModified |= armors.isModified();
    isModified |= enemies.isModified();
    isModified |= troops.isModified();
    isModified |= states.isModified();
    isModified |= animations.isModified();
    isModified |= tilesets.isModified();
    isModified |= commonEvents.isModified();
    isModified |= system.isModified();
    isModified |= mapInfos.isModified();
    // isModified |= plugins.isModified();
    // isModified |= gameConstants.isModified();
    isModified |= templates.isModified();
    isModified |= docs.isModified();
    return isModified;
  }

  bool checkEquipable(const int actorId, const int etypeId, const int dataId) const {
    if (dataId <= 0 || isEquipTypeSealed(actorId, etypeId)) {
      return false;
    }

    if (etypeId <= 1) {
      if (const auto& weapon = weapons.weapon(dataId); weapon && isEquipWeaponTypeOk(actorId, weapon->wtypeId())) {
        return true;
      }
    } else if (const auto& armor = armors.armor(dataId); armor && armor->etypeId() == etypeId && isEquipArmorTypeOk(actorId, armor->atypeId())) {
      return true;
    }

    return false;
  }

  std::string itemDisplayName(const bool isWeapon, const int dataId) const {
    if (dataId <= 0) {
      return trNOOP("None");
    }
    if (isWeapon) {
      return weaponNameOrId(dataId);
    }

    return armorNameOrId(dataId);
  }

  static std::string theNumberOf(const std::string& name) { return trFormat("The number of {}", name); }
  static std::string AOfB(const std::string& a, const std::string& b) { return trFormat("{0} of {1}", a, b); }

private:
  bool m_isMZ;
  void onAnyModifiableModified(IModifiable*) { emit_signal(databaseModified()); }
  void onAnyNonModifiableModified() { emit_signal(databaseModified()); }

  rpgmutils::signal<void()> m_actorsLoaded;
  rpgmutils::signal<void()> m_classesLoaded;
  rpgmutils::signal<void()> m_skillsLoaded;
  rpgmutils::signal<void()> m_itemsLoaded;
  rpgmutils::signal<void()> m_weaponsLoaded;
  rpgmutils::signal<void()> m_armorsLoaded;
  rpgmutils::signal<void()> m_enemiesLoaded;
  rpgmutils::signal<void()> m_troopsLoaded;
  rpgmutils::signal<void()> m_statesLoaded;
  rpgmutils::signal<void()> m_animationsLoaded;
  rpgmutils::signal<void()> m_tilesetsLoaded;
  rpgmutils::signal<void()> m_commonEventsLoaded;
  rpgmutils::signal<void()> m_systemLoaded;
  rpgmutils::signal<void()> m_pluginsLoaded;
  rpgmutils::signal<void()> m_mapInfosLoaded;
  rpgmutils::signal<void()> m_gameConstantsLoaded;
  rpgmutils::signal<void()> m_templatesLoaded;
  rpgmutils::signal<void()> m_docsLoaded;
  rpgmutils::signal<void()> m_localesLoaded;
  rpgmutils::signal<void()> m_databaseModified;

  static Database* m_instance;
};


code:
#include "Database/Doc.hpp"

Doc::Doc(const Doc& other)
: IModifiable(other)
, m_texts(other.m_texts)
, m_names(other.m_names) {}

Doc& Doc::operator=(const Doc& other) {
  IModifiable::operator=(other);
  m_texts = other.m_texts;
  m_names = other.m_names;
  m_oldtexts = other.m_oldtexts;
  m_oldnames = other.m_oldnames;
  return *this;
}

Doc::Doc(Doc&& other) noexcept
: IModifiable(std::move(other))
, m_texts(other.m_texts)
, m_names(std::move(other.m_names))
, m_oldtexts(other.m_oldtexts)
, m_oldnames(std::move(other.m_oldnames)) {}

Doc& Doc::operator=(Doc&& other) noexcept {
  IModifiable::operator=(std::move(other));
  m_texts = other.m_texts;
  m_names = std::move(other.m_names);
  m_oldtexts = other.m_oldtexts;
  m_oldnames = std::move(other.m_oldnames);
  return *this;
}

[[nodiscard]] std::vector<std::string> Doc::texts() const { return m_texts; }
void Doc::setTexts(const std::vector<std::string> texts) {
  if (m_texts == texts) {
    return;
  }
  MODIFIABLE_SET_OLD_VALUE(texts);
  m_texts = texts;
  if (!signalsDisabled()) {
   emit_signal(textsModified(), this, texts);
  }
  setModified();
}

[[nodiscard]] const std::vector<std::string>& Doc::names() const { return m_names; }
void Doc::setNames(const std::vector<std::string>& names) {
  if (m_names == names) {
    return;
  }
  MODIFIABLE_SET_OLD_VALUE(names);
  m_names = names;
  if (!signalsDisabled()) {
   emit_signal(namesModified(), this, names);
  }
  setModified();
}

void Doc::restoreOriginal() {
  IModifiable::restoreOriginal();
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(texts);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(names);
}
void Doc::acceptChanges() {
  IModifiable::acceptChanges();
  MODIFIABLE_ACCEPT_VALUE(texts);
  MODIFIABLE_ACCEPT_VALUE(names);
}
nlohmann::ordered_json Doc::serializeOldValues() const { return {MODIFIABLE_SERIALIZE_VALUE(texts), MODIFIABLE_SERIALIZE_VALUE(names)}; }

rpgmutils::signal<void(Doc*, std::vector<std::string>)>& Doc::textsModified() {
  if (!m_textsModified) {
    m_textsModified.emplace();
  }
  return *m_textsModified;
}
rpgmutils::signal<void(Doc*, const std::vector<std::string>&)>& Doc::namesModified() {
  if (!m_namesModified) {
    m_namesModified.emplace();
  }
  return *m_namesModified;
}

void to_json(nlohmann::ordered_json& j, const Doc& doc) {
  j = {
      {"texts", doc.m_texts},
      {"names", doc.m_names},
  };
}

void from_json(const nlohmann::ordered_json& j, Doc& doc) {
  doc.m_texts = j.value("texts", doc.m_texts);
  doc.m_names = j.value("names", doc.m_names);
}

Doc Doc::load(std::string_view path) {
  if (std::ifstream file(path.data()); file.is_open()) {
    try {
      nlohmann::ordered_json data = nlohmann::ordered_json::parse(file);
      Doc ret;
      data.get_to(ret);
      return ret;
    } catch (...) {}
  }

  return {};
}

bool Doc::serialize(const std::string_view path) {
  std::ofstream file(path.data());
  try {
    if (file.is_open()) {
      const nlohmann::ordered_json data = *this;
      file << data.dump(4);
      return true;
    }
  } catch (...) {}
  return false;
}


code:
#pragma once

#include "Database/IModifiable.hpp"

#include <nlohmann/json.hpp>
#include <string>

class Doc final : public IModifiable {
public:
  friend void to_json(nlohmann::ordered_json& j, const Doc& templ);
  friend void from_json(const nlohmann::ordered_json& j, Doc& templ);

  Doc() = default;
  Doc(const Doc& other);
  Doc& operator=(const Doc& other);
  Doc(Doc&& other) noexcept;
  Doc& operator=(Doc&& other) noexcept;
  Doc(std::vector<std::string> texts, std::vector<std::string> names)
  : m_texts(texts)
  , m_names(names) {}

  [[nodiscard]] std::vector<std::string> texts() const;
  void setTexts(std::vector<std::string> text);

  [[nodiscard]] const std::vector<std::string>& names() const;
  void setNames(const std::vector<std::string>& str);

  void restoreOriginal() override;
  void acceptChanges() override;
  nlohmann::ordered_json serializeOldValues() const override;

  rpgmutils::signal<void(Doc*, std::vector<std::string>)>& textsModified();
  rpgmutils::signal<void(Doc*, const std::vector<std::string>&)>& namesModified();

  static Doc load(std::string_view path);
  bool serialize(std::string_view path);

private:
  std::vector<std::string> m_names; // Names of categories
  std::vector<std::string> m_texts; // All text for a given category

  std::optional<std::vector<std::string>> m_oldnames;
  std::optional<std::vector<std::string>> m_oldtexts;

  std::optional<rpgmutils::signal<void(Doc*, std::vector<std::string>)>> m_textsModified;
  std::optional<rpgmutils::signal<void(Doc*, const std::vector<std::string>&)>> m_namesModified;
};

void to_json(nlohmann::ordered_json& j, const Doc& templ);
void from_json(const nlohmann::ordered_json& j, Doc& templ);


code:
#include "Database/Docs.hpp"

#include "Database/Database.hpp"

#include <format>
#include <fstream>

void to_json(nlohmann::ordered_json& j, const Docs& doc) {
  j = {
      {"docs", doc.docs},
  };
}

void from_json(const nlohmann::ordered_json& j, Docs& doc) { doc.docs = j.value("docs", doc.docs); }

Docs Docs::load(std::string_view path) {
  if (std::ifstream file(path.data()); file.is_open()) {
    try {
      nlohmann::ordered_json data = nlohmann::ordered_json::parse(file);
      Docs ret;
      data.get_to(ret);
      return ret;
    } catch (...) {}
  }

  return {};
}

bool Docs::serialize(const std::string_view path) {
  std::ofstream file(path.data());
  try {
    if (file.is_open()) {
      const nlohmann::ordered_json data = *this;
      file << data.dump(4);
      return true;
    }
  } catch (...) {}
  return false;
}


code:
#pragma once

#include "Database/Doc.hpp"

class Docs final : public IModifiable {

public:
  friend void to_json(nlohmann::ordered_json& j, const Docs& templ);
  friend void from_json(const nlohmann::ordered_json& j, Docs& templ);
  std::vector<Doc> docs;

  static Docs load(std::string_view path);
  bool serialize(std::string_view path);

  bool isModified() const override {
    return IModifiable::isModified() | std::ranges::any_of(docs, [](const Doc& t) { return t.isModified(); });
  }
};
void to_json(nlohmann::ordered_json& j, const Docs& templ);
void from_json(const nlohmann::ordered_json& j, Docs& templ);


code:
#include "Database/Effect.hpp"

Effect::Effect(const Effect& other)
: IModifiable(other)
, m_code(other.m_code)
, m_dataId(other.m_dataId)
, m_value1(other.m_value1)
, m_value2(other.m_value2)
, m_oldcode(other.m_oldcode)
, m_olddataId(other.m_olddataId)
, m_oldvalue1(other.m_oldvalue1)
, m_oldvalue2(other.m_oldvalue2) {}
Effect& Effect::operator=(const Effect& other) {
  IModifiable::operator=(other);
  m_code = other.m_code;
  m_dataId = other.m_dataId;
  m_value1 = other.m_value1;
  m_value2 = other.m_value2;
  m_oldcode = other.m_oldcode;
  m_olddataId = other.m_olddataId;
  m_oldvalue1 = other.m_oldvalue1;
  m_oldvalue2 = other.m_oldvalue2;
  return *this;
}
Effect::Effect(Effect&& other) noexcept
: IModifiable(std::move(other))
, m_code(other.m_code)
, m_dataId(other.m_dataId)
, m_value1(other.m_value1)
, m_value2(other.m_value2)
, m_oldcode(other.m_oldcode)
, m_olddataId(other.m_olddataId)
, m_oldvalue1(other.m_oldvalue1)
, m_oldvalue2(other.m_oldvalue2) {}
Effect& Effect::operator=(Effect&& other) noexcept {
  IModifiable::operator=(std::move(other));
  m_code = other.m_code;
  m_dataId = other.m_dataId;
  m_value1 = other.m_value1;
  m_value2 = other.m_value2;
  m_oldcode = other.m_oldcode;
  m_olddataId = other.m_olddataId;
  m_oldvalue1 = other.m_oldvalue1;
  m_oldvalue2 = other.m_oldvalue2;
  return *this;
}

EffectCode Effect::code() const { return m_code; }
void Effect::setCode(const EffectCode code) {
  MODIFIABLE_SET_OLD_VALUE(code);
  m_code = code;
  if (!signalsDisabled()) {
   emit_signal(codeModified(), this, code);
  }
  setModified();
}

int Effect::dataId() const { return m_dataId; }
void Effect::setDataId(const int dataId) {
  MODIFIABLE_SET_OLD_VALUE(dataId);
  m_dataId = dataId;
  if (!signalsDisabled()) {
   emit_signal(dataIdModified(), this, m_dataId);
  }
  setModified();
}

float Effect::value1() const { return m_value1; }
void Effect::setValue1(const float value1) {
  MODIFIABLE_SET_OLD_VALUE(value1);
  m_value1 = value1;
  if (!signalsDisabled()) {
   emit_signal(value1Modified(), this, m_value1);
  }
  setModified();
}

int Effect::value2() const { return m_value2; }

void Effect::setValue2(const int value2) {
  MODIFIABLE_SET_OLD_VALUE(value2);
  m_value2 = value2;
  if (!signalsDisabled()) {
   emit_signal(value2Modified(), this, m_value2);
  }
  setModified();
}

void Effect::restoreOriginal() {
  IModifiable::restoreOriginal();
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(code);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(dataId);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(value1);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(value2);
}

void Effect::acceptChanges() {
  IModifiable::acceptChanges();
  MODIFIABLE_ACCEPT_VALUE(code);
  MODIFIABLE_ACCEPT_VALUE(dataId);
  MODIFIABLE_ACCEPT_VALUE(value1);
  MODIFIABLE_ACCEPT_VALUE(value2);
}

nlohmann::ordered_json Effect::serializeOldValues() const {
  return {
      MODIFIABLE_SERIALIZE_VALUE(code),
      MODIFIABLE_SERIALIZE_VALUE(dataId),
      MODIFIABLE_SERIALIZE_VALUE(value1),
      MODIFIABLE_SERIALIZE_VALUE(value2),
  };
}

rpgmutils::signal<void(Effect*, EffectCode)>& Effect::codeModified() {
  if (!m_codeModified) {
    m_codeModified.emplace();
  }
  return *m_codeModified;
}

rpgmutils::signal<void(Effect*, int)>& Effect::dataIdModified() {
  if (!m_dataIdModified) {
    m_dataIdModified.emplace();
  }
  return *m_dataIdModified;
}

rpgmutils::signal<void(Effect*, float)>& Effect::value1Modified() {
  if (!m_value1Modified) {
    m_value1Modified.emplace();
  }
  return *m_value1Modified;
}

rpgmutils::signal<void(Effect*, int)>& Effect::value2Modified() {
  if (!m_value2Modified) {
    m_value2Modified.emplace();
  }
  return *m_value2Modified;
}

void to_json(nlohmann::ordered_json& to, const Effect& effect) {
  to = {
      {"code", effect.m_code},
      {"dataId", effect.m_dataId},
      {"value1", effect.m_value1},
      {"value2", effect.m_value2},
  };
}

void from_json(const nlohmann::ordered_json& from, Effect& effect) {
  effect.m_code = from.value("code", effect.m_code);
  effect.m_dataId = from.value("dataId", effect.m_dataId);
  effect.m_value1 = from.value("value1", effect.m_value1);
  effect.m_value2 = from.value("value2", effect.m_value2);
}


code:
#pragma once
#include "Database/Globals.hpp"
#include "Database/IModifiable.hpp"

#include <nlohmann/json.hpp>

class Effect final : public IModifiable {
  friend void to_json(nlohmann::ordered_json& to, const Effect& effect);
  friend void from_json(const nlohmann::ordered_json& from, Effect& effect);

public:
  Effect() = default;
  Effect(const Effect& other);
  Effect& operator=(const Effect& other);
  Effect(Effect&& other) noexcept;
  Effect& operator=(Effect&& other) noexcept;

  EffectCode code() const;
  void setCode(EffectCode code);

  int dataId() const; //! Object ID depending on the selection (Add State, Remove State, Common Event, Learn Skill)
  void setDataId(int dataId);

  float value1() const; //! Combobox member value OR dataId's value -- percent values are used as (1.00 ~ -1.00)
  void setValue1(float value1);

  int value2() const; //! Never used for object id; secondary value after combobox
  void setValue2(int value2);

  void restoreOriginal() override;
  void acceptChanges() override;
  nlohmann::ordered_json serializeOldValues() const override;

  rpgmutils::signal<void(Effect*, EffectCode)>& codeModified();
  rpgmutils::signal<void(Effect*, int)>& dataIdModified();
  rpgmutils::signal<void(Effect*, float)>& value1Modified();
  rpgmutils::signal<void(Effect*, int)>& value2Modified();

  bool operator==(const Effect& other) const { return m_code == other.m_code && m_dataId == other.m_dataId && m_value1 == other.m_value1 && m_value2 == other.m_value2; }

private:
  EffectCode m_code = EffectCode::Recover_HP;
  int m_dataId{0};
  float m_value1{1.f};
  int m_value2{0};

  std::optional<EffectCode> m_oldcode;
  std::optional<int> m_olddataId;
  std::optional<float> m_oldvalue1;
  std::optional<int> m_oldvalue2;

  std::optional<rpgmutils::signal<void(Effect*, EffectCode)>> m_codeModified;
  std::optional<rpgmutils::signal<void(Effect*, int)>> m_dataIdModified;
  std::optional<rpgmutils::signal<void(Effect*, float)>> m_value1Modified;
  std::optional<rpgmutils::signal<void(Effect*, int)>> m_value2Modified;
};

void to_json(nlohmann::ordered_json& to, const Effect& effect);
void from_json(const nlohmann::ordered_json& from, Effect& effect);


code: #include "Database/Enemies.hpp"


code:
#pragma once

#include "Database/Enemy.hpp"

class Enemies final : public IModifiable {
public:
  [[nodiscard]] Enemy* enemy(const int id) {
    for (auto& enemy : m_enemies) {
      if (enemy.id() == id && enemy.isValid()) {
        return &enemy;
      }
    }
    return nullptr;
  }

  [[nodiscard]] const Enemy* enemy(const int id) const {
    for (const auto& enemy : m_enemies) {
      if (enemy.id() == id && enemy.isValid()) {
        return &enemy;
      }
    }

    return nullptr;
  }

  [[nodiscard]] std::vector<Enemy>& enemies() { return m_enemies; }
  [[nodiscard]] const std::vector<Enemy>& enemies() const { return m_enemies; }
  int count() const { return m_enemies.size() - 1; }

  void resize(int newSize) {
    ++newSize;
    const int oldSize = m_enemies.size();
    m_enemies.resize(newSize);
    if (newSize > oldSize) {
      for (int i = oldSize; i < m_enemies.size(); ++i) {
        m_enemies[i].setId(i);
      }
    }
  }

  bool isModified() const override {
    return IModifiable::isModified() | std::ranges::any_of(m_enemies, [](const auto& enemy) { return enemy.isModified(); });
  }

private:
  std::vector<Enemy> m_enemies;
};


code:
#include "Enemy.hpp"

Enemy::Action::Action(const Action& other)
: IModifiable(other)
, m_conditionParam1(other.m_conditionParam1)
, m_conditionParam2(other.m_conditionParam2)
, m_conditionType(other.m_conditionType)
, m_rating(other.m_rating)
, m_skillId(other.m_skillId)
, m_oldconditionParam1(other.m_oldconditionParam1)
, m_oldconditionParam2(other.m_oldconditionParam2)
, m_oldconditionType(other.m_oldconditionType)
, m_oldrating(other.m_oldrating)
, m_oldskillId(other.m_oldskillId) {}

Enemy::Action& Enemy::Action::operator=(const Action& other) {
  IModifiable::operator=(other);
  m_conditionParam1 = other.m_conditionParam1;
  m_conditionParam2 = other.m_conditionParam2;
  m_conditionType = other.m_conditionType;
  m_rating = other.m_rating;
  m_skillId = other.m_skillId;
  m_oldconditionParam1 = other.m_oldconditionParam1;
  m_oldconditionParam2 = other.m_oldconditionParam2;
  m_oldconditionType = other.m_oldconditionType;
  m_oldrating = other.m_oldrating;
  m_oldskillId = other.m_oldskillId;
  return *this;
}
Enemy::Action::Action(Action&& other) noexcept
: IModifiable(std::move(other))
, m_conditionParam1(other.m_conditionParam1)
, m_conditionParam2(other.m_conditionParam2)
, m_conditionType(other.m_conditionType)
, m_rating(other.m_rating)
, m_skillId(other.m_skillId)
, m_oldconditionParam1(other.m_oldconditionParam1)
, m_oldconditionParam2(other.m_oldconditionParam2)
, m_oldconditionType(other.m_oldconditionType)
, m_oldrating(other.m_oldrating)
, m_oldskillId(other.m_oldskillId) {}

Enemy::Action& Enemy::Action::operator=(Action&& other) noexcept {
  IModifiable::operator=(std::move(other));
  m_conditionParam1 = other.m_conditionParam1;
  m_conditionParam2 = other.m_conditionParam2;
  m_conditionType = other.m_conditionType;
  m_rating = other.m_rating;
  m_skillId = other.m_skillId;
  m_oldconditionParam1 = other.m_oldconditionParam1;
  m_oldconditionParam2 = other.m_oldconditionParam2;
  m_oldconditionType = other.m_oldconditionType;
  m_oldrating = other.m_oldrating;
  m_oldskillId = other.m_oldskillId;
  return *this;
}

float Enemy::Action::conditionParam1() const { return m_conditionParam1; }
void Enemy::Action::setConditionParam1(const float conditionParam1) {
  MODIFIABLE_SET_OLD_VALUE(conditionParam1);
  m_conditionParam1 = conditionParam1;
  if (!signalsDisabled()) {
   emit_signal(conditionParam1Modified(), this, conditionParam1);
  }
  setModified();
}

float Enemy::Action::conditionParam2() const { return m_conditionParam2; }
void Enemy::Action::setConditionParam2(const float conditionParam2) {
  MODIFIABLE_SET_OLD_VALUE(conditionParam2);
  m_conditionParam2 = conditionParam2;
  if (!signalsDisabled()) {
   emit_signal(conditionParam2Modified(), this, conditionParam2);
  }
  setModified();
}

ActionCondition Enemy::Action::conditionType() const { return m_conditionType; }
void Enemy::Action::setConditionType(const ActionCondition conditionType) {
  MODIFIABLE_SET_OLD_VALUE(conditionType);
  m_conditionType = conditionType;
  if (!signalsDisabled()) {
   emit_signal(conditionTypeModified(), this, conditionType);
  }
  setModified();
}

int Enemy::Action::rating() const { return m_rating; }
void Enemy::Action::setRating(const int rating) {
  MODIFIABLE_SET_OLD_VALUE(rating);
  m_rating = rating;
  if (!signalsDisabled()) {
  emit_signal(ratingModified(), this, rating);
  }
  setModified();
}

int Enemy::Action::skillId() const { return m_skillId; }
void Enemy::Action::setSkillId(int skillId) {
  MODIFIABLE_SET_OLD_VALUE(skillId);
  m_skillId = skillId;
  if (!signalsDisabled()) {
  emit_signal(skillIdModified(), this, skillId);
  }
  setModified();
}

void Enemy::Action::restoreOriginal() {
  IModifiable::restoreOriginal();
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(conditionParam1);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(conditionParam2);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(rating);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(skillId);
}

void Enemy::Action::acceptChanges() {
  IModifiable::acceptChanges();
  MODIFIABLE_ACCEPT_VALUE(conditionParam1);
  MODIFIABLE_ACCEPT_VALUE(conditionParam2);
  MODIFIABLE_ACCEPT_VALUE(rating);
  MODIFIABLE_ACCEPT_VALUE(skillId);
}

nlohmann::ordered_json Enemy::Action::serializeOldValues() const {
  return {
      MODIFIABLE_SERIALIZE_VALUE(conditionParam1),
      MODIFIABLE_SERIALIZE_VALUE(conditionParam2),
      MODIFIABLE_SERIALIZE_VALUE(rating),
      MODIFIABLE_SERIALIZE_VALUE(skillId),
  };
}

rpgmutils::signal<void(Enemy::Action*, float)>& Enemy::Action::conditionParam1Modified() {
  if (!m_conditionParam1Modified) {
    m_conditionParam1Modified.emplace();
  }
  return *m_conditionParam1Modified;
}

rpgmutils::signal<void(Enemy::Action*, float)>& Enemy::Action::conditionParam2Modified() {
  if (!m_conditionParam2Modified) {
    m_conditionParam2Modified.emplace();
  }
  return *m_conditionParam2Modified;
}

rpgmutils::signal<void(Enemy::Action*, ActionCondition)>& Enemy::Action::conditionTypeModified() {
  if (!m_conditionTypeModified) {
    m_conditionTypeModified.emplace();
  }
  return *m_conditionTypeModified;
}

rpgmutils::signal<void(Enemy::Action*, int)>& Enemy::Action::ratingModified() {
  if (!m_ratingModified) {
    m_ratingModified.emplace();
  }
  return *m_ratingModified;
}

rpgmutils::signal<void(Enemy::Action*, int)>& Enemy::Action::skillIdModified() {
  if (!m_skillIdModified) {
    m_skillIdModified.emplace();
  }
  return *m_skillIdModified;
}

Enemy::DropItem::DropItem(const DropItem& other)
: IModifiable(other)
, m_dataId(other.m_dataId)
, m_denominator(other.m_denominator)
, m_kind(other.m_kind)
, m_olddataId(other.m_olddataId)
, m_olddenominator(other.m_olddenominator)
, m_oldkind(other.m_oldkind) {}
Enemy::DropItem& Enemy::DropItem::operator=(const DropItem& other) {
  IModifiable::operator=(other);
  m_dataId = other.m_dataId;
  m_denominator = other.m_denominator;
  m_kind = other.m_kind;
  m_olddataId = other.m_olddataId;
  m_olddenominator = other.m_olddenominator;
  m_oldkind = other.m_oldkind;
  return *this;
}
Enemy::DropItem::DropItem(DropItem&& other) noexcept
: IModifiable(std::move(other))
, m_dataId(other.m_dataId)
, m_denominator(other.m_denominator)
, m_kind(other.m_kind)
, m_olddataId(other.m_olddataId)
, m_olddenominator(other.m_olddenominator)
, m_oldkind(other.m_oldkind) {}

Enemy::DropItem& Enemy::DropItem::operator=(DropItem&& other) noexcept {
  IModifiable::operator=(std::move(other));
  m_dataId = other.m_dataId;
  m_denominator = other.m_denominator;
  m_kind = other.m_kind;
  m_olddataId = other.m_olddataId;
  m_olddenominator = other.m_olddenominator;
  m_oldkind = other.m_oldkind;
  return *this;
}

int Enemy::DropItem::dataId() const { return m_dataId; }
void Enemy::DropItem::setDataId(const int dataId) {
  MODIFIABLE_SET_OLD_VALUE(dataId);
  m_dataId = dataId;
  if (!signalsDisabled()) {
  emit_signal(dataIdModified(), this, dataId);
  }
  setModified();
}

int Enemy::DropItem::denominator() const { return m_denominator; }
void Enemy::DropItem::setDenominator(const int denominator) {
  MODIFIABLE_SET_OLD_VALUE(denominator);
  m_denominator = denominator;
  if (!signalsDisabled()) {
  emit_signal(denominatorModified(), this, denominator);
  }
  setModified();
}

int Enemy::DropItem::kind() const { return m_kind; }
void Enemy::DropItem::setKind(const int kind) {
  MODIFIABLE_SET_OLD_VALUE(kind);
  m_kind = kind;
  if (!signalsDisabled()) {
  emit_signal(kindModified(), this, kind);
  }
  setModified();
}

void Enemy::DropItem::restoreOriginal() {
  IModifiable::restoreOriginal();
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(dataId);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(denominator);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(kind);
}

void Enemy::DropItem::acceptChanges() {
  IModifiable::acceptChanges();
  MODIFIABLE_ACCEPT_VALUE(dataId);
  MODIFIABLE_ACCEPT_VALUE(denominator);
  MODIFIABLE_ACCEPT_VALUE(kind);
}

nlohmann::ordered_json Enemy::DropItem::serializeOldValues() const {
  return {
      MODIFIABLE_SERIALIZE_VALUE(dataId),
      MODIFIABLE_SERIALIZE_VALUE(denominator),
      MODIFIABLE_SERIALIZE_VALUE(kind),
  };
}
rpgmutils::signal<void(Enemy::DropItem*, int)>& Enemy::DropItem::dataIdModified() {
  if (!m_dataIdModified) {
    m_dataIdModified.emplace();
  }
  return *m_dataIdModified;
}
rpgmutils::signal<void(Enemy::DropItem*, int)>& Enemy::DropItem::denominatorModified() {
  if (!m_denominatorModified) {
    m_denominatorModified.emplace();
  }
  return *m_denominatorModified;
}
rpgmutils::signal<void(Enemy::DropItem*, int)>& Enemy::DropItem::kindModified() {
  if (!m_kindModified) {
    m_kindModified.emplace();
  }
  return *m_kindModified;
}

Enemy::Enemy(const Enemy& other)
: IModifiable(other)
, m_id(other.m_id)
, m_actions(other.m_actions)
, m_battlerHue(other.m_battlerHue)
, m_battlerName(other.m_battlerName)
, m_dropItems(other.m_dropItems)
, m_exp(other.m_exp)
, m_traits(other.m_traits)
, m_gold(other.m_gold)
, m_name(other.m_name)
, m_note(other.m_note)
, m_params(other.m_params)
, m_oldid(other.m_oldid)
, m_oldactions(other.m_oldactions)
, m_oldbattlerHue(other.m_oldbattlerHue)
, m_oldbattlerName(other.m_oldbattlerName)
, m_oldexp(other.m_oldexp)
, m_oldtraits(other.m_oldtraits)
, m_oldgold(other.m_oldgold)
, m_oldname(other.m_oldname)
, m_oldnote(other.m_oldnote)
, m_oldparams(other.m_oldparams)
, m_isValid(other.m_isValid) {}
Enemy& Enemy::operator=(const Enemy& other) {
  IModifiable::operator=(other);
  m_id = other.m_id;
  m_actions = other.m_actions;
  m_battlerHue = other.m_battlerHue;
  m_battlerName = other.m_battlerName;
  m_dropItems = other.m_dropItems;
  m_exp = other.m_exp;
  m_traits = other.m_traits;
  m_gold = other.m_gold;
  m_name = other.m_name;
  m_note = other.m_note;
  m_params = other.m_params;
  m_oldid = other.m_oldid;
  m_oldactions = other.m_oldactions;
  m_oldbattlerHue = other.m_oldbattlerHue;
  m_oldbattlerName = other.m_oldbattlerName;
  m_oldexp = other.m_oldexp;
  m_oldtraits = other.m_oldtraits;
  m_oldgold = other.m_oldgold;
  m_oldname = other.m_oldname;
  m_oldnote = other.m_oldnote;
  m_oldparams = other.m_oldparams;
  m_isValid = other.m_isValid;
  return *this;
}
Enemy::Enemy(Enemy&& other) noexcept
: IModifiable(std::move(other))
, m_id(other.m_id)
, m_actions(std::move(other.m_actions))
, m_battlerHue(other.m_battlerHue)
, m_battlerName(std::move(other.m_battlerName))
, m_dropItems(std::move(other.m_dropItems))
, m_exp(other.m_exp)
, m_traits(std::move(other.m_traits))
, m_gold(other.m_gold)
, m_name(std::move(other.m_name))
, m_note(std::move(other.m_note))
, m_params(std::move(other.m_params))
, m_oldid(other.m_oldid)
, m_oldactions(std::move(other.m_oldactions))
, m_oldbattlerHue(std::move(other.m_oldbattlerHue))
, m_oldbattlerName(std::move(other.m_oldbattlerName))
, m_oldexp(other.m_oldexp)
, m_oldtraits(std::move(other.m_oldtraits))
, m_oldgold(other.m_oldgold)
, m_oldname(std::move(other.m_oldname))
, m_oldnote(std::move(other.m_oldnote))
, m_oldparams(std::move(other.m_oldparams))
, m_isValid(other.m_isValid) {}
Enemy& Enemy::operator=(Enemy&& other) noexcept {
  IModifiable::operator=(std::move(other));
  m_id = other.m_id;
  m_actions = std::move(other.m_actions);
  m_battlerHue = other.m_battlerHue;
  m_battlerName = std::move(other.m_battlerName);
  m_dropItems = std::move(other.m_dropItems);
  m_exp = other.m_exp;
  m_traits = std::move(other.m_traits);
  m_gold = other.m_gold;
  m_name = std::move(other.m_name);
  m_note = std::move(other.m_note);
  m_params = std::move(other.m_params);
  m_oldid = other.m_oldid;
  m_oldactions = std::move(other.m_oldactions);
  m_oldbattlerHue = other.m_oldbattlerHue;
  m_oldbattlerName = std::move(other.m_oldbattlerName);
  m_oldexp = other.m_oldexp;
  m_oldtraits = std::move(other.m_oldtraits);
  m_oldgold = other.m_oldgold;
  m_oldname = std::move(other.m_oldname);
  m_oldnote = std::move(other.m_oldnote);
  m_oldparams = std::move(other.m_oldparams);
  m_isValid = other.m_isValid;
  return *this;
}

[[nodiscard]] int Enemy::id() const { return m_id; }
void Enemy::setId(const int id) {
  MODIFIABLE_SET_OLD_VALUE(id);
  m_id = id;
  if (!signalsDisabled()) {
  emit_signal(idModified(), this, id);
  }
  setModified();
}

std::vector<Enemy::Action>& Enemy::actions() { return m_actions; }
const std::vector<Enemy::Action>& Enemy::actions() const { return m_actions; }
void Enemy::setActions(const std::vector<Action>& actions) {
  MODIFIABLE_SET_OLD_VALUE(actions);
  m_actions = actions;
  if (!signalsDisabled()) {
  emit_signal(actionsModified(), this, actions);
  }
  setModified();
}

[[nodiscard]] int Enemy::battlerHue() const { return m_battlerHue; }
void Enemy::setBattlerHue(const int battlerHue) {
  MODIFIABLE_SET_OLD_VALUE(battlerHue);
  m_battlerHue = battlerHue;
  if (!signalsDisabled()) {
  emit_signal(battlerHueModified(), this, battlerHue);
  }
  setModified();
}

const std::string& Enemy::battlerName() const { return m_battlerName; }
void Enemy::setBattlerName(const std::string& battlerName) {
  MODIFIABLE_SET_OLD_VALUE(battlerName);
  m_battlerName = battlerName;
  if (!signalsDisabled()) {
  emit_signal(battlerNameModified(), this, battlerName);
  }
  setModified();
}

std::vector<Enemy::DropItem>& Enemy::dropItems() { return m_dropItems; }
const std::vector<Enemy::DropItem>& Enemy::dropItems() const { return m_dropItems; }
void Enemy::setDropItems(const std::vector<DropItem>& dropItems) {
  MODIFIABLE_SET_OLD_VALUE(dropItems);
  m_dropItems = dropItems;
  if (!signalsDisabled()) {
  emit_signal(dropItemsModified(), this, dropItems);
  }
  setModified();
}

[[nodiscard]] int Enemy::exp() const { return m_exp; }
void Enemy::setExp(const int exp) {
  MODIFIABLE_SET_OLD_VALUE(exp);
  m_exp = exp;
  if (!signalsDisabled()) {
  emit_signal(expModified(), this, exp);
  }
  setModified();
}

std::vector<Trait>& Enemy::traits() { return m_traits; }
void Enemy::setTraits(const std::vector<Trait>& traits) {
  MODIFIABLE_SET_OLD_VALUE(traits);
  m_traits = traits;
  if (!signalsDisabled()) {
  emit_signal(traitsModified(), this, traits);
  }
  setModified();
}

[[nodiscard]] int Enemy::gold() const { return m_gold; }
void Enemy::setGold(const int gold) {
  MODIFIABLE_SET_OLD_VALUE(gold);
  m_gold = gold;
  if (!signalsDisabled()) {
  emit_signal(goldModified(), this, gold);
  }
  setModified();
}

const std::string& Enemy::name() const { return m_name; }
void Enemy::setName(const std::string& name) {
  MODIFIABLE_SET_OLD_VALUE(name);
  m_name = name;
  if (!signalsDisabled()) {
  emit_signal(nameModified(), this, name);
  }
  setModified();
}

const std::string& Enemy::note() const { return m_note; }
void Enemy::setNote(const std::string& note) {
  MODIFIABLE_SET_OLD_VALUE(note);
  m_note = note;
  if (!signalsDisabled()) {
  emit_signal(noteModified(), this, note);
  }
  setModified();
}

const std::array<int, 8>& Enemy::params() const { return m_params; }
void Enemy::setParams(const std::array<int, 8>& params) {
  MODIFIABLE_SET_OLD_VALUE(params);
  m_params = params;
  if (!signalsDisabled()) {
  emit_signal(paramsModified(), this, params);
  }
  setModified();
}
[[nodiscard]] int Enemy::param(const int idx) const {
  assert(idx >= 0 && idx < m_params.size());
  return m_params[idx];
}
void Enemy::setParam(const int idx, const int param) {
  assert(idx >= 0 && idx < m_params.size());
  MODIFIABLE_SET_OLD_VALUE(params);
  m_params[idx] = param;
  if (!signalsDisabled()) {
  emit_signal(paramModified(), this, idx, param);
  }
  setModified();
}

bool Enemy::isValid() const { return m_isValid; }
void Enemy::setIsValid(const bool isValid) { m_isValid = isValid; }

void Enemy::restoreOriginal() {
  IModifiable::restoreOriginal();
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(id);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(actions);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(battlerHue);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(battlerName);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(dropItems);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(exp);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(traits);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(gold);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(name);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(note);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(params);
}

void Enemy::acceptChanges() {
  IModifiable::acceptChanges();
  MODIFIABLE_ACCEPT_VALUE(id);
  MODIFIABLE_ACCEPT_VALUE(actions);
  MODIFIABLE_ACCEPT_VALUE(battlerHue);
  MODIFIABLE_ACCEPT_VALUE(battlerName);
  MODIFIABLE_ACCEPT_VALUE(dropItems);
  MODIFIABLE_ACCEPT_VALUE(exp);
  MODIFIABLE_ACCEPT_VALUE(traits);
  MODIFIABLE_ACCEPT_VALUE(gold);
  MODIFIABLE_ACCEPT_VALUE(name);
  MODIFIABLE_ACCEPT_VALUE(note);
  MODIFIABLE_ACCEPT_VALUE(params);
}

nlohmann::ordered_json Enemy::serializeOldValues() const {
  return {
      MODIFIABLE_SERIALIZE_VALUE(id),
      MODIFIABLE_SERIALIZE_VALUE_CHILD_ARRAY(actions),
      MODIFIABLE_SERIALIZE_VALUE(battlerHue),
      MODIFIABLE_SERIALIZE_VALUE(battlerName),
      MODIFIABLE_SERIALIZE_VALUE_CHILD_ARRAY(dropItems),
      MODIFIABLE_SERIALIZE_VALUE(exp),
      MODIFIABLE_SERIALIZE_VALUE_CHILD_ARRAY(traits),
      MODIFIABLE_SERIALIZE_VALUE(gold),
      MODIFIABLE_SERIALIZE_VALUE(name),
      MODIFIABLE_SERIALIZE_VALUE(note),
      MODIFIABLE_SERIALIZE_VALUE(params),
  };
}
bool Enemy::isModified() const {
  bool modified = IModifiable::isModified();
  modified |= std::ranges::any_of(m_actions, [](const Action& action) { return action.isModified(); });
  modified |= std::ranges::any_of(m_dropItems, [](const DropItem& dropItem) { return dropItem.isModified(); });
  modified |= std::ranges::any_of(m_traits, [](const Trait& trait) { return trait.isModified(); });
  return modified;
}

rpgmutils::signal<void(Enemy*, int)>& Enemy::idModified() {
  if (!m_idModified) {
    m_idModified.emplace();
  }
  return *m_idModified;
}
rpgmutils::signal<void(Enemy*, const std::vector<Enemy::Action>&)>& Enemy::actionsModified() {
  if (!m_actionsModified) {
    m_actionsModified.emplace();
  }
  return *m_actionsModified;
}
rpgmutils::signal<void(Enemy*, int)>& Enemy::battlerHueModified() {
  if (!m_battlerHueModified) {
    m_battlerHueModified.emplace();
  }
  return *m_battlerHueModified;
}
rpgmutils::signal<void(Enemy*, const std::string&)>& Enemy::battlerNameModified() {
  if (!m_battlerNameModified) {
    m_battlerNameModified.emplace();
  }
  return *m_battlerNameModified;
}
rpgmutils::signal<void(Enemy*, const std::vector<Enemy::DropItem>&)>& Enemy::dropItemsModified() {
  if (!m_dropItemsModified) {
    m_dropItemsModified.emplace();
  }
  return *m_dropItemsModified;
}
rpgmutils::signal<void(Enemy*, int)>& Enemy::expModified() {
  if (!m_expModified) {
    m_expModified.emplace();
  }
  return *m_expModified;
}
rpgmutils::signal<void(Enemy*, const std::vector<Trait>&)>& Enemy::traitsModified() {
  if (!m_traitsModified) {
    m_traitsModified.emplace();
  }
  return *m_traitsModified;
}
rpgmutils::signal<void(Enemy*, int)>& Enemy::goldModified() {
  if (!m_goldModified) {
    m_goldModified.emplace();
  }
  return *m_goldModified;
}
rpgmutils::signal<void(Enemy*, const std::string&)>& Enemy::nameModified() {
  if (!m_nameModified) {
    m_nameModified.emplace();
  }
  return *m_nameModified;
}
rpgmutils::signal<void(Enemy*, const std::string&)>& Enemy::noteModified() {
  if (!m_noteModified) {
    m_noteModified.emplace();
  }
  return *m_noteModified;
}
rpgmutils::signal<void(Enemy*, const std::array<int, 8>&)>& Enemy::paramsModified() {
  if (!m_paramsModified) {
    m_paramsModified.emplace();
  }
  return *m_paramsModified;
}
rpgmutils::signal<void(Enemy*, int, int)>& Enemy::paramModified() {
  if (!m_paramModified) {
    m_paramModified.emplace();
  }
  return *m_paramModified;
}

void to_json(nlohmann::ordered_json& to, const Enemy& enemy) {
  to = {
      {"id", enemy.m_id},
      {"actions", enemy.m_actions},
      {"battlerHue", enemy.m_battlerHue},
      {"battlerName", enemy.m_battlerName},
      {"dropItems", enemy.m_dropItems},
      {"exp", enemy.m_exp},
      {"traits", enemy.m_traits},
      {"gold", enemy.m_gold},
      {"name", enemy.m_name},
      {"note", enemy.m_note},
      {"params", enemy.m_params},
  };
}
void from_json(const nlohmann::ordered_json& from, Enemy& enemy) {
  enemy.m_id = from.value("id", enemy.m_id);
  enemy.m_actions = from.value("actions", enemy.m_actions);
  enemy.m_battlerHue = from.value("battlerHue", enemy.m_battlerHue);
  enemy.m_battlerName = from.value("battlerName", enemy.m_battlerName);
  enemy.m_dropItems = from.value("dropItems", enemy.m_dropItems);
  enemy.m_exp = from.value("exp", enemy.m_exp);
  enemy.m_traits = from.value("traits", enemy.m_traits);
  enemy.m_gold = from.value("gold", enemy.m_gold);
  enemy.m_name = from.value("name", enemy.m_name);
  enemy.m_note = from.value("note", enemy.m_note);
  enemy.m_params = from.value("params", enemy.m_params);
}
void to_json(nlohmann::ordered_json& to, const Enemy::Action& action) {
  to = {
      {"conditionParam1", action.m_conditionParam1},
      {"conditionParam2", action.m_conditionParam2},
      {"conditionType", action.m_conditionType},
      {"rating", action.m_rating},
      {"skillId", action.m_skillId},
  };
}
void from_json(const nlohmann::ordered_json& from, Enemy::Action& action) {
  action.m_conditionParam1 = from.value("conditionParam1", action.m_conditionParam1);
  action.m_conditionParam2 = from.value("conditionParam2", action.m_conditionParam2);
  action.m_conditionType = from.value("conditionType", action.m_conditionType);
  action.m_rating = from.value("rating", action.m_rating);
  action.m_skillId = from.value("skillId", action.m_skillId);
}

void to_json(nlohmann::ordered_json& to, const Enemy::DropItem& dropitem) {
  to = {
      {"dataId", dropitem.m_dataId},
      {"denominator", dropitem.m_denominator},
      {"kind", dropitem.m_kind},
  };
}

void from_json(const nlohmann::ordered_json& from, Enemy::DropItem& dropitem) {
  dropitem.m_dataId = from.value("dataId", dropitem.m_dataId);
  dropitem.m_denominator = from.value("denominator", dropitem.m_denominator);
  dropitem.m_kind = from.value("kind", dropitem.m_kind);
}


code:
#pragma once
#include "Database/Globals.hpp"
#include "Database/Trait.hpp"

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

class Enemy final : public IModifiable {
  friend class EnemiesSerializer;

public:
  friend void to_json(nlohmann::ordered_json& to, const Enemy& enemy);
  friend void from_json(const nlohmann::ordered_json& from, Enemy& enemy);

  class Action final : public IModifiable {
    friend void to_json(nlohmann::ordered_json& to, const Action& action);
    friend void from_json(const nlohmann::ordered_json& from, Action& action);

  public:
    Action() = default;
    Action(const Action& other);
    Action& operator=(const Action& other);
    Action(Action&& other) noexcept;
    Action& operator=(Action&& other) noexcept;
    float conditionParam1() const;
    void setConditionParam1(float conditionParam1);

    float conditionParam2() const;
    void setConditionParam2(float conditionParam2);

    ActionCondition conditionType() const;
    void setConditionType(ActionCondition conditionType);

    int rating() const;
    void setRating(int rating);

    int skillId() const;
    void setSkillId(int skillId);

    void restoreOriginal() override;
    void acceptChanges() override;
    nlohmann::ordered_json serializeOldValues() const override;

    rpgmutils::signal<void(Action*, float)>& conditionParam1Modified();
    rpgmutils::signal<void(Action*, float)>& conditionParam2Modified();
    rpgmutils::signal<void(Action*, ActionCondition)>& conditionTypeModified();
    rpgmutils::signal<void(Action*, int)>& ratingModified();
    rpgmutils::signal<void(Action*, int)>& skillIdModified();

  private:
    float m_conditionParam1{};
    float m_conditionParam2{};
    ActionCondition m_conditionType = ActionCondition::Always;
    int m_rating{5};
    int m_skillId{1};

    std::optional<float> m_oldconditionParam1;
    std::optional<float> m_oldconditionParam2;
    std::optional<ActionCondition> m_oldconditionType;
    std::optional<int> m_oldrating;
    std::optional<int> m_oldskillId;

    std::optional<rpgmutils::signal<void(Action*, float)>> m_conditionParam1Modified;
    std::optional<rpgmutils::signal<void(Action*, float)>> m_conditionParam2Modified;
    std::optional<rpgmutils::signal<void(Action*, ActionCondition)>> m_conditionTypeModified;
    std::optional<rpgmutils::signal<void(Action*, int)>> m_ratingModified;
    std::optional<rpgmutils::signal<void(Action*, int)>> m_skillIdModified;
  };

  class DropItem final : public IModifiable {
    friend void to_json(nlohmann::ordered_json& to, const DropItem& dropitem);
    friend void from_json(const nlohmann::ordered_json& from, DropItem& dropitem);

  public:
    DropItem() = default;
    DropItem(const DropItem& other);
    DropItem& operator=(const DropItem& other);
    DropItem(DropItem&& other) noexcept;
    DropItem& operator=(DropItem&& other) noexcept;

    int dataId() const;
    void setDataId(int dataId);

    int denominator() const;
    void setDenominator(int denominator);

    int kind() const;
    void setKind(int kind);

    void restoreOriginal() override;
    void acceptChanges() override;
    nlohmann::ordered_json serializeOldValues() const override;

    rpgmutils::signal<void(DropItem*, int)>& dataIdModified();
    rpgmutils::signal<void(DropItem*, int)>& denominatorModified();
    rpgmutils::signal<void(DropItem*, int)>& kindModified();

  private:
    int m_dataId;
    int m_denominator;
    int m_kind;
    std::optional<int> m_olddataId;
    std::optional<int> m_olddenominator;
    std::optional<int> m_oldkind;

    std::optional<rpgmutils::signal<void(DropItem*, int)>> m_dataIdModified;
    std::optional<rpgmutils::signal<void(DropItem*, int)>> m_denominatorModified;
    std::optional<rpgmutils::signal<void(DropItem*, int)>> m_kindModified;
  };

  Enemy() = default;
  Enemy(const Enemy& other);
  Enemy& operator=(const Enemy& other);
  Enemy(Enemy&& other) noexcept;
  Enemy& operator=(Enemy&& other) noexcept;

  [[nodiscard]] int id() const;
  void setId(int id);

  std::vector<Action>& actions();
  const std::vector<Action>& actions() const;
  void setActions(const std::vector<Action>& actions);

  [[nodiscard]] int battlerHue() const;
  void setBattlerHue(int battlerHue);

  const std::string& battlerName() const;
  void setBattlerName(const std::string& battlerName);

  std::vector<DropItem>& dropItems();
  const std::vector<DropItem>& dropItems() const;
  void setDropItems(const std::vector<DropItem>& dropItems);

  [[nodiscard]] int exp() const;
  void setExp(int exp);

  std::vector<Trait>& traits();
  void setTraits(const std::vector<Trait>& traits);

  [[nodiscard]] int gold() const;
  void setGold(int gold);

  const std::string& name() const;
  void setName(const std::string& name);

  const std::string& note() const;
  void setNote(const std::string& note);

  const std::array<int, 8>& params() const;
  void setParams(const std::array<int, 8>& params);
  [[nodiscard]] int param(int idx) const;
  void setParam(int idx, int param);

  bool isValid() const;
  void setIsValid(bool isValid);

  void restoreOriginal() override;
  void acceptChanges() override;
  nlohmann::ordered_json serializeOldValues() const override;

  bool isModified() const override;

  rpgmutils::signal<void(Enemy*, int)>& idModified();
  rpgmutils::signal<void(Enemy*, const std::vector<Action>&)>& actionsModified();
  rpgmutils::signal<void(Enemy*, int)>& battlerHueModified();
  rpgmutils::signal<void(Enemy*, const std::string&)>& battlerNameModified();
  rpgmutils::signal<void(Enemy*, const std::vector<DropItem>&)>& dropItemsModified();
  rpgmutils::signal<void(Enemy*, int)>& expModified();
  rpgmutils::signal<void(Enemy*, const std::vector<Trait>&)>& traitsModified();
  rpgmutils::signal<void(Enemy*, int)>& goldModified();
  rpgmutils::signal<void(Enemy*, const std::string&)>& nameModified();
  rpgmutils::signal<void(Enemy*, const std::string&)>& noteModified();
  rpgmutils::signal<void(Enemy*, const std::array<int, 8>&)>& paramsModified();
  rpgmutils::signal<void(Enemy*, int, int)>& paramModified();

private:
  int m_id;
  std::vector<Action> m_actions;
  int m_battlerHue;
  std::string m_battlerName;
  std::vector<DropItem> m_dropItems;
  int m_exp;
  std::vector<Trait> m_traits;
  int m_gold;
  std::string m_name;
  std::string m_note;
  std::array<int, 8> m_params; // Attack, Defence, M.Attack, M.Defence, Agility, Luck, Max HP, Max MP

  std::optional<int> m_oldid;
  std::optional<std::vector<Action>> m_oldactions;
  std::optional<int> m_oldbattlerHue;
  std::optional<std::string> m_oldbattlerName;
  std::optional<std::vector<DropItem>> m_olddropItems;
  std::optional<int> m_oldexp;
  std::optional<std::vector<Trait>> m_oldtraits;
  std::optional<int> m_oldgold;
  std::optional<std::string> m_oldname;
  std::optional<std::string> m_oldnote;
  std::optional<std::array<int, 8>> m_oldparams; // Attack, Defence, M.Attack, M.Defence, Agility, Luck, Max HP, Max MP

  std::optional<rpgmutils::signal<void(Enemy*, int)>> m_idModified;
  std::optional<rpgmutils::signal<void(Enemy*, const std::vector<Action>&)>> m_actionsModified;
  std::optional<rpgmutils::signal<void(Enemy*, int)>> m_battlerHueModified;
  std::optional<rpgmutils::signal<void(Enemy*, const std::string&)>> m_battlerNameModified;
  std::optional<rpgmutils::signal<void(Enemy*, const std::vector<DropItem>&)>> m_dropItemsModified;
  std::optional<rpgmutils::signal<void(Enemy*, int)>> m_expModified;
  std::optional<rpgmutils::signal<void(Enemy*, const std::vector<Trait>&)>> m_traitsModified;
  std::optional<rpgmutils::signal<void(Enemy*, int)>> m_goldModified;
  std::optional<rpgmutils::signal<void(Enemy*, const std::string&)>> m_nameModified;
  std::optional<rpgmutils::signal<void(Enemy*, const std::string&)>> m_noteModified;
  std::optional<rpgmutils::signal<void(Enemy*, const std::array<int, 8>&)>> m_paramsModified;
  std::optional<rpgmutils::signal<void(Enemy*, int, int)>> m_paramModified;

  /*!
   * @name m_isValid
   * @details
   * Indicates that this is an actual valid entry and not a dummy
   * When making a new entry make sure to set this to true or it won't be
   * serialized.
   */
  bool m_isValid{false};
};
void to_json(nlohmann::ordered_json& to, const Enemy& enemy);
void from_json(const nlohmann::ordered_json& from, Enemy& enemy);
void to_json(nlohmann::ordered_json& to, const Enemy::Action& action);
void from_json(const nlohmann::ordered_json& from, Enemy::Action& action);
void to_json(nlohmann::ordered_json& to, const Enemy::DropItem& dropitem);
void from_json(const nlohmann::ordered_json& from, Enemy::DropItem& dropitem);


code:
#include "Database/Event.hpp"
#include "Database/IEventEditor.hpp"

Event::Event(const Event& other)
: IModifiable(other)
, m_id(other.m_id)
, m_name(other.m_name)
, m_note(other.m_note)
, m_pages(other.m_pages)
, m_x(other.m_x)
, m_y(other.m_y)
, m_oldid(other.m_oldid)
, m_oldname(other.m_oldname)
, m_oldnote(other.m_oldnote)
, m_oldpages(other.m_oldpages)
, m_oldx(other.m_oldx)
, m_oldy(other.m_oldy)
, m_editor(other.m_editor)
, m_renderer(other.m_renderer) {
  if (m_editor) {
    m_editor->setEventPtr(this);
  }
  if (m_renderer) {
    m_renderer->setEventPtr(this);
  }
}

Event& Event::operator=(const Event& other) {
  IModifiable::operator=(other);
  m_id = other.m_id;
  m_name = other.m_name;
  m_note = other.m_note;
  m_pages = other.m_pages;
  m_x = other.m_x;
  m_y = other.m_y;
  m_oldid = other.m_oldid;
  m_oldname = other.m_oldname;
  m_oldnote = other.m_oldnote;
  m_oldpages = other.m_oldpages;
  m_oldx = other.m_oldx;
  m_oldy = other.m_oldy;
  m_editor = other.m_editor;
  m_renderer = other.m_renderer;
  if (m_editor) {
    m_editor->setEventPtr(this);
  }
  if (m_renderer) {
    m_renderer->setEventPtr(this);
  }
  return *this;
}

Event::Event(Event&& other) noexcept
: IModifiable(std::move(other))
, m_id(other.m_id)
, m_name(std::move(other.m_name))
, m_note(std::move(other.m_note))
, m_pages(std::move(other.m_pages))
, m_x(other.m_x)
, m_y(other.m_y)
, m_oldid(other.m_oldid)
, m_oldname(std::move(other.m_oldname))
, m_oldnote(std::move(other.m_oldnote))
, m_oldpages(std::move(other.m_oldpages))
, m_oldx(other.m_oldx)
, m_oldy(other.m_oldy)
, m_editor(std::move(other.m_editor))
, m_renderer(std::move(other.m_renderer)) {
  if (m_editor) {
    m_editor->setEventPtr(this);
  }
  if (m_renderer) {
    m_renderer->setEventPtr(this);
  }
}

Event& Event::operator=(Event&& other) noexcept {
  IModifiable::operator=(std::move(other));
  m_id = other.m_id;
  m_name = std::move(other.m_name);
  m_note = std::move(other.m_note);
  m_pages = std::move(other.m_pages);
  m_x = other.m_x;
  m_y = other.m_y;
  m_oldid = other.m_oldid;
  m_oldname = std::move(other.m_oldname);
  m_oldnote = std::move(other.m_oldnote);
  m_oldpages = std::move(other.m_oldpages);
  m_oldx = other.m_oldx;
  m_oldy = other.m_oldy;
  m_editor = std::move(other.m_editor);
  m_renderer = std::move(other.m_renderer);
  if (m_editor) {
    m_editor->setEventPtr(this);
  }
  if (m_renderer) {
    m_renderer->setEventPtr(this);
  }
  return *this;
}

int Event::id() const { return m_id; }
void Event::setId(const int id) {
  if (m_id == id) {
    return;
  }
  MODIFIABLE_SET_OLD_VALUE(id);
  m_id = id;
  if (!signalsDisabled()) {
    emit_signal(idModified(), this, id);
  }
  setModified();
}

const std::string& Event::name() const { return m_name; }
void Event::setName(const std::string& name) {
  if (m_name == name) {
    return;
  }
  MODIFIABLE_SET_OLD_VALUE(name);
  m_name = name;
  if (!signalsDisabled()) {
    emit_signal(nameModified(), this, name);
  }
  setModified();
}

const std::string& Event::note() const { return m_note; }
void Event::setNote(const std::string& note) {
  if (m_note == note) {
    return;
  }
  MODIFIABLE_SET_OLD_VALUE(note);
  m_note = note;
  if (!signalsDisabled()) {
    emit_signal(noteModified(), this, note);
  }
  setModified();
}

std::vector<EventPage>& Event::pages() { return m_pages; }
const std::vector<EventPage>& Event::pages() const { return m_pages; }
void Event::setPages(const std::vector<EventPage>& pages) {
  MODIFIABLE_SET_OLD_VALUE(pages);
  m_pages = pages;
  if (!signalsDisabled()) {
    emit_signal(pagesModified(), this, pages);
  }
  setModified();
}

void Event::addPage(const EventPage& page) {
  MODIFIABLE_SET_OLD_VALUE(pages);
  m_pages.emplace_back(page);
  // m_pages.back().modified().connect<&Event::onPageModified>(this);
  // m_pages.back().connectAllSignals();

  if (!signalsDisabled()) {
    emit_signal(pageAdded(), this, &m_pages.back(), m_pages.size() - 1);
  }
  setModified();
}

void Event::insertPage(const EventPage& page, int index) {
  MODIFIABLE_SET_OLD_VALUE(pages);
  m_pages.insert(m_pages.begin() + index, page);
  if (!signalsDisabled()) {
    emit_signal(pageInserted(), this, &m_pages[index], index);
  }
  setModified();
}

void Event::removePage(const int index) {
  if (index < 0 || index >= m_pages.size()) {
    return;
  }
  MODIFIABLE_SET_OLD_VALUE(pages);
  m_pages.erase(m_pages.begin() + index);
  if (!signalsDisabled()) {
    emit_signal(pageRemoved(), this);
  }
  setModified();
}

int Event::x() const { return m_x; }
void Event::setX(const int x) {
  if (m_x == x) {
    return;
  }
  MODIFIABLE_SET_OLD_VALUE(x);
  m_x = x;
  if (!signalsDisabled()) {
    emit_signal(xModified(), this, m_x);
  }
  setModified();
}

int Event::y() const { return m_y; }
void Event::setY(const int y) {
  if (m_y == y) {
    return;
  }
  MODIFIABLE_SET_OLD_VALUE(y);
  m_y = y;
  if (!signalsDisabled()) {
    emit_signal(yModified(), this, m_y);
  }
  setModified();
}

void Event::swapPages(const int a, const int b) {
  if (a < 0 || a >= m_pages.size()) {
    return;
  }
  if (b < 0 || b >= m_pages.size()) {
    return;
  }

  MODIFIABLE_SET_OLD_VALUE(pages);
  std::swap(m_pages.at(a), m_pages.at(b));
  if (!signalsDisabled()) {
    emit_signal(pagesModified(), this, m_pages);
  }
  setModified();
}
std::map<int, bool> Event::isConditionalReference(int targetId, SearchType type) const {
  std::map<int, bool> events;
  int pageIndex{0};
  if (type == SearchType::Variable) {
    for (auto& pages : m_pages) {
      if (pages.conditions().variableValid()) {
        if (pages.conditions().variableId() == targetId) {
          events.emplace(pageIndex, true);
        }
      }
      pageIndex++;
    }
  } else if (type == SearchType::Switch) {
    for (auto& pages : m_pages) {
      if (pages.conditions().switch1Valid() || pages.conditions().switch2Valid()) {
        if (pages.conditions().switch1Id() == targetId || pages.conditions().switch2Id() == targetId) {
          events.emplace(pageIndex, true);
        }
      }
      pageIndex++;
    }
  }
  return events;
}
void Event::setEventReference(int pageId, int targetId, int newId, SearchType type) {

  if (type == SearchType::Variable) {
    if (page(pageId)->conditions().variableValid()) {
      if (targetId == page(pageId)->conditions().variableId()) {
        page(pageId)->conditions().setVariableId(newId);
      }
    }
  }
  if (type == SearchType::Switch) {
    if (page(pageId)->conditions().switch1Valid() || page(pageId)->conditions().switch2Valid()) {
      if (targetId == page(pageId)->conditions().switch1Id()) {
        page(pageId)->conditions().setSwitch1Id(newId);
      }
      if (targetId == page(pageId)->conditions().switch2Id()) {
        page(pageId)->conditions().setSwitch2Id(newId);
      }
    }
  }
}

void Event::restoreOriginal() {
  IModifiable::restoreOriginal();
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(id);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(name);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(note);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(pages);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(x);
  MODIFIABLE_RESTORE_ORIGINAL_VALUE(y);
}

void Event::acceptChanges() {
  IModifiable::acceptChanges();
  MODIFIABLE_ACCEPT_VALUE(id);
  MODIFIABLE_ACCEPT_VALUE(name);
  MODIFIABLE_ACCEPT_VALUE(note);
  MODIFIABLE_ACCEPT_VALUE(pages);
  MODIFIABLE_ACCEPT_VALUE(x);
  MODIFIABLE_ACCEPT_VALUE(y);
}

nlohmann::ordered_json Event::serializeOldValues() const {
  return {
      MODIFIABLE_SERIALIZE_VALUE(id), MODIFIABLE_SERIALIZE_VALUE(name), MODIFIABLE_SERIALIZE_VALUE(note), MODIFIABLE_SERIALIZE_VALUE_CHILD_ARRAY(pages),
      MODIFIABLE_SERIALIZE_VALUE(x),  MODIFIABLE_SERIALIZE_VALUE(y),
  };
}

rpgmutils::signal<void(Event*, int)>& Event::idModified() {
  if (!m_idModified) {
    m_idModified.emplace();
  }
  return *m_idModified;
}
rpgmutils::signal<void(Event*, const std::string&)>& Event::nameModified() {
  if (!m_nameModified) {
    m_nameModified.emplace();
  }
  return *m_nameModified;
}
rpgmutils::signal<void(Event*, const std::string&)>& Event::noteModified() {
  if (!m_noteModified) {
    m_noteModified.emplace();
  }
  return *m_noteModified;
}
rpgmutils::signal<void(Event*, const std::vector<EventPage>&)>& Event::pagesModified() {
  if (!m_pagesModified) {
    m_pagesModified.emplace();
  }
  return *m_pagesModified;
}
rpgmutils::signal<void(Event*, const EventPage*, int)>& Event::pageAdded() {
  if (!m_pageAdded) {
    m_pageAdded.emplace();
  }
  return *m_pageAdded;
}
rpgmutils::signal<void(Event*, const EventPage*, int)>& Event::pageInserted() {
  if (!m_pageInserted) {
    m_pageInserted.emplace();
  }
  return *m_pageInserted;
}
rpgmutils::signal<void(Event*)>& Event::pageRemoved() {
  if (!m_pageRemoved) {
    m_pageRemoved.emplace();
  }
  return *m_pageRemoved;
}
rpgmutils::signal<void(Event*, int)>& Event::xModified() {
  if (!m_xModified) {
    m_xModified.emplace();
  }
  return *m_xModified;
}
rpgmutils::signal<void(Event*, int)>& Event::yModified() {
  if (!m_yModified) {
    m_yModified.emplace();
  }
  return *m_yModified;
}

void Event::connectAllSignals() {
  return;
  for (auto& page : m_pages) {
    // Disconnect just in case
    page.modified().disconnect<&Event::onPageModified>(this);
    page.modified().connect<&Event::onPageModified>(this);
    page.connectAllSignals();
  }
}

void Event::onPageModified(IModifiable* p) { emit_signal(modified(), p); }

void to_json(nlohmann::ordered_json& to, const Event& event) {
  to["id"] = event.m_id;
  to["name"] = event.m_name;
  to["note"] = event.m_note;
  for (const auto& page : event.m_pages) {
    to["pages"].push_back(page.clone());
  }
  to["x"] = event.m_x;
  to["y"] = event.m_y;
}

void from_json(const nlohmann::ordered_json& from, Event& event) {
  event.m_id = from.value("id", event.m_id);
  event.m_name = from.value("name", event.m_name);
  event.m_note = from.value("note", event.m_note);
  event.m_pages = from.value("pages", event.m_pages);
  event.m_x = from.value("x", event.m_x);
  event.m_y = from.value("y", event.m_y);
}


code:
#pragma once

#include "Database/EventPage.hpp"
#include "Database/Globals.hpp"
#include "Database/IEventEditor.hpp"
#include <string>
#include <vector>

class Event;
class IEventRenderer {
public:
  IEventRenderer() = delete;
  virtual ~IEventRenderer() = default;

  virtual void draw(float mapScale, bool isHovered, bool selected, bool halfAlpha, bool updateOnly) = 0;
  virtual void setPage(int page) = 0;
  virtual EventPage* page() const = 0;
  virtual int pageIndex() const = 0;
  virtual double x() const = 0;
  virtual double y() const = 0;
  virtual int screenZ() const = 0;
  virtual uint32_t spriteId() const = 0;
  virtual bool isThrough() const = 0;

  /* Client code must implement this to create their own event renderer */
  static IEventRenderer* create(Event* ev);

protected:
  friend class Event;
  explicit IEventRenderer(Event* event)
  : m_event(event) {}
  virtual void eventPointerInvalidated() = 0;
  void setEventPtr(Event* event) {
    m_event = event;
    eventPointerInvalidated();
  }
  Event* m_event{nullptr};
};

class Event final : public IModifiable {
  friend void to_json(nlohmann::ordered_json& to, const Event& event);
  friend void from_json(const nlohmann::ordered_json& from, Event& event);

public:
  Event() = default;
  Event(const Event& other);
  Event& operator=(const Event& other);
  Event(Event&& other) noexcept;
  Event& operator=(Event&& other) noexcept;
  int id() const;
  void setId(int id);

  const std::string& name() const;
  void setName(const std::string& name);

  const std::string& note() const;
  void setNote(const std::string& note);

  int pageCount() const { return m_pages.size(); }

  std::vector<EventPage>& pages();
  const std::vector<EventPage>& pages() const;
  void setPages(const std::vector<EventPage>& pages);
  EventPage* page(const int page) {
    if (page < 0 || page >= pages().size()) {
      return nullptr;
    }
    return &m_pages[page];
  }

  const EventPage* page(const int page) const {
    if (page < 0 || page >= pages().size()) {
      return nullptr;
    }
    return &m_pages[page];
  }

  void addPage(const EventPage& page);
  void insertPage(const EventPage& page, int index);
  void removePage(int index);

  int x() const;
  void setX(int x);

  int y() const;
  void setY(int y);

  IEventRenderer* renderer() const {
    if (!m_renderer) {
      m_renderer.reset(IEventRenderer::create(const_cast<Event*>(this)));
    }
    return m_renderer.get();
  }
  IEventEditor* editor() const {
    if (!m_editor) {
      m_editor.reset(IEventEditor::create(const_cast<Event*>(this)));
    }
    return m_editor.get();
  }

  void restoreOriginal() override;
  void acceptChanges() override;
  nlohmann::ordered_json serializeOldValues() const override;

  bool isModified() const override {
    return IModifiable::isModified() | std::ranges::any_of(m_pages, [](const auto& page) { return page.isModified(); });
  }

  rpgmutils::signal<void(Event*, int)>& idModified();
  rpgmutils::signal<void(Event*, const std::string&)>& nameModified();
  rpgmutils::signal<void(Event*, const std::string&)>& noteModified();
  rpgmutils::signal<void(Event*, const std::vector<EventPage>&)>& pagesModified();
  rpgmutils::signal<void(Event*, const EventPage*, int)>& pageAdded();
  rpgmutils::signal<void(Event*, const EventPage*, int)>& pageInserted();
  rpgmutils::signal<void(Event*)>& pageRemoved();
  rpgmutils::signal<void(Event*, int)>& xModified();
  rpgmutils::signal<void(Event*, int)>& yModified();

  void swapPages(int a, int b);

  Event clone() const { return Event(*this, 1); }

  bool operator==(const Event& other) const { return m_id == other.m_id && m_name == other.m_name && m_note == other.m_note && m_pages == other.m_pages && m_x == other.m_x && m_y == other.m_y; }
  std::map<int, bool> isConditionalReference(int targetId, SearchType type) const override;
  void setEventReference(int pageId, int targetId, int newId, SearchType type);

  void connectAllSignals();

private:
  Event(const Event& other, int)
  : IModifiable(other)
  , m_id(other.m_id)
  , m_name(other.m_name)
  , m_note(other.m_note)
  , m_x(other.m_x)
  , m_y(other.m_y) {
    /* Clone our pages */
    for (const auto& page : other.m_pages) {
      m_pages.push_back(page.clone());
    }
  }
  int m_id{};
  std::string m_name{};
  std::string m_note{};
  std::vector<EventPage> m_pages{};
  int m_x{};
  int m_y{};

  std::optional<int> m_oldid;
  std::optional<std::string> m_oldname;
  std::optional<std::string> m_oldnote;
  std::optional<std::vector<EventPage>> m_oldpages;
  std::optional<int> m_oldx;
  std::optional<int> m_oldy;

  std::optional<rpgmutils::signal<void(Event*, int)>> m_idModified;
  std::optional<rpgmutils::signal<void(Event*, const std::string&)>> m_nameModified;
  std::optional<rpgmutils::signal<void(Event*, const std::string&)>> m_noteModified;
  std::optional<rpgmutils::signal<void(Event*, const std::vector<EventPage>&)>> m_pagesModified;
  std::optional<rpgmutils::signal<void(Event*, const EventPage*, int)>> m_pageAdded;
  std::optional<rpgmutils::signal<void(Event*, const EventPage*, int)>> m_pageInserted;
  std::optional<rpgmutils::signal<void(Event*)>> m_pageRemoved;
  std::optional<rpgmutils::signal<void(Event*, int)>> m_xModified;
  std::optional<rpgmutils::signal<void(Event*, int)>> m_yModified;
  mutable std::shared_ptr<IEventEditor> m_editor;
  mutable std::shared_ptr<IEventRenderer> m_renderer;

  void onPageModified(IModifiable* p);
};

void to_json(nlohmann::ordered_json& to, const Event& event);
void from_json(const nlohmann::ordered_json& from, Event& event);