osb/source/game/StarTilesetDatabase.cpp
2023-06-20 14:33:09 +10:00

362 lines
14 KiB
C++

#include "StarAssets.hpp"
#include "StarCasting.hpp"
#include "StarRoot.hpp"
#include "StarTilesetDatabase.hpp"
namespace Star {
namespace Tiled {
using namespace Dungeon;
EnumMap<TileLayer> 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<BrushConstPtr> getClearBrush(bool value, Tiled::Properties&) {
if (value)
return Maybe<BrushConstPtr>(as<Brush>(make_shared<const ClearBrush>()));
return {};
}
BrushConstPtr getFrontBrush(String const& materialName, Tiled::Properties& properties) {
Maybe<float> hueshift = properties.opt<float>("hueshift");
Maybe<MaterialColorVariant> colorVariant = properties.opt<size_t>("colorVariant");
Maybe<String> mod = properties.opt<String>("mod");
Maybe<float> modhueshift = properties.opt<float>("modhueshift");
return make_shared<const FrontBrush>(materialName, mod, hueshift, modhueshift, colorVariant);
}
BrushConstPtr getBackBrush(String const& materialName, Tiled::Properties& properties) {
Maybe<float> hueshift = properties.opt<float>("hueshift");
Maybe<MaterialColorVariant> colorVariant = properties.opt<size_t>("colorVariant");
Maybe<String> mod = properties.opt<String>("mod");
Maybe<float> modhueshift = properties.opt<float>("modhueshift");
return make_shared<const BackBrush>(materialName, mod, hueshift, modhueshift, colorVariant);
}
BrushConstPtr getMaterialBrush(String const& materialName, Tiled::Properties& properties) {
TileLayer layer = LayerNames.getLeft(properties.get<String>("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<PlayerStartBrush>();
}
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<String>("tilesetDirection"));
if (properties.contains("flipX"))
direction = -direction;
if (properties.contains("parameters")) {
parameters = properties.get<Json>("parameters");
}
parameters = parameters.opt().value(JsonObject{});
return make_shared<const ObjectBrush>(objectName, direction, parameters);
}
BrushConstPtr getVehicleBrush(String const& vehicleName, Tiled::Properties& properties) {
Json parameters = JsonObject{};
if (properties.contains("parameters")) {
parameters = properties.get<Json>("parameters");
}
return make_shared<const VehicleBrush>(vehicleName, parameters);
}
BrushConstPtr getWireBrush(String const& group, Tiled::Properties& properties) {
bool local = properties.opt<bool>("local").value(true);
return make_shared<const WireBrush>(group, local);
}
Json getSeed(Tiled::Properties& properties) {
String seed = properties.get<String>("seed");
if (seed == "stable")
return seed;
return lexicalCast<uint64_t>(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<String>("typeName");
brush["parameters"] = properties.opt<Json>("parameters").value(JsonObject{});
return make_shared<const NpcBrush>(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<Json>("parameters").value(JsonObject{});
return make_shared<const NpcBrush>(brush);
}
BrushConstPtr getStagehandBrush(String const& typeName, Tiled::Properties& properties) {
JsonObject brush;
brush["type"] = typeName;
brush["parameters"] = properties.opt<Json>("parameters").value(JsonObject{});
if (properties.contains("broadcastArea"))
brush["parameters"] = brush["parameters"].set("broadcastArea", properties.get<Json>("broadcastArea"));
if (typeName == "radiomessage" && properties.contains("radioMessage"))
brush["parameters"] = brush["parameters"].set("radioMessage", properties.get<Json>("radioMessage"));
return make_shared<const StagehandBrush>(brush);
}
BrushConstPtr getDungeonIdBrush(String const& dungeonId, Tiled::Properties&) {
return make_shared<const DungeonIdBrush>(maybeLexicalCast<DungeonId>(dungeonId).value(NoDungeonId));
}
BrushConstPtr getBiomeItemsBrush(String const&, Tiled::Properties&) {
return make_shared<const BiomeItemsBrush>();
}
BrushConstPtr getBiomeTreeBrush(String const&, Tiled::Properties&) {
return make_shared<const BiomeTreeBrush>();
}
BrushConstPtr getItemBrush(String const& itemName, Tiled::Properties& properties) {
size_t count = properties.opt<size_t>("count").value(1);
Json parameters = properties.opt<Json>("parameters").value(JsonObject{});
ItemDescriptor item(itemName, count, parameters);
return make_shared<const ItemBrush>(item);
}
BrushConstPtr getSurfaceBrush(String const& variantStr, Tiled::Properties& properties) {
TileLayer layer = LayerNames.getLeft(properties.get<String>("layer"));
Maybe<int> variant = maybeLexicalCast<int>(variantStr);
Maybe<String> mod = properties.opt<String>("mod");
if (layer == TileLayer::Background)
return make_shared<const SurfaceBackgroundBrush>(variant, mod);
return make_shared<const SurfaceBrush>(variant, mod);
}
BrushConstPtr getLiquidBrush(String const& liquidName, Tiled::Properties& properties) {
float quantity = properties.opt<float>("quantity").value(1.0f);
bool source = properties.opt<bool>("source").value(false);
return make_shared<const LiquidBrush>(liquidName, quantity, source);
}
Maybe<BrushConstPtr> getInvalidBrush(bool invalidValue, Tiled::Properties& properties) {
if (!invalidValue)
return {};
return as<const Brush>(make_shared<const InvalidBrush>(properties.opt<String>("//name")));
}
RuleConstPtr getAirRule(String const&, Tiled::Properties& properties) {
TileLayer layer = LayerNames.getLeft(properties.get<String>("layer"));
return make_shared<const WorldGenMustContainAirRule>(layer);
}
RuleConstPtr getSolidRule(String const&, Tiled::Properties& properties) {
TileLayer layer = LayerNames.getLeft(properties.get<String>("layer"));
return make_shared<const WorldGenMustContainSolidRule>(layer);
}
RuleConstPtr getLiquidRule(String const&, Tiled::Properties&) {
return make_shared<const WorldGenMustContainLiquidRule>();
}
RuleConstPtr getNotLiquidRule(String const&, Tiled::Properties&) {
return make_shared<const WorldGenMustNotContainLiquidRule>();
}
RuleConstPtr getAllowOverdrawingRule(String const&, Tiled::Properties&) {
return make_shared<const AllowOverdrawingRule>();
}
template <typename T>
class PropertyReader {
public:
template <typename PropertyType,
typename GetterReturn = T,
typename Getter = function<GetterReturn(PropertyType, Tiled::Properties&)>>
void optRead(List<T>& list, String const& propertyName, Getter getter, Tiled::Properties& properties) {
auto appendFn = bind(&List<T>::append, &list, _1);
read<PropertyType, Getter>(propertyName, getter, properties).exec(appendFn);
}
private:
template <typename PropertyType, typename Getter>
Maybe<T> read(String const& propertyName, Getter getter, Tiled::Properties& properties) {
Maybe<PropertyType> propertyValue = properties.opt<PropertyType>(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<String>("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<BrushConstPtr> br;
br.optRead<bool, Maybe<BrushConstPtr>>(brushes, "clear", getClearBrush, properties);
br.optRead<String>(brushes, "material", getMaterialBrush, properties);
br.optRead<String>(brushes, "front", getFrontBrush, properties);
br.optRead<String>(brushes, "back", getBackBrush, properties);
br.optRead<String>(brushes, "playerstart", getPlayerStartBrush, properties);
br.optRead<String>(brushes, "object", getObjectBrush, properties);
br.optRead<String>(brushes, "vehicle", getVehicleBrush, properties);
br.optRead<String>(brushes, "wire", getWireBrush, properties);
br.optRead<String>(brushes, "npc", getNpcBrush, properties);
br.optRead<String>(brushes, "monster", getMonsterBrush, properties);
br.optRead<String>(brushes, "stagehand", getStagehandBrush, properties);
br.optRead<String>(brushes, "dungeonid", getDungeonIdBrush, properties);
br.optRead<String>(brushes, "biomeitems", getBiomeItemsBrush, properties);
br.optRead<String>(brushes, "biometree", getBiomeTreeBrush, properties);
br.optRead<String>(brushes, "item", getItemBrush, properties);
br.optRead<String>(brushes, "surface", getSurfaceBrush, properties);
br.optRead<String>(brushes, "liquid", getLiquidBrush, properties);
br.optRead<bool, Maybe<BrushConstPtr>>(brushes, "invalid", getInvalidBrush, properties);
PropertyReader<RuleConstPtr> rr;
rr.optRead<String>(rules, "worldGenMustContainAir", getAirRule, properties);
rr.optRead<String>(rules, "worldGenMustContainSolid", getSolidRule, properties);
rr.optRead<String>(rules, "worldGenMustContainLiquid", getLiquidRule, properties);
rr.optRead<String>(rules, "worldGenMustNotContainLiquid", getNotLiquidRule, properties);
rr.optRead<String>(rules, "allowOverdrawing", getAllowOverdrawingRule, properties);
if (auto connectorName = properties.opt<String>("connector")) {
auto newConnector = TileConnector();
newConnector.value = *connectorName;
auto connectForwardOnly = properties.opt<bool>("connectForwardOnly");
newConnector.forwardOnly = connectForwardOnly.value(false);
if (auto connectDirection = properties.opt<String>("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<size_t>(entry.first);
Properties properties = Properties(entry.second).inherit(tilesetProperties);
m_tilesBack[index] = make_shared<Tile>(properties, TileLayer::Background);
m_tilesFront[index] = make_shared<Tile>(properties, TileLayer::Foreground);
}
}
TileConstPtr const& Tileset::getTile(size_t id, TileLayer layer) const {
List<TileConstPtr> const& tileset = tiles(layer);
return tileset[id];
}
size_t Tileset::size() const {
starAssert(m_tilesBack.size() == m_tilesFront.size());
return m_tilesBack.size();
}
List<TileConstPtr> 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<Tiled::Tileset>(assets->json(path));
}
namespace Tiled {
Json PropertyConverter<Json>::to(String const& propertyValue) {
try {
return Json::parseJson(propertyValue);
} catch (JsonParsingException const& e) {
throw StarException::format("Error parsing Tiled property as Json: %s", outputException(e, false));
}
}
String PropertyConverter<Json>::from(Json const& propertyValue) {
return propertyValue.repr();
}
String PropertyConverter<String>::to(String const& propertyValue) {
return propertyValue;
}
String PropertyConverter<String>::from(String const& propertyValue) {
return propertyValue;
}
}
}