v1.4.4
This commit is contained in:
commit
9c94d113d3
10260 changed files with 1237388 additions and 0 deletions
262
source/utility/update_tilesets.cpp
Normal file
262
source/utility/update_tilesets.cpp
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue