diff options
author | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
---|---|---|
committer | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
commit | 6352e8e3196f78388b6c771073f9e03eaa612673 (patch) | |
tree | e23772f79a7fbc41bc9108951e9e136857484bf4 /source/utility/update_tilesets.cpp | |
parent | 6741a057e5639280d85d0f88ba26f000baa58f61 (diff) |
everything everywhere
all at once
Diffstat (limited to 'source/utility/update_tilesets.cpp')
-rw-r--r-- | source/utility/update_tilesets.cpp | 262 |
1 files changed, 262 insertions, 0 deletions
diff --git a/source/utility/update_tilesets.cpp b/source/utility/update_tilesets.cpp new file mode 100644 index 0000000..f21f886 --- /dev/null +++ b/source/utility/update_tilesets.cpp @@ -0,0 +1,262 @@ +#include "StarAssets.hpp" +#include "StarLiquidsDatabase.hpp" +#include "StarMaterialDatabase.hpp" +#include "StarObject.hpp" +#include "StarObjectDatabase.hpp" +#include "StarRootLoader.hpp" +#include "tileset_updater.hpp" + +using namespace Star; + +String const InboundNode = "/tilesets/inboundnode.png"; +String const OutboundNode = "/tilesets/outboundnode.png"; +Vec3B const SourceLiquidBorderColor(0x80, 0x80, 0x00); + +void scanMaterials(TilesetUpdater& updater) { + auto& root = Root::singleton(); + auto materials = root.materialDatabase(); + + for (String materialName : materials->materialNames()) { + MaterialId id = materials->materialId(materialName); + Maybe<String> path = materials->materialPath(id); + if (!path) + continue; + String source = root.assets()->assetSource(*path); + + auto renderProfile = materials->materialRenderProfile(id); + if (renderProfile == nullptr) + continue; + + String tileset = materials->materialCategory(id); + String imagePath = renderProfile->pieceImage(renderProfile->representativePiece, 0); + ImageConstPtr image = root.assets()->image(imagePath); + + Tiled::Properties properties; + properties.set("material", materialName); + properties.set("//name", materialName); + properties.set("//shortdescription", materials->materialShortDescription(id)); + properties.set("//description", materials->materialDescription(id)); + + auto tile = make_shared<Tile>(Tile{source, "materials", tileset.toLower(), materialName, image, properties}); + updater.defineTile(tile); + } +} + +// imagePosition might not be aligned to a whole number, i.e. the image origin +// might not align with the tile grid. We do, however want Tile Objects in Tiled +// to be grid-aligned (valid positions are offset relative to the grid not +// completely free-form), so we correct the alignment by adding padding to the +// image that we export. +// We're going to ignore the fact that some objects have imagePositions that +// aren't even aligned _to pixels_ (e.g. giftsmallmonsterbox). +Vec2U objectPositionPadding(Vec2I imagePosition) { + int pixelsX = imagePosition.x(); + int pixelsY = imagePosition.y(); + + // Unsigned modulo operation gives the padding to use (in pixels) + unsigned padX = (unsigned)pixelsX % TilePixels; + unsigned padY = (unsigned)pixelsY % TilePixels; + return Vec2U(padX, padY); +} + +StringSet categorizeObject(String const& objectName, Vec2U imageSize) { + if (imageSize[0] >= 256 || imageSize[1] >= 256) + return StringSet{"huge-objects"}; + + auto& root = Root::singleton(); + auto assets = root.assets(); + auto objects = root.objectDatabase(); + + Json defaultCategories = assets->json("/objects/defaultCategories.config"); + + auto objectConfig = objects->getConfig(objectName); + + StringSet categories; + if (objectConfig->category != defaultCategories.getString("category")) + categories.insert("objects-by-category/" + objectConfig->category); + for (String const& tag : objectConfig->colonyTags) + categories.insert("objects-by-colonytag/" + tag); + if (objectConfig->type != defaultCategories.getString("objectType")) + categories.insert("objects-by-type/" + objectConfig->type); + if (objectConfig->race != defaultCategories.getString("race")) + categories.insert("objects-by-race/" + objectConfig->race); + + if (categories.size() == 0) + categories.insert("objects-uncategorized"); + + return transform<StringSet>(categories, [](String const& category) { return category.toLower(); }); +} + +void drawNodes(ImagePtr const& image, Vec2I imagePosition, JsonArray nodes, String nodeImagePath) { + ImageConstPtr nodeImage = Root::singleton().assets()->image(nodeImagePath); + for (Json const& node : nodes) { + Vec2I nodePos = jsonToVec2I(node) * TilePixels + Vec2I(0, TilePixels - nodeImage->height()); + Vec2U nodeImagePos = Vec2U(nodePos - imagePosition); + image->drawInto(nodeImagePos, *nodeImage); + } +} + +void defineObjectOrientation(TilesetUpdater& updater, + String const& objectName, + List<ObjectOrientationPtr> const& orientations, + int orientationIndex) { + auto& root = Root::singleton(); + auto assets = root.assets(); + auto objects = root.objectDatabase(); + + ObjectOrientationPtr orientation = orientations[orientationIndex]; + + Vec2I imagePosition = Vec2I(orientation->imagePosition * TilePixels); + + List<ImageConstPtr> layers; + unsigned width = 0, height = 0; + for (auto const& imageLayer : orientation->imageLayers) { + String imageName = imageLayer.imagePart().image.replaceTags(StringMap<String>{}, true, "default"); + + ImageConstPtr image = assets->image(imageName); + layers.append(image); + width = max(width, image->width()); + height = max(height, image->height()); + } + + Vec2U imagePadding = objectPositionPadding(imagePosition); + imagePosition -= Vec2I(imagePadding); + + // Padding is added to the right hand side as well as the left so that + // when objects are flipped in the editor, they're still aligned correctly. + Vec2U imageSize(width + 2 * imagePadding.x(), height + imagePadding.y()); + + ImagePtr combinedImage = make_shared<Image>(imageSize, PixelFormat::RGBA32); + combinedImage->fill(Vec4B(0, 0, 0, 0)); + for (ImageConstPtr const& layer : layers) { + combinedImage->drawInto(imagePadding, *layer); + } + + // Overlay the image with the wiring nodes: + auto objectConfig = objects->getConfig(objectName); + + drawNodes(combinedImage, imagePosition, objectConfig->config.getArray("inputNodes", {}), InboundNode); + drawNodes(combinedImage, imagePosition, objectConfig->config.getArray("outputNodes", {}), OutboundNode); + + ObjectPtr example = objects->createObject(objectName); + + Tiled::Properties properties; + properties.set("object", objectName); + properties.set("imagePositionX", imagePosition.x()); + properties.set("imagePositionY", imagePosition.y()); + properties.set("//shortdescription", example->shortDescription()); + properties.set("//description", example->description()); + + if (orientation->directionAffinity.isValid()) { + Direction direction = *orientation->directionAffinity; + if (orientation->flipImages) + direction = -direction; + properties.set("tilesetDirection", DirectionNames.getRight(direction)); + } + + StringSet tilesets = categorizeObject(objectName, imageSize); + + // tileName becomes part of the filename for the tile's image. Different + // orientations require different images, so the tileName must be different + // for each orientation. + String tileName = objectName; + if (orientationIndex != 0) + tileName += "_orientation" + toString(orientationIndex); + properties.set("//name", tileName); + + String source = assets->assetSource(objectConfig->path); + + for (String const& tileset : tilesets) { + TilePtr tile = make_shared<Tile>(Tile{source, "objects", tileset, tileName, combinedImage, properties}); + updater.defineTile(tile); + } +} + +void scanObjects(TilesetUpdater& updater) { + auto& root = Root::singleton(); + auto objects = root.objectDatabase(); + + for (String const& objectName : objects->allObjects()) { + auto orientations = objects->getOrientations(objectName); + if (orientations.size() < 1) { + Logger::warn("Object %s has no orientations and will not be exported", objectName); + continue; + } + + // Always export the first orientation + ObjectOrientationPtr orientation = orientations[0]; + defineObjectOrientation(updater, objectName, orientations, 0); + + // If there are more than 2 orientations or the imagePositions are different + // then horizontal flipping in the editor is not enough to get all the + // orientations and display them correctly, so we export each orientation + // as a separate tile. + for (unsigned i = 1; i < orientations.size(); ++i) { + if (i >= 2 || orientation->imagePosition != orientations[i]->imagePosition) + defineObjectOrientation(updater, objectName, orientations, i); + } + } +} + +void scanLiquids(TilesetUpdater& updater) { + auto& root = Root::singleton(); + auto liquids = root.liquidsDatabase(); + auto assets = root.assets(); + + Vec2U imageSize(TilePixels, TilePixels); + + for (auto liquid : liquids->allLiquidSettings()) { + ImagePtr image = make_shared<Image>(imageSize, PixelFormat::RGBA32); + image->fill(liquid->liquidColor); + + String assetSource = assets->assetSource(liquid->path); + + Tiled::Properties properties; + properties.set("liquid", liquid->name); + properties.set("//name", liquid->name); + auto tile = make_shared<Tile>(Tile{assetSource, "liquids", "liquids", liquid->name, image, properties}); + updater.defineTile(tile); + + ImagePtr sourceImage = make_shared<Image>(imageSize, PixelFormat::RGBA32); + sourceImage->copyInto(Vec2U(), *image.get()); + sourceImage->fillRect(Vec2U(), Vec2U(image->width(), 1), SourceLiquidBorderColor); + sourceImage->fillRect(Vec2U(), Vec2U(1, image->height()), SourceLiquidBorderColor); + + String sourceName = liquid->name + "_source"; + properties.set("source", true); + properties.set("//name", sourceName); + properties.set("//shortdescription", "Endless " + liquid->name); + auto sourceTile = make_shared<Tile>(Tile{assetSource, "liquids", "liquids", sourceName, sourceImage, properties}); + updater.defineTile(sourceTile); + } +} + +int main(int argc, char** argv) { + try { + RootLoader rootLoader({{}, {}, {}, LogLevel::Error, false, {}}); + + rootLoader.setSummary("Updates Tiled JSON tilesets in unpacked assets directories"); + + RootUPtr root; + OptionParser::Options options; + tie(root, options) = rootLoader.commandInitOrDie(argc, argv); + + TilesetUpdater updater; + + for (String source : root->assets()->assetSources()) { + Logger::info("Assets source: \"%s\"", source); + updater.defineAssetSource(source); + } + + scanMaterials(updater); + scanObjects(updater); + scanLiquids(updater); + + updater.exportTilesets(); + + return 0; + } catch (std::exception const& e) { + cerrf("exception caught: %s\n", outputException(e, true)); + return 1; + } +} |