diff options
Diffstat (limited to 'source/game/StarWorldStructure.cpp')
-rw-r--r-- | source/game/StarWorldStructure.cpp | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/source/game/StarWorldStructure.cpp b/source/game/StarWorldStructure.cpp new file mode 100644 index 0000000..a014023 --- /dev/null +++ b/source/game/StarWorldStructure.cpp @@ -0,0 +1,267 @@ +#include "StarWorldStructure.hpp" +#include "StarRoot.hpp" +#include "StarJsonExtra.hpp" +#include "StarDataStreamExtra.hpp" +#include "StarMaterialDatabase.hpp" +#include "StarImageMetadataDatabase.hpp" +#include "StarImage.hpp" +#include "StarAssets.hpp" + +namespace Star { + +WorldStructure::WorldStructure() {} + +WorldStructure::WorldStructure(String const& configPath) { + auto assets = Root::singleton().assets(); + auto imgMetadata = Root::singleton().imageMetadataDatabase(); + auto settings = assets->json(configPath); + + m_region = RectI::null(); + m_config = settings.getObject("config", JsonObject()); + + // Read all the background / foreground overlays, and combine the image size + // in tiles with the full structure range + + for (auto overlaySettings : settings.getArray("backgroundOverlays", JsonArray())) { + Overlay overlay = Overlay{jsonToVec2F(overlaySettings.get("position")), + AssetPath::relativeTo(configPath, overlaySettings.getString("image")), + overlaySettings.getBool("fullbright", false)}; + m_backgroundOverlays.append(overlay); + m_region.combine(RectI::withSize( + Vec2I::floor(overlay.min), Vec2I::ceil(Vec2F(imgMetadata->imageSize(overlay.image)) / TilePixels))); + } + + for (auto overlaySettings : settings.getArray("foregroundOverlays", JsonArray())) { + Overlay overlay = Overlay{jsonToVec2F(overlaySettings.get("position")), + AssetPath::relativeTo(configPath, overlaySettings.getString("image")), + overlaySettings.getBool("fullbright", false)}; + m_foregroundOverlays.append(overlay); + m_region.combine(RectI::withSize( + Vec2I::floor(overlay.min), Vec2I::ceil(Vec2F(imgMetadata->imageSize(overlay.image)) / TilePixels))); + } + + // Read block position, keys, and then use that to interpret the block image, + // if given. + + auto blockPosition = jsonToVec2I(settings.getArray("blocksPosition", JsonArray{0, 0})); + HashMap<Vec4B, BlockKey> blockKeys; + auto matDb = Root::singleton().materialDatabase(); + for (auto const& blockKeyConfig : + assets->fetchJson(settings.get("blockKey", JsonArray()), configPath).iterateArray()) { + auto foregroundMat = blockKeyConfig.getString("foregroundMat", ""); + MaterialId foregroundMatId; + if (foregroundMat == "") + foregroundMatId = StructureMaterialId; + else + foregroundMatId = matDb->materialId(foregroundMat); + + auto backgroundMat = blockKeyConfig.getString("backgroundMat", ""); + MaterialId backgroundMatId; + if (backgroundMat == "") + backgroundMatId = StructureMaterialId; + else + backgroundMatId = matDb->materialId(backgroundMat); + + BlockKey blockKey{blockKeyConfig.getBool("anchor", false), + blockKeyConfig.getBool("foregroundBlock", false), + foregroundMatId, + blockKeyConfig.getBool("foregroundResidual", false), + blockKeyConfig.getBool("backgroundBlock", false), + backgroundMatId, + blockKeyConfig.getBool("backgroundResidual", false), + blockKeyConfig.getString("object", ""), + DirectionNames.getLeft(blockKeyConfig.getString("objectDirection", "left")), + blockKeyConfig.getObject("objectParameters", JsonObject()), + blockKeyConfig.getBool("objectResidual", false), + jsonToStringList(blockKeyConfig.get("flags", JsonArray()))}; + blockKeys[jsonToColor(blockKeyConfig.get("value")).toRgba()] = move(blockKey); + } + + Maybe<Vec2I> anchorPosition; + if (settings.contains("blockImage")) { + auto blocksImage = assets->image(AssetPath::relativeTo(configPath, settings.getString("blockImage"))); + const BlockKey defaultBlockKey = {false, + false, + StructureMaterialId, + false, + false, + StructureMaterialId, + false, + String(), + Direction::Left, + Json(), + false, + StringList()}; + + for (size_t y = 0; y < blocksImage->height(); ++y) { + for (size_t x = 0; x < blocksImage->width(); ++x) { + auto blockKey = blockKeys.value(blocksImage->getrgb(x, y), defaultBlockKey); + auto pos = blockPosition + Vec2I(x, y); + + if (blockKey.anchor) { + if (anchorPosition) + throw WorldStructureException( + strf("Multiple anchor points defined in blockImage, first point is at %s, second at %s", + *anchorPosition, + pos)); + anchorPosition = pos; + } + + if (blockKey.foregroundBlock) + m_foregroundBlocks.append({pos, blockKey.foregroundMat, blockKey.foregroundResidual}); + if (blockKey.backgroundBlock) + m_backgroundBlocks.append({pos, blockKey.backgroundMat, blockKey.backgroundResidual}); + + if (!blockKey.object.empty()) + m_objects.append(Object{ + pos, blockKey.object, blockKey.objectDirection, blockKey.objectParameters, blockKey.objectResidual}); + + for (auto const& flag : blockKey.flags) + m_flaggedBlocks[flag].append(pos); + + m_region.combine(pos); + } + } + + if (anchorPosition) + m_anchorPosition = *anchorPosition; + else + m_anchorPosition = m_region.center(); + + // Objects put into the list are from top to bottom, need to place them + // from bottom to top for objects on top of other objects. + reverse(m_objects); + } +} + +WorldStructure::WorldStructure(Json const& store) { + auto overlayFromJson = [](Json const& v) -> Overlay { + return Overlay{jsonToVec2F(v.get("min")), v.getString("image"), v.getBool("fullbright")}; + }; + + auto blockFromJson = [](Json const& v) -> Block { + return Block{jsonToVec2I(v.get("position")), MaterialId(v.getUInt("materialId")), v.getBool("residual")}; + }; + + auto objectFromJson = [](Json const& v) -> Object { + return Object{jsonToVec2I(v.get("position")), + v.getString("name"), + DirectionNames.getLeft(v.getString("direction")), + v.get("parameters", {}), + v.getBool("residual", false)}; + }; + + m_region = jsonToRectI(store.get("region")); + m_anchorPosition = jsonToVec2I(store.get("anchorPosition")); + m_config = store.get("config"); + m_backgroundOverlays = store.getArray("backgroundOverlays").transformed(overlayFromJson); + m_foregroundOverlays = store.getArray("foregroundOverlays").transformed(overlayFromJson); + m_backgroundBlocks = store.getArray("backgroundBlocks").transformed(blockFromJson); + m_foregroundBlocks = store.getArray("foregroundBlocks").transformed(blockFromJson); + m_objects = store.getArray("objects").transformed(objectFromJson); + m_flaggedBlocks = transform<StringMap<List<Vec2I>>>(store.getObject("flaggedBlocks"), + [](pair<String, Json> const& p) { return make_pair(p.first, p.second.toArray().transformed(jsonToVec2I)); }); +} + +Json WorldStructure::configValue(String const& name) const { + return m_config.get(name, Json()); +} + +auto WorldStructure::backgroundOverlays() const -> List<Overlay> const & { + return m_backgroundOverlays; +} + +auto WorldStructure::foregroundOverlays() const -> List<Overlay> const & { + return m_foregroundOverlays; +} + +auto WorldStructure::backgroundBlocks() const -> List<Block> const & { + return m_backgroundBlocks; +} + +auto WorldStructure::foregroundBlocks() const -> List<Block> const & { + return m_foregroundBlocks; +} + +auto WorldStructure::objects() const -> List<Object> const & { + return m_objects; +} + +auto WorldStructure::flaggedBlocks(String const& flag) const -> List<Vec2I> { + return m_flaggedBlocks.value(flag); +} + +RectI WorldStructure::region() const { + return m_region; +} + +Vec2I WorldStructure::anchorPosition() const { + return m_anchorPosition; +} + +void WorldStructure::setAnchorPosition(Vec2I const& anchorPosition) { + translate(anchorPosition - m_anchorPosition); +} + +void WorldStructure::translate(Vec2I const& distance) { + if (!m_region.isNull()) + m_region.translate(distance); + + m_anchorPosition += distance; + + for (auto& bg : m_backgroundOverlays) + bg.min += Vec2F(distance); + + for (auto& fg : m_foregroundOverlays) + fg.min += Vec2F(distance); + + for (auto& b : m_backgroundBlocks) + b.position += distance; + + for (auto& b : m_foregroundBlocks) + b.position += distance; + + for (auto& object : m_objects) + object.position += distance; + + for (auto& flagPair : m_flaggedBlocks) { + for (auto& pos : flagPair.second) + pos += distance; + } +} + +Json WorldStructure::store() const { + auto overlayToJson = [](Overlay const& o) -> Json { + return JsonObject{{"min", jsonFromVec2F(o.min)}, {"image", o.image}, {"fullbright", o.fullbright}}; + }; + + auto blockToJson = [](Block const& b) -> Json { + return JsonObject{{"position", jsonFromVec2I(b.position)}, {"materialId", b.materialId}, {"residual", b.residual}}; + }; + + auto objectToJson = [](Object const& o) -> Json { + return JsonObject{ + {"position", jsonFromVec2I(o.position)}, + {"name", o.name}, + {"direction", DirectionNames.getRight(o.direction)}, + {"parameters", o.parameters}, + {"residual", o.residual}, + }; + }; + + return JsonObject{{"region", jsonFromRectI(m_region)}, + {"anchorPosition", jsonFromVec2I(m_anchorPosition)}, + {"config", m_config}, + {"backgroundOverlays", m_backgroundOverlays.transformed(overlayToJson)}, + {"foregroundOverlays", m_foregroundOverlays.transformed(overlayToJson)}, + {"backgroundBlocks", m_backgroundBlocks.transformed(blockToJson)}, + {"foregroundBlocks", m_foregroundBlocks.transformed(blockToJson)}, + {"objects", m_objects.transformed(objectToJson)}, + {"flaggedBlocks", + transform<JsonObject>(m_flaggedBlocks, + [](pair<String, List<Vec2I>> const& p) { + return pair<String, Json>(p.first, p.second.transformed(jsonFromVec2I)); + })}}; +} + +} |