#include "StarAssets.hpp" #include "StarCasting.hpp" #include "StarRoot.hpp" #include "StarTilesetDatabase.hpp" namespace Star { namespace Tiled { using namespace Dungeon; EnumMap const LayerNames{{TileLayer::Foreground, "front"}, {TileLayer::Background, "back"}}; Properties::Properties() : m_properties(JsonObject{}) {} Properties::Properties(Json const& json) : m_properties(json) {} Json Properties::toJson() const { return m_properties; } Properties Properties::inherit(Json const& properties) const { return jsonMerge(properties, m_properties); } Properties Properties::inherit(Properties const& properties) const { return jsonMerge(properties.m_properties, m_properties); } bool Properties::contains(String const& name) const { return m_properties.contains(name); } Maybe getClearBrush(bool value, Tiled::Properties&) { if (value) return Maybe(as(make_shared())); return {}; } BrushConstPtr getFrontBrush(String const& materialName, Tiled::Properties& properties) { Maybe hueshift = properties.opt("hueshift"); Maybe colorVariant = properties.opt("colorVariant"); Maybe mod = properties.opt("mod"); Maybe modhueshift = properties.opt("modhueshift"); return make_shared(materialName, mod, hueshift, modhueshift, colorVariant); } BrushConstPtr getBackBrush(String const& materialName, Tiled::Properties& properties) { Maybe hueshift = properties.opt("hueshift"); Maybe colorVariant = properties.opt("colorVariant"); Maybe mod = properties.opt("mod"); Maybe modhueshift = properties.opt("modhueshift"); return make_shared(materialName, mod, hueshift, modhueshift, colorVariant); } BrushConstPtr getMaterialBrush(String const& materialName, Tiled::Properties& properties) { TileLayer layer = LayerNames.getLeft(properties.get("layer")); if (layer == TileLayer::Background) { return getBackBrush(materialName, properties); } else { starAssert(layer == TileLayer::Foreground); return getFrontBrush(materialName, properties); } } BrushConstPtr getPlayerStartBrush(String const&, Tiled::Properties&) { return make_shared(); } BrushConstPtr getObjectBrush(String const& objectName, Tiled::Properties& properties) { Star::Direction direction = Star::Direction::Right; Json parameters; if (properties.contains("tilesetDirection")) direction = DirectionNames.getLeft(properties.get("tilesetDirection")); if (properties.contains("flipX")) direction = -direction; if (properties.contains("parameters")) { parameters = properties.get("parameters"); } parameters = parameters.opt().value(JsonObject{}); return make_shared(objectName, direction, parameters); } BrushConstPtr getVehicleBrush(String const& vehicleName, Tiled::Properties& properties) { Json parameters = JsonObject{}; if (properties.contains("parameters")) { parameters = properties.get("parameters"); } return make_shared(vehicleName, parameters); } BrushConstPtr getWireBrush(String const& group, Tiled::Properties& properties) { bool local = properties.opt("local").value(true); return make_shared(group, local); } Json getSeed(Tiled::Properties& properties) { String seed = properties.get("seed"); if (seed == "stable") return seed; return lexicalCast(seed); } BrushConstPtr getNpcBrush(String const& species, Tiled::Properties& properties) { JsonObject brush; brush["kind"] = "npc"; brush["species"] = species; // this may be a single species or a comma // separated list to be parsed later if (properties.contains("seed")) { brush["seed"] = getSeed(properties); } if (properties.contains("typeName")) brush["typeName"] = properties.get("typeName"); brush["parameters"] = properties.opt("parameters").value(JsonObject{}); return make_shared(brush); } BrushConstPtr getMonsterBrush(String const& typeName, Tiled::Properties& properties) { JsonObject brush; brush["kind"] = "monster"; brush["typeName"] = typeName; if (properties.contains("seed")) { brush["seed"] = getSeed(properties); } brush["parameters"] = properties.opt("parameters").value(JsonObject{}); return make_shared(brush); } BrushConstPtr getStagehandBrush(String const& typeName, Tiled::Properties& properties) { JsonObject brush; brush["type"] = typeName; brush["parameters"] = properties.opt("parameters").value(JsonObject{}); if (properties.contains("broadcastArea")) brush["parameters"] = brush["parameters"].set("broadcastArea", properties.get("broadcastArea")); if (typeName == "radiomessage" && properties.contains("radioMessage")) brush["parameters"] = brush["parameters"].set("radioMessage", properties.get("radioMessage")); return make_shared(brush); } BrushConstPtr getDungeonIdBrush(String const& dungeonId, Tiled::Properties&) { return make_shared(maybeLexicalCast(dungeonId).value(NoDungeonId)); } BrushConstPtr getBiomeItemsBrush(String const&, Tiled::Properties&) { return make_shared(); } BrushConstPtr getBiomeTreeBrush(String const&, Tiled::Properties&) { return make_shared(); } BrushConstPtr getItemBrush(String const& itemName, Tiled::Properties& properties) { size_t count = properties.opt("count").value(1); Json parameters = properties.opt("parameters").value(JsonObject{}); ItemDescriptor item(itemName, count, parameters); return make_shared(item); } BrushConstPtr getSurfaceBrush(String const& variantStr, Tiled::Properties& properties) { TileLayer layer = LayerNames.getLeft(properties.get("layer")); Maybe variant = maybeLexicalCast(variantStr); Maybe mod = properties.opt("mod"); if (layer == TileLayer::Background) return make_shared(variant, mod); return make_shared(variant, mod); } BrushConstPtr getLiquidBrush(String const& liquidName, Tiled::Properties& properties) { float quantity = properties.opt("quantity").value(1.0f); bool source = properties.opt("source").value(false); return make_shared(liquidName, quantity, source); } Maybe getInvalidBrush(bool invalidValue, Tiled::Properties& properties) { if (!invalidValue) return {}; return as(make_shared(properties.opt("//name"))); } RuleConstPtr getAirRule(String const&, Tiled::Properties& properties) { TileLayer layer = LayerNames.getLeft(properties.get("layer")); return make_shared(layer); } RuleConstPtr getSolidRule(String const&, Tiled::Properties& properties) { TileLayer layer = LayerNames.getLeft(properties.get("layer")); return make_shared(layer); } RuleConstPtr getLiquidRule(String const&, Tiled::Properties&) { return make_shared(); } RuleConstPtr getNotLiquidRule(String const&, Tiled::Properties&) { return make_shared(); } RuleConstPtr getAllowOverdrawingRule(String const&, Tiled::Properties&) { return make_shared(); } template class PropertyReader { public: template > void optRead(List& list, String const& propertyName, Getter getter, Tiled::Properties& properties) { auto appendFn = bind(&List::append, &list, _1); read(propertyName, getter, properties).exec(appendFn); } private: template Maybe read(String const& propertyName, Getter getter, Tiled::Properties& properties) { Maybe propertyValue = properties.opt(propertyName); if (propertyValue.isValid()) return getter(*propertyValue, properties); return {}; } }; Tile::Tile(Properties const& tileProperties, TileLayer layer, bool flipX) : Dungeon::Tile(), properties(tileProperties) { JsonObject computedProperties; if (!properties.contains("layer")) { computedProperties["layer"] = LayerNames.getRight(layer); } else { layer = LayerNames.getLeft(properties.get("layer")); } if (flipX) computedProperties["flipX"] = "true"; if (layer == TileLayer::Background && !properties.contains("clear")) // The magic pink tile/brush has the clear property set to "false". All // other tiles default to clear="true". computedProperties["clear"] = "true"; properties = properties.inherit(computedProperties); PropertyReader br; br.optRead>(brushes, "clear", getClearBrush, properties); br.optRead(brushes, "material", getMaterialBrush, properties); br.optRead(brushes, "front", getFrontBrush, properties); br.optRead(brushes, "back", getBackBrush, properties); br.optRead(brushes, "playerstart", getPlayerStartBrush, properties); br.optRead(brushes, "object", getObjectBrush, properties); br.optRead(brushes, "vehicle", getVehicleBrush, properties); br.optRead(brushes, "wire", getWireBrush, properties); br.optRead(brushes, "npc", getNpcBrush, properties); br.optRead(brushes, "monster", getMonsterBrush, properties); br.optRead(brushes, "stagehand", getStagehandBrush, properties); br.optRead(brushes, "dungeonid", getDungeonIdBrush, properties); br.optRead(brushes, "biomeitems", getBiomeItemsBrush, properties); br.optRead(brushes, "biometree", getBiomeTreeBrush, properties); br.optRead(brushes, "item", getItemBrush, properties); br.optRead(brushes, "surface", getSurfaceBrush, properties); br.optRead(brushes, "liquid", getLiquidBrush, properties); br.optRead>(brushes, "invalid", getInvalidBrush, properties); PropertyReader rr; rr.optRead(rules, "worldGenMustContainAir", getAirRule, properties); rr.optRead(rules, "worldGenMustContainSolid", getSolidRule, properties); rr.optRead(rules, "worldGenMustContainLiquid", getLiquidRule, properties); rr.optRead(rules, "worldGenMustNotContainLiquid", getNotLiquidRule, properties); rr.optRead(rules, "allowOverdrawing", getAllowOverdrawingRule, properties); if (auto connectorName = properties.opt("connector")) { auto newConnector = TileConnector(); newConnector.value = *connectorName; auto connectForwardOnly = properties.opt("connectForwardOnly"); newConnector.forwardOnly = connectForwardOnly.value(false); if (auto connectDirection = properties.opt("connectDirection")) newConnector.direction = DungeonDirectionNames.getLeft(*connectDirection); connector = newConnector; } } Tileset::Tileset(Json const& json) { Properties tilesetProperties(json.opt("properties").value(JsonObject{})); Json tileProperties = json.opt("tileproperties").value(JsonObject{}); m_tilesBack.resize(json.getInt("tilecount")); m_tilesFront.resize(json.getInt("tilecount")); for (auto const& entry : tileProperties.iterateObject()) { size_t index = lexicalCast(entry.first); Properties properties = Properties(entry.second).inherit(tilesetProperties); m_tilesBack[index] = make_shared(properties, TileLayer::Background); m_tilesFront[index] = make_shared(properties, TileLayer::Foreground); } } TileConstPtr const& Tileset::getTile(size_t id, TileLayer layer) const { List const& tileset = tiles(layer); return tileset[id]; } size_t Tileset::size() const { starAssert(m_tilesBack.size() == m_tilesFront.size()); return m_tilesBack.size(); } List const& Tileset::tiles(TileLayer layer) const { if (layer == TileLayer::Background) return m_tilesBack; starAssert(layer == TileLayer::Foreground); return m_tilesFront; } } TilesetDatabase::TilesetDatabase() : m_cacheMutex(), m_tilesetCache() {} Tiled::TilesetConstPtr TilesetDatabase::get(String const& path) const { MutexLocker locker(m_cacheMutex); return m_tilesetCache.get(path, TilesetDatabase::readTileset); } Tiled::TilesetConstPtr TilesetDatabase::readTileset(String const& path) { auto assets = Root::singleton().assets(); return make_shared(assets->json(path)); } namespace Tiled { Json PropertyConverter::to(String const& propertyValue) { try { return Json::parseJson(propertyValue); } catch (JsonParsingException const& e) { throw StarException::format("Error parsing Tiled property as Json: {}", outputException(e, false)); } } String PropertyConverter::from(Json const& propertyValue) { return propertyValue.repr(); } String PropertyConverter::to(String const& propertyValue) { return propertyValue; } String PropertyConverter::from(String const& propertyValue) { return propertyValue; } } }