Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/utility/update_tilesets.cpp
diff options
context:
space:
mode:
authorKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
committerKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
commit6352e8e3196f78388b6c771073f9e03eaa612673 (patch)
treee23772f79a7fbc41bc9108951e9e136857484bf4 /source/utility/update_tilesets.cpp
parent6741a057e5639280d85d0f88ba26f000baa58f61 (diff)
everything everywhere
all at once
Diffstat (limited to 'source/utility/update_tilesets.cpp')
-rw-r--r--source/utility/update_tilesets.cpp262
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;
+ }
+}