diff --git a/src/plugins/yy/jsonwriter.cpp b/src/plugins/yy/jsonwriter.cpp index ce417d36d6..6be4efafaa 100644 --- a/src/plugins/yy/jsonwriter.cpp +++ b/src/plugins/yy/jsonwriter.cpp @@ -59,13 +59,16 @@ void JsonWriter::writeStartScope(Scope scope, const char *name) m_valueWritten = false; } -void JsonWriter::writeEndScope(Scope scope) +void JsonWriter::writeEndScope(Scope scope, bool forceNewLine) { Q_ASSERT(m_scopes.last() == scope); m_scopes.pop(); if (m_valueWritten) { write(m_valueSeparator); // This is not JSON-conform, but it's what GameMaker does - writeNewline(); + + // GameMaker minimization logic + if (m_scopes.size() < 2 || forceNewLine) + writeNewline(forceNewLine); } write(scope == Object ? '}' : ']'); m_newLine = false; @@ -74,17 +77,10 @@ void JsonWriter::writeEndScope(Scope scope) void JsonWriter::writeValue(double value) { - if (qIsFinite(value)) { - // Force at least one decimal to avoid double values from being written - // as integer, which may confuse GameMaker. - if (std::ceil(value) == value) { - writeUnquotedValue(QByteArray::number(value, 'f', 1)); - } else { - writeUnquotedValue(QByteArray::number(value, 'g', QLocale::FloatingPointShortest)); - } - } else { + if (qIsFinite(value)) + writeUnquotedValue(QByteArray::number(value, 'g', QLocale::FloatingPointShortest)); + else writeUnquotedValue("null"); // +INF || -INF || NaN (see RFC4627#section2.4) - } } void JsonWriter::writeValue(const QByteArray &value) @@ -114,20 +110,51 @@ void JsonWriter::writeValue(const QJsonValue &value) writeValue(value.toString()); break; case QJsonValue::Array: { - writeStartArray(); const QJsonArray array = value.toArray(); + + bool arrayContainedObject = false; + qsizetype index = 0; + + writeStartArray(); for (auto v : array) { - prepareNewLine(); + arrayContainedObject |= v.isObject(); + + if (m_tileSerialiseWidth > 0) { + // force new line when starting a new row of tiles + prepareNewLine(index % m_tileSerialiseWidth == 0); + } else { + // force new line if value is an object + prepareNewLine(v.isObject()); + } + writeValue(v); + ++index; } - writeEndArray(); + writeEndArray(arrayContainedObject || m_tileSerialiseWidth > 0); break; } case QJsonValue::Object: { - writeStartObject(); const QJsonObject object = value.toObject(); - for (auto it = object.begin(); it != object.end(); ++it) - writeMember(it.key().toLatin1().constData(), it.value()); + + // GameMaker 2024 requires the keys to be sorted case-insensitively + auto keys = object.keys(); + keys.sort(Qt::CaseInsensitive); + + writeStartObject(); + for (const auto &key : keys) { + const auto value = object.value(key); + + const bool writingTiles = key == QLatin1String("tiles"); + if (writingTiles) { + const auto tilesObject = value.toObject(); + m_tileSerialiseWidth = tilesObject.value(QLatin1String("SerialiseWidth")).toInt(); + } + + writeMember(key.toLatin1().constData(), value); + + if (writingTiles) + m_tileSerialiseWidth = 0; + } writeEndObject(); break; } @@ -215,13 +242,13 @@ QString JsonWriter::quote(const QString &str) return quoted; } -void JsonWriter::prepareNewLine() +void JsonWriter::prepareNewLine(bool forceNewLine) { if (m_valueWritten) { write(m_valueSeparator); m_valueWritten = false; } - writeNewline(); + writeNewline(forceNewLine); } void JsonWriter::prepareNewValue() @@ -238,10 +265,10 @@ void JsonWriter::writeIndent() write(" "); } -void JsonWriter::writeNewline() +void JsonWriter::writeNewline(bool force) { if (!m_newLine) { - if (!m_minimize && !m_suppressNewlines) { + if (force || (!m_minimize && !m_suppressNewlines && m_scopes.size() < 3)) { write('\n'); writeIndent(); } @@ -255,7 +282,7 @@ void JsonWriter::writeKey(const char *key) prepareNewLine(); write('"'); write(key); - write(m_minimize ? "\":" : "\": "); + write("\":"); } void JsonWriter::write(const char *bytes, qint64 length) diff --git a/src/plugins/yy/jsonwriter.h b/src/plugins/yy/jsonwriter.h index a41abe1b75..d0db7fb95e 100644 --- a/src/plugins/yy/jsonwriter.h +++ b/src/plugins/yy/jsonwriter.h @@ -53,7 +53,7 @@ class JsonWriter void writeStartArray() { writeStartScope(Array); } void writeStartArray(const char *name) { writeStartScope(Array, name); } - void writeEndArray() { writeEndScope(Array); } + void writeEndArray(bool forceNewLine = false) { writeEndScope(Array, forceNewLine); } void writeValue(int value); void writeValue(unsigned value); @@ -83,7 +83,7 @@ class JsonWriter void setMinimize(bool minimize); bool minimize() const; - void prepareNewLine(); + void prepareNewLine(bool forceNewLine = false); bool hasError() const { return m_error; } @@ -92,12 +92,12 @@ class JsonWriter private: void writeStartScope(Scope scope); void writeStartScope(Scope scope, const char *name); - void writeEndScope(Scope scope); + void writeEndScope(Scope scope, bool forceNewLine = false); void prepareNewValue(); void writeIndent(); - void writeNewline(); + void writeNewline(bool force = false); void writeKey(const char *key); void write(const char *bytes, qint64 length); void write(const char *bytes); @@ -113,6 +113,7 @@ class JsonWriter bool m_newLine { true }; bool m_valueWritten { false }; bool m_error { false }; + int m_tileSerialiseWidth { 0 }; }; inline void JsonWriter::writeValue(int value) diff --git a/src/plugins/yy/yyplugin.cpp b/src/plugins/yy/yyplugin.cpp index ab32007fbf..02b317c76d 100644 --- a/src/plugins/yy/yyplugin.cpp +++ b/src/plugins/yy/yyplugin.cpp @@ -91,6 +91,7 @@ enum ResourceType GMRPathLayerType, GMRSpriteGraphicType, GMRTileLayerType, + GMRoomType, }; static const char *resourceTypeStr(ResourceType type) @@ -107,47 +108,137 @@ static const char *resourceTypeStr(ResourceType type) case GMRPathLayerType: return "GMRPathLayer"; case GMRSpriteGraphicType: return "GMRSpriteGraphic"; case GMRTileLayerType: return "GMRTileLayer"; + case GMRoomType: return "GMRoom"; } return "Unknown"; } +static const char *resourceTypeTagValue(ResourceType type) +{ + switch (type) { + case GMOverriddenPropertyType: return "v1"; + case GMPathType: return ""; + case GMRAssetLayerType: return ""; + case GMRBackgroundLayerType: return ""; + case GMRGraphicType: return ""; + case GMRInstanceLayerType: return ""; + case GMRInstanceType: return "v1"; + case GMRLayerType: return ""; + case GMRPathLayerType: return ""; + case GMRSpriteGraphicType: return ""; + case GMRTileLayerType: return ""; + case GMRoomType: return "v1"; + } + + return ""; +} + +static QJsonValue idValue(const QString &id, const QString &scope) +{ + if (id.isEmpty()) + return QJsonValue(QJsonValue::Null); + + return QJsonObject { + { "name", id }, + { "path", QStringLiteral("%1/%2/%2.yy").arg(scope, id) } + }; +} + +static double colorToAbgr(const QColor &color) +{ + const QRgb rgba = color.rgba(); + return ((qAlpha(rgba) & 0xffu) << 24) | + ((qBlue(rgba) & 0xffu) << 16) | + ((qGreen(rgba) & 0xffu) << 8) | + (qRed(rgba) & 0xffu); +} + + struct GMRView { bool inherit = false; bool visible = false; int xview = 0; int yview = 0; - int wview = 1366; + int wview = 1024; int hview = 768; int xport = 0; int yport = 0; - int wport = 1366; + int wport = 1024; int hport = 768; int hborder = 32; int vborder = 32; int hspeed = -1; int vspeed = -1; QString objectId; + + QJsonObject toJson() const; }; +QJsonObject GMRView::toJson() const +{ + return { + { "inherit", inherit }, + { "visible", visible }, + { "xview", xview }, + { "yview", yview }, + { "wview", wview }, + { "hview", hview }, + { "xport", xport }, + { "yport", yport }, + { "wport", wport }, + { "hport", hport }, + { "hborder", hborder }, + { "vborder", vborder }, + { "hspeed", hspeed }, + { "vspeed", vspeed }, + { "objectId", idValue(objectId, QStringLiteral("objects")) } + }; +} + + struct GMResource { GMResource(ResourceType type) : resourceType(type) {} virtual ~GMResource() = default; - QString resourceVersion = QStringLiteral("1.0"); + virtual QJsonObject toJson() const; + + QString resourceVersion = QStringLiteral("2.0"); QString name; QStringList tags; ResourceType resourceType; }; +QJsonObject GMResource::toJson() const +{ + QJsonObject json; + + const char *type = resourceTypeStr(resourceType); + + json[QByteArray("$") + type] = resourceTypeTagValue(resourceType); + json["%Name"] = name; + json["resourceVersion"] = resourceVersion; + json["name"] = name; + + if (!tags.isEmpty()) + json["tags"] = QJsonArray::fromStringList(tags); + + json["resourceType"] = type; + + return json; +} + + struct GMRGraphic final : GMResource { GMRGraphic(bool isSprite) : GMResource(isSprite ? GMRSpriteGraphicType : GMRGraphicType) {} + QJsonObject toJson() const override; + QString spriteId; union { @@ -181,19 +272,80 @@ struct GMRGraphic final : GMResource double y = 0.0; }; +QJsonObject GMRGraphic::toJson() const +{ + QJsonObject json = GMResource::toJson(); + + json["spriteId"] = idValue(spriteId, QStringLiteral("sprites")); + + if (resourceType == GMRSpriteGraphicType) { + json["headPosition"] = headPosition; + json["rotation"] = rotation; + json["scaleX"] = scaleX; + json["scaleY"] = scaleY; + json["animationSpeed"] = animationSpeed; + } else { + json["w"] = w; + json["h"] = h; + json["u0"] = u0; + json["v0"] = v0; + json["u1"] = u1; + json["v1"] = v1; + } + + json["colour"] = colorToAbgr(colour); + + if (inheritedItemId.isEmpty()) { + json["inheritedItemId"] = QJsonValue(QJsonValue::Null); + } else { + json["inheritedItemId"] = QJsonObject { + { "name", inheritedItemId }, + { "path", inheritedItemPath } + }; + } + + json["frozen"] = frozen; + json["ignore"] = ignore; + json["inheritItemSettings"] = inheritItemSettings; + json["x"] = x; + json["y"] = y; + + return json; +} + + struct GMOverriddenProperty final : GMResource { GMOverriddenProperty() : GMResource(GMOverriddenPropertyType) {} + QJsonObject toJson() const override; + QString propertyId; QString objectId; QString value; }; +QJsonObject GMOverriddenProperty::toJson() const +{ + QJsonObject json = GMResource::toJson(); + + json["propertyId"] = QJsonObject { + { "name", propertyId }, + { "path", QStringLiteral("%1/%2/%2.yy").arg(QStringLiteral("objects"), objectId) } + }; + json["objectId"] = idValue(objectId, QStringLiteral("objects")); + json["value"] = value; + + return json; +} + + struct GMRInstance final : GMResource { GMRInstance() : GMResource(GMRInstanceType) {} + QJsonObject toJson() const override; + std::vector properties; bool isDnd = false; QString objectId; @@ -214,20 +366,94 @@ struct GMRInstance final : GMResource double y = 0.0; }; +QJsonObject GMRInstance::toJson() const +{ + QJsonObject json = GMResource::toJson(); + + QJsonArray propertiesJson; + for (const GMOverriddenProperty &prop : properties) + propertiesJson.append(prop.toJson()); + json["properties"] = propertiesJson; + + json["isDnd"] = isDnd; + + json["objectId"] = idValue(objectId, QStringLiteral("objects")); + + json["inheritCode"] = inheritCode; + json["hasCreationCode"] = hasCreationCode; + json["colour"] = colorToAbgr(colour); + json["rotation"] = rotation; + json["scaleX"] = scaleX; + json["scaleY"] = scaleY; + json["imageIndex"] = imageIndex; + json["imageSpeed"] = imageSpeed; + + if (inheritedItemId.isEmpty()) { + json["inheritedItemId"] = QJsonValue(QJsonValue::Null); + } else { + json["inheritedItemId"] = QJsonObject { + { "name", inheritedItemId }, + { "path", inheritedItemPath } + }; + } + + json["frozen"] = frozen; + json["ignore"] = ignore; + json["inheritItemSettings"] = inheritItemSettings; + json["x"] = x; + json["y"] = y; + + return json; +} + + struct GMPath final : GMResource { GMPath() : GMResource(GMPathType) {} + QJsonObject toJson() const override; + int kind = 0; bool closed = false; int precision = 4; QVector points; }; +QJsonObject GMPath::toJson() const +{ + QJsonObject json = GMResource::toJson(); + + json["kind"] = kind; + json["closed"] = closed; + json["precision"] = precision; + + // todo: + // "parent":{ + // "name":"Rooms", + // "path":"folders/Rooms.yy", + // }, + + QJsonArray pointsJson; + for (const QPointF &point : points) { + pointsJson.append(QJsonObject { + { "speed", 100.0 }, + { "x", point.x() }, + { "y", point.y() } + }); + } + + json["points"] = pointsJson; + + return json; +} + + struct GMRLayer : GMResource { GMRLayer(ResourceType type = GMRLayerType) : GMResource(type) {} + QJsonObject toJson() const override; + bool visible = true; int depth = 0; bool userdefinedDepth = false; @@ -239,10 +465,40 @@ struct GMRLayer : GMResource bool hierarchyFrozen = false; }; +QJsonObject GMRLayer::toJson() const +{ + QJsonObject json = GMResource::toJson(); + + json["visible"] = visible; + json["depth"] = depth; + json["userdefinedDepth"] = userdefinedDepth; + json["inheritLayerDepth"] = inheritLayerDepth; + json["inheritLayerSettings"] = inheritLayerSettings; + json["inheritSubLayers"] = true; + json["inheritVisibility"] = true; + json["gridX"] = gridX; + json["gridY"] = gridY; + json["effectEnabled"] = true; + json["effectType"] = QJsonValue(QJsonValue::Null); + + QJsonArray layersJson; + for (const std::unique_ptr &layer : layers) + layersJson.append(layer->toJson()); + + json["layers"] = layersJson; + json["hierarchyFrozen"] = hierarchyFrozen; + json["properties"] = QJsonArray(); + + return json; +} + + struct GMRTileLayer final : GMRLayer { GMRTileLayer() : GMRLayer(GMRTileLayerType) {} + QJsonObject toJson() const override; + QString tilesetId; int x = 0; int y = 0; @@ -251,32 +507,101 @@ struct GMRTileLayer final : GMRLayer std::vector tiles; }; +QJsonObject GMRTileLayer::toJson() const +{ + QJsonObject json = GMRLayer::toJson(); + + json["tilesetId"] = idValue(tilesetId, QStringLiteral("tilesets")); + json["x"] = x; + json["y"] = y; + + QJsonArray tilesJson; + for (size_t index = 0; index < tiles.size(); ++index) + tilesJson.append((double) tiles.at(index)); + + json["tiles"] = QJsonObject { + { "SerialiseWidth", SerialiseWidth }, + { "SerialiseHeight", SerialiseHeight }, + { "TileSerialiseData", tilesJson } + }; + + return json; +} + + struct GMRAssetLayer final : GMRLayer { GMRAssetLayer() : GMRLayer(GMRAssetLayerType) {} + QJsonObject toJson() const override; + std::vector assets; }; +QJsonObject GMRAssetLayer::toJson() const +{ + QJsonObject json = GMRLayer::toJson(); + + QJsonArray assetsJson; + for (const auto &asset : assets) + assetsJson.append(asset.toJson()); + + json["assets"] = assetsJson; + + return json; +} + + struct GMRInstanceLayer final : GMRLayer { GMRInstanceLayer() : GMRLayer(GMRInstanceLayerType) {} + QJsonObject toJson() const override; + std::vector instances; }; +QJsonObject GMRInstanceLayer::toJson() const +{ + QJsonObject json = GMRLayer::toJson(); + + QJsonArray instancesJson; + for (const auto &instance : instances) + instancesJson.append(instance.toJson()); + + json["instances"] = instancesJson; + + return json; +} + + struct GMRPathLayer final : GMRLayer { GMRPathLayer() : GMRLayer(GMRPathLayerType) {} + QJsonObject toJson() const; + QString pathId; QColor colour = Qt::red; }; +QJsonObject GMRPathLayer::toJson() const +{ + QJsonObject json = GMRLayer::toJson(); + + json["pathId"] = idValue(pathId, QStringLiteral("paths")); + json["colour"] = colorToAbgr(colour); + + return json; +} + + struct GMRBackgroundLayer final : GMRLayer { GMRBackgroundLayer() : GMRLayer(GMRBackgroundLayerType) {} + QJsonObject toJson() const; + QString spriteId; QColor colour = Qt::white; int x = 0; @@ -291,6 +616,27 @@ struct GMRBackgroundLayer final : GMRLayer bool userdefinedAnimFPS = false; }; +QJsonObject GMRBackgroundLayer::toJson() const +{ + QJsonObject json = GMRLayer::toJson(); + + json["spriteId"] = idValue(spriteId, QStringLiteral("sprites")); + json["colour"] = colorToAbgr(colour); + json["x"] = x; + json["y"] = y; + json["htiled"] = htiled; + json["vtiled"] = vtiled; + json["hspeed"] = hspeed; + json["vspeed"] = vspeed; + json["stretch"] = stretch; + json["animationFPS"] = animationFPS; + json["animationSpeedType"] = animationSpeedType; + json["userdefinedAnimFPS"] = userdefinedAnimFPS; + + return json; +} + + struct InstanceCreation { QString name; @@ -300,11 +646,120 @@ struct InstanceCreation { return creationOrder < other.creationOrder; } }; -struct Context + +struct GMRoom final : GMResource { + GMRoom() : GMResource(GMRoomType) {} + + QJsonObject toJson() const override; + + bool isDnd = false; + double volume = 1.0; std::vector views; - std::vector paths; + std::vector> layers; + bool inheritLayers = false; + QString creationCodeFile; + bool inheritCode = false; std::vector instanceCreationOrder; + bool inheritCreationOrder = false; + + struct { + bool inheritRoomSettings = false; + int Width = 0; + int Height = 0; + bool persistent = false; + } roomSettings; + + struct { + bool inheritViewSettings = false; + bool enableViews = false; + bool clearViewBackground = false; + bool clearDisplayBuffer = false; + } viewSettings; + + struct { + bool inheritPhysicsSettings = false; + bool PhysicsWorld = false; + double PhysicsWorldGravityX = 0.0; + double PhysicsWorldGravityY = 10.0; + double PhysicsWorldPixToMetres = 0.1; + } physicsSettings; + + QString parent = QStringLiteral("Rooms"); + QString roomPathInProject; +}; + +QJsonObject GMRoom::toJson() const +{ + QJsonObject json = GMResource::toJson(); + + json["isDnd"] = isDnd; + json["volume"] = volume; + json["parentRoom"] = QJsonValue(QJsonValue::Null); // TODO: Provide a way to set this? + + QJsonArray viewsJson; + for (const GMRView &view : views) + viewsJson.append(view.toJson()); + + json["views"] = viewsJson; + + QJsonArray layersJson; + for (const auto &layer : layers) + layersJson.append(layer->toJson()); + + json["layers"] = layersJson; + + json["inheritLayers"] = inheritLayers; + json["creationCodeFile"] = creationCodeFile; + json["inheritCode"] = inheritCode; + + QJsonArray instanceCreationOrderJson; + for (const InstanceCreation &creation : instanceCreationOrder) { + instanceCreationOrderJson.append(QJsonObject { + { "name", creation.name }, + { "path", roomPathInProject } + }); + } + + json["instanceCreationOrder"] = instanceCreationOrderJson; + json["inheritCreationOrder"] = inheritCreationOrder; + json["sequenceId"] = QJsonValue(QJsonValue::Null); + + json["roomSettings"] = QJsonObject { + { "inheritRoomSettings", roomSettings.inheritRoomSettings }, + { "Width", roomSettings.Width }, + { "Height", roomSettings.Height }, + { "persistent", roomSettings.persistent } + }; + + json["viewSettings"] = QJsonObject { + { "inheritViewSettings", viewSettings.inheritViewSettings }, + { "enableViews", viewSettings.enableViews }, + { "clearViewBackground", viewSettings.clearViewBackground }, + { "clearDisplayBuffer", viewSettings.clearDisplayBuffer } + }; + + json["physicsSettings"] = QJsonObject { + { "inheritPhysicsSettings", physicsSettings.inheritPhysicsSettings }, + { "PhysicsWorld", physicsSettings.PhysicsWorld }, + { "PhysicsWorldGravityX", physicsSettings.PhysicsWorldGravityX }, + { "PhysicsWorldGravityY", physicsSettings.PhysicsWorldGravityY }, + { "PhysicsWorldPixToMetres", physicsSettings.PhysicsWorldPixToMetres } + }; + + json["parent"] = QJsonObject { + { "name", QFileInfo(parent).fileName() }, + { "path", QStringLiteral("folders/%1.yy").arg(parent) } + }; + + return json; +} + + +struct Context +{ + GMRoom room; + std::vector paths; std::unique_ptr renderer; ExportContext exportContext; @@ -354,37 +809,25 @@ struct Context } // namespace Yy template -static T optionalProperty(const Object *object, const QString &name, const T &def) +static void readProperty(const Object *object, const QString &name, T &out) { const QVariant var = object->resolvedProperty(name); - return var.isValid() ? var.value() : def; + if (var.isValid()) + out = var.value(); } template -static T takeProperty(Properties &properties, const QString &name, const T &def) +static T optionalProperty(const Object *object, const QString &name, const T &def) { - const QVariant var = properties.take(name); + const QVariant var = object->resolvedProperty(name); return var.isValid() ? var.value() : def; } template -static void writeProperty(JsonWriter &json, - const Object *object, - const QString &propertyName, - const char *memberName, - const T &def) -{ - const T value = optionalProperty(object, propertyName, def); - json.writeMember(memberName, value); -} - -template -static void writeProperty(JsonWriter &json, - const Object *object, - const char *name, - const T &def) +static T takeProperty(Properties &properties, const QString &name, const T &def) { - writeProperty(json, object, QString::fromLatin1(name), name, def); + const QVariant var = properties.take(name); + return var.isValid() ? var.value() : def; } static QStringList readTags(const Object *object) @@ -398,36 +841,6 @@ static QStringList readTags(const Object *object) return tagList; } -static void writeTags(JsonWriter &json, const QStringList &tags) -{ - json.writeMember("tags", QJsonArray::fromStringList(tags)); -} - -static void writeTags(JsonWriter &json, const Object *object) -{ - writeTags(json, readTags(object)); -} - -static void writeResourceProperties(JsonWriter &json, const GMResource &resource) -{ - json.writeMember("resourceVersion", resource.resourceVersion); - json.writeMember("name", resource.name); - writeTags(json, resource.tags); - json.writeMember("resourceType", resourceTypeStr(resource.resourceType)); -} - -static void writeId(JsonWriter &json, const char *member, const QString &id, const QString &scope) -{ - if (id.isEmpty()) { - json.writeMember(member, QJsonValue(QJsonValue::Null)); - } else { - json.writeStartObject(member); - json.writeMember("name", id); - json.writeMember("path", QStringLiteral("%1/%2/%2.yy").arg(scope, id)); - json.writeEndObject(); - } -} - static QString spriteId(const Object *object, const QUrl &imageUrl, Context &context) { // If the custom property "sprite" exist use it instead of crawling the file system @@ -438,15 +851,6 @@ static QString spriteId(const Object *object, const QUrl &imageUrl, Context &con return context.resourceId(imageUrl.path()); } -static unsigned colorToAbgr(const QColor &color) -{ - const QRgb rgba = color.rgba(); - return ((qAlpha(rgba) & 0xffu) << 24) | - ((qBlue(rgba) & 0xffu) << 16) | - ((qGreen(rgba) & 0xffu) << 8) | - (qRed(rgba) & 0xffu); -} - static QString toOverriddenPropertyValue(const QVariant &value, Context &context) { if (value.userType() == objectRefTypeId()) { @@ -473,208 +877,6 @@ static QString toOverriddenPropertyValue(const QVariant &value, Context &context } } -static void writeLayers(JsonWriter &json, const std::vector> &layers) -{ - json.writeStartArray("layers"); - - for (const std::unique_ptr &layer : layers) { - json.prepareNewLine(); - json.writeStartObject(); - - switch (layer->resourceType) { - case GMRAssetLayerType: { - auto &assetLayer = static_cast(*layer); - - json.writeStartArray("assets"); - - for (const auto &asset : assetLayer.assets) { - json.prepareNewLine(); - json.writeStartObject(); - const bool wasMinimize = json.minimize(); - json.setMinimize(true); - - writeId(json, "spriteId", asset.spriteId, QStringLiteral("sprites")); - - if (asset.resourceType == GMRSpriteGraphicType) { - json.writeMember("headPosition", asset.headPosition); - json.writeMember("rotation", asset.rotation); - json.writeMember("scaleX", asset.scaleX); - json.writeMember("scaleY", asset.scaleY); - json.writeMember("animationSpeed", asset.animationSpeed); - } else { - json.writeMember("w", asset.w); - json.writeMember("h", asset.h); - json.writeMember("u0", asset.u0); - json.writeMember("v0", asset.v0); - json.writeMember("u1", asset.u1); - json.writeMember("v1", asset.v1); - } - json.writeMember("colour", colorToAbgr(asset.colour)); - if (asset.inheritedItemId.isEmpty()) { - json.writeMember("inheritedItemId", QJsonValue(QJsonValue::Null)); - } else { - json.writeStartObject("inheritedItemId"); - json.writeMember("name", asset.inheritedItemId); - json.writeMember("path", asset.inheritedItemPath); - json.writeEndObject(); - } - json.writeMember("frozen", asset.frozen); - json.writeMember("ignore", asset.ignore); - json.writeMember("inheritItemSettings", asset.inheritItemSettings); - json.writeMember("x", asset.x); - json.writeMember("y", asset.y); - - writeResourceProperties(json, asset); - - json.writeEndObject(); - json.setMinimize(wasMinimize); - } - - json.writeEndArray(); // assets - break; - } - case GMRBackgroundLayerType: { - auto &backgroundLayer = static_cast(*layer); - - writeId(json, "spriteId", backgroundLayer.spriteId, QStringLiteral("sprites")); - - json.writeMember("colour", colorToAbgr(backgroundLayer.colour)); - json.writeMember("x", backgroundLayer.x); - json.writeMember("y", backgroundLayer.y); - json.writeMember("htiled", backgroundLayer.htiled); - json.writeMember("vtiled", backgroundLayer.vtiled); - json.writeMember("hspeed", backgroundLayer.hspeed); - json.writeMember("vspeed", backgroundLayer.vspeed); - json.writeMember("stretch", backgroundLayer.stretch); - json.writeMember("animationFPS", backgroundLayer.animationFPS); - json.writeMember("animationSpeedType", backgroundLayer.animationSpeedType); - json.writeMember("userdefinedAnimFPS", backgroundLayer.userdefinedAnimFPS); - break; - } - case GMRInstanceLayerType: { - auto &instanceLayer = static_cast(*layer); - json.writeStartArray("instances"); - - for (const auto &instance : instanceLayer.instances) { - json.prepareNewLine(); - json.writeStartObject(); - const bool wasMinimize = json.minimize(); - json.setMinimize(true); - - json.writeStartArray("properties"); - for (const GMOverriddenProperty &prop : instance.properties) { - json.writeStartObject(); - - json.writeStartObject("propertyId"); - json.writeMember("name", prop.propertyId); - json.writeMember("path", QStringLiteral("%1/%2/%2.yy").arg(QStringLiteral("objects"), prop.objectId)); - json.writeEndObject(); // propertyId - - writeId(json, "objectId", prop.objectId, "objects"); - - json.writeMember("value", prop.value); - - writeResourceProperties(json, prop); - - json.writeEndObject(); - } - json.writeEndArray(); // properties - - json.writeMember("isDnd", instance.isDnd); - - writeId(json, "objectId", instance.objectId, QStringLiteral("objects")); - - json.writeMember("inheritCode", instance.inheritCode); - json.writeMember("hasCreationCode", instance.hasCreationCode); - json.writeMember("colour", colorToAbgr(instance.colour)); - json.writeMember("rotation", instance.rotation); - json.writeMember("scaleX", instance.scaleX); - json.writeMember("scaleY", instance.scaleY); - json.writeMember("imageIndex", instance.imageIndex); - json.writeMember("imageSpeed", instance.imageSpeed); - if (instance.inheritedItemId.isEmpty()) { - json.writeMember("inheritedItemId", QJsonValue(QJsonValue::Null)); - } else { - json.writeStartObject("inheritedItemId"); - json.writeMember("name", instance.inheritedItemId); - json.writeMember("path", instance.inheritedItemPath); - json.writeEndObject(); - } - json.writeMember("frozen", instance.frozen); - json.writeMember("ignore", instance.ignore); - json.writeMember("inheritItemSettings", instance.inheritItemSettings); - json.writeMember("x", instance.x); - json.writeMember("y", instance.y); - - writeResourceProperties(json, instance); - - json.writeEndObject(); - json.setMinimize(wasMinimize); - } - - json.writeEndArray(); // instances - break; - } - case GMRPathLayerType: { - auto &pathLayer = static_cast(*layer); - - writeId(json, "pathId", pathLayer.pathId, QStringLiteral("paths")); - - json.writeMember("colour", colorToAbgr(pathLayer.colour)); - break; - } - case GMRTileLayerType: { - auto &tileLayer = static_cast(*layer); - - writeId(json, "tilesetId", tileLayer.tilesetId, QStringLiteral("tilesets")); - - json.writeMember("x", tileLayer.x); - json.writeMember("y", tileLayer.y); - - json.writeStartObject("tiles"); - json.writeMember("SerialiseWidth", tileLayer.SerialiseWidth); - json.writeMember("SerialiseHeight", tileLayer.SerialiseHeight); - json.writeStartArray("TileSerialiseData"); - - size_t index = 0; - - for (int y = 0; y < tileLayer.SerialiseHeight; ++y) { - json.prepareNewLine(); - - for (int x = 0; x < tileLayer.SerialiseWidth; ++x) { - json.writeValue(tileLayer.tiles.at(index)); - ++index; - } - } - - json.writeEndArray(); // TileSerialiseData - json.writeEndObject(); // tiles - break; - } - default: - break; - } - - json.writeMember("visible", layer->visible); - json.writeMember("depth", layer->depth); - json.writeMember("userdefinedDepth", layer->userdefinedDepth); - json.writeMember("inheritLayerDepth", layer->inheritLayerDepth); - json.writeMember("inheritLayerSettings", layer->inheritLayerSettings); - json.writeMember("gridX", layer->gridX); - json.writeMember("gridY", layer->gridY); - - writeLayers(json, layer->layers); - - json.writeMember("hierarchyFrozen", layer->hierarchyFrozen); - - writeResourceProperties(json, *layer); - - json.writeEndObject(); - } - - json.writeEndArray(); // layers -} - static void fillTileLayer(GMRTileLayer &gmrTileLayer, const TileLayer *tileLayer, const Tileset *tileset) { const auto layerOffset = tileLayer->totalOffset().toPoint(); @@ -829,7 +1031,7 @@ static void createAssetsFromTiles(std::vector &assets, g.colour = color; g.frozen = frozen; - g.ignore = optionalProperty(tileLayer, "ignore", g.ignore); + readProperty(tileLayer, "ignore", g.ignore); g.x = pos.x(); g.y = pos.y() - size.height(); @@ -930,15 +1132,16 @@ static std::unique_ptr processObjectGroup(const ObjectGroup *objectGro if (className == QLatin1String("view")) { // GM only has 8 views so drop anything more than that - if (context.views.size() > 7) { + if (context.room.views.size() > 7) { Tiled::ERROR(QLatin1String("YY plugin: Can't export more than 8 views."), Tiled::JumpToObject { mapObject }); continue; } - GMRView &view = context.views.emplace_back(); + // Last view in Object layer is the first view in the room + GMRView &view = context.room.views.emplace_back(); - view.inherit = optionalProperty(mapObject, "inherit", false); + readProperty(mapObject, "inherit", view.inherit); view.visible = mapObject->isVisible(); // Note: GM only supports ints for positioning // so views could be off if user doesn't align to whole number @@ -955,7 +1158,7 @@ static std::unique_ptr processObjectGroup(const ObjectGroup *objectGro view.vborder = qRound(optionalProperty(mapObject, "vborder", 32.0)); view.hspeed = qRound(optionalProperty(mapObject, "hspeed", -1.0)); view.vspeed = qRound(optionalProperty(mapObject, "vspeed", -1.0)); - view.objectId = optionalProperty(mapObject, "objectId", QString()); + readProperty(mapObject, "objectId", view.objectId); } else if (!className.isEmpty()) { @@ -1028,7 +1231,7 @@ static std::unique_ptr processObjectGroup(const ObjectGroup *objectGro instance.tags = readTags(mapObject); props.remove(QStringLiteral("tags")); - InstanceCreation &instanceCreation = context.instanceCreationOrder.emplace_back(); + InstanceCreation &instanceCreation = context.room.instanceCreationOrder.emplace_back(); instanceCreation.name = instance.name; instanceCreation.creationOrder = takeProperty(props, "creationOrder", 0); @@ -1077,8 +1280,8 @@ static std::unique_ptr processObjectGroup(const ObjectGroup *objectGro } // Allow overriding the scale using custom properties - g.scaleX = optionalProperty(mapObject, "scaleX", g.scaleX); - g.scaleY = optionalProperty(mapObject, "scaleY", g.scaleY); + readProperty(mapObject, "scaleX", g.scaleX); + readProperty(mapObject, "scaleY", g.scaleY); g.animationSpeed = optionalProperty(mapObject, "animationSpeed", 1.0); } else { @@ -1212,11 +1415,11 @@ static std::unique_ptr processImageLayer(const ImageLayer *imageLayer, gmrBackgroundLayer->htiled = optionalProperty(imageLayer, "htiled", imageLayer->repeatX()); gmrBackgroundLayer->vtiled = optionalProperty(imageLayer, "vtiled", imageLayer->repeatY()); - gmrBackgroundLayer->hspeed = optionalProperty(imageLayer, "hspeed", gmrBackgroundLayer->hspeed); - gmrBackgroundLayer->vspeed = optionalProperty(imageLayer, "vspeed", gmrBackgroundLayer->vspeed); - gmrBackgroundLayer->stretch = optionalProperty(imageLayer, "stretch", gmrBackgroundLayer->stretch); - gmrBackgroundLayer->animationFPS = optionalProperty(imageLayer, "animationFPS", gmrBackgroundLayer->animationFPS); - gmrBackgroundLayer->animationSpeedType = optionalProperty(imageLayer, "animationSpeedType", gmrBackgroundLayer->animationSpeedType); + readProperty(imageLayer, "hspeed", gmrBackgroundLayer->hspeed); + readProperty(imageLayer, "vspeed", gmrBackgroundLayer->vspeed); + readProperty(imageLayer, "stretch", gmrBackgroundLayer->stretch); + readProperty(imageLayer, "animationFPS", gmrBackgroundLayer->animationFPS); + readProperty(imageLayer, "animationSpeedType", gmrBackgroundLayer->animationSpeedType); gmrBackgroundLayer->userdefinedAnimFPS = imageLayer->resolvedProperty(QStringLiteral("animationFPS")).isValid(); // Workaround compilation issue with mingw49 @@ -1344,146 +1547,72 @@ YyPlugin::YyPlugin() bool YyPlugin::write(const Map *map, const QString &fileName, Options options) { - // Not using SaveFile here, because GameMaker's reload functionality does - // not work correctly when the file is replaced. - QFile file(fileName); + SaveFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { mError = QCoreApplication::translate("File Errors", "Could not open file for writing."); return false; } - const QString baseName = QFileInfo(fileName).completeBaseName(); - - JsonWriter json(&file); - - json.setMinimize(options.testFlag(WriteMinimized)); - - json.writeStartObject(); - - writeProperty(json, map, "isDnd", false); - writeProperty(json, map, "volume", 1.0); - json.writeMember("parentRoom", QJsonValue(QJsonValue::Null)); // TODO: Provide a way to set this? - Context context; context.renderer = MapRenderer::create(map); + GMRoom &room = context.room; - std::vector> layers; - processLayers(layers, map->layers(), context); + room.name = QFileInfo(fileName).completeBaseName();; + room.roomPathInProject = QStringLiteral("rooms/%1/%1.yy").arg(room.name); + + readProperty(map, QStringLiteral("name"), room.name); + readProperty(map, QStringLiteral("isDnd"), room.isDnd); + readProperty(map, QStringLiteral("volume"), room.volume); + + processLayers(room.layers, map->layers(), context); // If a valid background color is set, create a background layer with this color. if (map->backgroundColor().isValid()) { auto gmrBackgroundLayer = std::make_unique(); gmrBackgroundLayer->name = QStringLiteral("Background_Color"); gmrBackgroundLayer->colour = map->backgroundColor(); - layers.push_back(std::move(gmrBackgroundLayer)); + room.layers.push_back(std::move(gmrBackgroundLayer)); } - autoAssignDepth(layers); - - const bool enableViews = !context.views.empty(); - - // Write out views - // Last view in Object layer is the first view in the room - json.writeStartArray("views"); - context.views.resize(8); // GameMaker always stores 8 views - for (const GMRView &view : std::as_const(context.views)) { - json.prepareNewLine(); - json.writeStartObject(); - const bool wasMinimize = json.minimize(); - json.setMinimize(true); - - json.writeMember("inherit", view.inherit); - json.writeMember("visible", view.visible); - json.writeMember("xview", view.xview); - json.writeMember("yview", view.yview); - json.writeMember("wview", view.wview); - json.writeMember("hview", view.hview); - json.writeMember("xport", view.xport); - json.writeMember("yport", view.yport); - json.writeMember("wport", view.wport); - json.writeMember("hport", view.hport); - json.writeMember("hborder", view.hborder); - json.writeMember("vborder", view.vborder); - json.writeMember("hspeed", view.hspeed); - json.writeMember("vspeed", view.vspeed); - - writeId(json, "objectId", view.objectId, "objects"); - - json.writeEndObject(); - json.setMinimize(wasMinimize); - } + autoAssignDepth(room.layers); - json.writeEndArray(); // views + room.viewSettings.enableViews = !room.views.empty(); + room.views.resize(8); // GameMaker always stores 8 views - writeLayers(json, layers); + std::stable_sort(room.instanceCreationOrder.begin(), + room.instanceCreationOrder.end()); - writeProperty(json, map, "inheritLayers", false); - writeProperty(json, map, "creationCodeFile", QString()); - writeProperty(json, map, "inheritCode", false); + readProperty(map, QStringLiteral("inheritLayers"), room.inheritLayers); + readProperty(map, QStringLiteral("creationCodeFile"), room.creationCodeFile); + readProperty(map, QStringLiteral("inheritCode"), room.inheritCode); + readProperty(map, QStringLiteral("inheritCreationOrder"), room.inheritCreationOrder); - const QString currentRoomPath = QStringLiteral("rooms/%1/%1.yy").arg(baseName); + readProperty(map, QStringLiteral("inheritRoomSettings"), room.roomSettings.inheritRoomSettings); + room.roomSettings.Width = map->tileWidth() * map->width(); + room.roomSettings.Height = map->tileHeight() * map->height(); + readProperty(map, QStringLiteral("persistent"), room.roomSettings.persistent); - std::stable_sort(context.instanceCreationOrder.begin(), - context.instanceCreationOrder.end()); + readProperty(map, QStringLiteral("inheritViewSettings"), room.viewSettings.inheritViewSettings); + readProperty(map, QStringLiteral("enableViews"), room.viewSettings.enableViews); + readProperty(map, QStringLiteral("clearViewBackground"), room.viewSettings.clearViewBackground); + readProperty(map, QStringLiteral("clearDisplayBuffer"), room.viewSettings.clearDisplayBuffer); - json.writeStartArray("instanceCreationOrder"); - for (const auto &creation : context.instanceCreationOrder) { - json.prepareNewLine(); - json.writeStartObject(); - const bool wasMinimize = json.minimize(); - json.setMinimize(true); - json.writeMember("name", creation.name); - json.writeMember("path", currentRoomPath); - json.writeEndObject(); - json.setMinimize(wasMinimize); - } - json.writeEndArray(); - - writeProperty(json, map, "inheritCreationOrder", false); - json.writeMember("sequenceId", QJsonValue(QJsonValue::Null)); - - const int mapPixelWidth = map->tileWidth() * map->width(); - const int mapPixelHeight = map->tileHeight() * map->height(); - - json.writeStartObject("roomSettings"); - writeProperty(json, map, "inheritRoomSettings", false); - json.writeMember("Width", mapPixelWidth); - json.writeMember("Height", mapPixelHeight); - writeProperty(json, map, "persistent", false); - json.writeEndObject(); - - json.writeStartObject("viewSettings"); - writeProperty(json, map, "inheritViewSettings", false); - writeProperty(json, map, "enableViews", enableViews); - writeProperty(json, map, "clearViewBackground", false); - writeProperty(json, map, "clearDisplayBuffer", true); - json.writeEndObject(); - - json.writeStartObject("physicsSettings"); - writeProperty(json, map, "inheritPhysicsSettings", false); - writeProperty(json, map, "PhysicsWorld", false); - writeProperty(json, map, "PhysicsWorldGravityX", 0.0); - writeProperty(json, map, "PhysicsWorldGravityY", 10.0); - writeProperty(json, map, "PhysicsWorldPixToMetres", 0.1); - json.writeEndObject(); - - json.writeStartObject("parent"); - const QString parent = optionalProperty(map, "parent", QStringLiteral("Rooms")); - json.writeMember("name", QFileInfo(parent).fileName()); - json.writeMember("path", QStringLiteral("folders/%1.yy").arg(parent)); - json.writeEndObject(); - - writeProperty(json, map, "resourceVersion", QString("1.0")); - writeProperty(json, map, "name", baseName); - writeTags(json, map); - json.writeMember("resourceType", "GMRoom"); - - json.writeEndObject(); - json.writeEndDocument(); + readProperty(map, QStringLiteral("inheritPhysicsSettings"), room.physicsSettings.inheritPhysicsSettings); + readProperty(map, QStringLiteral("PhysicsWorld"), room.physicsSettings.PhysicsWorld); + readProperty(map, QStringLiteral("PhysicsWorldGravityX"), room.physicsSettings.PhysicsWorldGravityX); + readProperty(map, QStringLiteral("PhysicsWorldGravityY"), room.physicsSettings.PhysicsWorldGravityY); + readProperty(map, QStringLiteral("PhysicsWorldPixToMetres"), room.physicsSettings.PhysicsWorldPixToMetres); - file.flush(); + readProperty(map, QStringLiteral("parent"), room.parent); + + room.tags = readTags(map); + + JsonWriter json(file.device()); + json.setMinimize(options.testFlag(WriteMinimized)); + json.writeValue(room.toJson()); + json.writeEndDocument(); - if (file.error() != QFileDevice::NoError) { + if (!file.commit()) { mError = file.errorString(); return false; }