New JSON config format

This commit is contained in:
GriffinR
2026-02-06 23:47:06 -05:00
parent e3e42f39f0
commit 3571b4ea4b
56 changed files with 2005 additions and 1883 deletions

22
include/core/basegame.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#ifndef BASEGAMEVERSION_H
#define BASEGAMEVERSION_H
#include <QString>
#include <QIcon>
namespace BaseGame {
enum Version {
none, // TODO: Go through and make sure this is a valid state
pokeruby,
pokefirered,
pokeemerald,
};
Version stringToVersion(const QString &string);
QString versionToString(Version version);
QString getPlayerIconPath(Version version, int character);
QIcon getPlayerIcon(Version version, int character);
};
#endif // BASEGAMEVERSION_H

View File

@@ -26,7 +26,12 @@ public:
static uint16_t getMaxCollision();
static uint16_t getMaxElevation();
static const uint16_t maxValue;
// Upper limit for metatile ID, collision, and elevation masks. Used externally.
static constexpr uint16_t MaxValue = 0xFFFF;
static constexpr uint16_t DefaultMetatileIdMask = 0x03FF;
static constexpr uint16_t DefaultCollisionMask = 0x0C00;
static constexpr uint16_t DefaultElevationMask = 0xF000;
private:
uint16_t m_metatileId;

342
include/core/converter.h Normal file
View File

@@ -0,0 +1,342 @@
#pragma once
#ifndef CONVERTER_H
#define CONVERTER_H
#include <QJsonValue>
#include "magic_enum.hpp"
#include "orderedset.h"
#include "scriptsettings.h"
#include "gridsettings.h"
#include "basegame.h"
/*
These are templates for type conversion to/from JSON,
though other type conversions can be implemented here too.
This is mostly useful when converting the type is complicated,
or when the type is generalized away.
## Example Usage ##
QSize size;
QJsonValue json = Converter<QSize>::toJson(size);
QSize sameSize = Converter<QSize>::fromJson(json);
## Adding a new conversion ##
To add a new type conversion, add a new 'Converter' template:
template <>
struct Converter<NewType> : DefaultConverter<NewType> {
// And re-implement any of the desired conversion functions.
// Any functions not implemented will be inherited from DefaultConverter.
static QJsonValue toJson(const NewType& value) {
// your conversion to JSON
return QJsonValue();
}
static NewType fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
// your conversion from JSON
return NewType();
}
};
Note: When serializing to/from JSON, anything that can be serialized to/from
a string is trivially serializable for JSON. In this case, rather than
inheriting from 'DefaultConverter' and reimplementing 'toJson' and 'fromJson',
you can inherit from 'DefaultStringConverter' and/or reimplement 'toString'/'fromString'.
Appropriately implementing 'toString'/'fromString' has the added benefit that your type
can automatically be used as a JSON key if it for example appears as the key in a QMap.
*/
template <typename T>
struct DefaultConverter {
// Defaults to straightforward QJsonValue construction.
// This handles most of the primitive types.
static QJsonValue toJson(const T& value) {
return QJsonValue{value};
}
static T fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
const QVariant v = json.toVariant();
if (!v.canConvert<T>()) {
if (errors) errors->append(QString("Can't convert from type '%1'").arg(v.typeName()));
// Failed conversion will return a default-constructed object below
}
return v.value<T>();
}
// Default to identity
static QString toString(const T& value) {return value;}
static T fromString(const QString& string, QStringList* = nullptr) {return string;}
};
template <typename T, typename Enable = void>
struct Converter : DefaultConverter<T> {};
// This template implements JSON conversion by first converting the data to/from a string.
// This allows any type that can describe how to stringify itself to automatically also
// support JSON conversion with no additional work.
template <typename T>
struct DefaultStringConverter : DefaultConverter<T> {
static QJsonValue toJson(const T& value) {
return Converter<QString>::toJson(Converter<T>::toString(value));
}
static T fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
const auto string = Converter<QString>::fromJson(json, errors);
return Converter<T>::fromString(string, errors);
}
// Many types have a 'toString' function, so we default to trying that.
static QString toString(const T& value) {
return value.toString();
}
};
template <>
struct Converter<QUrl> : DefaultStringConverter<QUrl> {};
template <>
struct Converter<QKeySequence> : DefaultStringConverter<QKeySequence> {};
template <>
struct Converter<uint32_t> : DefaultConverter<uint32_t> {
// Constructing a QJsonValue from uint32_t is ambiguous, so we need an explicit cast.
static QJsonValue toJson(uint32_t value) {
return QJsonValue{static_cast<qint64>(value)};
}
};
// Template for generic enum values.
// Converts JSON -> string/int -> enum, handling the unsafe conversion if the int is out of range of the enum.
// Qt has a system for this (Q_ENUM) but they don't use it for all their internal enums, so we use magic_enum instead.
template <typename T>
struct Converter<T, std::enable_if_t<std::is_enum_v<T>>> : DefaultStringConverter<T> {
static QString toString(const T& value) {
const std::string s = std::string(magic_enum::enum_name(value));
return QString::fromStdString(s);
}
static T fromString(const QString& string, QStringList* errors = nullptr) {
auto e = magic_enum::enum_cast<T>(string.toStdString(), magic_enum::case_insensitive);
if (!e.has_value()) {
if (errors) errors->append(QString("'%1' is not a named enum value.").arg(string));
return magic_enum::enum_value<T>(0);
}
return e.value();
}
// When reading from JSON, handle either the named enum or an enum's number value.
static T fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
if (json.isString()) return Converter<T>::fromString(json.toString());
auto value = Converter<int>::fromJson(json, errors);
auto e = magic_enum::enum_cast<T>(value);
if (!e.has_value()) {
if (errors) errors->append(QString("'%1' is out of range of enum.").arg(QString::number(value)));
return magic_enum::enum_value<T>(0);
}
return e.value();
}
};
template <>
struct Converter<QVersionNumber> : DefaultStringConverter<QVersionNumber> {
static QVersionNumber fromString(const QString& string, QStringList* = nullptr) {
return QVersionNumber::fromString(string);
}
};
template <>
struct Converter<QDateTime> : DefaultStringConverter<QDateTime> {
static QString toString(const QDateTime& value) {
return value.toUTC().toString();
}
static QDateTime fromString(const QString& string, QStringList* = nullptr) {
return QDateTime::fromString(string).toLocalTime();
}
};
template <>
struct Converter<QColor> : DefaultStringConverter<QColor> {
static QString toString(const QColor& value) {
return value.name();
}
static QColor fromString(const QString& string, QStringList* errors = nullptr) {
const QColor color(string);
if (!color.isValid()) {
if (errors) errors->append(QString("'%1' is not a valid color.").arg(string));
return QColorConstants::Black;
}
return color;
}
};
template <>
struct Converter<QFont> : DefaultStringConverter<QFont> {
static QFont fromString(const QString& string, QStringList* errors = nullptr) {
QFont font;
if (!font.fromString(string) && errors) {
errors->append(QString("'%1' is not a valid font description.").arg(string));
}
return font;
}
};
template <>
struct Converter<BaseGame::Version> : DefaultStringConverter<BaseGame::Version> {
static QString toString(const BaseGame::Version& value) {
return BaseGame::versionToString(value);
}
static BaseGame::Version fromString(const QString& string, QStringList* = nullptr) {
return BaseGame::stringToVersion(string);
}
};
template <typename T>
struct Converter<QList<T>> : DefaultConverter<QList<T>> {
static QJsonValue toJson(const QList<T>& list) {
QJsonArray arr;
for (auto& elem : list) arr.append(Converter<T>::toJson(elem));
return arr;
}
static QList<T> fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
const auto arr = Converter<QJsonArray>::fromJson(json, errors);
QList<T> list;
for (auto& elem : arr) list.append(Converter<T>::fromJson(elem, errors));
return list;
}
};
template <typename K, typename V>
struct Converter<QMap<K,V>> : DefaultConverter<QMap<K,V>> {
static QJsonObject toJson(const QMap<K,V>& map) {
QJsonObject obj;
for (auto it = map.begin(); it != map.end(); it++) {
const QString key = Converter<K>::toString(it.key());
obj[key] = Converter<V>::toJson(it.value());
}
return obj;
}
static QMap<K,V> fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
const auto obj = Converter<QJsonObject>::fromJson(json, errors);
QMap<K,V> map;
for (auto it = obj.begin(); it != obj.end(); it++) {
const auto key = Converter<K>::fromString(it.key(), errors);
map.insert(key, Converter<V>::fromJson(it.value(), errors));
}
return map;
}
};
template <typename K, typename V>
struct Converter<QMultiMap<K,V>> : DefaultConverter<QMultiMap<K,V>> {
static QJsonObject toJson(const QMultiMap<K,V>& map) {
QJsonObject obj;
for (const auto& uniqueKey : map.uniqueKeys()) {
const QString key = Converter<K>::toString(uniqueKey);
obj[key] = Converter<QList<V>>::toJson(map.values(uniqueKey));
}
return obj;
}
static QMultiMap<K,V> fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
const auto obj = Converter<QJsonObject>::fromJson(json, errors);
QMultiMap<K,V> map;
for (auto it = obj.begin(); it != obj.end(); it++) {
const auto key = Converter<K>::fromString(it.key(), errors);
const auto values = Converter<QList<V>>::fromJson(it.value(), errors);
for (const auto& value : values) map.insert(key, value);
}
return map;
}
};
template <typename T>
struct Converter<OrderedSet<T>> : DefaultConverter<OrderedSet<T>> {
static QJsonValue toJson(const OrderedSet<T>& set) {
QJsonArray arr;
for (auto& elem : set) arr.append(Converter<T>::toJson(elem));
return arr;
}
static OrderedSet<T> fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
const auto arr = Converter<QJsonArray>::fromJson(json, errors);
OrderedSet<T> set;
for (auto& elem : arr) set.insert(Converter<T>::fromJson(elem, errors));
return set;
}
};
template <>
struct Converter<QSize> : DefaultConverter<QSize> {
static QJsonValue toJson(const QSize& value) {
QJsonObject obj;
obj["width"] = value.width();
obj["height"] = value.height();
return obj;
}
static QSize fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
const auto obj = Converter<QJsonObject>::fromJson(json, errors);
QSize size;
size.setWidth(obj.value("width").toInt());
size.setHeight(obj.value("height").toInt());
return size;
}
};
template <>
struct Converter<QMargins> : DefaultConverter<QMargins> {
static QJsonValue toJson(const QMargins& value) {
QJsonObject obj;
obj["top"] = value.top();
obj["bottom"] = value.bottom();
obj["left"] = value.left();
obj["right"] = value.right();
return obj;
}
static QMargins fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
const auto obj = Converter<QJsonObject>::fromJson(json, errors);
QMargins margins;
margins.setTop(obj.value("top").toInt());
margins.setBottom(obj.value("bottom").toInt());
margins.setLeft(obj.value("left").toInt());
margins.setRight(obj.value("right").toInt());
return margins;
}
};
template <>
struct Converter<ScriptSettings> : DefaultConverter<ScriptSettings> {
static QJsonValue toJson(const ScriptSettings& value) {
QJsonObject obj;
obj["path"] = value.path;
obj["enabled"] = value.enabled;
return obj;
}
static ScriptSettings fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
const auto obj = Converter<QJsonObject>::fromJson(json, errors);
ScriptSettings settings;
settings.path = obj.value("path").toString();
settings.enabled = obj.value("enabled").toBool();
return settings;
}
};
template <>
struct Converter<GridSettings> : DefaultConverter<GridSettings> {
static QJsonValue toJson(const GridSettings& value) {
return value.toJson();
}
static GridSettings fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
const auto obj = Converter<QJsonObject>::fromJson(json, errors);
return GridSettings::fromJson(obj);
}
};
template <>
struct Converter<QByteArray> : DefaultConverter<QByteArray> {
static QJsonValue toJson(const QByteArray& value) {
return QString::fromLocal8Bit(value.toBase64());
}
static QByteArray fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
const auto s = Converter<QString>::fromJson(json, errors);
return QByteArray::fromBase64(s.toLocal8Bit());
}
};
#endif // CONVERTER_H

View File

@@ -8,6 +8,7 @@
#include <QPixmap>
#include <QString>
#include <QUndoStack>
#include <QJsonObject>
class Map;
class LayoutPixmapItem;

View File

@@ -3,11 +3,13 @@
#define METATILE_H
#include "tile.h"
#include "config.h"
#include "basegame.h"
#include "bitpacker.h"
#include <QImage>
#include <QPoint>
#include <QString>
#include <QMap>
#include <QSet>
class Project;
@@ -41,7 +43,7 @@ public:
uint32_t getAttributes() const;
uint32_t getAttribute(Metatile::Attr attr) const { return this->attributes.value(attr, 0); }
void setAttributes(uint32_t data);
void setAttributes(uint32_t data, BaseGameVersion version);
void setAttributes(uint32_t data, BaseGame::Version version);
void setAttribute(Metatile::Attr attr, uint32_t value);
// For convenience
@@ -56,17 +58,18 @@ public:
static int getIndexInTileset(int);
static QPoint coordFromPixmapCoord(const QPointF &pixelCoord);
static uint32_t getDefaultAttributesMask(BaseGameVersion version, Metatile::Attr attr);
static uint32_t getDefaultAttributesMask(BaseGame::Version version, Metatile::Attr attr);
static uint32_t getMaxAttributesMask();
static int getDefaultAttributesSize(BaseGameVersion version);
static int getDefaultAttributesSize(BaseGame::Version version);
static void setLayout(Project*);
static QString getMetatileIdString(uint16_t metatileId);
static QString getMetatileIdStrings(const QList<uint16_t> &metatileIds);
static QString getLayerName(int layerNum);
static int numLayers();
static constexpr int tileWidth() { return 2; }
static constexpr int tileHeight() { return 2; }
static constexpr int tilesPerLayer() { return Metatile::tileWidth() * Metatile::tileHeight(); }
static int maxTiles() { return Metatile::numLayers() * Metatile::tilesPerLayer(); }
static constexpr int pixelWidth() { return Metatile::tileWidth() * Tile::pixelWidth(); }
static constexpr int pixelHeight() { return Metatile::tileHeight() * Tile::pixelHeight(); }
static constexpr QSize pixelSize() { return QSize(pixelWidth(), pixelHeight()); }

30
include/core/orderedset.h Normal file
View File

@@ -0,0 +1,30 @@
#pragma once
#ifndef ORDERED_SET_H
#define ORDERED_SET_H
#include <QJsonArray>
template <typename T>
class OrderedSet : public std::set<T>
{
using std::set<T>::set;
public:
// Not introduced to std::set until C++20
#if __cplusplus < 202002L
bool contains(const T& value) const {
return this->find(value) != this->end();
}
#endif
QSet<T> toQSet() const {
return QSet<T>(this->begin(), this->end());
}
static QSet<T> fromQSet(const QSet<T>& set) {
return OrderedSet<T>(set.begin(), set.end());
}
bool isEmpty() const {
return this->empty();
}
};
#endif // ORDERED_SET_H

View File

@@ -5,6 +5,7 @@
#include "map.h"
#include "tilemaptileselector.h"
#include "history.h"
#include "config.h"
#include <QStringList>
#include <QString>

View File

@@ -0,0 +1,27 @@
#pragma once
#ifndef SCRIPTSETTINGS_H
#define SCRIPTSETTINGS_H
#include <QString>
#include <QList>
// Holds the basic user-provided information about a plug-in script.
struct ScriptSettings {
QString path;
bool enabled = true;
// Scripts can either by specific to the project, or specific to the user.
// This allows projects to send scripts downstream to their users,
// while still allowing them to use their own personal scripts.
bool userOnly = true;
static QStringList filter(const QList<ScriptSettings>& scripts) {
QStringList paths;
for (auto& script : scripts) {
if (script.enabled) paths.append(script.path);
}
return paths;
}
};
#endif // SCRIPTSETTINGS_H

View File

@@ -5,9 +5,6 @@
#include <QObject>
#include <QSize>
// TODO: Replace once config refactoring is complete.
extern bool ConfigDisplayIdsHexadecimal;
class Tile
{
public:
@@ -30,7 +27,8 @@ public:
QString toString() const;
static QString getTileIdString(uint16_t tileId);
static const uint16_t maxValue;
// Upper limit for raw value (i.e., uint16_t max).
static constexpr uint16_t MaxValue = 0xFFFF;
static constexpr int pixelWidth() { return 8; }
static constexpr int pixelHeight() { return 8; }

View File

@@ -21,6 +21,19 @@ namespace Util {
QColorSpace toColorSpace(int colorSpaceInt);
QString mkpath(const QString& dirPath);
QString getFileHash(const QString &filepath);
// Given a QMap<T,QString>, erases all entries with empty strings.
// Returns the number of entries erased.
template <typename T>
int removeEmptyStrings(QMap<T,QString> *map) {
if (!map) return 0;
int numRemoved = 0;
for (auto it = map->begin(); it != map->end();) {
if (it.value().isEmpty()) it = map->erase(it);
else {it++; numRemoved++;}
}
return numRemoved;
}
}
#endif // UTILITY_H