mirror of
https://github.com/huderlem/porymap.git
synced 2026-08-28 19:46:14 -05:00
New JSON config format
This commit is contained in:
22
include/core/basegame.h
Normal file
22
include/core/basegame.h
Normal 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
|
||||
@@ -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
342
include/core/converter.h
Normal 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
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <QPixmap>
|
||||
#include <QString>
|
||||
#include <QUndoStack>
|
||||
#include <QJsonObject>
|
||||
|
||||
class Map;
|
||||
class LayoutPixmapItem;
|
||||
|
||||
@@ -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
30
include/core/orderedset.h
Normal 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
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "map.h"
|
||||
#include "tilemaptileselector.h"
|
||||
#include "history.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <QStringList>
|
||||
#include <QString>
|
||||
|
||||
27
include/core/scriptsettings.h
Normal file
27
include/core/scriptsettings.h
Normal 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
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user