diff --git a/source/base/StarAssets.cpp b/source/base/StarAssets.cpp index 6bd9c09..e5313ea 100644 --- a/source/base/StarAssets.cpp +++ b/source/base/StarAssets.cpp @@ -234,11 +234,10 @@ void Assets::queueJsons(StringList const& paths) const { })); } -ImageConstPtr Assets::image(String const& path) const { - auto components = AssetPath::split(path); - validatePath(components, true, true); +ImageConstPtr Assets::image(AssetPath const& path) const { + validatePath(path, true, true); - return as(getAsset(AssetId{AssetType::Image, move(components)}))->image; + return as(getAsset(AssetId{AssetType::Image, path}))->image; } void Assets::queueImages(StringList const& paths) const { @@ -250,11 +249,10 @@ void Assets::queueImages(StringList const& paths) const { })); } -ImageConstPtr Assets::tryImage(String const& path) const { - auto components = AssetPath::split(path); - validatePath(components, true, true); +ImageConstPtr Assets::tryImage(AssetPath const& path) const { + validatePath(path, true, true); - if (auto imageData = as(tryAsset(AssetId{AssetType::Image, move(components)}))) + if (auto imageData = as(tryAsset(AssetId{AssetType::Image, path}))) return imageData->image; else return {}; diff --git a/source/base/StarAssets.hpp b/source/base/StarAssets.hpp index f6dc70a..15b3b50 100644 --- a/source/base/StarAssets.hpp +++ b/source/base/StarAssets.hpp @@ -119,12 +119,12 @@ public: // .frames or default.frames, going up to assets // root. May return the same ImageConstPtr for different paths if the paths // are equivalent or they are aliases of other image paths. - ImageConstPtr image(String const& path) const; + ImageConstPtr image(AssetPath const& path) const; // Load images using background processing void queueImages(StringList const& paths) const; // Return the given image *if* it is already loaded, otherwise queue it for // loading. - ImageConstPtr tryImage(String const& path) const; + ImageConstPtr tryImage(AssetPath const& path) const; // Returns the best associated FramesSpecification for a given image path, if // it exists. The given path must not contain sub-paths or directives, and diff --git a/source/core/StarAssetPath.cpp b/source/core/StarAssetPath.cpp index eb1cd4d..b49f24d 100644 --- a/source/core/StarAssetPath.cpp +++ b/source/core/StarAssetPath.cpp @@ -66,7 +66,7 @@ AssetPath AssetPath::split(String const& path) { ++i; } - if (!directives.empty()); + if (!directives.empty()) components.directives.append(move(directives)); } @@ -178,4 +178,8 @@ std::ostream& operator<<(std::ostream& os, AssetPath const& rhs) { return os; } +size_t hash::operator()(AssetPath const& s) const { + return hashOf(s.basePath, s.subPath, s.directives); +} + } diff --git a/source/core/StarAssetPath.hpp b/source/core/StarAssetPath.hpp index 3ca3f60..fd77005 100644 --- a/source/core/StarAssetPath.hpp +++ b/source/core/StarAssetPath.hpp @@ -2,6 +2,7 @@ #define STAR_ASSET_PATH_HPP #include "StarDirectives.hpp" +#include "StarHash.hpp" namespace Star { @@ -64,6 +65,11 @@ struct AssetPath { std::ostream& operator<<(std::ostream& os, AssetPath const& rhs); +template <> +struct hash { + size_t operator()(AssetPath const& s) const; +}; + } #endif \ No newline at end of file diff --git a/source/core/StarDirectives.cpp b/source/core/StarDirectives.cpp index e486ea9..8dcf8da 100644 --- a/source/core/StarDirectives.cpp +++ b/source/core/StarDirectives.cpp @@ -10,6 +10,16 @@ Directives::Entry::Entry(ImageOperation&& newOperation, String&& newString) { string = move(newString); } +Directives::Entry::Entry(ImageOperation const& newOperation, String const& newString) { + operation = newOperation; + string = newString; +} + +Directives::Entry::Entry(Entry const& other) { + operation = other.operation; + string = other.string; +} + Directives::Directives() {} Directives::Directives(String const& directives) { parse(directives); @@ -20,6 +30,17 @@ Directives::Directives(String&& directives) { parse(mine); } +Directives::Directives(const char* directives) { + String string(directives); + parse(string); +} + +Directives::Directives(List&& newEntries) { + entries = std::make_shared const>(move(newEntries)); + String directives = toString(); // This needs to be better + hash = XXH3_64bits(directives.utf8Ptr(), directives.utf8Size()); +} + void Directives::parse(String const& directives) { List newList; StringList split = directives.split('?'); @@ -41,6 +62,32 @@ void Directives::buildString(String& out) const { } } +String Directives::toString() const { + String result; + buildString(result); + return result; +} + +inline bool Directives::empty() const { + return entries->empty(); +} + + +DataStream& operator>>(DataStream& ds, Directives& directives) { + String string; + ds.read(string); + + directives.parse(string); + + return ds; +} + +DataStream& operator<<(DataStream& ds, Directives const& directives) { + ds.write(directives.toString()); + + return ds; +} + DirectivesGroup::DirectivesGroup() : m_count(0) {} DirectivesGroup::DirectivesGroup(String const& directives) { m_directives.emplace_back(directives); @@ -66,10 +113,21 @@ inline bool DirectivesGroup::compare(DirectivesGroup const& other) const { } void DirectivesGroup::append(Directives const& directives) { - m_directives.push_back(directives); + m_directives.emplace_back(directives); m_count += m_directives.back().entries->size(); } +void DirectivesGroup::append(List&& entries) { + size_t size = entries.size(); + m_directives.emplace_back(move(entries)); + m_count += size; +} + +void DirectivesGroup::clear() { + m_directives.clear(); + m_count = 0; +} + DirectivesGroup& DirectivesGroup::operator+=(Directives const& other) { append(other); return *this; diff --git a/source/core/StarDirectives.hpp b/source/core/StarDirectives.hpp index e954387..ca4364c 100644 --- a/source/core/StarDirectives.hpp +++ b/source/core/StarDirectives.hpp @@ -3,28 +3,40 @@ #include "StarImageProcessing.hpp" #include "StarHash.hpp" +#include "StarDataStream.hpp" namespace Star { +STAR_CLASS(Directives); STAR_CLASS(DirectivesGroup); STAR_EXCEPTION(DirectivesException, StarException); // Kae: My attempt at reducing memory allocation and per-frame string parsing for extremely long directives -struct Directives { +// entries must never be a null ptr! +class Directives { +public: struct Entry { ImageOperation operation; String string; Entry(ImageOperation&& newOperation, String&& newString); + Entry(ImageOperation const& newOperation, String const& newString); + Entry(Entry const& other); }; Directives(); Directives(String const& directives); Directives(String&& directives); + Directives(const char* directives); + Directives(List&& entries); void parse(String const& directives); - void buildString(String& out) const; + String toString() const; + inline bool empty() const; + + friend DataStream& operator>>(DataStream& ds, Directives& directives); + friend DataStream& operator<<(DataStream& ds, Directives const& directives); std::shared_ptr const> entries; size_t hash = 0; @@ -41,6 +53,9 @@ public: inline bool empty() const; bool compare(DirectivesGroup const& other) const; void append(Directives const& other); + void append(List&& entries); + void clear(); + DirectivesGroup& operator+=(Directives const& other); inline String toString() const; diff --git a/source/frontend/StarMainInterface.cpp b/source/frontend/StarMainInterface.cpp index 6c5dc1d..18a3b70 100644 --- a/source/frontend/StarMainInterface.cpp +++ b/source/frontend/StarMainInterface.cpp @@ -965,8 +965,9 @@ void MainInterface::renderBreath() { size_t blocks = round((10 * breath) / breathMax); if (blocks < 10) { - m_guiContext->drawQuad("/interface/breath/breath.png", - RectF::withCenter(breathBackgroundCenterPos, Vec2F(imgMetadata->imageSize("/interface/breath/breath.png")) * interfaceScale())); + String path = "/interface/breath/breath.png"; + m_guiContext->drawQuad(path, + RectF::withCenter(breathBackgroundCenterPos, Vec2F(imgMetadata->imageSize(path)) * interfaceScale())); for (size_t i = 0; i < 10; i++) { if (i >= blocks) { if (blocks == 0 && Time::monotonicMilliseconds() % 500 > 250) diff --git a/source/game/StarDrawable.cpp b/source/game/StarDrawable.cpp index 5fd5071..0aea570 100644 --- a/source/game/StarDrawable.cpp +++ b/source/game/StarDrawable.cpp @@ -9,37 +9,41 @@ namespace Star { -void Drawable::ImagePart::addDirectives(String const& directives, bool keepImageCenterPosition) { - if (directives.empty()) - return; +Drawable::ImagePart& Drawable::ImagePart::addDirectives(Directives const& directives, bool keepImageCenterPosition) { + if (directives.entries->empty()) + return *this; if (keepImageCenterPosition) { auto imageMetadata = Root::singleton().imageMetadataDatabase(); Vec2F imageSize = Vec2F(imageMetadata->imageSize(image)); - image = AssetPath::addDirectives(image, {directives}); + image.directives += directives; Vec2F newImageSize = Vec2F(imageMetadata->imageSize(image)); // If we are trying to maintain the image center, PRE translate the image by // the change in size / 2 transformation *= Mat3F::translation((imageSize - newImageSize) / 2); } else { - image = AssetPath::addDirectives(image, {directives}); + image.directives += directives; } + + return *this; } -void Drawable::ImagePart::removeDirectives(bool keepImageCenterPosition) { +Drawable::ImagePart& Drawable::ImagePart::removeDirectives(bool keepImageCenterPosition) { if (keepImageCenterPosition) { auto imageMetadata = Root::singleton().imageMetadataDatabase(); Vec2F imageSize = Vec2F(imageMetadata->imageSize(image)); - image = AssetPath::removeDirectives(image); + image.directives.clear(); Vec2F newImageSize = Vec2F(imageMetadata->imageSize(image)); // If we are trying to maintain the image center, PRE translate the image by // the change in size / 2 transformation *= Mat3F::translation((imageSize - newImageSize) / 2); } else { - image = AssetPath::removeDirectives(image); + image.directives.clear(); } + + return *this; } Drawable Drawable::makeLine(Line2F const& line, float lineWidth, Color const& color, Vec2F const& position) { @@ -60,7 +64,7 @@ Drawable Drawable::makePoly(PolyF poly, Color const& color, Vec2F const& positio return drawable; } -Drawable Drawable::makeImage(String image, float pixelSize, bool centered, Vec2F const& position, Color const& color) { +Drawable Drawable::makeImage(AssetPath image, float pixelSize, bool centered, Vec2F const& position, Color const& color) { Drawable drawable; Mat3F transformation = Mat3F::identity(); if (centered) { @@ -120,7 +124,7 @@ Json Drawable::toJson() const { } else if (auto poly = part.ptr()) { json.set("poly", jsonFromPolyF(poly->poly)); } else if (auto image = part.ptr()) { - json.set("image", image->image); + json.set("image", AssetPath::join(image->image)); json.set("transformation", jsonFromMat3F(image->transformation)); } @@ -242,14 +246,15 @@ DataStream& operator<<(DataStream& ds, Drawable::PolyPart const& poly) { return ds; } +// I need to find out if this is for network serialization or not eventually DataStream& operator>>(DataStream& ds, Drawable::ImagePart& image) { - ds >> image.image; + ds >> AssetPath::join(image.image); ds >> image.transformation; return ds; } DataStream& operator<<(DataStream& ds, Drawable::ImagePart const& image) { - ds << image.image; + ds << AssetPath::join(image.image); ds << image.transformation; return ds; } diff --git a/source/game/StarDrawable.hpp b/source/game/StarDrawable.hpp index 0109611..cf1c8c8 100644 --- a/source/game/StarDrawable.hpp +++ b/source/game/StarDrawable.hpp @@ -6,6 +6,7 @@ #include "StarPoly.hpp" #include "StarColor.hpp" #include "StarJson.hpp" +#include "StarAssetPath.hpp" namespace Star { @@ -20,7 +21,7 @@ struct Drawable { }; struct ImagePart { - String image; + AssetPath image; // Transformation of the image in pixel space (0, 0) - (width, height) to // the final drawn space Mat3F transformation; @@ -28,17 +29,17 @@ struct Drawable { // Add directives to this ImagePart, while optionally keeping the // transformed center of the image the same if the directives change the // image size. - void addDirectives(String const& directives, bool keepImageCenterPosition); + ImagePart& addDirectives(Directives const& directives, bool keepImageCenterPosition = false); // Remove directives from this ImagePart, while optionally keeping the // transformed center of the image the same if the directives change the // image size. - void removeDirectives(bool keepImageCenterPosition); + ImagePart& removeDirectives(bool keepImageCenterPosition = false); }; static Drawable makeLine(Line2F const& line, float lineWidth, Color const& color, Vec2F const& position = Vec2F()); static Drawable makePoly(PolyF poly, Color const& color, Vec2F const& position = Vec2F()); - static Drawable makeImage(String image, float pixelSize, bool centered, Vec2F const& position, Color const& color = Color::White); + static Drawable makeImage(AssetPath image, float pixelSize, bool centered, Vec2F const& position, Color const& color = Color::White); template static void translateAll(DrawablesContainer& drawables, Vec2F const& translation); diff --git a/source/game/StarHumanoid.cpp b/source/game/StarHumanoid.cpp index acbed89..b33df4b 100644 --- a/source/game/StarHumanoid.cpp +++ b/source/game/StarHumanoid.cpp @@ -42,7 +42,11 @@ HumanoidIdentity::HumanoidIdentity(Json config) { hairType = config.getString("hairType", "male1"); hairDirectives = config.getString("hairDirectives", ""); bodyDirectives = config.getString("bodyDirectives", ""); - emoteDirectives = config.getString("emoteDirectives", bodyDirectives); + if (auto jEmoteDirectives = config.optString("emoteDirectives")) // Passing Directives as a default arg would be inefficient + emoteDirectives = jEmoteDirectives.take(); + else + emoteDirectives = bodyDirectives; + facialHairGroup = config.getString("facialHairGroup", ""); facialHairType = config.getString("facialHairType", ""); facialHairDirectives = config.getString("facialHairDirectives", ""); @@ -67,15 +71,15 @@ Json HumanoidIdentity::toJson() const { {"gender", GenderNames.getRight(gender)}, {"hairGroup", hairGroup}, {"hairType", hairType}, - {"hairDirectives", hairDirectives}, - {"bodyDirectives", bodyDirectives}, - {"emoteDirectives", emoteDirectives}, + {"hairDirectives", hairDirectives.toString()}, + {"bodyDirectives", bodyDirectives.toString()}, + {"emoteDirectives", emoteDirectives.toString()}, {"facialHairGroup", facialHairGroup}, {"facialHairType", facialHairType}, - {"facialHairDirectives", facialHairDirectives}, + {"facialHairDirectives", facialHairDirectives.toString()}, {"facialMaskGroup", facialMaskGroup}, {"facialMaskType", facialMaskType}, - {"facialMaskDirectives", facialMaskDirectives}, + {"facialMaskDirectives", facialMaskDirectives.toString()}, {"personalityIdle", personality.idle}, {"personalityArmIdle", personality.armIdle}, {"personalityHeadOffset", jsonFromVec2F(personality.headOffset)}, @@ -300,7 +304,7 @@ HumanoidIdentity const& Humanoid::identity() const { return m_identity; } -void Humanoid::setHeadArmorDirectives(String directives) { +void Humanoid::setHeadArmorDirectives(Directives directives) { m_headArmorDirectives = move(directives); } @@ -308,7 +312,7 @@ void Humanoid::setHeadArmorFrameset(String headFrameset) { m_headArmorFrameset = move(headFrameset); } -void Humanoid::setChestArmorDirectives(String directives) { +void Humanoid::setChestArmorDirectives(Directives directives) { m_chestArmorDirectives = move(directives); } @@ -324,7 +328,7 @@ void Humanoid::setFrontSleeveFrameset(String frontSleeveFrameset) { m_frontSleeveFrameset = move(frontSleeveFrameset); } -void Humanoid::setLegsArmorDirectives(String directives) { +void Humanoid::setLegsArmorDirectives(Directives directives) { m_legsArmorDirectives = move(directives); } @@ -332,7 +336,7 @@ void Humanoid::setLegsArmorFrameset(String legsFrameset) { m_legsArmorFrameset = move(legsFrameset); } -void Humanoid::setBackArmorDirectives(String directives) { +void Humanoid::setBackArmorDirectives(Directives directives) { m_backArmorDirectives = move(directives); } @@ -340,7 +344,7 @@ void Humanoid::setBackArmorFrameset(String backFrameset) { m_backArmorFrameset = move(backFrameset); } -void Humanoid::setHelmetMaskDirectives(String helmetMaskDirectives) { +void Humanoid::setHelmetMaskDirectives(Directives helmetMaskDirectives) { m_helmetMaskDirectives = move(helmetMaskDirectives); } @@ -464,7 +468,7 @@ List Humanoid::render() { int armStateSeq = getArmStateSequence(); int bodyStateSeq = getBodyStateSequence(); - int emoteStateSeq = m_timing.emoteStateSeq(m_emoteAnimationTimer, m_emoteState); + int emoteStateSeq = getEmoteStateSequence(); float bobYOffset = getBobYOffset(); Maybe dance = getDance(); Maybe danceStep = {}; @@ -492,9 +496,10 @@ List Humanoid::render() { drawables.append(move(drawable)); }; - auto backArmDrawable = [&](String const& frameSet, String const& directives) -> Drawable { - String image = strf("%s:%s%s", frameSet, backHand.backFrame, directives); + auto backArmDrawable = [&](String const& frameSet, Directives const& directives) -> Drawable { + String image = strf("%s:%s", frameSet, backHand.backFrame); Drawable backArm = Drawable::makeImage(move(image), 1.0f / TilePixels, true, backArmFrameOffset); + backArm.imagePart().addDirectives(directives); backArm.rotate(backHand.angle, backArmFrameOffset + m_backArmRotationCenter + m_backArmOffset); return backArm; }; @@ -505,13 +510,15 @@ List Humanoid::render() { frameGroup = "runbackwards"; String image; if (dance.isValid() && danceStep->bodyFrame) - image = strf("%s:%s%s", m_backArmorFrameset, *danceStep->bodyFrame, getBackDirectives()); + image = strf("%s:%s", m_backArmorFrameset, *danceStep->bodyFrame); else if (m_state == Idle) - image = strf("%s:%s%s", m_backArmorFrameset, m_identity.personality.idle, getBackDirectives()); + image = strf("%s:%s", m_backArmorFrameset, m_identity.personality.idle); else - image = strf("%s:%s.%s%s", m_backArmorFrameset, frameGroup, bodyStateSeq, getBackDirectives()); + image = strf("%s:%s.%s", m_backArmorFrameset, frameGroup, bodyStateSeq); - addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, Vec2F())); + auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, Vec2F()); + drawable.imagePart().addDirectives(getBackDirectives()); + addDrawable(move(drawable)); } if (backHand.holdingItem && !dance.isValid()) { @@ -535,33 +542,35 @@ List Humanoid::render() { String image; Vec2F position; if (dance.isValid() && danceStep->backArmFrame) { - image = strf("%s:%s%s", m_backArmFrameset, *danceStep->backArmFrame, getBodyDirectives()); + image = strf("%s:%s", m_backArmFrameset, *danceStep->backArmFrame); position = danceStep->backArmOffset / TilePixels; } else if (m_state == Idle) { - image = strf("%s:%s%s", m_backArmFrameset, m_identity.personality.armIdle, getBodyDirectives()); + image = strf("%s:%s", m_backArmFrameset, m_identity.personality.armIdle); position = m_identity.personality.armOffset / TilePixels; } else - image = strf("%s:%s.%s%s", m_backArmFrameset, frameBase(m_state), armStateSeq, getBodyDirectives()); + image = strf("%s:%s.%s", m_backArmFrameset, frameBase(m_state), armStateSeq); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position); + drawable.imagePart().addDirectives(getBodyDirectives()); if (dance.isValid()) drawable.rotate(danceStep->backArmRotation); - addDrawable(drawable, m_bodyFullbright); + addDrawable(move(drawable), m_bodyFullbright); } if (!m_backSleeveFrameset.empty()) { String image; Vec2F position; if (dance.isValid() && danceStep->backArmFrame) { - image = strf("%s:%s%s", m_backSleeveFrameset, *danceStep->backArmFrame, getChestDirectives()); + image = strf("%s:%s", m_backSleeveFrameset, *danceStep->backArmFrame); position = danceStep->backArmOffset / TilePixels; } else if (m_state == Idle) { - image = strf("%s:%s%s", m_backSleeveFrameset, m_identity.personality.armIdle, getChestDirectives()); + image = strf("%s:%s", m_backSleeveFrameset, m_identity.personality.armIdle); position = m_identity.personality.armOffset / TilePixels; } else - image = strf("%s:%s.%s%s", m_backSleeveFrameset, frameBase(m_state), armStateSeq, getChestDirectives()); + image = strf("%s:%s.%s", m_backSleeveFrameset, frameBase(m_state), armStateSeq); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position); + drawable.imagePart().addDirectives(getChestDirectives()); if (dance.isValid()) drawable.rotate(danceStep->backArmRotation); - addDrawable(drawable); + addDrawable(move(drawable)); } } @@ -582,80 +591,99 @@ List Humanoid::render() { headPosition += m_headLayOffset; if (!m_headFrameset.empty() && !m_bodyHidden) { - String image = strf("%s:normal%s", m_headFrameset, getBodyDirectives()); - addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition), m_bodyFullbright); + String image = strf("%s:normal", m_headFrameset); + auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition); + drawable.imagePart().addDirectives(getBodyDirectives()); + addDrawable(move(drawable), m_bodyFullbright); } if (!m_emoteFrameset.empty() && !m_bodyHidden) { - String image = strf("%s:%s.%s%s", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq, getEmoteDirectives()); - addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition), m_bodyFullbright); + String image = strf("%s:%s.%s", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq); + auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition); + drawable.imagePart().addDirectives(getEmoteDirectives()); + addDrawable(move(drawable), m_bodyFullbright); } if (!m_hairFrameset.empty() && !m_bodyHidden) { - String image = strf("%s:normal%s", m_hairFrameset, getHairDirectives() + getHelmetMaskDirectives()); - addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition), m_bodyFullbright); + String image = strf("%s:normal", m_hairFrameset); + auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition); + drawable.imagePart().addDirectives(getHairDirectives()).addDirectives(getHelmetMaskDirectives()); + addDrawable(move(drawable), m_bodyFullbright); } if (!m_bodyFrameset.empty() && !m_bodyHidden) { String image; if (dance.isValid() && danceStep->bodyFrame) - image = strf("%s:%s%s", m_bodyFrameset, *danceStep->bodyFrame, getBodyDirectives()); + image = strf("%s:%s", m_bodyFrameset, *danceStep->bodyFrame); else if (m_state == Idle) - image = strf("%s:%s%s", m_bodyFrameset, m_identity.personality.idle, getBodyDirectives()); + image = strf("%s:%s", m_bodyFrameset, m_identity.personality.idle); else - image = strf("%s:%s.%s%s", m_bodyFrameset, frameBase(m_state), bodyStateSeq, getBodyDirectives()); - addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, {}), m_bodyFullbright); + image = strf("%s:%s.%s", m_bodyFrameset, frameBase(m_state), bodyStateSeq); + auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, {}); + drawable.imagePart().addDirectives(getBodyDirectives()); + addDrawable(move(drawable), m_bodyFullbright); } if (!m_legsArmorFrameset.empty()) { String image; if (dance.isValid() && danceStep->bodyFrame) - image = strf("%s:%s%s", m_legsArmorFrameset, *danceStep->bodyFrame, getLegsDirectives()); + image = strf("%s:%s", m_legsArmorFrameset, *danceStep->bodyFrame); else if (m_state == Idle) - image = strf("%s:%s%s", m_legsArmorFrameset, m_identity.personality.idle, getLegsDirectives()); + image = strf("%s:%s", m_legsArmorFrameset, m_identity.personality.idle); else - image = strf("%s:%s.%s%s", m_legsArmorFrameset, frameBase(m_state), bodyStateSeq, getLegsDirectives()); - addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, {})); + image = strf("%s:%s.%s", m_legsArmorFrameset, frameBase(m_state), bodyStateSeq); + auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, {}); + drawable.imagePart().addDirectives(getLegsDirectives()); + addDrawable(move(drawable)); } if (!m_chestArmorFrameset.empty()) { String image; Vec2F position; if (dance.isValid() && danceStep->bodyFrame) - image = strf("%s:%s%s", m_chestArmorFrameset, *danceStep->bodyFrame, getChestDirectives()); + image = strf("%s:%s", m_chestArmorFrameset, *danceStep->bodyFrame); else if (m_state == Run) - image = strf("%s:run%s", m_chestArmorFrameset, getChestDirectives()); + image = strf("%s:run", m_chestArmorFrameset); else if (m_state == Idle) - image = strf("%s:%s%s", m_chestArmorFrameset, m_identity.personality.idle, getChestDirectives()); + image = strf("%s:%s", m_chestArmorFrameset, m_identity.personality.idle); else if (m_state == Duck) - image = strf("%s:duck%s", m_chestArmorFrameset, getChestDirectives()); + image = strf("%s:duck", m_chestArmorFrameset); else if ((m_state == Swim) || (m_state == SwimIdle)) - image = strf("%s:swim%s", m_chestArmorFrameset, getChestDirectives()); + image = strf("%s:swim", m_chestArmorFrameset); else - image = strf("%s:chest.1%s", m_chestArmorFrameset, getChestDirectives()); + image = strf("%s:chest.1", m_chestArmorFrameset); if (m_state != Duck) position[1] += bobYOffset; - addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, position)); + auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position); + drawable.imagePart().addDirectives(getChestDirectives()); + addDrawable(move(drawable)); } if (!m_facialHairFrameset.empty() && !m_bodyHidden) { - String image = strf("%s:normal%s", m_facialHairFrameset, getFacialHairDirectives() + getHelmetMaskDirectives()); - addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition), m_bodyFullbright); + String image = strf("%s:normal", m_facialHairFrameset); + auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition); + drawable.imagePart().addDirectives(getFacialHairDirectives()).addDirectives(getHelmetMaskDirectives()); + addDrawable(move(drawable), m_bodyFullbright); } if (!m_facialMaskFrameset.empty() && !m_bodyHidden) { - String image = strf("%s:normal%s", m_facialMaskFrameset, getFacialMaskDirectives() + getHelmetMaskDirectives()); - addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition)); + String image = strf("%s:normal", m_facialMaskFrameset); + auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition); + drawable.imagePart().addDirectives(getFacialMaskDirectives()).addDirectives(getHelmetMaskDirectives()); + addDrawable(move(drawable)); } if (!m_headArmorFrameset.empty()) { - String image = strf("%s:normal%s", m_headArmorFrameset, getHeadDirectives()); - addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition)); + String image = strf("%s:normal", m_headArmorFrameset); + auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition); + drawable.imagePart().addDirectives(getHeadDirectives()); + addDrawable(move(drawable)); } - auto frontArmDrawable = [&](String const& frameSet, String const& directives) -> Drawable { - String image = strf("%s:%s%s", frameSet, frontHand.frontFrame, directives); + auto frontArmDrawable = [&](String const& frameSet, Directives const& directives) -> Drawable { + String image = strf("%s:%s", frameSet, frontHand.frontFrame); Drawable frontArm = Drawable::makeImage(image, 1.0f / TilePixels, true, frontArmFrameOffset); + frontArm.imagePart().addDirectives(directives); frontArm.rotate(frontHand.angle, frontArmFrameOffset + m_frontArmRotationCenter); return frontArm; }; @@ -682,14 +710,15 @@ List Humanoid::render() { String image; Vec2F position; if (dance.isValid() && danceStep->frontArmFrame) { - image = strf("%s:%s%s", m_frontArmFrameset, *danceStep->frontArmFrame, getBodyDirectives()); + image = strf("%s:%s", m_frontArmFrameset, *danceStep->frontArmFrame); position = danceStep->frontArmOffset / TilePixels; } else if (m_state == Idle) { - image = strf("%s:%s%s", m_frontArmFrameset, m_identity.personality.armIdle, getBodyDirectives()); + image = strf("%s:%s", m_frontArmFrameset, m_identity.personality.armIdle); position = m_identity.personality.armOffset / TilePixels; } else - image = strf("%s:%s.%s%s", m_frontArmFrameset, frameBase(m_state), armStateSeq, getBodyDirectives()); + image = strf("%s:%s.%s", m_frontArmFrameset, frameBase(m_state), armStateSeq); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position); + drawable.imagePart().addDirectives(getBodyDirectives()); if (dance.isValid()) drawable.rotate(danceStep->frontArmRotation); addDrawable(drawable, m_bodyFullbright); @@ -699,14 +728,15 @@ List Humanoid::render() { String image; Vec2F position; if (dance.isValid() && danceStep->frontArmFrame) { - image = strf("%s:%s%s", m_frontSleeveFrameset, *danceStep->frontArmFrame, getChestDirectives()); + image = strf("%s:%s", m_frontSleeveFrameset, *danceStep->frontArmFrame); position = danceStep->frontArmOffset / TilePixels; } else if (m_state == Idle) { - image = strf("%s:%s%s", m_frontSleeveFrameset, m_identity.personality.armIdle, getChestDirectives()); + image = strf("%s:%s", m_frontSleeveFrameset, m_identity.personality.armIdle); position = m_identity.personality.armOffset / TilePixels; } else - image = strf("%s:%s.%s%s", m_frontSleeveFrameset, frameBase(m_state), armStateSeq, getChestDirectives()); - auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position); + image = strf("%s:%s.%s", m_frontSleeveFrameset, frameBase(m_state), armStateSeq); + auto drawable = Drawable::makeImage(image, 1.0f / TilePixels, true, position); + drawable.imagePart().addDirectives(getChestDirectives()); if (dance.isValid()) drawable.rotate(danceStep->frontArmRotation); addDrawable(drawable); @@ -717,7 +747,7 @@ List Humanoid::render() { auto image = strf("%s:%d", m_vaporTrailFrameset, m_timing.genericSeq(m_animationTimer, m_vaporTrailCycle, m_vaporTrailFrames, true)); - addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, {})); + addDrawable(Drawable::makeImage(AssetPath::split(image), 1.0f / TilePixels, true, {})); } if (m_primaryHand.nonRotatedDrawables.size()) @@ -736,18 +766,19 @@ List Humanoid::renderPortrait(PortraitMode mode) const { List drawables; int emoteStateSeq = m_timing.emoteStateSeq(m_emoteAnimationTimer, m_emoteState); - auto addDrawable = [&](Drawable drawable) { + auto addDrawable = [&](Drawable&& drawable) -> Drawable& { if (mode != PortraitMode::Full && mode != PortraitMode::FullNeutral && mode != PortraitMode::FullNude && mode != PortraitMode::FullNeutralNude) { // TODO: make this configurable - drawable.imagePart().addDirectives("addmask=/humanoid/portraitMask.png;0;0", false); + drawable.imagePart().addDirectives(String("addmask=/humanoid/portraitMask.png;0;0"), false); } drawables.append(std::move(drawable)); + return drawables.back(); }; bool dressed = !(mode == PortraitMode::FullNude || mode == PortraitMode::FullNeutralNude); - auto helmetMaskDirective = dressed ? getHelmetMaskDirectives() : ""; + Directives helmetMaskDirective = dressed ? getHelmetMaskDirectives() : Directives(); auto personality = m_identity.personality; if (mode == PortraitMode::FullNeutral || mode == PortraitMode::FullNeutralNude) @@ -755,78 +786,105 @@ List Humanoid::renderPortrait(PortraitMode mode) const { if (mode != PortraitMode::Head) { if (!m_backArmFrameset.empty()) { - String image = strf("%s:%s%s", m_backArmFrameset, personality.armIdle, getBodyDirectives()); - addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.armOffset)); + String image = strf("%s:%s", m_backArmFrameset, personality.armIdle); + Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.armOffset); + drawable.imagePart().addDirectives(getBodyDirectives()); + addDrawable(move(drawable)); } if (dressed && !m_backSleeveFrameset.empty()) { - String image = strf("%s:%s%s", m_backSleeveFrameset, personality.armIdle, getChestDirectives()); - addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.armOffset)); + String image = strf("%s:%s", m_backSleeveFrameset, personality.armIdle); + Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.armOffset); + drawable.imagePart().addDirectives(getChestDirectives()); + addDrawable(move(drawable)); } if (mode != PortraitMode::Bust) { if (dressed && !m_backArmorFrameset.empty()) { - String image = strf("%s:%s%s", m_backArmorFrameset, personality.idle, getBackDirectives()); - addDrawable(Drawable::makeImage(move(image), 1.0f, true, {})); + String image = strf("%s:%s", m_backArmorFrameset, personality.idle); + Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, {}); + drawable.imagePart().addDirectives(getBackDirectives()); + addDrawable(move(drawable)); } } } if (!m_headFrameset.empty()) { - String image = strf("%s:normal%s", m_headFrameset, getBodyDirectives()); - addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.headOffset)); + String image = strf("%s:normal", m_headFrameset); + Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset); + drawable.imagePart().addDirectives(getBodyDirectives()); + addDrawable(move(drawable)); } if (!m_emoteFrameset.empty()) { - String image = - strf("%s:%s.%s%s", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq, getEmoteDirectives()); - addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.headOffset)); + String image = strf("%s:%s.%s", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq); + Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset); + drawable.imagePart().addDirectives(getEmoteDirectives()); + addDrawable(move(drawable)); } if (!m_hairFrameset.empty()) { - String image = strf("%s:normal%s", m_hairFrameset, getHairDirectives() + helmetMaskDirective); - addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.headOffset)); + String image = strf("%s:normal", m_hairFrameset); + Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset); + drawable.imagePart().addDirectives(getHairDirectives()).addDirectives(helmetMaskDirective); + addDrawable(move(drawable)); } if (!m_bodyFrameset.empty()) { - String image = strf("%s:%s%s", m_bodyFrameset, personality.idle, getBodyDirectives()); - addDrawable(Drawable::makeImage(move(image), 1.0f, true, {})); + String image = strf("%s:%s", m_bodyFrameset, personality.idle); + Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, {}); + drawable.imagePart().addDirectives(getBodyDirectives()); + addDrawable(move(drawable)); } if (mode != PortraitMode::Head) { if (dressed && !m_legsArmorFrameset.empty()) { - String image = strf("%s:%s%s", m_legsArmorFrameset, personality.idle, getLegsDirectives()); - addDrawable(Drawable::makeImage(move(image), 1.0f, true, {})); + String image = strf("%s:%s", m_legsArmorFrameset, personality.idle); + Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, {}); + drawable.imagePart().addDirectives(getLegsDirectives()); + addDrawable(move(drawable)); } if (dressed && !m_chestArmorFrameset.empty()) { - String image = strf("%s:%s%s", m_chestArmorFrameset, personality.idle, getChestDirectives()); - addDrawable(Drawable::makeImage(move(image), 1.0f, true, {})); + String image = strf("%s:%s", m_chestArmorFrameset, personality.idle); + Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, {}); + drawable.imagePart().addDirectives(getChestDirectives()); + addDrawable(move(drawable)); } } if (!m_facialHairFrameset.empty()) { - String image = strf("%s:normal%s", m_facialHairFrameset, getFacialHairDirectives() + helmetMaskDirective); - addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.headOffset)); + String image = strf("%s:normal", m_facialHairFrameset); + Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset); + drawable.imagePart().addDirectives(getFacialHairDirectives()).addDirectives(helmetMaskDirective); + addDrawable(move(drawable)); } if (!m_facialMaskFrameset.empty()) { - String image = strf("%s:normal%s", m_facialMaskFrameset, getFacialMaskDirectives() + helmetMaskDirective); - addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.headOffset)); + String image = strf("%s:normal", m_facialMaskFrameset); + Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset); + drawable.imagePart().addDirectives(getFacialMaskDirectives()).addDirectives(helmetMaskDirective); + addDrawable(move(drawable)); } if (dressed && !m_headArmorFrameset.empty()) { - String image = strf("%s:normal%s", m_headArmorFrameset, getHeadDirectives()); - addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.headOffset)); + String image = strf("%s:normal", m_headArmorFrameset); + Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset); + drawable.imagePart().addDirectives(getHeadDirectives()); + addDrawable(move(drawable)); } if (mode != PortraitMode::Head) { if (!m_frontArmFrameset.empty()) { - String image = strf("%s:%s%s", m_frontArmFrameset, personality.armIdle, getBodyDirectives()); - addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.armOffset)); + String image = strf("%s:%s", m_frontArmFrameset, personality.armIdle); + Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.armOffset); + drawable.imagePart().addDirectives(getBodyDirectives()); + addDrawable(move(drawable)); } if (dressed && !m_frontSleeveFrameset.empty()) { - String image = strf("%s:%s%s", m_frontSleeveFrameset, personality.armIdle, getChestDirectives()); - addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.armOffset)); + String image = strf("%s:%s", m_frontSleeveFrameset, personality.armIdle); + Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.armOffset); + drawable.imagePart().addDirectives(getChestDirectives()); + addDrawable(move(drawable)); } } @@ -1062,46 +1120,50 @@ String Humanoid::getVaporTrailFrameset() const { return "/humanoid/any/flames.png"; } -String Humanoid::getBodyDirectives() const { +Directives Humanoid::getBodyDirectives() const { return m_identity.bodyDirectives; } -String Humanoid::getHairDirectives() const { +Directives Humanoid::getHairDirectives() const { return m_identity.hairDirectives; } -String Humanoid::getEmoteDirectives() const { +Directives Humanoid::getEmoteDirectives() const { return m_identity.emoteDirectives; } -String Humanoid::getFacialHairDirectives() const { +Directives Humanoid::getFacialHairDirectives() const { return m_identity.facialHairDirectives; } -String Humanoid::getFacialMaskDirectives() const { +Directives Humanoid::getFacialMaskDirectives() const { return m_identity.facialMaskDirectives; } -String Humanoid::getHelmetMaskDirectives() const { +Directives Humanoid::getHelmetMaskDirectives() const { return m_helmetMaskDirectives; } -String Humanoid::getHeadDirectives() const { +Directives Humanoid::getHeadDirectives() const { return m_headArmorDirectives; } -String Humanoid::getChestDirectives() const { +Directives Humanoid::getChestDirectives() const { return m_chestArmorDirectives; } -String Humanoid::getLegsDirectives() const { +Directives Humanoid::getLegsDirectives() const { return m_legsArmorDirectives; } -String Humanoid::getBackDirectives() const { +Directives Humanoid::getBackDirectives() const { return m_backArmorDirectives; } +int Humanoid::getEmoteStateSequence() const { + return m_timing.emoteStateSeq(m_emoteAnimationTimer, m_emoteState); +} + int Humanoid::getArmStateSequence() const { int stateSeq = m_timing.stateSeq(m_animationTimer, m_state); diff --git a/source/game/StarHumanoid.hpp b/source/game/StarHumanoid.hpp index a4ef264..9850d4a 100644 --- a/source/game/StarHumanoid.hpp +++ b/source/game/StarHumanoid.hpp @@ -60,15 +60,15 @@ struct HumanoidIdentity { String hairGroup; // Must have :normal and :climb String hairType; - String hairDirectives; - String bodyDirectives; - String emoteDirectives; + Directives hairDirectives; + Directives bodyDirectives; + Directives emoteDirectives; String facialHairGroup; String facialHairType; - String facialHairDirectives; + Directives facialHairDirectives; String facialMaskGroup; String facialMaskType; - String facialMaskDirectives; + Directives facialMaskDirectives; Personality personality; Vec4B color; @@ -125,11 +125,11 @@ public: // empty string, it is disabled. // Asset directives for the head armor. - void setHeadArmorDirectives(String directives); + void setHeadArmorDirectives(Directives directives); // Must have :normal, climb void setHeadArmorFrameset(String headFrameset); // Asset directives for the chest, back and front arms armor. - void setChestArmorDirectives(String directives); + void setChestArmorDirectives(Directives directives); // Will have :run, :normal, and :duck void setChestArmorFrameset(String chest); // Same as back arm image frames @@ -138,16 +138,16 @@ public: void setFrontSleeveFrameset(String frontSleeveFrameset); // Asset directives for the legs armor. - void setLegsArmorDirectives(String directives); + void setLegsArmorDirectives(Directives directives); // Must have :idle, :duck, :walk[1-8], :run[1-8], :jump[1-4], :fall[1-4] void setLegsArmorFrameset(String legsFrameset); // Asset directives for the back armor. - void setBackArmorDirectives(String directives); + void setBackArmorDirectives(Directives directives); // Must have :idle, :duck, :walk[1-8], :run[1-8], :jump[1-4], :fall[1-4] void setBackArmorFrameset(String backFrameset); - void setHelmetMaskDirectives(String helmetMaskDirectives); + void setHelmetMaskDirectives(Directives helmetMaskDirectives); void setBodyHidden(bool hidden); @@ -260,16 +260,16 @@ private: String getFrontArmFromIdentity() const; String getVaporTrailFrameset() const; - String getBodyDirectives() const; - String getHairDirectives() const; - String getEmoteDirectives() const; - String getFacialHairDirectives() const; - String getFacialMaskDirectives() const; - String getHelmetMaskDirectives() const; - String getHeadDirectives() const; - String getChestDirectives() const; - String getLegsDirectives() const; - String getBackDirectives() const; + Directives getBodyDirectives() const; + Directives getHairDirectives() const; + Directives getEmoteDirectives() const; + Directives getFacialHairDirectives() const; + Directives getFacialMaskDirectives() const; + Directives getHelmetMaskDirectives() const; + Directives getHeadDirectives() const; + Directives getChestDirectives() const; + Directives getLegsDirectives() const; + Directives getBackDirectives() const; int getEmoteStateSequence() const; int getArmStateSequence() const; @@ -327,14 +327,14 @@ private: String m_backSleeveFrameset; String m_frontSleeveFrameset; String m_headArmorFrameset; - String m_headArmorDirectives; + Directives m_headArmorDirectives; String m_chestArmorFrameset; - String m_chestArmorDirectives; + Directives m_chestArmorDirectives; String m_legsArmorFrameset; - String m_legsArmorDirectives; + Directives m_legsArmorDirectives; String m_backArmorFrameset; - String m_backArmorDirectives; - String m_helmetMaskDirectives; + Directives m_backArmorDirectives; + Directives m_helmetMaskDirectives; State m_state; HumanoidEmote m_emoteState; diff --git a/source/game/StarImageMetadataDatabase.cpp b/source/game/StarImageMetadataDatabase.cpp index 7c619d3..3bf8d96 100644 --- a/source/game/StarImageMetadataDatabase.cpp +++ b/source/game/StarImageMetadataDatabase.cpp @@ -10,7 +10,7 @@ namespace Star { -Vec2U ImageMetadataDatabase::imageSize(String const& path) const { +Vec2U ImageMetadataDatabase::imageSize(AssetPath const& path) const { MutexLocker locker(m_mutex); auto i = m_sizeCache.find(path); if (i != m_sizeCache.end()) @@ -24,7 +24,7 @@ Vec2U ImageMetadataDatabase::imageSize(String const& path) const { return size; } -List ImageMetadataDatabase::imageSpaces(String const& path, Vec2F position, float fillLimit, bool flip) const { +List ImageMetadataDatabase::imageSpaces(AssetPath const& path, Vec2F position, float fillLimit, bool flip) const { SpacesEntry key = make_tuple(path, Vec2I::round(position), fillLimit, flip); MutexLocker locker(m_mutex); @@ -33,7 +33,7 @@ List ImageMetadataDatabase::imageSpaces(String const& path, Vec2F positio return i->second; } - String filteredPath = filterProcessing(path); + auto filteredPath = filterProcessing(path); SpacesEntry filteredKey = make_tuple(filteredPath, Vec2I::round(position), fillLimit, flip); auto j = m_spacesCache.find(filteredKey); @@ -88,14 +88,14 @@ List ImageMetadataDatabase::imageSpaces(String const& path, Vec2F positio return spaces; } -RectU ImageMetadataDatabase::nonEmptyRegion(String const& path) const { +RectU ImageMetadataDatabase::nonEmptyRegion(AssetPath const& path) const { MutexLocker locker(m_mutex); auto i = m_regionCache.find(path); if (i != m_regionCache.end()) { return i->second; } - String filteredPath = filterProcessing(path); + auto filteredPath = filterProcessing(path); auto j = m_regionCache.find(filteredPath); if (j != m_regionCache.end()) { m_regionCache[path] = j->second; @@ -117,12 +117,11 @@ RectU ImageMetadataDatabase::nonEmptyRegion(String const& path) const { return region; } -String ImageMetadataDatabase::filterProcessing(String const& path) { - AssetPath components = AssetPath::split(path); - auto directives = move(components.directives); - String joined = AssetPath::join(components); +AssetPath ImageMetadataDatabase::filterProcessing(AssetPath const& path) { + AssetPath newPath = { path.basePath, path.subPath, {} }; - directives.forEach([&](auto const& entry) { + List filtered; + path.directives.forEach([&](auto const& entry) { ImageOperation const& operation = entry.operation; if (!(operation.is() || operation.is() || @@ -130,15 +129,15 @@ String ImageMetadataDatabase::filterProcessing(String const& path) { operation.is() || operation.is() || operation.is())) { - joined += "?"; - joined += entry.string; + filtered.emplace_back(entry); } }); - return AssetPath::join(components); + newPath.directives.append(move(filtered)); + return newPath; } -Vec2U ImageMetadataDatabase::calculateImageSize(String const& path) const { +Vec2U ImageMetadataDatabase::calculateImageSize(AssetPath const& path) const { // Carefully calculate an image's size while trying not to actually load it. // In error cases, this will fall back to calling Assets::image, so that image // can possibly produce a missing image asset or properly report the error. @@ -149,18 +148,17 @@ Vec2U ImageMetadataDatabase::calculateImageSize(String const& path) const { return assets->image(path)->size(); }; - AssetPath components = AssetPath::split(path); - if (!assets->assetExists(components.basePath)) { + if (!assets->assetExists(path.basePath)) { return fallback(); } Vec2U imageSize; - if (components.subPath) { - auto frames = assets->imageFrames(components.basePath); + if (path.subPath) { + auto frames = assets->imageFrames(path.basePath); if (!frames) return fallback(); - if (auto rect = frames->getRect(*components.subPath)) + if (auto rect = frames->getRect(*path.subPath)) imageSize = rect->size(); else return fallback(); @@ -169,13 +167,13 @@ Vec2U ImageMetadataDatabase::calculateImageSize(String const& path) const { // so we don't have to call Image::readPngMetadata on the same file more // than once. MutexLocker locker(m_mutex); - if (auto size = m_sizeCache.maybe(components.basePath)) { + if (auto size = m_sizeCache.maybe(path.basePath)) { imageSize = *size; } else { locker.unlock(); - imageSize = get<0>(Image::readPngMetadata(assets->openFile(components.basePath))); + imageSize = get<0>(Image::readPngMetadata(assets->openFile(path.basePath))); locker.lock(); - m_sizeCache[components.basePath] = imageSize; + m_sizeCache[path.basePath] = imageSize; } } @@ -230,7 +228,7 @@ Vec2U ImageMetadataDatabase::calculateImageSize(String const& path) const { OperationSizeAdjust osa(imageSize); - bool complete = components.directives.forEachAbortable([&](auto const& entry) -> bool { + bool complete = path.directives.forEachAbortable([&](auto const& entry) -> bool { entry.operation.call(osa); return !osa.hasError; }); diff --git a/source/game/StarImageMetadataDatabase.hpp b/source/game/StarImageMetadataDatabase.hpp index 1f29da6..b8e6a33 100644 --- a/source/game/StarImageMetadataDatabase.hpp +++ b/source/game/StarImageMetadataDatabase.hpp @@ -5,6 +5,7 @@ #include "StarMap.hpp" #include "StarString.hpp" #include "StarThread.hpp" +#include "StarAssetPath.hpp" namespace Star { @@ -15,24 +16,24 @@ STAR_CLASS(ImageMetadataDatabase); // because they are expensive to compute and cheap to keep around. class ImageMetadataDatabase { public: - Vec2U imageSize(String const& path) const; - List imageSpaces(String const& path, Vec2F position, float fillLimit, bool flip) const; - RectU nonEmptyRegion(String const& path) const; + Vec2U imageSize(AssetPath const& path) const; + List imageSpaces(AssetPath const& path, Vec2F position, float fillLimit, bool flip) const; + RectU nonEmptyRegion(AssetPath const& path) const; private: // Removes image processing directives that don't affect image spaces / // non-empty regions. - static String filterProcessing(String const& path); + static AssetPath filterProcessing(AssetPath const& path); - Vec2U calculateImageSize(String const& path) const; + Vec2U calculateImageSize(AssetPath const& path) const; // Path, position, fillLimit, and flip - typedef tuple SpacesEntry; + typedef tuple SpacesEntry; mutable Mutex m_mutex; - mutable StringMap m_sizeCache; + mutable HashMap m_sizeCache; mutable HashMap> m_spacesCache; - mutable StringMap m_regionCache; + mutable HashMap m_regionCache; }; } diff --git a/source/game/StarObject.cpp b/source/game/StarObject.cpp index 0a35ee7..0441262 100644 --- a/source/game/StarObject.cpp +++ b/source/game/StarObject.cpp @@ -1204,8 +1204,9 @@ List Object::orientationDrawables(size_t orientationIndex) const { if (!m_orientationDrawablesCache || orientationIndex != m_orientationDrawablesCache->first) { m_orientationDrawablesCache = make_pair(orientationIndex, List()); for (auto const& layer : orientation->imageLayers) { - auto drawable = layer; - drawable.imagePart().image = drawable.imagePart().image.replaceTags(m_imageKeys, true, "default"); + Drawable drawable = layer; + auto& image = drawable.imagePart().image; + image = AssetPath::join(image).replaceTags(m_imageKeys, true, "default"); if (orientation->flipImages) drawable.scale(Vec2F(-1, 1), drawable.boundBox(false).center() - drawable.position); m_orientationDrawablesCache->second.append(move(drawable)); diff --git a/source/game/StarObjectDatabase.cpp b/source/game/StarObjectDatabase.cpp index d23b206..11c45c7 100644 --- a/source/game/StarObjectDatabase.cpp +++ b/source/game/StarObjectDatabase.cpp @@ -157,10 +157,11 @@ List ObjectDatabase::parseOrientations(String const& path, orientation->config = orientationSettings; if (orientationSettings.contains("imageLayers")) { - for (auto layer : orientationSettings.get("imageLayers").iterateArray()) { + for (Json layer : orientationSettings.get("imageLayers").iterateArray()) { + if (auto image = layer.opt("image")) + layer = layer.set("image", AssetPath::relativeTo(path, image->toString())); Drawable drawable(layer.set("centered", layer.getBool("centered", false))); drawable.scale(1.0f / TilePixels); - drawable.imagePart().image = AssetPath::relativeTo(path, drawable.imagePart().image); orientation->imageLayers.append(drawable); } } else { @@ -191,7 +192,7 @@ List ObjectDatabase::parseOrientations(String const& path, auto spaceScanSpaces = Set::from(orientation->spaces); for (auto const& layer : orientation->imageLayers) { spaceScanSpaces.addAll(root.imageMetadataDatabase()->imageSpaces( - layer.imagePart().image.replaceTags(StringMap(), true, "default"), + AssetPath::join(layer.imagePart().image).replaceTags(StringMap(), true, "default"), imagePosition, orientationSettings.getDouble("spaceScan"), orientation->flipImages)); @@ -578,8 +579,9 @@ List ObjectDatabase::cursorHintDrawables(World const* world, String co auto orientation = config->orientations.at(orientationIndex); for (auto const& layer : orientation->imageLayers) { - auto drawable = layer; - drawable.imagePart().image = drawable.imagePart().image.replaceTags(StringMap(), true, "default"); + Drawable drawable = layer; + auto& image = drawable.imagePart().image; + image = AssetPath::join(image).replaceTags(StringMap(), true, "default"); if (orientation->flipImages) drawable.scale(Vec2F(-1, 1), drawable.boundBox(false).center() - drawable.position); drawables.append(move(drawable)); diff --git a/source/game/items/StarArmors.cpp b/source/game/items/StarArmors.cpp index 17714fb..4abcde8 100644 --- a/source/game/items/StarArmors.cpp +++ b/source/game/items/StarArmors.cpp @@ -6,6 +6,7 @@ #include "StarRoot.hpp" #include "StarStoredFunctions.hpp" #include "StarPlayer.hpp" +#include "StarDirectives.hpp" namespace Star { @@ -20,7 +21,7 @@ ArmorItem::ArmorItem(Json const& config, String const& directory, Json const& da m_directives = instanceValue("directives", "").toString(); m_colorOptions = colorDirectivesFromConfig(config.getArray("colorOptions", JsonArray{""})); - if (m_directives.empty()) + if (m_directives.entries->empty()) m_directives = "?" + m_colorOptions.wrap(instanceValue("colorIndex", 0).toUInt()); refreshIconDrawables(); @@ -39,7 +40,7 @@ List const& ArmorItem::colorOptions() { return m_colorOptions; } -String const& ArmorItem::directives() const { +Directives const& ArmorItem::directives() const { return m_directives; } @@ -88,9 +89,11 @@ HeadArmor::HeadArmor(Json const& config, String const& directory, Json const& da m_maleImage = AssetPath::relativeTo(directory, config.getString("maleFrames")); m_femaleImage = AssetPath::relativeTo(directory, config.getString("femaleFrames")); - m_maskDirectives = instanceValue("mask").toString(); - if (!m_maskDirectives.empty() && !m_maskDirectives.contains("?")) - m_maskDirectives = "?addmask=" + AssetPath::relativeTo(directory, m_maskDirectives) + ";0;0"; + String maskDirectivesStr = instanceValue("mask").toString(); + if (!maskDirectivesStr.empty() && !maskDirectivesStr.contains("?")) + m_maskDirectives = "?addmask=" + AssetPath::relativeTo(directory, maskDirectivesStr) + ";0;0"; + else + m_maskDirectives = maskDirectivesStr; } ItemPtr HeadArmor::clone() const { @@ -104,7 +107,7 @@ String const& HeadArmor::frameset(Gender gender) const { return m_femaleImage; } -String const& HeadArmor::maskDirectives() const { +Directives const& HeadArmor::maskDirectives() const { return m_maskDirectives; } diff --git a/source/game/items/StarArmors.hpp b/source/game/items/StarArmors.hpp index 9438259..70bca6f 100644 --- a/source/game/items/StarArmors.hpp +++ b/source/game/items/StarArmors.hpp @@ -25,7 +25,7 @@ public: List const& colorOptions(); - String const& directives() const; + Directives const& directives() const; bool hideBody() const; @@ -38,7 +38,7 @@ private: List m_colorOptions; List m_statusEffects; StringSet m_effectSources; - String m_directives; + Directives m_directives; bool m_hideBody; Maybe m_techModule; }; @@ -51,14 +51,14 @@ public: virtual ItemPtr clone() const; String const& frameset(Gender gender) const; - String const& maskDirectives() const; + Directives const& maskDirectives() const; virtual List preview(PlayerPtr const& viewer = {}) const; private: String m_maleImage; String m_femaleImage; - String m_maskDirectives; + Directives m_maskDirectives; }; class ChestArmor : public ArmorItem, public PreviewableItem { diff --git a/source/game/items/StarMaterialItem.cpp b/source/game/items/StarMaterialItem.cpp index 9edd1fe..4ba1609 100644 --- a/source/game/items/StarMaterialItem.cpp +++ b/source/game/items/StarMaterialItem.cpp @@ -17,8 +17,10 @@ MaterialItem::MaterialItem(Json const& config, String const& directory, Json con if (materialHueShift() != MaterialHue()) { auto drawables = iconDrawables(); for (auto& d : drawables) { - if (d.isImage()) - d.imagePart().addDirectives(strf("?hueshift=%s", materialHueToDegrees(m_materialHueShift)), false); + if (d.isImage()) { + String image = strf("?hueshift=%s", materialHueToDegrees(m_materialHueShift)); + d.imagePart().addDirectives(image, false); + } } setIconDrawables(move(drawables)); } diff --git a/source/game/items/StarTools.cpp b/source/game/items/StarTools.cpp index aef26a5..6edf3fb 100644 --- a/source/game/items/StarTools.cpp +++ b/source/game/items/StarTools.cpp @@ -610,7 +610,7 @@ List PaintingBeamTool::drawables() const { auto result = BeamItem::drawables(); for (auto& entry : result) { if (entry.isImage()) - entry.imagePart().image = entry.imagePart().image + m_colorKeys[m_colorIndex]; + entry.imagePart().image.directives += m_colorKeys[m_colorIndex]; } return result; } diff --git a/source/rendering/StarAssetTextureGroup.cpp b/source/rendering/StarAssetTextureGroup.cpp index 44b614a..afffab7 100644 --- a/source/rendering/StarAssetTextureGroup.cpp +++ b/source/rendering/StarAssetTextureGroup.cpp @@ -13,16 +13,16 @@ AssetTextureGroup::AssetTextureGroup(TextureGroupPtr textureGroup) Root::singleton().registerReloadListener(m_reloadTracker); } -TexturePtr AssetTextureGroup::loadTexture(String const& imageName) { - return loadTexture(imageName, false); +TexturePtr AssetTextureGroup::loadTexture(AssetPath const& imagePath) { + return loadTexture(imagePath, false); } -TexturePtr AssetTextureGroup::tryTexture(String const& imageName) { - return loadTexture(imageName, true); +TexturePtr AssetTextureGroup::tryTexture(AssetPath const& imagePath) { + return loadTexture(imagePath, true); } -bool AssetTextureGroup::textureLoaded(String const& imageName) const { - return m_textureMap.contains(imageName); +bool AssetTextureGroup::textureLoaded(AssetPath const& imagePath) const { + return m_textureMap.contains(imagePath); } void AssetTextureGroup::cleanup(int64_t textureTimeout) { @@ -50,8 +50,8 @@ void AssetTextureGroup::cleanup(int64_t textureTimeout) { } } -TexturePtr AssetTextureGroup::loadTexture(String const& imageName, bool tryTexture) { - if (auto p = m_textureMap.ptr(imageName)) { +TexturePtr AssetTextureGroup::loadTexture(AssetPath const& imagePath, bool tryTexture) { + if (auto p = m_textureMap.ptr(imagePath)) { p->second = Time::monotonicMilliseconds(); return p->first; } @@ -60,9 +60,9 @@ TexturePtr AssetTextureGroup::loadTexture(String const& imageName, bool tryTextu ImageConstPtr image; if (tryTexture) - image = assets->tryImage(imageName); + image = assets->tryImage(imagePath); else - image = assets->image(imageName); + image = assets->image(imagePath); if (!image) return {}; @@ -72,11 +72,11 @@ TexturePtr AssetTextureGroup::loadTexture(String const& imageName, bool tryTextu // in the texture group for these, so we keep track of the image pointers // returned to deduplicate them. if (auto existingTexture = m_textureDeduplicationMap.value(image)) { - m_textureMap.add(imageName, {existingTexture, Time::monotonicMilliseconds()}); + m_textureMap.add(imagePath, {existingTexture, Time::monotonicMilliseconds()}); return existingTexture; } else { auto texture = m_textureGroup->create(*image); - m_textureMap.add(imageName, {texture, Time::monotonicMilliseconds()}); + m_textureMap.add(imagePath, {texture, Time::monotonicMilliseconds()}); m_textureDeduplicationMap.add(image, texture); return texture; } diff --git a/source/rendering/StarAssetTextureGroup.hpp b/source/rendering/StarAssetTextureGroup.hpp index 0941ad0..205a321 100644 --- a/source/rendering/StarAssetTextureGroup.hpp +++ b/source/rendering/StarAssetTextureGroup.hpp @@ -6,6 +6,7 @@ #include "StarBiMap.hpp" #include "StarListener.hpp" #include "StarRenderer.hpp" +#include "StarAssetPath.hpp" namespace Star { @@ -20,14 +21,14 @@ public: // Load the given texture into the texture group if it is not loaded, and // return the texture pointer. - TexturePtr loadTexture(String const& imageName); + TexturePtr loadTexture(AssetPath const& imagePath); // If the texture is loaded and ready, returns the texture pointer, otherwise // queues the texture using Assets::tryImage and returns nullptr. - TexturePtr tryTexture(String const& imageName); + TexturePtr tryTexture(AssetPath const& imagePath); // Has the texture been loaded? - bool textureLoaded(String const& imageName) const; + bool textureLoaded(AssetPath const& imagePath) const; // Frees textures that haven't been used in more than 'textureTimeout' time. // If Root has been reloaded, will simply clear the texture group. @@ -37,10 +38,10 @@ private: // Returns the texture parameters. If tryTexture is true, then returns none // if the texture is not loaded, and queues it, otherwise loads texture // immediately - TexturePtr loadTexture(String const& imageName, bool tryTexture); + TexturePtr loadTexture(AssetPath const& imagePath, bool tryTexture); TextureGroupPtr m_textureGroup; - StringMap> m_textureMap; + HashMap> m_textureMap; HashMap m_textureDeduplicationMap; TrackerListenerPtr m_reloadTracker; }; diff --git a/source/windowing/StarGuiContext.cpp b/source/windowing/StarGuiContext.cpp index 463e341..70ef67b 100644 --- a/source/windowing/StarGuiContext.cpp +++ b/source/windowing/StarGuiContext.cpp @@ -313,7 +313,7 @@ bool GuiContext::trySetCursor(Drawable const& drawable, Vec2I const& offset, int auto assets = Root::singleton().assets(); auto& imagePath = drawable.imagePart().image; - return applicationController()->setCursorImage(imagePath, assets->image(imagePath), pixelRatio, offset); + return applicationController()->setCursorImage(AssetPath::join(imagePath), assets->image(imagePath), pixelRatio, offset); } RectF GuiContext::renderText(String const& s, TextPositioning const& position) { diff --git a/source/windowing/StarImageWidget.cpp b/source/windowing/StarImageWidget.cpp index c189eac..ef82c33 100644 --- a/source/windowing/StarImageWidget.cpp +++ b/source/windowing/StarImageWidget.cpp @@ -41,9 +41,10 @@ void ImageWidget::setRotation(float rotation) { } String ImageWidget::image() const { - if (!m_drawables.size()) + if (m_drawables.empty()) return ""; - return m_drawables[0].imagePart().image; + else + return AssetPath::join(m_drawables[0].imagePart().image); } void ImageWidget::setDrawables(List drawables) {