Pretty much working now

This commit is contained in:
Kae 2023-06-24 22:49:47 +10:00
parent aa08eaac99
commit 7eb010d4a1
23 changed files with 392 additions and 233 deletions

View File

@ -234,11 +234,10 @@ void Assets::queueJsons(StringList const& paths) const {
})); }));
} }
ImageConstPtr Assets::image(String const& path) const { ImageConstPtr Assets::image(AssetPath const& path) const {
auto components = AssetPath::split(path); validatePath(path, true, true);
validatePath(components, true, true);
return as<ImageData>(getAsset(AssetId{AssetType::Image, move(components)}))->image; return as<ImageData>(getAsset(AssetId{AssetType::Image, path}))->image;
} }
void Assets::queueImages(StringList const& paths) const { 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 { ImageConstPtr Assets::tryImage(AssetPath const& path) const {
auto components = AssetPath::split(path); validatePath(path, true, true);
validatePath(components, true, true);
if (auto imageData = as<ImageData>(tryAsset(AssetId{AssetType::Image, move(components)}))) if (auto imageData = as<ImageData>(tryAsset(AssetId{AssetType::Image, path})))
return imageData->image; return imageData->image;
else else
return {}; return {};

View File

@ -119,12 +119,12 @@ public:
// <full-path-minus-extension>.frames or default.frames, going up to assets // <full-path-minus-extension>.frames or default.frames, going up to assets
// root. May return the same ImageConstPtr for different paths if the paths // root. May return the same ImageConstPtr for different paths if the paths
// are equivalent or they are aliases of other image 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 // Load images using background processing
void queueImages(StringList const& paths) const; void queueImages(StringList const& paths) const;
// Return the given image *if* it is already loaded, otherwise queue it for // Return the given image *if* it is already loaded, otherwise queue it for
// loading. // loading.
ImageConstPtr tryImage(String const& path) const; ImageConstPtr tryImage(AssetPath const& path) const;
// Returns the best associated FramesSpecification for a given image path, if // Returns the best associated FramesSpecification for a given image path, if
// it exists. The given path must not contain sub-paths or directives, and // it exists. The given path must not contain sub-paths or directives, and

View File

@ -66,7 +66,7 @@ AssetPath AssetPath::split(String const& path) {
++i; ++i;
} }
if (!directives.empty()); if (!directives.empty())
components.directives.append(move(directives)); components.directives.append(move(directives));
} }
@ -178,4 +178,8 @@ std::ostream& operator<<(std::ostream& os, AssetPath const& rhs) {
return os; return os;
} }
size_t hash<AssetPath>::operator()(AssetPath const& s) const {
return hashOf(s.basePath, s.subPath, s.directives);
}
} }

View File

@ -2,6 +2,7 @@
#define STAR_ASSET_PATH_HPP #define STAR_ASSET_PATH_HPP
#include "StarDirectives.hpp" #include "StarDirectives.hpp"
#include "StarHash.hpp"
namespace Star { namespace Star {
@ -64,6 +65,11 @@ struct AssetPath {
std::ostream& operator<<(std::ostream& os, AssetPath const& rhs); std::ostream& operator<<(std::ostream& os, AssetPath const& rhs);
template <>
struct hash<AssetPath> {
size_t operator()(AssetPath const& s) const;
};
} }
#endif #endif

View File

@ -10,6 +10,16 @@ Directives::Entry::Entry(ImageOperation&& newOperation, String&& newString) {
string = move(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() {}
Directives::Directives(String const& directives) { Directives::Directives(String const& directives) {
parse(directives); parse(directives);
@ -20,6 +30,17 @@ Directives::Directives(String&& directives) {
parse(mine); parse(mine);
} }
Directives::Directives(const char* directives) {
String string(directives);
parse(string);
}
Directives::Directives(List<Entry>&& newEntries) {
entries = std::make_shared<List<Entry> const>(move(newEntries));
String directives = toString(); // This needs to be better
hash = XXH3_64bits(directives.utf8Ptr(), directives.utf8Size());
}
void Directives::parse(String const& directives) { void Directives::parse(String const& directives) {
List<Entry> newList; List<Entry> newList;
StringList split = directives.split('?'); 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() : m_count(0) {}
DirectivesGroup::DirectivesGroup(String const& directives) { DirectivesGroup::DirectivesGroup(String const& directives) {
m_directives.emplace_back(directives); m_directives.emplace_back(directives);
@ -66,10 +113,21 @@ inline bool DirectivesGroup::compare(DirectivesGroup const& other) const {
} }
void DirectivesGroup::append(Directives const& directives) { void DirectivesGroup::append(Directives const& directives) {
m_directives.push_back(directives); m_directives.emplace_back(directives);
m_count += m_directives.back().entries->size(); m_count += m_directives.back().entries->size();
} }
void DirectivesGroup::append(List<Directives::Entry>&& 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) { DirectivesGroup& DirectivesGroup::operator+=(Directives const& other) {
append(other); append(other);
return *this; return *this;

View File

@ -3,28 +3,40 @@
#include "StarImageProcessing.hpp" #include "StarImageProcessing.hpp"
#include "StarHash.hpp" #include "StarHash.hpp"
#include "StarDataStream.hpp"
namespace Star { namespace Star {
STAR_CLASS(Directives);
STAR_CLASS(DirectivesGroup); STAR_CLASS(DirectivesGroup);
STAR_EXCEPTION(DirectivesException, StarException); STAR_EXCEPTION(DirectivesException, StarException);
// Kae: My attempt at reducing memory allocation and per-frame string parsing for extremely long directives // 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 { struct Entry {
ImageOperation operation; ImageOperation operation;
String string; String string;
Entry(ImageOperation&& newOperation, String&& newString); Entry(ImageOperation&& newOperation, String&& newString);
Entry(ImageOperation const& newOperation, String const& newString);
Entry(Entry const& other);
}; };
Directives(); Directives();
Directives(String const& directives); Directives(String const& directives);
Directives(String&& directives); Directives(String&& directives);
Directives(const char* directives);
Directives(List<Entry>&& entries);
void parse(String const& directives); void parse(String const& directives);
void buildString(String& out) const; 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<List<Entry> const> entries; std::shared_ptr<List<Entry> const> entries;
size_t hash = 0; size_t hash = 0;
@ -41,6 +53,9 @@ public:
inline bool empty() const; inline bool empty() const;
bool compare(DirectivesGroup const& other) const; bool compare(DirectivesGroup const& other) const;
void append(Directives const& other); void append(Directives const& other);
void append(List<Directives::Entry>&& entries);
void clear();
DirectivesGroup& operator+=(Directives const& other); DirectivesGroup& operator+=(Directives const& other);
inline String toString() const; inline String toString() const;

View File

@ -965,8 +965,9 @@ void MainInterface::renderBreath() {
size_t blocks = round((10 * breath) / breathMax); size_t blocks = round((10 * breath) / breathMax);
if (blocks < 10) { if (blocks < 10) {
m_guiContext->drawQuad("/interface/breath/breath.png", String path = "/interface/breath/breath.png";
RectF::withCenter(breathBackgroundCenterPos, Vec2F(imgMetadata->imageSize("/interface/breath/breath.png")) * interfaceScale())); m_guiContext->drawQuad(path,
RectF::withCenter(breathBackgroundCenterPos, Vec2F(imgMetadata->imageSize(path)) * interfaceScale()));
for (size_t i = 0; i < 10; i++) { for (size_t i = 0; i < 10; i++) {
if (i >= blocks) { if (i >= blocks) {
if (blocks == 0 && Time::monotonicMilliseconds() % 500 > 250) if (blocks == 0 && Time::monotonicMilliseconds() % 500 > 250)

View File

@ -9,37 +9,41 @@
namespace Star { namespace Star {
void Drawable::ImagePart::addDirectives(String const& directives, bool keepImageCenterPosition) { Drawable::ImagePart& Drawable::ImagePart::addDirectives(Directives const& directives, bool keepImageCenterPosition) {
if (directives.empty()) if (directives.entries->empty())
return; return *this;
if (keepImageCenterPosition) { if (keepImageCenterPosition) {
auto imageMetadata = Root::singleton().imageMetadataDatabase(); auto imageMetadata = Root::singleton().imageMetadataDatabase();
Vec2F imageSize = Vec2F(imageMetadata->imageSize(image)); Vec2F imageSize = Vec2F(imageMetadata->imageSize(image));
image = AssetPath::addDirectives(image, {directives}); image.directives += directives;
Vec2F newImageSize = Vec2F(imageMetadata->imageSize(image)); Vec2F newImageSize = Vec2F(imageMetadata->imageSize(image));
// If we are trying to maintain the image center, PRE translate the image by // If we are trying to maintain the image center, PRE translate the image by
// the change in size / 2 // the change in size / 2
transformation *= Mat3F::translation((imageSize - newImageSize) / 2); transformation *= Mat3F::translation((imageSize - newImageSize) / 2);
} else { } else {
image = AssetPath::addDirectives(image, {directives}); image.directives += directives;
}
} }
void Drawable::ImagePart::removeDirectives(bool keepImageCenterPosition) { return *this;
}
Drawable::ImagePart& Drawable::ImagePart::removeDirectives(bool keepImageCenterPosition) {
if (keepImageCenterPosition) { if (keepImageCenterPosition) {
auto imageMetadata = Root::singleton().imageMetadataDatabase(); auto imageMetadata = Root::singleton().imageMetadataDatabase();
Vec2F imageSize = Vec2F(imageMetadata->imageSize(image)); Vec2F imageSize = Vec2F(imageMetadata->imageSize(image));
image = AssetPath::removeDirectives(image); image.directives.clear();
Vec2F newImageSize = Vec2F(imageMetadata->imageSize(image)); Vec2F newImageSize = Vec2F(imageMetadata->imageSize(image));
// If we are trying to maintain the image center, PRE translate the image by // If we are trying to maintain the image center, PRE translate the image by
// the change in size / 2 // the change in size / 2
transformation *= Mat3F::translation((imageSize - newImageSize) / 2); transformation *= Mat3F::translation((imageSize - newImageSize) / 2);
} else { } else {
image = AssetPath::removeDirectives(image); image.directives.clear();
} }
return *this;
} }
Drawable Drawable::makeLine(Line2F const& line, float lineWidth, Color const& color, Vec2F const& position) { 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; 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; Drawable drawable;
Mat3F transformation = Mat3F::identity(); Mat3F transformation = Mat3F::identity();
if (centered) { if (centered) {
@ -120,7 +124,7 @@ Json Drawable::toJson() const {
} else if (auto poly = part.ptr<PolyPart>()) { } else if (auto poly = part.ptr<PolyPart>()) {
json.set("poly", jsonFromPolyF(poly->poly)); json.set("poly", jsonFromPolyF(poly->poly));
} else if (auto image = part.ptr<ImagePart>()) { } else if (auto image = part.ptr<ImagePart>()) {
json.set("image", image->image); json.set("image", AssetPath::join(image->image));
json.set("transformation", jsonFromMat3F(image->transformation)); json.set("transformation", jsonFromMat3F(image->transformation));
} }
@ -242,14 +246,15 @@ DataStream& operator<<(DataStream& ds, Drawable::PolyPart const& poly) {
return ds; return ds;
} }
// I need to find out if this is for network serialization or not eventually
DataStream& operator>>(DataStream& ds, Drawable::ImagePart& image) { DataStream& operator>>(DataStream& ds, Drawable::ImagePart& image) {
ds >> image.image; ds >> AssetPath::join(image.image);
ds >> image.transformation; ds >> image.transformation;
return ds; return ds;
} }
DataStream& operator<<(DataStream& ds, Drawable::ImagePart const& image) { DataStream& operator<<(DataStream& ds, Drawable::ImagePart const& image) {
ds << image.image; ds << AssetPath::join(image.image);
ds << image.transformation; ds << image.transformation;
return ds; return ds;
} }

View File

@ -6,6 +6,7 @@
#include "StarPoly.hpp" #include "StarPoly.hpp"
#include "StarColor.hpp" #include "StarColor.hpp"
#include "StarJson.hpp" #include "StarJson.hpp"
#include "StarAssetPath.hpp"
namespace Star { namespace Star {
@ -20,7 +21,7 @@ struct Drawable {
}; };
struct ImagePart { struct ImagePart {
String image; AssetPath image;
// Transformation of the image in pixel space (0, 0) - (width, height) to // Transformation of the image in pixel space (0, 0) - (width, height) to
// the final drawn space // the final drawn space
Mat3F transformation; Mat3F transformation;
@ -28,17 +29,17 @@ struct Drawable {
// Add directives to this ImagePart, while optionally keeping the // Add directives to this ImagePart, while optionally keeping the
// transformed center of the image the same if the directives change the // transformed center of the image the same if the directives change the
// image size. // 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 // Remove directives from this ImagePart, while optionally keeping the
// transformed center of the image the same if the directives change the // transformed center of the image the same if the directives change the
// image size. // 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 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 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 <typename DrawablesContainer> template <typename DrawablesContainer>
static void translateAll(DrawablesContainer& drawables, Vec2F const& translation); static void translateAll(DrawablesContainer& drawables, Vec2F const& translation);

View File

@ -42,7 +42,11 @@ HumanoidIdentity::HumanoidIdentity(Json config) {
hairType = config.getString("hairType", "male1"); hairType = config.getString("hairType", "male1");
hairDirectives = config.getString("hairDirectives", ""); hairDirectives = config.getString("hairDirectives", "");
bodyDirectives = config.getString("bodyDirectives", ""); 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", ""); facialHairGroup = config.getString("facialHairGroup", "");
facialHairType = config.getString("facialHairType", ""); facialHairType = config.getString("facialHairType", "");
facialHairDirectives = config.getString("facialHairDirectives", ""); facialHairDirectives = config.getString("facialHairDirectives", "");
@ -67,15 +71,15 @@ Json HumanoidIdentity::toJson() const {
{"gender", GenderNames.getRight(gender)}, {"gender", GenderNames.getRight(gender)},
{"hairGroup", hairGroup}, {"hairGroup", hairGroup},
{"hairType", hairType}, {"hairType", hairType},
{"hairDirectives", hairDirectives}, {"hairDirectives", hairDirectives.toString()},
{"bodyDirectives", bodyDirectives}, {"bodyDirectives", bodyDirectives.toString()},
{"emoteDirectives", emoteDirectives}, {"emoteDirectives", emoteDirectives.toString()},
{"facialHairGroup", facialHairGroup}, {"facialHairGroup", facialHairGroup},
{"facialHairType", facialHairType}, {"facialHairType", facialHairType},
{"facialHairDirectives", facialHairDirectives}, {"facialHairDirectives", facialHairDirectives.toString()},
{"facialMaskGroup", facialMaskGroup}, {"facialMaskGroup", facialMaskGroup},
{"facialMaskType", facialMaskType}, {"facialMaskType", facialMaskType},
{"facialMaskDirectives", facialMaskDirectives}, {"facialMaskDirectives", facialMaskDirectives.toString()},
{"personalityIdle", personality.idle}, {"personalityIdle", personality.idle},
{"personalityArmIdle", personality.armIdle}, {"personalityArmIdle", personality.armIdle},
{"personalityHeadOffset", jsonFromVec2F(personality.headOffset)}, {"personalityHeadOffset", jsonFromVec2F(personality.headOffset)},
@ -300,7 +304,7 @@ HumanoidIdentity const& Humanoid::identity() const {
return m_identity; return m_identity;
} }
void Humanoid::setHeadArmorDirectives(String directives) { void Humanoid::setHeadArmorDirectives(Directives directives) {
m_headArmorDirectives = move(directives); m_headArmorDirectives = move(directives);
} }
@ -308,7 +312,7 @@ void Humanoid::setHeadArmorFrameset(String headFrameset) {
m_headArmorFrameset = move(headFrameset); m_headArmorFrameset = move(headFrameset);
} }
void Humanoid::setChestArmorDirectives(String directives) { void Humanoid::setChestArmorDirectives(Directives directives) {
m_chestArmorDirectives = move(directives); m_chestArmorDirectives = move(directives);
} }
@ -324,7 +328,7 @@ void Humanoid::setFrontSleeveFrameset(String frontSleeveFrameset) {
m_frontSleeveFrameset = move(frontSleeveFrameset); m_frontSleeveFrameset = move(frontSleeveFrameset);
} }
void Humanoid::setLegsArmorDirectives(String directives) { void Humanoid::setLegsArmorDirectives(Directives directives) {
m_legsArmorDirectives = move(directives); m_legsArmorDirectives = move(directives);
} }
@ -332,7 +336,7 @@ void Humanoid::setLegsArmorFrameset(String legsFrameset) {
m_legsArmorFrameset = move(legsFrameset); m_legsArmorFrameset = move(legsFrameset);
} }
void Humanoid::setBackArmorDirectives(String directives) { void Humanoid::setBackArmorDirectives(Directives directives) {
m_backArmorDirectives = move(directives); m_backArmorDirectives = move(directives);
} }
@ -340,7 +344,7 @@ void Humanoid::setBackArmorFrameset(String backFrameset) {
m_backArmorFrameset = move(backFrameset); m_backArmorFrameset = move(backFrameset);
} }
void Humanoid::setHelmetMaskDirectives(String helmetMaskDirectives) { void Humanoid::setHelmetMaskDirectives(Directives helmetMaskDirectives) {
m_helmetMaskDirectives = move(helmetMaskDirectives); m_helmetMaskDirectives = move(helmetMaskDirectives);
} }
@ -464,7 +468,7 @@ List<Drawable> Humanoid::render() {
int armStateSeq = getArmStateSequence(); int armStateSeq = getArmStateSequence();
int bodyStateSeq = getBodyStateSequence(); int bodyStateSeq = getBodyStateSequence();
int emoteStateSeq = m_timing.emoteStateSeq(m_emoteAnimationTimer, m_emoteState); int emoteStateSeq = getEmoteStateSequence();
float bobYOffset = getBobYOffset(); float bobYOffset = getBobYOffset();
Maybe<DancePtr> dance = getDance(); Maybe<DancePtr> dance = getDance();
Maybe<DanceStep> danceStep = {}; Maybe<DanceStep> danceStep = {};
@ -492,9 +496,10 @@ List<Drawable> Humanoid::render() {
drawables.append(move(drawable)); drawables.append(move(drawable));
}; };
auto backArmDrawable = [&](String const& frameSet, String const& directives) -> Drawable { auto backArmDrawable = [&](String const& frameSet, Directives const& directives) -> Drawable {
String image = strf("%s:%s%s", frameSet, backHand.backFrame, directives); String image = strf("%s:%s", frameSet, backHand.backFrame);
Drawable backArm = Drawable::makeImage(move(image), 1.0f / TilePixels, true, backArmFrameOffset); Drawable backArm = Drawable::makeImage(move(image), 1.0f / TilePixels, true, backArmFrameOffset);
backArm.imagePart().addDirectives(directives);
backArm.rotate(backHand.angle, backArmFrameOffset + m_backArmRotationCenter + m_backArmOffset); backArm.rotate(backHand.angle, backArmFrameOffset + m_backArmRotationCenter + m_backArmOffset);
return backArm; return backArm;
}; };
@ -505,13 +510,15 @@ List<Drawable> Humanoid::render() {
frameGroup = "runbackwards"; frameGroup = "runbackwards";
String image; String image;
if (dance.isValid() && danceStep->bodyFrame) 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) 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 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()) { if (backHand.holdingItem && !dance.isValid()) {
@ -535,33 +542,35 @@ List<Drawable> Humanoid::render() {
String image; String image;
Vec2F position; Vec2F position;
if (dance.isValid() && danceStep->backArmFrame) { 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; position = danceStep->backArmOffset / TilePixels;
} else if (m_state == Idle) { } 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; position = m_identity.personality.armOffset / TilePixels;
} else } 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); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position);
drawable.imagePart().addDirectives(getBodyDirectives());
if (dance.isValid()) if (dance.isValid())
drawable.rotate(danceStep->backArmRotation); drawable.rotate(danceStep->backArmRotation);
addDrawable(drawable, m_bodyFullbright); addDrawable(move(drawable), m_bodyFullbright);
} }
if (!m_backSleeveFrameset.empty()) { if (!m_backSleeveFrameset.empty()) {
String image; String image;
Vec2F position; Vec2F position;
if (dance.isValid() && danceStep->backArmFrame) { 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; position = danceStep->backArmOffset / TilePixels;
} else if (m_state == Idle) { } 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; position = m_identity.personality.armOffset / TilePixels;
} else } 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); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position);
drawable.imagePart().addDirectives(getChestDirectives());
if (dance.isValid()) if (dance.isValid())
drawable.rotate(danceStep->backArmRotation); drawable.rotate(danceStep->backArmRotation);
addDrawable(drawable); addDrawable(move(drawable));
} }
} }
@ -582,80 +591,99 @@ List<Drawable> Humanoid::render() {
headPosition += m_headLayOffset; headPosition += m_headLayOffset;
if (!m_headFrameset.empty() && !m_bodyHidden) { if (!m_headFrameset.empty() && !m_bodyHidden) {
String image = strf("%s:normal%s", m_headFrameset, getBodyDirectives()); String image = strf("%s:normal", m_headFrameset);
addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition), m_bodyFullbright); 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) { if (!m_emoteFrameset.empty() && !m_bodyHidden) {
String image = strf("%s:%s.%s%s", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq, getEmoteDirectives()); String image = strf("%s:%s.%s", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq);
addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition), m_bodyFullbright); 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) { if (!m_hairFrameset.empty() && !m_bodyHidden) {
String image = strf("%s:normal%s", m_hairFrameset, getHairDirectives() + getHelmetMaskDirectives()); String image = strf("%s:normal", m_hairFrameset);
addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition), m_bodyFullbright); 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) { if (!m_bodyFrameset.empty() && !m_bodyHidden) {
String image; String image;
if (dance.isValid() && danceStep->bodyFrame) 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) 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 else
image = strf("%s:%s.%s%s", m_bodyFrameset, frameBase(m_state), bodyStateSeq, getBodyDirectives()); image = strf("%s:%s.%s", m_bodyFrameset, frameBase(m_state), bodyStateSeq);
addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, {}), m_bodyFullbright); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, {});
drawable.imagePart().addDirectives(getBodyDirectives());
addDrawable(move(drawable), m_bodyFullbright);
} }
if (!m_legsArmorFrameset.empty()) { if (!m_legsArmorFrameset.empty()) {
String image; String image;
if (dance.isValid() && danceStep->bodyFrame) 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) 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 else
image = strf("%s:%s.%s%s", m_legsArmorFrameset, frameBase(m_state), bodyStateSeq, getLegsDirectives()); image = strf("%s:%s.%s", m_legsArmorFrameset, frameBase(m_state), bodyStateSeq);
addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, {})); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, {});
drawable.imagePart().addDirectives(getLegsDirectives());
addDrawable(move(drawable));
} }
if (!m_chestArmorFrameset.empty()) { if (!m_chestArmorFrameset.empty()) {
String image; String image;
Vec2F position; Vec2F position;
if (dance.isValid() && danceStep->bodyFrame) 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) else if (m_state == Run)
image = strf("%s:run%s", m_chestArmorFrameset, getChestDirectives()); image = strf("%s:run", m_chestArmorFrameset);
else if (m_state == Idle) 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) 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)) else if ((m_state == Swim) || (m_state == SwimIdle))
image = strf("%s:swim%s", m_chestArmorFrameset, getChestDirectives()); image = strf("%s:swim", m_chestArmorFrameset);
else else
image = strf("%s:chest.1%s", m_chestArmorFrameset, getChestDirectives()); image = strf("%s:chest.1", m_chestArmorFrameset);
if (m_state != Duck) if (m_state != Duck)
position[1] += bobYOffset; 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) { if (!m_facialHairFrameset.empty() && !m_bodyHidden) {
String image = strf("%s:normal%s", m_facialHairFrameset, getFacialHairDirectives() + getHelmetMaskDirectives()); String image = strf("%s:normal", m_facialHairFrameset);
addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition), m_bodyFullbright); 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) { if (!m_facialMaskFrameset.empty() && !m_bodyHidden) {
String image = strf("%s:normal%s", m_facialMaskFrameset, getFacialMaskDirectives() + getHelmetMaskDirectives()); String image = strf("%s:normal", m_facialMaskFrameset);
addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition)); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition);
drawable.imagePart().addDirectives(getFacialMaskDirectives()).addDirectives(getHelmetMaskDirectives());
addDrawable(move(drawable));
} }
if (!m_headArmorFrameset.empty()) { if (!m_headArmorFrameset.empty()) {
String image = strf("%s:normal%s", m_headArmorFrameset, getHeadDirectives()); String image = strf("%s:normal", m_headArmorFrameset);
addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition)); 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 { auto frontArmDrawable = [&](String const& frameSet, Directives const& directives) -> Drawable {
String image = strf("%s:%s%s", frameSet, frontHand.frontFrame, directives); String image = strf("%s:%s", frameSet, frontHand.frontFrame);
Drawable frontArm = Drawable::makeImage(image, 1.0f / TilePixels, true, frontArmFrameOffset); Drawable frontArm = Drawable::makeImage(image, 1.0f / TilePixels, true, frontArmFrameOffset);
frontArm.imagePart().addDirectives(directives);
frontArm.rotate(frontHand.angle, frontArmFrameOffset + m_frontArmRotationCenter); frontArm.rotate(frontHand.angle, frontArmFrameOffset + m_frontArmRotationCenter);
return frontArm; return frontArm;
}; };
@ -682,14 +710,15 @@ List<Drawable> Humanoid::render() {
String image; String image;
Vec2F position; Vec2F position;
if (dance.isValid() && danceStep->frontArmFrame) { 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; position = danceStep->frontArmOffset / TilePixels;
} else if (m_state == Idle) { } 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; position = m_identity.personality.armOffset / TilePixels;
} else } 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); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position);
drawable.imagePart().addDirectives(getBodyDirectives());
if (dance.isValid()) if (dance.isValid())
drawable.rotate(danceStep->frontArmRotation); drawable.rotate(danceStep->frontArmRotation);
addDrawable(drawable, m_bodyFullbright); addDrawable(drawable, m_bodyFullbright);
@ -699,14 +728,15 @@ List<Drawable> Humanoid::render() {
String image; String image;
Vec2F position; Vec2F position;
if (dance.isValid() && danceStep->frontArmFrame) { 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; position = danceStep->frontArmOffset / TilePixels;
} else if (m_state == Idle) { } 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; position = m_identity.personality.armOffset / TilePixels;
} else } else
image = strf("%s:%s.%s%s", m_frontSleeveFrameset, frameBase(m_state), armStateSeq, getChestDirectives()); image = strf("%s:%s.%s", m_frontSleeveFrameset, frameBase(m_state), armStateSeq);
auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position); auto drawable = Drawable::makeImage(image, 1.0f / TilePixels, true, position);
drawable.imagePart().addDirectives(getChestDirectives());
if (dance.isValid()) if (dance.isValid())
drawable.rotate(danceStep->frontArmRotation); drawable.rotate(danceStep->frontArmRotation);
addDrawable(drawable); addDrawable(drawable);
@ -717,7 +747,7 @@ List<Drawable> Humanoid::render() {
auto image = strf("%s:%d", auto image = strf("%s:%d",
m_vaporTrailFrameset, m_vaporTrailFrameset,
m_timing.genericSeq(m_animationTimer, m_vaporTrailCycle, m_vaporTrailFrames, true)); 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()) if (m_primaryHand.nonRotatedDrawables.size())
@ -736,18 +766,19 @@ List<Drawable> Humanoid::renderPortrait(PortraitMode mode) const {
List<Drawable> drawables; List<Drawable> drawables;
int emoteStateSeq = m_timing.emoteStateSeq(m_emoteAnimationTimer, m_emoteState); 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 if (mode != PortraitMode::Full && mode != PortraitMode::FullNeutral
&& mode != PortraitMode::FullNude && mode != PortraitMode::FullNeutralNude) { && mode != PortraitMode::FullNude && mode != PortraitMode::FullNeutralNude) {
// TODO: make this configurable // 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)); drawables.append(std::move(drawable));
return drawables.back();
}; };
bool dressed = !(mode == PortraitMode::FullNude || mode == PortraitMode::FullNeutralNude); bool dressed = !(mode == PortraitMode::FullNude || mode == PortraitMode::FullNeutralNude);
auto helmetMaskDirective = dressed ? getHelmetMaskDirectives() : ""; Directives helmetMaskDirective = dressed ? getHelmetMaskDirectives() : Directives();
auto personality = m_identity.personality; auto personality = m_identity.personality;
if (mode == PortraitMode::FullNeutral || mode == PortraitMode::FullNeutralNude) if (mode == PortraitMode::FullNeutral || mode == PortraitMode::FullNeutralNude)
@ -755,78 +786,105 @@ List<Drawable> Humanoid::renderPortrait(PortraitMode mode) const {
if (mode != PortraitMode::Head) { if (mode != PortraitMode::Head) {
if (!m_backArmFrameset.empty()) { if (!m_backArmFrameset.empty()) {
String image = strf("%s:%s%s", m_backArmFrameset, personality.armIdle, getBodyDirectives()); String image = strf("%s:%s", m_backArmFrameset, personality.armIdle);
addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.armOffset)); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.armOffset);
drawable.imagePart().addDirectives(getBodyDirectives());
addDrawable(move(drawable));
} }
if (dressed && !m_backSleeveFrameset.empty()) { if (dressed && !m_backSleeveFrameset.empty()) {
String image = strf("%s:%s%s", m_backSleeveFrameset, personality.armIdle, getChestDirectives()); String image = strf("%s:%s", m_backSleeveFrameset, personality.armIdle);
addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.armOffset)); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.armOffset);
drawable.imagePart().addDirectives(getChestDirectives());
addDrawable(move(drawable));
} }
if (mode != PortraitMode::Bust) { if (mode != PortraitMode::Bust) {
if (dressed && !m_backArmorFrameset.empty()) { if (dressed && !m_backArmorFrameset.empty()) {
String image = strf("%s:%s%s", m_backArmorFrameset, personality.idle, getBackDirectives()); String image = strf("%s:%s", m_backArmorFrameset, personality.idle);
addDrawable(Drawable::makeImage(move(image), 1.0f, true, {})); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, {});
drawable.imagePart().addDirectives(getBackDirectives());
addDrawable(move(drawable));
} }
} }
} }
if (!m_headFrameset.empty()) { if (!m_headFrameset.empty()) {
String image = strf("%s:normal%s", m_headFrameset, getBodyDirectives()); String image = strf("%s:normal", m_headFrameset);
addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.headOffset)); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset);
drawable.imagePart().addDirectives(getBodyDirectives());
addDrawable(move(drawable));
} }
if (!m_emoteFrameset.empty()) { if (!m_emoteFrameset.empty()) {
String image = String image = strf("%s:%s.%s", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq);
strf("%s:%s.%s%s", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq, getEmoteDirectives()); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset);
addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.headOffset)); drawable.imagePart().addDirectives(getEmoteDirectives());
addDrawable(move(drawable));
} }
if (!m_hairFrameset.empty()) { if (!m_hairFrameset.empty()) {
String image = strf("%s:normal%s", m_hairFrameset, getHairDirectives() + helmetMaskDirective); String image = strf("%s:normal", m_hairFrameset);
addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.headOffset)); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset);
drawable.imagePart().addDirectives(getHairDirectives()).addDirectives(helmetMaskDirective);
addDrawable(move(drawable));
} }
if (!m_bodyFrameset.empty()) { if (!m_bodyFrameset.empty()) {
String image = strf("%s:%s%s", m_bodyFrameset, personality.idle, getBodyDirectives()); String image = strf("%s:%s", m_bodyFrameset, personality.idle);
addDrawable(Drawable::makeImage(move(image), 1.0f, true, {})); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, {});
drawable.imagePart().addDirectives(getBodyDirectives());
addDrawable(move(drawable));
} }
if (mode != PortraitMode::Head) { if (mode != PortraitMode::Head) {
if (dressed && !m_legsArmorFrameset.empty()) { if (dressed && !m_legsArmorFrameset.empty()) {
String image = strf("%s:%s%s", m_legsArmorFrameset, personality.idle, getLegsDirectives()); String image = strf("%s:%s", m_legsArmorFrameset, personality.idle);
addDrawable(Drawable::makeImage(move(image), 1.0f, true, {})); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, {});
drawable.imagePart().addDirectives(getLegsDirectives());
addDrawable(move(drawable));
} }
if (dressed && !m_chestArmorFrameset.empty()) { if (dressed && !m_chestArmorFrameset.empty()) {
String image = strf("%s:%s%s", m_chestArmorFrameset, personality.idle, getChestDirectives()); String image = strf("%s:%s", m_chestArmorFrameset, personality.idle);
addDrawable(Drawable::makeImage(move(image), 1.0f, true, {})); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, {});
drawable.imagePart().addDirectives(getChestDirectives());
addDrawable(move(drawable));
} }
} }
if (!m_facialHairFrameset.empty()) { if (!m_facialHairFrameset.empty()) {
String image = strf("%s:normal%s", m_facialHairFrameset, getFacialHairDirectives() + helmetMaskDirective); String image = strf("%s:normal", m_facialHairFrameset);
addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.headOffset)); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset);
drawable.imagePart().addDirectives(getFacialHairDirectives()).addDirectives(helmetMaskDirective);
addDrawable(move(drawable));
} }
if (!m_facialMaskFrameset.empty()) { if (!m_facialMaskFrameset.empty()) {
String image = strf("%s:normal%s", m_facialMaskFrameset, getFacialMaskDirectives() + helmetMaskDirective); String image = strf("%s:normal", m_facialMaskFrameset);
addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.headOffset)); 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()) { if (dressed && !m_headArmorFrameset.empty()) {
String image = strf("%s:normal%s", m_headArmorFrameset, getHeadDirectives()); String image = strf("%s:normal", m_headArmorFrameset);
addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.headOffset)); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset);
drawable.imagePart().addDirectives(getHeadDirectives());
addDrawable(move(drawable));
} }
if (mode != PortraitMode::Head) { if (mode != PortraitMode::Head) {
if (!m_frontArmFrameset.empty()) { if (!m_frontArmFrameset.empty()) {
String image = strf("%s:%s%s", m_frontArmFrameset, personality.armIdle, getBodyDirectives()); String image = strf("%s:%s", m_frontArmFrameset, personality.armIdle);
addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.armOffset)); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.armOffset);
drawable.imagePart().addDirectives(getBodyDirectives());
addDrawable(move(drawable));
} }
if (dressed && !m_frontSleeveFrameset.empty()) { if (dressed && !m_frontSleeveFrameset.empty()) {
String image = strf("%s:%s%s", m_frontSleeveFrameset, personality.armIdle, getChestDirectives()); String image = strf("%s:%s", m_frontSleeveFrameset, personality.armIdle);
addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.armOffset)); 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"; return "/humanoid/any/flames.png";
} }
String Humanoid::getBodyDirectives() const { Directives Humanoid::getBodyDirectives() const {
return m_identity.bodyDirectives; return m_identity.bodyDirectives;
} }
String Humanoid::getHairDirectives() const { Directives Humanoid::getHairDirectives() const {
return m_identity.hairDirectives; return m_identity.hairDirectives;
} }
String Humanoid::getEmoteDirectives() const { Directives Humanoid::getEmoteDirectives() const {
return m_identity.emoteDirectives; return m_identity.emoteDirectives;
} }
String Humanoid::getFacialHairDirectives() const { Directives Humanoid::getFacialHairDirectives() const {
return m_identity.facialHairDirectives; return m_identity.facialHairDirectives;
} }
String Humanoid::getFacialMaskDirectives() const { Directives Humanoid::getFacialMaskDirectives() const {
return m_identity.facialMaskDirectives; return m_identity.facialMaskDirectives;
} }
String Humanoid::getHelmetMaskDirectives() const { Directives Humanoid::getHelmetMaskDirectives() const {
return m_helmetMaskDirectives; return m_helmetMaskDirectives;
} }
String Humanoid::getHeadDirectives() const { Directives Humanoid::getHeadDirectives() const {
return m_headArmorDirectives; return m_headArmorDirectives;
} }
String Humanoid::getChestDirectives() const { Directives Humanoid::getChestDirectives() const {
return m_chestArmorDirectives; return m_chestArmorDirectives;
} }
String Humanoid::getLegsDirectives() const { Directives Humanoid::getLegsDirectives() const {
return m_legsArmorDirectives; return m_legsArmorDirectives;
} }
String Humanoid::getBackDirectives() const { Directives Humanoid::getBackDirectives() const {
return m_backArmorDirectives; return m_backArmorDirectives;
} }
int Humanoid::getEmoteStateSequence() const {
return m_timing.emoteStateSeq(m_emoteAnimationTimer, m_emoteState);
}
int Humanoid::getArmStateSequence() const { int Humanoid::getArmStateSequence() const {
int stateSeq = m_timing.stateSeq(m_animationTimer, m_state); int stateSeq = m_timing.stateSeq(m_animationTimer, m_state);

View File

@ -60,15 +60,15 @@ struct HumanoidIdentity {
String hairGroup; String hairGroup;
// Must have :normal and :climb // Must have :normal and :climb
String hairType; String hairType;
String hairDirectives; Directives hairDirectives;
String bodyDirectives; Directives bodyDirectives;
String emoteDirectives; Directives emoteDirectives;
String facialHairGroup; String facialHairGroup;
String facialHairType; String facialHairType;
String facialHairDirectives; Directives facialHairDirectives;
String facialMaskGroup; String facialMaskGroup;
String facialMaskType; String facialMaskType;
String facialMaskDirectives; Directives facialMaskDirectives;
Personality personality; Personality personality;
Vec4B color; Vec4B color;
@ -125,11 +125,11 @@ public:
// empty string, it is disabled. // empty string, it is disabled.
// Asset directives for the head armor. // Asset directives for the head armor.
void setHeadArmorDirectives(String directives); void setHeadArmorDirectives(Directives directives);
// Must have :normal, climb // Must have :normal, climb
void setHeadArmorFrameset(String headFrameset); void setHeadArmorFrameset(String headFrameset);
// Asset directives for the chest, back and front arms armor. // Asset directives for the chest, back and front arms armor.
void setChestArmorDirectives(String directives); void setChestArmorDirectives(Directives directives);
// Will have :run, :normal, and :duck // Will have :run, :normal, and :duck
void setChestArmorFrameset(String chest); void setChestArmorFrameset(String chest);
// Same as back arm image frames // Same as back arm image frames
@ -138,16 +138,16 @@ public:
void setFrontSleeveFrameset(String frontSleeveFrameset); void setFrontSleeveFrameset(String frontSleeveFrameset);
// Asset directives for the legs armor. // 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] // Must have :idle, :duck, :walk[1-8], :run[1-8], :jump[1-4], :fall[1-4]
void setLegsArmorFrameset(String legsFrameset); void setLegsArmorFrameset(String legsFrameset);
// Asset directives for the back armor. // 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] // Must have :idle, :duck, :walk[1-8], :run[1-8], :jump[1-4], :fall[1-4]
void setBackArmorFrameset(String backFrameset); void setBackArmorFrameset(String backFrameset);
void setHelmetMaskDirectives(String helmetMaskDirectives); void setHelmetMaskDirectives(Directives helmetMaskDirectives);
void setBodyHidden(bool hidden); void setBodyHidden(bool hidden);
@ -260,16 +260,16 @@ private:
String getFrontArmFromIdentity() const; String getFrontArmFromIdentity() const;
String getVaporTrailFrameset() const; String getVaporTrailFrameset() const;
String getBodyDirectives() const; Directives getBodyDirectives() const;
String getHairDirectives() const; Directives getHairDirectives() const;
String getEmoteDirectives() const; Directives getEmoteDirectives() const;
String getFacialHairDirectives() const; Directives getFacialHairDirectives() const;
String getFacialMaskDirectives() const; Directives getFacialMaskDirectives() const;
String getHelmetMaskDirectives() const; Directives getHelmetMaskDirectives() const;
String getHeadDirectives() const; Directives getHeadDirectives() const;
String getChestDirectives() const; Directives getChestDirectives() const;
String getLegsDirectives() const; Directives getLegsDirectives() const;
String getBackDirectives() const; Directives getBackDirectives() const;
int getEmoteStateSequence() const; int getEmoteStateSequence() const;
int getArmStateSequence() const; int getArmStateSequence() const;
@ -327,14 +327,14 @@ private:
String m_backSleeveFrameset; String m_backSleeveFrameset;
String m_frontSleeveFrameset; String m_frontSleeveFrameset;
String m_headArmorFrameset; String m_headArmorFrameset;
String m_headArmorDirectives; Directives m_headArmorDirectives;
String m_chestArmorFrameset; String m_chestArmorFrameset;
String m_chestArmorDirectives; Directives m_chestArmorDirectives;
String m_legsArmorFrameset; String m_legsArmorFrameset;
String m_legsArmorDirectives; Directives m_legsArmorDirectives;
String m_backArmorFrameset; String m_backArmorFrameset;
String m_backArmorDirectives; Directives m_backArmorDirectives;
String m_helmetMaskDirectives; Directives m_helmetMaskDirectives;
State m_state; State m_state;
HumanoidEmote m_emoteState; HumanoidEmote m_emoteState;

View File

@ -10,7 +10,7 @@
namespace Star { namespace Star {
Vec2U ImageMetadataDatabase::imageSize(String const& path) const { Vec2U ImageMetadataDatabase::imageSize(AssetPath const& path) const {
MutexLocker locker(m_mutex); MutexLocker locker(m_mutex);
auto i = m_sizeCache.find(path); auto i = m_sizeCache.find(path);
if (i != m_sizeCache.end()) if (i != m_sizeCache.end())
@ -24,7 +24,7 @@ Vec2U ImageMetadataDatabase::imageSize(String const& path) const {
return size; return size;
} }
List<Vec2I> ImageMetadataDatabase::imageSpaces(String const& path, Vec2F position, float fillLimit, bool flip) const { List<Vec2I> ImageMetadataDatabase::imageSpaces(AssetPath const& path, Vec2F position, float fillLimit, bool flip) const {
SpacesEntry key = make_tuple(path, Vec2I::round(position), fillLimit, flip); SpacesEntry key = make_tuple(path, Vec2I::round(position), fillLimit, flip);
MutexLocker locker(m_mutex); MutexLocker locker(m_mutex);
@ -33,7 +33,7 @@ List<Vec2I> ImageMetadataDatabase::imageSpaces(String const& path, Vec2F positio
return i->second; return i->second;
} }
String filteredPath = filterProcessing(path); auto filteredPath = filterProcessing(path);
SpacesEntry filteredKey = make_tuple(filteredPath, Vec2I::round(position), fillLimit, flip); SpacesEntry filteredKey = make_tuple(filteredPath, Vec2I::round(position), fillLimit, flip);
auto j = m_spacesCache.find(filteredKey); auto j = m_spacesCache.find(filteredKey);
@ -88,14 +88,14 @@ List<Vec2I> ImageMetadataDatabase::imageSpaces(String const& path, Vec2F positio
return spaces; return spaces;
} }
RectU ImageMetadataDatabase::nonEmptyRegion(String const& path) const { RectU ImageMetadataDatabase::nonEmptyRegion(AssetPath const& path) const {
MutexLocker locker(m_mutex); MutexLocker locker(m_mutex);
auto i = m_regionCache.find(path); auto i = m_regionCache.find(path);
if (i != m_regionCache.end()) { if (i != m_regionCache.end()) {
return i->second; return i->second;
} }
String filteredPath = filterProcessing(path); auto filteredPath = filterProcessing(path);
auto j = m_regionCache.find(filteredPath); auto j = m_regionCache.find(filteredPath);
if (j != m_regionCache.end()) { if (j != m_regionCache.end()) {
m_regionCache[path] = j->second; m_regionCache[path] = j->second;
@ -117,12 +117,11 @@ RectU ImageMetadataDatabase::nonEmptyRegion(String const& path) const {
return region; return region;
} }
String ImageMetadataDatabase::filterProcessing(String const& path) { AssetPath ImageMetadataDatabase::filterProcessing(AssetPath const& path) {
AssetPath components = AssetPath::split(path); AssetPath newPath = { path.basePath, path.subPath, {} };
auto directives = move(components.directives);
String joined = AssetPath::join(components);
directives.forEach([&](auto const& entry) { List<Directives::Entry> filtered;
path.directives.forEach([&](auto const& entry) {
ImageOperation const& operation = entry.operation; ImageOperation const& operation = entry.operation;
if (!(operation.is<HueShiftImageOperation>() || if (!(operation.is<HueShiftImageOperation>() ||
operation.is<SaturationShiftImageOperation>() || operation.is<SaturationShiftImageOperation>() ||
@ -130,15 +129,15 @@ String ImageMetadataDatabase::filterProcessing(String const& path) {
operation.is<FadeToColorImageOperation>() || operation.is<FadeToColorImageOperation>() ||
operation.is<ScanLinesImageOperation>() || operation.is<ScanLinesImageOperation>() ||
operation.is<SetColorImageOperation>())) { operation.is<SetColorImageOperation>())) {
joined += "?"; filtered.emplace_back(entry);
joined += entry.string;
} }
}); });
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. // 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 // 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. // 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(); return assets->image(path)->size();
}; };
AssetPath components = AssetPath::split(path); if (!assets->assetExists(path.basePath)) {
if (!assets->assetExists(components.basePath)) {
return fallback(); return fallback();
} }
Vec2U imageSize; Vec2U imageSize;
if (components.subPath) { if (path.subPath) {
auto frames = assets->imageFrames(components.basePath); auto frames = assets->imageFrames(path.basePath);
if (!frames) if (!frames)
return fallback(); return fallback();
if (auto rect = frames->getRect(*components.subPath)) if (auto rect = frames->getRect(*path.subPath))
imageSize = rect->size(); imageSize = rect->size();
else else
return fallback(); 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 // so we don't have to call Image::readPngMetadata on the same file more
// than once. // than once.
MutexLocker locker(m_mutex); MutexLocker locker(m_mutex);
if (auto size = m_sizeCache.maybe(components.basePath)) { if (auto size = m_sizeCache.maybe(path.basePath)) {
imageSize = *size; imageSize = *size;
} else { } else {
locker.unlock(); locker.unlock();
imageSize = get<0>(Image::readPngMetadata(assets->openFile(components.basePath))); imageSize = get<0>(Image::readPngMetadata(assets->openFile(path.basePath)));
locker.lock(); 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); 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); entry.operation.call(osa);
return !osa.hasError; return !osa.hasError;
}); });

View File

@ -5,6 +5,7 @@
#include "StarMap.hpp" #include "StarMap.hpp"
#include "StarString.hpp" #include "StarString.hpp"
#include "StarThread.hpp" #include "StarThread.hpp"
#include "StarAssetPath.hpp"
namespace Star { namespace Star {
@ -15,24 +16,24 @@ STAR_CLASS(ImageMetadataDatabase);
// because they are expensive to compute and cheap to keep around. // because they are expensive to compute and cheap to keep around.
class ImageMetadataDatabase { class ImageMetadataDatabase {
public: public:
Vec2U imageSize(String const& path) const; Vec2U imageSize(AssetPath const& path) const;
List<Vec2I> imageSpaces(String const& path, Vec2F position, float fillLimit, bool flip) const; List<Vec2I> imageSpaces(AssetPath const& path, Vec2F position, float fillLimit, bool flip) const;
RectU nonEmptyRegion(String const& path) const; RectU nonEmptyRegion(AssetPath const& path) const;
private: private:
// Removes image processing directives that don't affect image spaces / // Removes image processing directives that don't affect image spaces /
// non-empty regions. // 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 // Path, position, fillLimit, and flip
typedef tuple<String, Vec2I, float, bool> SpacesEntry; typedef tuple<AssetPath, Vec2I, float, bool> SpacesEntry;
mutable Mutex m_mutex; mutable Mutex m_mutex;
mutable StringMap<Vec2U> m_sizeCache; mutable HashMap<AssetPath, Vec2U> m_sizeCache;
mutable HashMap<SpacesEntry, List<Vec2I>> m_spacesCache; mutable HashMap<SpacesEntry, List<Vec2I>> m_spacesCache;
mutable StringMap<RectU> m_regionCache; mutable HashMap<AssetPath, RectU> m_regionCache;
}; };
} }

View File

@ -1204,8 +1204,9 @@ List<Drawable> Object::orientationDrawables(size_t orientationIndex) const {
if (!m_orientationDrawablesCache || orientationIndex != m_orientationDrawablesCache->first) { if (!m_orientationDrawablesCache || orientationIndex != m_orientationDrawablesCache->first) {
m_orientationDrawablesCache = make_pair(orientationIndex, List<Drawable>()); m_orientationDrawablesCache = make_pair(orientationIndex, List<Drawable>());
for (auto const& layer : orientation->imageLayers) { for (auto const& layer : orientation->imageLayers) {
auto drawable = layer; Drawable drawable = layer;
drawable.imagePart().image = drawable.imagePart().image.replaceTags(m_imageKeys, true, "default"); auto& image = drawable.imagePart().image;
image = AssetPath::join(image).replaceTags(m_imageKeys, true, "default");
if (orientation->flipImages) if (orientation->flipImages)
drawable.scale(Vec2F(-1, 1), drawable.boundBox(false).center() - drawable.position); drawable.scale(Vec2F(-1, 1), drawable.boundBox(false).center() - drawable.position);
m_orientationDrawablesCache->second.append(move(drawable)); m_orientationDrawablesCache->second.append(move(drawable));

View File

@ -157,10 +157,11 @@ List<ObjectOrientationPtr> ObjectDatabase::parseOrientations(String const& path,
orientation->config = orientationSettings; orientation->config = orientationSettings;
if (orientationSettings.contains("imageLayers")) { 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 drawable(layer.set("centered", layer.getBool("centered", false)));
drawable.scale(1.0f / TilePixels); drawable.scale(1.0f / TilePixels);
drawable.imagePart().image = AssetPath::relativeTo(path, drawable.imagePart().image);
orientation->imageLayers.append(drawable); orientation->imageLayers.append(drawable);
} }
} else { } else {
@ -191,7 +192,7 @@ List<ObjectOrientationPtr> ObjectDatabase::parseOrientations(String const& path,
auto spaceScanSpaces = Set<Vec2I>::from(orientation->spaces); auto spaceScanSpaces = Set<Vec2I>::from(orientation->spaces);
for (auto const& layer : orientation->imageLayers) { for (auto const& layer : orientation->imageLayers) {
spaceScanSpaces.addAll(root.imageMetadataDatabase()->imageSpaces( spaceScanSpaces.addAll(root.imageMetadataDatabase()->imageSpaces(
layer.imagePart().image.replaceTags(StringMap<String>(), true, "default"), AssetPath::join(layer.imagePart().image).replaceTags(StringMap<String>(), true, "default"),
imagePosition, imagePosition,
orientationSettings.getDouble("spaceScan"), orientationSettings.getDouble("spaceScan"),
orientation->flipImages)); orientation->flipImages));
@ -578,8 +579,9 @@ List<Drawable> ObjectDatabase::cursorHintDrawables(World const* world, String co
auto orientation = config->orientations.at(orientationIndex); auto orientation = config->orientations.at(orientationIndex);
for (auto const& layer : orientation->imageLayers) { for (auto const& layer : orientation->imageLayers) {
auto drawable = layer; Drawable drawable = layer;
drawable.imagePart().image = drawable.imagePart().image.replaceTags(StringMap<String>(), true, "default"); auto& image = drawable.imagePart().image;
image = AssetPath::join(image).replaceTags(StringMap<String>(), true, "default");
if (orientation->flipImages) if (orientation->flipImages)
drawable.scale(Vec2F(-1, 1), drawable.boundBox(false).center() - drawable.position); drawable.scale(Vec2F(-1, 1), drawable.boundBox(false).center() - drawable.position);
drawables.append(move(drawable)); drawables.append(move(drawable));

View File

@ -6,6 +6,7 @@
#include "StarRoot.hpp" #include "StarRoot.hpp"
#include "StarStoredFunctions.hpp" #include "StarStoredFunctions.hpp"
#include "StarPlayer.hpp" #include "StarPlayer.hpp"
#include "StarDirectives.hpp"
namespace Star { namespace Star {
@ -20,7 +21,7 @@ ArmorItem::ArmorItem(Json const& config, String const& directory, Json const& da
m_directives = instanceValue("directives", "").toString(); m_directives = instanceValue("directives", "").toString();
m_colorOptions = colorDirectivesFromConfig(config.getArray("colorOptions", JsonArray{""})); 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()); m_directives = "?" + m_colorOptions.wrap(instanceValue("colorIndex", 0).toUInt());
refreshIconDrawables(); refreshIconDrawables();
@ -39,7 +40,7 @@ List<String> const& ArmorItem::colorOptions() {
return m_colorOptions; return m_colorOptions;
} }
String const& ArmorItem::directives() const { Directives const& ArmorItem::directives() const {
return m_directives; 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_maleImage = AssetPath::relativeTo(directory, config.getString("maleFrames"));
m_femaleImage = AssetPath::relativeTo(directory, config.getString("femaleFrames")); m_femaleImage = AssetPath::relativeTo(directory, config.getString("femaleFrames"));
m_maskDirectives = instanceValue("mask").toString(); String maskDirectivesStr = instanceValue("mask").toString();
if (!m_maskDirectives.empty() && !m_maskDirectives.contains("?")) if (!maskDirectivesStr.empty() && !maskDirectivesStr.contains("?"))
m_maskDirectives = "?addmask=" + AssetPath::relativeTo(directory, m_maskDirectives) + ";0;0"; m_maskDirectives = "?addmask=" + AssetPath::relativeTo(directory, maskDirectivesStr) + ";0;0";
else
m_maskDirectives = maskDirectivesStr;
} }
ItemPtr HeadArmor::clone() const { ItemPtr HeadArmor::clone() const {
@ -104,7 +107,7 @@ String const& HeadArmor::frameset(Gender gender) const {
return m_femaleImage; return m_femaleImage;
} }
String const& HeadArmor::maskDirectives() const { Directives const& HeadArmor::maskDirectives() const {
return m_maskDirectives; return m_maskDirectives;
} }

View File

@ -25,7 +25,7 @@ public:
List<String> const& colorOptions(); List<String> const& colorOptions();
String const& directives() const; Directives const& directives() const;
bool hideBody() const; bool hideBody() const;
@ -38,7 +38,7 @@ private:
List<String> m_colorOptions; List<String> m_colorOptions;
List<PersistentStatusEffect> m_statusEffects; List<PersistentStatusEffect> m_statusEffects;
StringSet m_effectSources; StringSet m_effectSources;
String m_directives; Directives m_directives;
bool m_hideBody; bool m_hideBody;
Maybe<String> m_techModule; Maybe<String> m_techModule;
}; };
@ -51,14 +51,14 @@ public:
virtual ItemPtr clone() const; virtual ItemPtr clone() const;
String const& frameset(Gender gender) const; String const& frameset(Gender gender) const;
String const& maskDirectives() const; Directives const& maskDirectives() const;
virtual List<Drawable> preview(PlayerPtr const& viewer = {}) const; virtual List<Drawable> preview(PlayerPtr const& viewer = {}) const;
private: private:
String m_maleImage; String m_maleImage;
String m_femaleImage; String m_femaleImage;
String m_maskDirectives; Directives m_maskDirectives;
}; };
class ChestArmor : public ArmorItem, public PreviewableItem { class ChestArmor : public ArmorItem, public PreviewableItem {

View File

@ -17,8 +17,10 @@ MaterialItem::MaterialItem(Json const& config, String const& directory, Json con
if (materialHueShift() != MaterialHue()) { if (materialHueShift() != MaterialHue()) {
auto drawables = iconDrawables(); auto drawables = iconDrawables();
for (auto& d : drawables) { for (auto& d : drawables) {
if (d.isImage()) if (d.isImage()) {
d.imagePart().addDirectives(strf("?hueshift=%s", materialHueToDegrees(m_materialHueShift)), false); String image = strf("?hueshift=%s", materialHueToDegrees(m_materialHueShift));
d.imagePart().addDirectives(image, false);
}
} }
setIconDrawables(move(drawables)); setIconDrawables(move(drawables));
} }

View File

@ -610,7 +610,7 @@ List<Drawable> PaintingBeamTool::drawables() const {
auto result = BeamItem::drawables(); auto result = BeamItem::drawables();
for (auto& entry : result) { for (auto& entry : result) {
if (entry.isImage()) if (entry.isImage())
entry.imagePart().image = entry.imagePart().image + m_colorKeys[m_colorIndex]; entry.imagePart().image.directives += m_colorKeys[m_colorIndex];
} }
return result; return result;
} }

View File

@ -13,16 +13,16 @@ AssetTextureGroup::AssetTextureGroup(TextureGroupPtr textureGroup)
Root::singleton().registerReloadListener(m_reloadTracker); Root::singleton().registerReloadListener(m_reloadTracker);
} }
TexturePtr AssetTextureGroup::loadTexture(String const& imageName) { TexturePtr AssetTextureGroup::loadTexture(AssetPath const& imagePath) {
return loadTexture(imageName, false); return loadTexture(imagePath, false);
} }
TexturePtr AssetTextureGroup::tryTexture(String const& imageName) { TexturePtr AssetTextureGroup::tryTexture(AssetPath const& imagePath) {
return loadTexture(imageName, true); return loadTexture(imagePath, true);
} }
bool AssetTextureGroup::textureLoaded(String const& imageName) const { bool AssetTextureGroup::textureLoaded(AssetPath const& imagePath) const {
return m_textureMap.contains(imageName); return m_textureMap.contains(imagePath);
} }
void AssetTextureGroup::cleanup(int64_t textureTimeout) { void AssetTextureGroup::cleanup(int64_t textureTimeout) {
@ -50,8 +50,8 @@ void AssetTextureGroup::cleanup(int64_t textureTimeout) {
} }
} }
TexturePtr AssetTextureGroup::loadTexture(String const& imageName, bool tryTexture) { TexturePtr AssetTextureGroup::loadTexture(AssetPath const& imagePath, bool tryTexture) {
if (auto p = m_textureMap.ptr(imageName)) { if (auto p = m_textureMap.ptr(imagePath)) {
p->second = Time::monotonicMilliseconds(); p->second = Time::monotonicMilliseconds();
return p->first; return p->first;
} }
@ -60,9 +60,9 @@ TexturePtr AssetTextureGroup::loadTexture(String const& imageName, bool tryTextu
ImageConstPtr image; ImageConstPtr image;
if (tryTexture) if (tryTexture)
image = assets->tryImage(imageName); image = assets->tryImage(imagePath);
else else
image = assets->image(imageName); image = assets->image(imagePath);
if (!image) if (!image)
return {}; 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 // in the texture group for these, so we keep track of the image pointers
// returned to deduplicate them. // returned to deduplicate them.
if (auto existingTexture = m_textureDeduplicationMap.value(image)) { if (auto existingTexture = m_textureDeduplicationMap.value(image)) {
m_textureMap.add(imageName, {existingTexture, Time::monotonicMilliseconds()}); m_textureMap.add(imagePath, {existingTexture, Time::monotonicMilliseconds()});
return existingTexture; return existingTexture;
} else { } else {
auto texture = m_textureGroup->create(*image); 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); m_textureDeduplicationMap.add(image, texture);
return texture; return texture;
} }

View File

@ -6,6 +6,7 @@
#include "StarBiMap.hpp" #include "StarBiMap.hpp"
#include "StarListener.hpp" #include "StarListener.hpp"
#include "StarRenderer.hpp" #include "StarRenderer.hpp"
#include "StarAssetPath.hpp"
namespace Star { namespace Star {
@ -20,14 +21,14 @@ public:
// Load the given texture into the texture group if it is not loaded, and // Load the given texture into the texture group if it is not loaded, and
// return the texture pointer. // 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 // If the texture is loaded and ready, returns the texture pointer, otherwise
// queues the texture using Assets::tryImage and returns nullptr. // queues the texture using Assets::tryImage and returns nullptr.
TexturePtr tryTexture(String const& imageName); TexturePtr tryTexture(AssetPath const& imagePath);
// Has the texture been loaded? // 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. // Frees textures that haven't been used in more than 'textureTimeout' time.
// If Root has been reloaded, will simply clear the texture group. // 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 // Returns the texture parameters. If tryTexture is true, then returns none
// if the texture is not loaded, and queues it, otherwise loads texture // if the texture is not loaded, and queues it, otherwise loads texture
// immediately // immediately
TexturePtr loadTexture(String const& imageName, bool tryTexture); TexturePtr loadTexture(AssetPath const& imagePath, bool tryTexture);
TextureGroupPtr m_textureGroup; TextureGroupPtr m_textureGroup;
StringMap<pair<TexturePtr, int64_t>> m_textureMap; HashMap<AssetPath, pair<TexturePtr, int64_t>> m_textureMap;
HashMap<ImageConstPtr, TexturePtr> m_textureDeduplicationMap; HashMap<ImageConstPtr, TexturePtr> m_textureDeduplicationMap;
TrackerListenerPtr m_reloadTracker; TrackerListenerPtr m_reloadTracker;
}; };

View File

@ -313,7 +313,7 @@ bool GuiContext::trySetCursor(Drawable const& drawable, Vec2I const& offset, int
auto assets = Root::singleton().assets(); auto assets = Root::singleton().assets();
auto& imagePath = drawable.imagePart().image; 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) { RectF GuiContext::renderText(String const& s, TextPositioning const& position) {

View File

@ -41,9 +41,10 @@ void ImageWidget::setRotation(float rotation) {
} }
String ImageWidget::image() const { String ImageWidget::image() const {
if (!m_drawables.size()) if (m_drawables.empty())
return ""; return "";
return m_drawables[0].imagePart().image; else
return AssetPath::join(m_drawables[0].imagePart().image);
} }
void ImageWidget::setDrawables(List<Drawable> drawables) { void ImageWidget::setDrawables(List<Drawable> drawables) {