diff --git a/source/base/StarAssets.cpp b/source/base/StarAssets.cpp index aab4468..e5313ea 100644 --- a/source/base/StarAssets.cpp +++ b/source/base/StarAssets.cpp @@ -1,4 +1,5 @@ #include "StarAssets.hpp" +#include "StarAssetPath.hpp" #include "StarFile.hpp" #include "StarTime.hpp" #include "StarDirectoryAssetSource.hpp" @@ -59,169 +60,6 @@ static void validatePath(AssetPath const& components, bool canContainSubPath, bo throw AssetException::format("Path '%s' cannot contain directives", components); } -// The filename is everything after the last slash (excluding directives) and -// up to the first directive marker. -static Maybe> findFilenameRange(std::string const& pathUtf8) { - size_t firstDirectiveOrSubPath = pathUtf8.find_first_of(":?"); - size_t filenameStart = 0; - while (true) { - size_t find = pathUtf8.find('/', filenameStart); - if (find >= firstDirectiveOrSubPath) - break; - filenameStart = find + 1; - } - - if (filenameStart == NPos) { - return {}; - } else if (firstDirectiveOrSubPath == NPos) { - return {{filenameStart, pathUtf8.size()}}; - } else { - return {{filenameStart, firstDirectiveOrSubPath}}; - } -} - -AssetPath AssetPath::split(String const& path) { - auto i = path.begin(); - auto end = path.end(); - - AssetPath components; - - // base paths cannot have any ':' or '?' characters, stop at the first one. - while (i != end) { - String::Char c = *i; - if (c == ':' || c == '?') - break; - - components.basePath += c; - ++i; - } - - // Sub-paths must immediately follow base paths and must start with a ':', - // after this point any further ':' characters are not special. - if (i != end && *i == ':') { - ++i; - while (i != end) { - String::Char c = *i; - if (c == '?') - break; - - if (!components.subPath) - components.subPath.emplace(); - - *components.subPath += c; - ++i; - } - } - - // Directives must follow the base path and optional sub-path, and each - // directive is separated by one or more '?' characters. - while (i != end && *i == '?') { - ++i; - String directive; - while (i != end) { - String::Char c = *i; - if (c == '?') - break; - - directive += c; - ++i; - } - if (!directive.empty()) - components.directives.append(move(directive)); - } - - starAssert(i == end); - - return components; -} - -String AssetPath::join(AssetPath const& components) { - return toString(components); -} - -String AssetPath::setSubPath(String const& path, String const& subPath) { - auto components = split(path); - components.subPath = subPath; - return join(components); -} - -String AssetPath::removeSubPath(String const& path) { - auto components = split(path); - components.subPath.reset(); - return join(components); -} - -String AssetPath::getDirectives(String const& path) { - size_t firstDirective = path.find('?'); - if (firstDirective == NPos) - return {}; - return path.substr(firstDirective + 1); -} - -String AssetPath::addDirectives(String const& path, String const& directives) { - return String::joinWith("?", path, directives); -} - -String AssetPath::removeDirectives(String const& path) { - size_t firstDirective = path.find('?'); - if (firstDirective == NPos) - return path; - return path.substr(0, firstDirective); -} - -String AssetPath::directory(String const& path) { - if (auto p = findFilenameRange(path.utf8())) { - return String(path.utf8().substr(0, p->first)); - } else { - return String(); - } -} - -String AssetPath::filename(String const& path) { - if (auto p = findFilenameRange(path.utf8())) { - return String(path.utf8().substr(p->first, p->second)); - } else { - return String(); - } -} - -String AssetPath::extension(String const& path) { - auto file = filename(path); - auto lastDot = file.findLast("."); - if (lastDot == NPos) - return ""; - - return file.substr(lastDot + 1); -} - -String AssetPath::relativeTo(String const& sourcePath, String const& givenPath) { - if (!givenPath.empty() && givenPath[0] == '/') - return givenPath; - - auto path = directory(sourcePath); - path.append(givenPath); - return path; -} - -bool AssetPath::operator==(AssetPath const& rhs) const { - return tie(basePath, subPath, directives) == tie(rhs.basePath, rhs.subPath, rhs.directives); -} - -std::ostream& operator<<(std::ostream& os, AssetPath const& rhs) { - os << rhs.basePath; - if (rhs.subPath) { - os << ":"; - os << *rhs.subPath; - } - - for (auto const& directive : rhs.directives) { - os << "?"; - os << directive; - } - - return os; -} - Maybe FramesSpecification::getRect(String const& frame) const { if (auto alias = aliases.ptr(frame)) { return frames.get(*alias); @@ -396,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 { @@ -412,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 {}; @@ -1006,9 +842,14 @@ shared_ptr Assets::loadImage(AssetPath const& path) const { as(loadAsset(AssetId{AssetType::Image, {path.basePath, path.subPath, {}}})); if (!source) return {}; - List operations = path.directives.transformed(imageOperationFromString); StringMap references; - for (auto const& ref : imageOperationReferences(operations)) { + StringList referencePaths; + path.directives.forEach([&](auto const& entry) { + addImageOperationReferences(entry.operation, referencePaths); + }); // TODO: This can definitely be better, was changed quickly to support the new Directives. + + + for (auto const& ref : referencePaths) { auto components = AssetPath::split(ref); validatePath(components, true, false); auto refImage = as(loadAsset(AssetId{AssetType::Image, move(components)})); @@ -1019,8 +860,11 @@ shared_ptr Assets::loadImage(AssetPath const& path) const { return unlockDuring([&]() { auto newData = make_shared(); - newData->image = make_shared(processImageOperations( - operations, *source->image, [&](String const& ref) { return references.get(ref).get(); })); + Image newImage = *source->image; + path.directives.forEach([&](auto const& entry) { + processImageOperation(entry.operation, newImage, [&](String const& ref) { return references.get(ref).get(); }); + }); + newData->image = make_shared(move(newImage)); return newData; }); diff --git a/source/base/StarAssets.hpp b/source/base/StarAssets.hpp index 5c47c9e..15b3b50 100644 --- a/source/base/StarAssets.hpp +++ b/source/base/StarAssets.hpp @@ -7,6 +7,7 @@ #include "StarBiMap.hpp" #include "StarThread.hpp" #include "StarAssetSource.hpp" +#include "StarAssetPath.hpp" namespace Star { @@ -18,61 +19,6 @@ STAR_CLASS(Assets); STAR_EXCEPTION(AssetException, StarException); -// Asset paths are not filesystem paths. '/' is always the directory separator, -// and it is not possible to escape any asset source directory. '\' is never a -// valid directory separator. All asset paths are considered case-insensitive. -// -// In addition to the path portion of the asset path, some asset types may also -// have a sub-path, which is always separated from the path portion of the asset -// by ':'. There can be at most 1 sub-path component. -// -// Image paths may also have a directives portion of the full asset path, which -// must come after the path and optional sub-path comopnent. The directives -// portion of the path starts with a '?', and '?' separates each subsquent -// directive. -struct AssetPath { - static AssetPath split(String const& path); - static String join(AssetPath const& path); - - // Get / modify sub-path directly on a joined path string - static String setSubPath(String const& joinedPath, String const& subPath); - static String removeSubPath(String const& joinedPath); - - // Get / modify directives directly on a joined path string - static String getDirectives(String const& joinedPath); - static String addDirectives(String const& joinedPath, String const& directives); - static String removeDirectives(String const& joinedPath); - - // The base directory name for any given path, including the trailing '/'. - // Ignores sub-path and directives. - static String directory(String const& path); - - // The file part of any given path, ignoring sub-path and directives. Path - // must be a file not a directory. - static String filename(String const& path); - - // The file extension of a given file path, ignoring directives and - // sub-paths. - static String extension(String const& path); - - // Computes an absolute asset path from a relative path relative to another - // asset. The sourcePath must be an absolute path (may point to a directory - // or an asset in a directory, and ignores ':' sub-path or ? directives), - // and the givenPath may be either an absolute *or* a relative path. If it - // is an absolute path, it is returned unchanged. If it is a relative path, - // then it is computed as relative to the directory component of the - // sourcePath. - static String relativeTo(String const& sourcePath, String const& givenPath); - - String basePath; - Maybe subPath; - StringList directives; - - bool operator==(AssetPath const& rhs) const; -}; - -std::ostream& operator<<(std::ostream& os, AssetPath const& rhs); - // The contents of an assets .frames file, which can be associated with one or // more images, and specifies named sub-rects of those images. struct FramesSpecification { @@ -173,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/CMakeLists.txt b/source/core/CMakeLists.txt index d0a53b1..6668109 100644 --- a/source/core/CMakeLists.txt +++ b/source/core/CMakeLists.txt @@ -7,6 +7,7 @@ SET (star_core_HEADERS StarAStar.hpp StarAlgorithm.hpp StarArray.hpp + StarAssetPath.hpp StarAtomicSharedPtr.hpp StarAudio.hpp StarBTree.hpp @@ -23,6 +24,7 @@ SET (star_core_HEADERS StarDataStream.hpp StarDataStreamDevices.hpp StarDataStreamExtra.hpp + StarDirectives.hpp StarDynamicLib.hpp StarEither.hpp StarEncode.hpp @@ -121,6 +123,7 @@ SET (star_core_HEADERS SET (star_core_SOURCES StarAudio.cpp + StarAssetPath.cpp StarBTreeDatabase.cpp StarBuffer.cpp StarByteArray.cpp @@ -128,6 +131,7 @@ SET (star_core_SOURCES StarCompression.cpp StarDataStream.cpp StarDataStreamDevices.cpp + StarDirectives.cpp StarEncode.cpp StarFile.cpp StarFont.cpp diff --git a/source/core/StarAssetPath.cpp b/source/core/StarAssetPath.cpp new file mode 100644 index 0000000..82eca93 --- /dev/null +++ b/source/core/StarAssetPath.cpp @@ -0,0 +1,205 @@ +#include "StarAssetPath.hpp" +#include "StarLexicalCast.hpp" + +namespace Star { + +// The filename is everything after the last slash (excluding directives) and +// up to the first directive marker. +static Maybe> findFilenameRange(std::string const& pathUtf8) { + size_t firstDirectiveOrSubPath = pathUtf8.find_first_of(":?"); + size_t filenameStart = 0; + while (true) { + size_t find = pathUtf8.find('/', filenameStart); + if (find >= firstDirectiveOrSubPath) + break; + filenameStart = find + 1; + } + + if (filenameStart == NPos) { + return {}; + } else if (firstDirectiveOrSubPath == NPos) { + return {{filenameStart, pathUtf8.size()}}; + } else { + return {{filenameStart, firstDirectiveOrSubPath}}; + } +} + +AssetPath AssetPath::split(String const& path) { + auto i = path.begin(); + auto end = path.end(); + + AssetPath components; + + // base paths cannot have any ':' or '?' characters, stop at the first one. + while (i != end) { + String::Char c = *i; + if (c == ':' || c == '?') + break; + + components.basePath += c; + ++i; + } + + // Sub-paths must immediately follow base paths and must start with a ':', + // after this point any further ':' characters are not special. + if (i != end && *i == ':') { + ++i; + while (i != end) { + String::Char c = *i; + if (c == '?') + break; + + if (!components.subPath) + components.subPath.emplace(); + + *components.subPath += c; + ++i; + } + } + + // Directives must follow the base path and optional sub-path, and each + // directive is separated by one or more '?' characters. + while (i != end && *i == '?') { + String directives; + while (i != end) { + directives.append(*i); + ++i; + } + + if (!directives.empty()) + components.directives.append(move(directives)); + } + + starAssert(i == end); + + return components; +} + +String AssetPath::join(AssetPath const& components) { + return toString(components); +} + +String AssetPath::setSubPath(String const& path, String const& subPath) { + auto components = split(path); + components.subPath = subPath; + return join(components); +} + +String AssetPath::removeSubPath(String const& path) { + auto components = split(path); + components.subPath.reset(); + return join(components); +} + +String AssetPath::getDirectives(String const& path) { + size_t firstDirective = path.find('?'); + if (firstDirective == NPos) + return {}; + return path.substr(firstDirective + 1); +} + +String AssetPath::addDirectives(String const& path, String const& directives) { + return String::joinWith("?", path, directives); +} + +String AssetPath::removeDirectives(String const& path) { + size_t firstDirective = path.find('?'); + if (firstDirective == NPos) + return path; + return path.substr(0, firstDirective); +} + +String AssetPath::directory(String const& path) { + if (auto p = findFilenameRange(path.utf8())) { + return String(path.utf8().substr(0, p->first)); + } else { + return String(); + } +} + +String AssetPath::filename(String const& path) { + if (auto p = findFilenameRange(path.utf8())) { + return String(path.utf8().substr(p->first, p->second)); + } else { + return String(); + } +} + +String AssetPath::extension(String const& path) { + auto file = filename(path); + auto lastDot = file.findLast("."); + if (lastDot == NPos) + return ""; + + return file.substr(lastDot + 1); +} + +String AssetPath::relativeTo(String const& sourcePath, String const& givenPath) { + if (!givenPath.empty() && givenPath[0] == '/') + return givenPath; + + auto path = directory(sourcePath); + path.append(givenPath); + return path; +} + +bool AssetPath::operator==(AssetPath const& rhs) const { + return tie(basePath, subPath, directives) == tie(rhs.basePath, rhs.subPath, rhs.directives); +} + +AssetPath::AssetPath(const char* path) { + *this = move(AssetPath::split(path)); +} + + +AssetPath::AssetPath(String const& path) { + *this = move(AssetPath::split(path)); +} + +AssetPath::AssetPath(String&& basePath, Maybe&& subPath, DirectivesGroup&& directives) { + this->basePath = move(basePath); + this->subPath = move(subPath); + this->directives = move(directives); +} + +AssetPath::AssetPath(String const& basePath, Maybe const& subPath, DirectivesGroup const& directives) { + this->basePath = basePath; + this->subPath = subPath; + this->directives = directives; +} + +std::ostream& operator<<(std::ostream& os, AssetPath const& rhs) { + os << rhs.basePath; + if (rhs.subPath) { + os << ":"; + os << *rhs.subPath; + } + + rhs.directives.forEach([&](auto const& entry) { + os << "?"; + os << entry.string; + }); + + return os; +} + +size_t hash::operator()(AssetPath const& s) const { + return hashOf(s.basePath, s.subPath, s.directives); +} + +DataStream& operator>>(DataStream& ds, AssetPath& path) { + String string; + ds.read(string); + + path = move(string); + + return ds; +} + +DataStream& operator<<(DataStream& ds, AssetPath const& path) { + ds.write(AssetPath::join(path)); + + return ds; +} + +} diff --git a/source/core/StarAssetPath.hpp b/source/core/StarAssetPath.hpp new file mode 100644 index 0000000..93fa8eb --- /dev/null +++ b/source/core/StarAssetPath.hpp @@ -0,0 +1,80 @@ +#ifndef STAR_ASSET_PATH_HPP +#define STAR_ASSET_PATH_HPP + +#include "StarDirectives.hpp" +#include "StarHash.hpp" +#include "StarDataStream.hpp" + +namespace Star { + +// Asset paths are not filesystem paths. '/' is always the directory separator, +// and it is not possible to escape any asset source directory. '\' is never a +// valid directory separator. All asset paths are considered case-insensitive. +// +// In addition to the path portion of the asset path, some asset types may also +// have a sub-path, which is always separated from the path portion of the asset +// by ':'. There can be at most 1 sub-path component. +// +// Image paths may also have a directives portion of the full asset path, which +// must come after the path and optional sub-path comopnent. The directives +// portion of the path starts with a '?', and '?' separates each subsquent +// directive. +struct AssetPath { + static AssetPath split(String const& path); + static String join(AssetPath const& path); + + // Get / modify sub-path directly on a joined path string + static String setSubPath(String const& joinedPath, String const& subPath); + static String removeSubPath(String const& joinedPath); + + // Get / modify directives directly on a joined path string + static String getDirectives(String const& joinedPath); + static String addDirectives(String const& joinedPath, String const& directives); + static String removeDirectives(String const& joinedPath); + + // The base directory name for any given path, including the trailing '/'. + // Ignores sub-path and directives. + static String directory(String const& path); + + // The file part of any given path, ignoring sub-path and directives. Path + // must be a file not a directory. + static String filename(String const& path); + + // The file extension of a given file path, ignoring directives and + // sub-paths. + static String extension(String const& path); + + // Computes an absolute asset path from a relative path relative to another + // asset. The sourcePath must be an absolute path (may point to a directory + // or an asset in a directory, and ignores ':' sub-path or ? directives), + // and the givenPath may be either an absolute *or* a relative path. If it + // is an absolute path, it is returned unchanged. If it is a relative path, + // then it is computed as relative to the directory component of the + // sourcePath. + static String relativeTo(String const& sourcePath, String const& givenPath); + + AssetPath() = default; + AssetPath(const char* path); + AssetPath(String const& path); + AssetPath(String&& basePath, Maybe&& subPath, DirectivesGroup&& directives); + AssetPath(const String& basePath, const Maybe& subPath, const DirectivesGroup& directives); + String basePath; + Maybe subPath; + DirectivesGroup directives; + + bool operator==(AssetPath const& rhs) const; +}; + +DataStream& operator>>(DataStream& ds, AssetPath& path); +DataStream& operator<<(DataStream& ds, AssetPath const& path); + +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 new file mode 100644 index 0000000..9214abf --- /dev/null +++ b/source/core/StarDirectives.cpp @@ -0,0 +1,241 @@ +#include "StarImage.hpp" +#include "StarImageProcessing.hpp" +#include "StarDirectives.hpp" +#include "StarXXHash.hpp" +#include "StarLogging.hpp" + +namespace Star { + +Directives::Entry::Entry(ImageOperation&& newOperation, String&& newString) { + operation = move(newOperation); + 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() : hash(0) {} +Directives::Directives(String const& directives) : hash(0) { + string = directives; + parse(string); +} + +Directives::Directives(String&& directives) : hash(0) { + string = move(directives); + parse(string); +} + +Directives::Directives(const char* directives) : hash(0), string(directives) { + parse(string); +} + +Directives::Directives(List&& newEntries) { + entries = std::make_shared const>(move(newEntries)); + String newString; + string = move(buildString(newString)); + hash = XXH3_64bits(string.utf8Ptr(), string.utf8Size()); +} + +void Directives::parse(String const& directives) { + if (directives.empty()) + return; + + List newList; + StringList split = directives.split('?'); + newList.reserve(split.size()); + for (String& str : split) { + if (!str.empty()) { + try { + ImageOperation operation = imageOperationFromString(str); + newList.emplace_back(move(operation), move(str)); + } catch (StarException const& e) { + Logger::logf(LogLevel::Error, "Error parsing image operation: %s", e.what()); + } + } + } + + if (newList.empty()) + return; + + entries = std::make_shared const>(move(newList)); + hash = XXH3_64bits(directives.utf8Ptr(), directives.utf8Size()); + //if (directives.utf8Size() > 1000) + // Logger::logf(LogLevel::Debug, "Directives: Parsed %u character long string", directives.utf8Size()); +} + +String& Directives::buildString(String& out) const { + if (entries) { + for (auto& entry : *entries) { + out += "?"; + out += entry.string; + } + } + + return out; +} + +String Directives::toString() const { + return string; +} + +inline bool Directives::empty() const { + return !entries || entries->empty(); +} + +inline Directives::operator bool() const { + return !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_count(0) { + if (directives.empty()) + return; + + Directives parsed(directives); + if (parsed) { + m_directives.emplace_back(move(parsed)); + m_count = m_directives.back().entries->size(); + } +} +DirectivesGroup::DirectivesGroup(String&& directives) : m_count(0) { + if (directives.empty()) { + directives.clear(); + return; + } + + Directives parsed(move(directives)); + if (parsed) { + m_directives.emplace_back(move(parsed)); + m_count = m_directives.back().entries->size(); + } +} + +inline bool DirectivesGroup::empty() const { + return m_count == 0; +} + +inline DirectivesGroup::operator bool() const { + return empty(); +} + +inline bool DirectivesGroup::compare(DirectivesGroup const& other) const { + if (m_count != other.m_count) + return false; + + if (empty()) + return true; + + return hash() == other.hash(); +} + +void DirectivesGroup::append(Directives const& directives) { + m_directives.emplace_back(directives); + if (directives.entries) + 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; +} + +inline String DirectivesGroup::toString() const { + String string; + addToString(string); + return string; +} + +void DirectivesGroup::addToString(String& string) const { + for (auto& directives : m_directives) + string += directives.string; +} + +void DirectivesGroup::forEach(DirectivesCallback callback) const { + for (auto& directives : m_directives) { + if (directives.entries) { + for (auto& entry : *directives.entries) + callback(entry); + } + } +} + +bool DirectivesGroup::forEachAbortable(AbortableDirectivesCallback callback) const { + for (auto& directives : m_directives) { + if (directives.entries) { + for (auto& entry : *directives.entries) { + if (!callback(entry)) + return false; + } + } + } + + return true; +} + +inline Image DirectivesGroup::applyNewImage(Image const& image) const { + Image result = image; + applyExistingImage(result); + return result; +} + +void DirectivesGroup::applyExistingImage(Image& image) const { + forEach([&](auto const& entry) { + processImageOperation(entry.operation, image); + }); +} + +inline size_t DirectivesGroup::hash() const { + XXHash3 hasher; + for (auto& directives : m_directives) + hasher.push((const char*)&directives.hash, sizeof(directives.hash)); + + return hasher.digest(); +} + +bool operator==(DirectivesGroup const& a, DirectivesGroup const& b) { + return a.compare(b); +} + +bool operator!=(DirectivesGroup const& a, DirectivesGroup const& b) { + return !a.compare(b); +} + +size_t hash::operator()(DirectivesGroup const& s) const { + return s.hash(); +} + +} \ No newline at end of file diff --git a/source/core/StarDirectives.hpp b/source/core/StarDirectives.hpp new file mode 100644 index 0000000..440b33a --- /dev/null +++ b/source/core/StarDirectives.hpp @@ -0,0 +1,96 @@ +#ifndef STAR_DIRECTIVES_HPP +#define STAR_DIRECTIVES_HPP + +#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 +class Directives { +public: + struct Entry { + ImageOperation operation; + String string; // One day, we can make this a string_view pointing to Entry::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); + String& buildString(String& out) const; + String toString() const; + inline bool empty() const; + inline operator bool() 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; + String string; +}; + +class DirectivesGroup { +public: + DirectivesGroup(); + DirectivesGroup(String const& directives); + DirectivesGroup(String&& directives); + + void parseDirectivesIntoLeaf(String const& directives); + + inline bool empty() const; + inline operator bool() 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; + void addToString(String& string) const; + + typedef function DirectivesCallback; + typedef function AbortableDirectivesCallback; + + void forEach(DirectivesCallback callback) const; + bool forEachAbortable(AbortableDirectivesCallback callback) const; + + inline Image applyNewImage(const Image& image) const; + void applyExistingImage(Image& image) const; + + inline size_t hash() const; + + friend bool operator==(DirectivesGroup const& a, DirectivesGroup const& b); + friend bool operator!=(DirectivesGroup const& a, DirectivesGroup const& b); +private: + void buildString(String& string, const DirectivesGroup& directives) const; + + List m_directives; + size_t m_count; +}; + + +template <> +struct hash { + size_t operator()(DirectivesGroup const& s) const; +}; + +typedef DirectivesGroup ImageDirectives; + +} + +#endif diff --git a/source/core/StarImageProcessing.cpp b/source/core/StarImageProcessing.cpp index fd07e1c..c2f980e 100644 --- a/source/core/StarImageProcessing.cpp +++ b/source/core/StarImageProcessing.cpp @@ -328,12 +328,23 @@ String imageOperationToString(ImageOperation const& operation) { return ""; } -List parseImageOperations(String const& params) { - List operations; +void parseImageOperations(String const& params, function outputter) { for (auto const& op : params.split('?')) { + if (!op.empty()) + outputter(imageOperationFromString(op)); + } +} + +List parseImageOperations(String const& params) { + auto split = params.split('?'); + List operations; + operations.reserve(split.size()); + + for (auto const& op : split) { if (!op.empty()) operations.append(imageOperationFromString(op)); } + return operations; } @@ -341,227 +352,233 @@ String printImageOperations(List const& list) { return StringList(list.transformed(imageOperationToString)).join("?"); } +void addImageOperationReferences(ImageOperation const& operation, StringList& out) { + if (auto op = operation.ptr()) + out.appendAll(op->maskImages); + else if (auto op = operation.ptr()) + out.appendAll(op->blendImages); +} + StringList imageOperationReferences(List const& operations) { StringList references; - for (auto const& operation : operations) { - if (auto op = operation.ptr()) - references.appendAll(op->maskImages); - else if (auto op = operation.ptr()) - references.appendAll(op->blendImages); - } + for (auto const& operation : operations) + addImageOperationReferences(operation, references); return references; } -Image processImageOperations(List const& operations, Image image, ImageReferenceCallback refCallback) { - for (auto const& operation : operations) { - if (auto op = operation.ptr()) { - image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { - if (pixel[3] != 0) - pixel = Color::hueShiftVec4B(pixel, op->hueShiftAmount); - }); - } else if (auto op = operation.ptr()) { - image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { - if (pixel[3] != 0) { - Color color = Color::rgba(pixel); - color.setSaturation(clamp(color.saturation() + op->saturationShiftAmount, 0.0f, 1.0f)); - pixel = color.toRgba(); - } - }); - } else if (auto op = operation.ptr()) { - image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { - if (pixel[3] != 0) { - Color color = Color::rgba(pixel); - color.setValue(clamp(color.value() * op->brightnessMultiply, 0.0f, 1.0f)); - pixel = color.toRgba(); - } - }); - } else if (auto op = operation.ptr()) { - image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { - pixel[0] = op->rTable[pixel[0]]; - pixel[1] = op->gTable[pixel[1]]; - pixel[2] = op->bTable[pixel[2]]; - }); - } else if (auto op = operation.ptr()) { - image.forEachPixel([&op](unsigned, unsigned y, Vec4B& pixel) { - if (y % 2 == 0) { - pixel[0] = op->fade1.rTable[pixel[0]]; - pixel[1] = op->fade1.gTable[pixel[1]]; - pixel[2] = op->fade1.bTable[pixel[2]]; - } else { - pixel[0] = op->fade2.rTable[pixel[0]]; - pixel[1] = op->fade2.gTable[pixel[1]]; - pixel[2] = op->fade2.bTable[pixel[2]]; - } - }); - } else if (auto op = operation.ptr()) { - image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { - pixel[0] = op->color[0]; - pixel[1] = op->color[1]; - pixel[2] = op->color[2]; - }); - } else if (auto op = operation.ptr()) { - image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { - if (auto m = op->colorReplaceMap.maybe(pixel)) - pixel = *m; - }); +void processImageOperation(ImageOperation const& operation, Image& image, ImageReferenceCallback refCallback) { + if (auto op = operation.ptr()) { + image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { + if (pixel[3] != 0) + pixel = Color::hueShiftVec4B(pixel, op->hueShiftAmount); + }); + } else if (auto op = operation.ptr()) { + image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { + if (pixel[3] != 0) { + Color color = Color::rgba(pixel); + color.setSaturation(clamp(color.saturation() + op->saturationShiftAmount, 0.0f, 1.0f)); + pixel = color.toRgba(); + } + }); + } else if (auto op = operation.ptr()) { + image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { + if (pixel[3] != 0) { + Color color = Color::rgba(pixel); + color.setValue(clamp(color.value() * op->brightnessMultiply, 0.0f, 1.0f)); + pixel = color.toRgba(); + } + }); + } else if (auto op = operation.ptr()) { + image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { + pixel[0] = op->rTable[pixel[0]]; + pixel[1] = op->gTable[pixel[1]]; + pixel[2] = op->bTable[pixel[2]]; + }); + } else if (auto op = operation.ptr()) { + image.forEachPixel([&op](unsigned, unsigned y, Vec4B& pixel) { + if (y % 2 == 0) { + pixel[0] = op->fade1.rTable[pixel[0]]; + pixel[1] = op->fade1.gTable[pixel[1]]; + pixel[2] = op->fade1.bTable[pixel[2]]; + } else { + pixel[0] = op->fade2.rTable[pixel[0]]; + pixel[1] = op->fade2.gTable[pixel[1]]; + pixel[2] = op->fade2.bTable[pixel[2]]; + } + }); + } else if (auto op = operation.ptr()) { + image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { + pixel[0] = op->color[0]; + pixel[1] = op->color[1]; + pixel[2] = op->color[2]; + }); + } else if (auto op = operation.ptr()) { + image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { + if (auto m = op->colorReplaceMap.maybe(pixel)) + pixel = *m; + }); - } else if (auto op = operation.ptr()) { - if (op->maskImages.empty()) - continue; + } else if (auto op = operation.ptr()) { + if (op->maskImages.empty()) + return; - if (!refCallback) - throw StarException("Missing image ref callback during AlphaMaskImageOperation in ImageProcessor::process"); + if (!refCallback) + throw StarException("Missing image ref callback during AlphaMaskImageOperation in ImageProcessor::process"); - List maskImages; - for (auto const& reference : op->maskImages) - maskImages.append(refCallback(reference)); + List maskImages; + for (auto const& reference : op->maskImages) + maskImages.append(refCallback(reference)); - image.forEachPixel([&op, &maskImages](unsigned x, unsigned y, Vec4B& pixel) { - uint8_t maskAlpha = 0; - Vec2U pos = Vec2U(Vec2I(x, y) + op->offset); - for (auto mask : maskImages) { - if (pos[0] < mask->width() && pos[1] < mask->height()) { - if (op->mode == AlphaMaskImageOperation::Additive) { - // We produce our mask alpha from the maximum alpha of any of - // the - // mask images. - maskAlpha = std::max(maskAlpha, mask->get(pos)[3]); - } else if (op->mode == AlphaMaskImageOperation::Subtractive) { - // We produce our mask alpha from the minimum alpha of any of - // the - // mask images. - maskAlpha = std::min(maskAlpha, mask->get(pos)[3]); - } - } - } - pixel[3] = std::min(pixel[3], maskAlpha); - }); - - } else if (auto op = operation.ptr()) { - if (op->blendImages.empty()) - continue; - - if (!refCallback) - throw StarException("Missing image ref callback during BlendImageOperation in ImageProcessor::process"); - - List blendImages; - for (auto const& reference : op->blendImages) - blendImages.append(refCallback(reference)); - - image.forEachPixel([&op, &blendImages](unsigned x, unsigned y, Vec4B& pixel) { - Vec2U pos = Vec2U(Vec2I(x, y) + op->offset); - Vec4F fpixel = Color::v4bToFloat(pixel); - for (auto blend : blendImages) { - if (pos[0] < blend->width() && pos[1] < blend->height()) { - Vec4F blendPixel = Color::v4bToFloat(blend->get(pos)); - if (op->mode == BlendImageOperation::Multiply) - fpixel = fpixel.piecewiseMultiply(blendPixel); - else if (op->mode == BlendImageOperation::Screen) - fpixel = Vec4F::filled(1.0f) - (Vec4F::filled(1.0f) - fpixel).piecewiseMultiply(Vec4F::filled(1.0f) - blendPixel); - } - } - pixel = Color::v4fToByte(fpixel); - }); - - } else if (auto op = operation.ptr()) { - image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { - pixel = pixel.combine(op->color, [](uint8_t a, uint8_t b) -> uint8_t { - return (uint8_t)(((int)a * (int)b) / 255); - }); - }); - - } else if (auto op = operation.ptr()) { - Image borderImage(image.size() + Vec2U::filled(op->pixels * 2), PixelFormat::RGBA32); - borderImage.copyInto(Vec2U::filled(op->pixels), image); - Vec2I borderImageSize = Vec2I(borderImage.size()); - - borderImage.forEachPixel([&op, &image, &borderImageSize](int x, int y, Vec4B& pixel) { - int pixels = op->pixels; - bool includeTransparent = op->includeTransparent; - if (pixel[3] == 0 || (includeTransparent && pixel[3] != 255)) { - int dist = std::numeric_limits::max(); - for (int j = -pixels; j < pixels + 1; j++) { - for (int i = -pixels; i < pixels + 1; i++) { - if (i + x >= pixels && j + y >= pixels && i + x < borderImageSize[0] - pixels && j + y < borderImageSize[1] - pixels) { - Vec4B remotePixel = image.get(i + x - pixels, j + y - pixels); - if (remotePixel[3] != 0) { - dist = std::min(dist, abs(i) + abs(j)); - if (dist == 1) // Early out, if dist is 1 it ain't getting shorter - break; - } - } - } - } - - if (dist < std::numeric_limits::max()) { - float percent = (dist - 1) / (2.0f * pixels - 1); - Color color = Color::rgba(op->startColor).mix(Color::rgba(op->endColor), percent); - if (pixel[3] != 0) { - if (op->outlineOnly) { - float pixelA = byteToFloat(pixel[3]); - color.setAlphaF((1.0f - pixelA) * fminf(pixelA, 0.5f) * 2.0f); - } - else { - Color pixelF = Color::rgba(pixel); - float pixelA = pixelF.alphaF(), colorA = color.alphaF(); - colorA += pixelA * (1.0f - colorA); - pixelF.convertToLinear(); //Mix in linear color space as it is more perceptually accurate - color.convertToLinear(); - color = color.mix(pixelF, pixelA); - color.convertToSRGB(); - color.setAlphaF(colorA); - } - } - pixel = color.toRgba(); - } - } else if (op->outlineOnly) { - pixel = Vec4B(0, 0, 0, 0); - } - }); - - image = borderImage; - - } else if (auto op = operation.ptr()) { - if (op->mode == ScaleImageOperation::Nearest) - image = scaleNearest(image, op->scale); - else if (op->mode == ScaleImageOperation::Bilinear) - image = scaleBilinear(image, op->scale); - else if (op->mode == ScaleImageOperation::Bicubic) - image = scaleBicubic(image, op->scale); - - } else if (auto op = operation.ptr()) { - image = image.subImage(Vec2U(op->subset.min()), Vec2U(op->subset.size())); - - } else if (auto op = operation.ptr()) { - if (op->mode == FlipImageOperation::FlipX || op->mode == FlipImageOperation::FlipXY) { - for (size_t y = 0; y < image.height(); ++y) { - for (size_t xLeft = 0; xLeft < image.width() / 2; ++xLeft) { - size_t xRight = image.width() - 1 - xLeft; - - auto left = image.get(xLeft, y); - auto right = image.get(xRight, y); - - image.set(xLeft, y, right); - image.set(xRight, y, left); + image.forEachPixel([&op, &maskImages](unsigned x, unsigned y, Vec4B& pixel) { + uint8_t maskAlpha = 0; + Vec2U pos = Vec2U(Vec2I(x, y) + op->offset); + for (auto mask : maskImages) { + if (pos[0] < mask->width() && pos[1] < mask->height()) { + if (op->mode == AlphaMaskImageOperation::Additive) { + // We produce our mask alpha from the maximum alpha of any of + // the + // mask images. + maskAlpha = std::max(maskAlpha, mask->get(pos)[3]); + } else if (op->mode == AlphaMaskImageOperation::Subtractive) { + // We produce our mask alpha from the minimum alpha of any of + // the + // mask images. + maskAlpha = std::min(maskAlpha, mask->get(pos)[3]); } } } + pixel[3] = std::min(pixel[3], maskAlpha); + }); - if (op->mode == FlipImageOperation::FlipY || op->mode == FlipImageOperation::FlipXY) { - for (size_t x = 0; x < image.width(); ++x) { - for (size_t yTop = 0; yTop < image.height() / 2; ++yTop) { - size_t yBottom = image.height() - 1 - yTop; + } else if (auto op = operation.ptr()) { + if (op->blendImages.empty()) + return; - auto top = image.get(x, yTop); - auto bottom = image.get(x, yBottom); + if (!refCallback) + throw StarException("Missing image ref callback during BlendImageOperation in ImageProcessor::process"); - image.set(x, yTop, bottom); - image.set(x, yBottom, top); + List blendImages; + for (auto const& reference : op->blendImages) + blendImages.append(refCallback(reference)); + + image.forEachPixel([&op, &blendImages](unsigned x, unsigned y, Vec4B& pixel) { + Vec2U pos = Vec2U(Vec2I(x, y) + op->offset); + Vec4F fpixel = Color::v4bToFloat(pixel); + for (auto blend : blendImages) { + if (pos[0] < blend->width() && pos[1] < blend->height()) { + Vec4F blendPixel = Color::v4bToFloat(blend->get(pos)); + if (op->mode == BlendImageOperation::Multiply) + fpixel = fpixel.piecewiseMultiply(blendPixel); + else if (op->mode == BlendImageOperation::Screen) + fpixel = Vec4F::filled(1.0f) - (Vec4F::filled(1.0f) - fpixel).piecewiseMultiply(Vec4F::filled(1.0f) - blendPixel); + } + } + pixel = Color::v4fToByte(fpixel); + }); + + } else if (auto op = operation.ptr()) { + image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) { + pixel = pixel.combine(op->color, [](uint8_t a, uint8_t b) -> uint8_t { + return (uint8_t)(((int)a * (int)b) / 255); + }); + }); + + } else if (auto op = operation.ptr()) { + Image borderImage(image.size() + Vec2U::filled(op->pixels * 2), PixelFormat::RGBA32); + borderImage.copyInto(Vec2U::filled(op->pixels), image); + Vec2I borderImageSize = Vec2I(borderImage.size()); + + borderImage.forEachPixel([&op, &image, &borderImageSize](int x, int y, Vec4B& pixel) { + int pixels = op->pixels; + bool includeTransparent = op->includeTransparent; + if (pixel[3] == 0 || (includeTransparent && pixel[3] != 255)) { + int dist = std::numeric_limits::max(); + for (int j = -pixels; j < pixels + 1; j++) { + for (int i = -pixels; i < pixels + 1; i++) { + if (i + x >= pixels && j + y >= pixels && i + x < borderImageSize[0] - pixels && j + y < borderImageSize[1] - pixels) { + Vec4B remotePixel = image.get(i + x - pixels, j + y - pixels); + if (remotePixel[3] != 0) { + dist = std::min(dist, abs(i) + abs(j)); + if (dist == 1) // Early out, if dist is 1 it ain't getting shorter + break; + } + } } } + + if (dist < std::numeric_limits::max()) { + float percent = (dist - 1) / (2.0f * pixels - 1); + Color color = Color::rgba(op->startColor).mix(Color::rgba(op->endColor), percent); + if (pixel[3] != 0) { + if (op->outlineOnly) { + float pixelA = byteToFloat(pixel[3]); + color.setAlphaF((1.0f - pixelA) * fminf(pixelA, 0.5f) * 2.0f); + } + else { + Color pixelF = Color::rgba(pixel); + float pixelA = pixelF.alphaF(), colorA = color.alphaF(); + colorA += pixelA * (1.0f - colorA); + pixelF.convertToLinear(); //Mix in linear color space as it is more perceptually accurate + color.convertToLinear(); + color = color.mix(pixelF, pixelA); + color.convertToSRGB(); + color.setAlphaF(colorA); + } + } + pixel = color.toRgba(); + } + } else if (op->outlineOnly) { + pixel = Vec4B(0, 0, 0, 0); + } + }); + + image = borderImage; + + } else if (auto op = operation.ptr()) { + if (op->mode == ScaleImageOperation::Nearest) + image = scaleNearest(image, op->scale); + else if (op->mode == ScaleImageOperation::Bilinear) + image = scaleBilinear(image, op->scale); + else if (op->mode == ScaleImageOperation::Bicubic) + image = scaleBicubic(image, op->scale); + + } else if (auto op = operation.ptr()) { + image = image.subImage(Vec2U(op->subset.min()), Vec2U(op->subset.size())); + + } else if (auto op = operation.ptr()) { + if (op->mode == FlipImageOperation::FlipX || op->mode == FlipImageOperation::FlipXY) { + for (size_t y = 0; y < image.height(); ++y) { + for (size_t xLeft = 0; xLeft < image.width() / 2; ++xLeft) { + size_t xRight = image.width() - 1 - xLeft; + + auto left = image.get(xLeft, y); + auto right = image.get(xRight, y); + + image.set(xLeft, y, right); + image.set(xRight, y, left); + } + } + } + + if (op->mode == FlipImageOperation::FlipY || op->mode == FlipImageOperation::FlipXY) { + for (size_t x = 0; x < image.width(); ++x) { + for (size_t yTop = 0; yTop < image.height() / 2; ++yTop) { + size_t yBottom = image.height() - 1 - yTop; + + auto top = image.get(x, yTop); + auto bottom = image.get(x, yBottom); + + image.set(x, yTop, bottom); + image.set(x, yBottom, top); + } } } } +} + +Image processImageOperations(List const& operations, Image image, ImageReferenceCallback refCallback) { + for (auto const& operation : operations) + processImageOperation(operation, image, refCallback); return image; } diff --git a/source/core/StarImageProcessing.hpp b/source/core/StarImageProcessing.hpp index bcb5936..30640bf 100644 --- a/source/core/StarImageProcessing.hpp +++ b/source/core/StarImageProcessing.hpp @@ -136,6 +136,8 @@ typedef Variant outputter); + // Each operation is assumed to be separated by '?', with parameters // separated by ';' or '=' List parseImageOperations(String const& params); @@ -143,10 +145,14 @@ List parseImageOperations(String const& params); // Each operation separated by '?', returns string with leading '?' String printImageOperations(List const& operations); +void addImageOperationReferences(ImageOperation const& operation, StringList& out); + StringList imageOperationReferences(List const& operations); typedef function ImageReferenceCallback; +void processImageOperation(ImageOperation const& operation, Image& input, ImageReferenceCallback refCallback = {}); + Image processImageOperations(List const& operations, Image input, ImageReferenceCallback refCallback = {}); } diff --git a/source/core/StarLexicalCast.hpp b/source/core/StarLexicalCast.hpp index bee2692..6e6c66e 100644 --- a/source/core/StarLexicalCast.hpp +++ b/source/core/StarLexicalCast.hpp @@ -47,7 +47,7 @@ Type lexicalCast(std::string const& s, std::ios_base::fmtflags flags = std::ios_ if (m) return m.take(); else - throw BadLexicalCast(); + throw BadLexicalCast(strf("Lexical cast failed on '%s'", s)); } template diff --git a/source/frontend/StarMainInterface.cpp b/source/frontend/StarMainInterface.cpp index 6c5dc1d..1dcb6c4 100644 --- a/source/frontend/StarMainInterface.cpp +++ b/source/frontend/StarMainInterface.cpp @@ -965,8 +965,8 @@ 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 breathPath = "/interface/breath/breath.png"; + m_guiContext->drawQuad(breathPath, RectF::withCenter(breathBackgroundCenterPos, Vec2F(imgMetadata->imageSize(breathPath)) * interfaceScale())); for (size_t i = 0; i < 10; i++) { if (i >= blocks) { if (blocks == 0 && Time::monotonicMilliseconds() % 500 > 250) diff --git a/source/game/StarAnimation.cpp b/source/game/StarAnimation.cpp index be13975..7f3847c 100644 --- a/source/game/StarAnimation.cpp +++ b/source/game/StarAnimation.cpp @@ -43,14 +43,10 @@ void Animation::setAngle(float angle) { m_angle = angle; } -void Animation::setProcessing(String processing) { +void Animation::setProcessing(Directives processing) { m_processing = move(processing); } -void Animation::addProcessing(String const& processing) { - m_processing = String::joinWith("?", m_processing, processing); -} - void Animation::setColor(Color color) { m_color = color; } @@ -73,9 +69,8 @@ Drawable Animation::drawable(float pixelSize) const { if (m_appendFrame) baseFrame += ":" + toString(m_frame); - baseFrame = String::joinWith("?", baseFrame, m_processing); - Drawable drawable = Drawable::makeImage(move(baseFrame), pixelSize, m_centered, m_offset); + drawable.imagePart().addDirectives(m_processing); drawable.rotate(m_angle); drawable.color = m_color; return drawable; diff --git a/source/game/StarAnimation.hpp b/source/game/StarAnimation.hpp index 1294fd0..e4a2ece 100644 --- a/source/game/StarAnimation.hpp +++ b/source/game/StarAnimation.hpp @@ -15,8 +15,7 @@ public: void setAngle(float angle); - void setProcessing(String processing); - void addProcessing(String const& processing); + void setProcessing(Directives processing); void setColor(Color color); @@ -44,7 +43,7 @@ private: float m_angle; Vec2F m_offset; bool m_centered; - String m_processing; + Directives m_processing; Color m_color; int m_variantOffset; diff --git a/source/game/StarArmorWearer.cpp b/source/game/StarArmorWearer.cpp index 86f05fc..fffde38 100644 --- a/source/game/StarArmorWearer.cpp +++ b/source/game/StarArmorWearer.cpp @@ -16,7 +16,7 @@ namespace Star { -ArmorWearer::ArmorWearer() { +ArmorWearer::ArmorWearer() : m_lastNude(true), m_needsHumanoidSync(true) { addNetElement(&m_headItemDataNetState); addNetElement(&m_chestItemDataNetState); addNetElement(&m_legsItemDataNetState); @@ -27,7 +27,14 @@ ArmorWearer::ArmorWearer() { addNetElement(&m_backCosmeticItemDataNetState); } -void ArmorWearer::setupHumanoidClothingDrawables(Humanoid& humanoid, bool forceNude) const { +void ArmorWearer::setupHumanoidClothingDrawables(Humanoid& humanoid, bool forceNude) { + if (m_lastNude != forceNude) + m_lastNude = forceNude; + else if (!m_needsHumanoidSync) + return; + + m_needsHumanoidSync = false; + bool bodyHidden = false; if (m_headCosmeticItem && !forceNude) { humanoid.setHeadArmorFrameset(m_headCosmeticItem->frameset(humanoid.identity().gender)); @@ -161,35 +168,59 @@ List ArmorWearer::statusEffects() const { } void ArmorWearer::setHeadItem(HeadArmorPtr headItem) { + if (!Item::itemsEqual(m_headItem, headItem)) + return; m_headItem = headItem; + m_needsHumanoidSync = true; } void ArmorWearer::setHeadCosmeticItem(HeadArmorPtr headCosmeticItem) { + if (!Item::itemsEqual(m_headCosmeticItem, headCosmeticItem)) + return; m_headCosmeticItem = headCosmeticItem; + m_needsHumanoidSync = true; } void ArmorWearer::setChestCosmeticItem(ChestArmorPtr chestCosmeticItem) { + if (!Item::itemsEqual(m_chestCosmeticItem, chestCosmeticItem)) + return; m_chestCosmeticItem = chestCosmeticItem; + m_needsHumanoidSync = true; } void ArmorWearer::setChestItem(ChestArmorPtr chestItem) { + if (!Item::itemsEqual(m_chestItem, chestItem)) + return; m_chestItem = chestItem; + m_needsHumanoidSync = true; } void ArmorWearer::setLegsItem(LegsArmorPtr legsItem) { + if (!Item::itemsEqual(m_legsItem, legsItem)) + return; m_legsItem = legsItem; + m_needsHumanoidSync = true; } void ArmorWearer::setLegsCosmeticItem(LegsArmorPtr legsCosmeticItem) { + if (!Item::itemsEqual(m_legsCosmeticItem, legsCosmeticItem)) + return; m_legsCosmeticItem = legsCosmeticItem; + m_needsHumanoidSync = true; } void ArmorWearer::setBackItem(BackArmorPtr backItem) { + if (!Item::itemsEqual(m_backItem, backItem)) + return; m_backItem = backItem; + m_needsHumanoidSync = true; } void ArmorWearer::setBackCosmeticItem(BackArmorPtr backCosmeticItem) { + if (!Item::itemsEqual(m_backCosmeticItem, backCosmeticItem)) + return; m_backCosmeticItem = backCosmeticItem; + m_needsHumanoidSync = true; } HeadArmorPtr ArmorWearer::headItem() const { @@ -275,23 +306,26 @@ ItemDescriptor ArmorWearer::backCosmeticItemDescriptor() const { void ArmorWearer::netElementsNeedLoad(bool) { auto itemDatabase = Root::singleton().itemDatabase(); + bool changed = false; if (m_headItemDataNetState.pullUpdated()) - itemDatabase->loadItem(m_headItemDataNetState.get(), m_headItem); + changed |= itemDatabase->loadItem(m_headItemDataNetState.get(), m_headItem); if (m_chestItemDataNetState.pullUpdated()) - itemDatabase->loadItem(m_chestItemDataNetState.get(), m_chestItem); + changed |= itemDatabase->loadItem(m_chestItemDataNetState.get(), m_chestItem); if (m_legsItemDataNetState.pullUpdated()) - itemDatabase->loadItem(m_legsItemDataNetState.get(), m_legsItem); + changed |= itemDatabase->loadItem(m_legsItemDataNetState.get(), m_legsItem); if (m_backItemDataNetState.pullUpdated()) - itemDatabase->loadItem(m_backItemDataNetState.get(), m_backItem); + changed |= itemDatabase->loadItem(m_backItemDataNetState.get(), m_backItem); if (m_headCosmeticItemDataNetState.pullUpdated()) - itemDatabase->loadItem(m_headCosmeticItemDataNetState.get(), m_headCosmeticItem); + changed |= itemDatabase->loadItem(m_headCosmeticItemDataNetState.get(), m_headCosmeticItem); if (m_chestCosmeticItemDataNetState.pullUpdated()) - itemDatabase->loadItem(m_chestCosmeticItemDataNetState.get(), m_chestCosmeticItem); + changed |= itemDatabase->loadItem(m_chestCosmeticItemDataNetState.get(), m_chestCosmeticItem); if (m_legsCosmeticItemDataNetState.pullUpdated()) - itemDatabase->loadItem(m_legsCosmeticItemDataNetState.get(), m_legsCosmeticItem); + changed |= itemDatabase->loadItem(m_legsCosmeticItemDataNetState.get(), m_legsCosmeticItem); if (m_backCosmeticItemDataNetState.pullUpdated()) - itemDatabase->loadItem(m_backCosmeticItemDataNetState.get(), m_backCosmeticItem); + changed |= itemDatabase->loadItem(m_backCosmeticItemDataNetState.get(), m_backCosmeticItem); + + m_needsHumanoidSync = changed; } void ArmorWearer::netElementsNeedStore() { diff --git a/source/game/StarArmorWearer.hpp b/source/game/StarArmorWearer.hpp index 4297d59..e4ddf01 100644 --- a/source/game/StarArmorWearer.hpp +++ b/source/game/StarArmorWearer.hpp @@ -26,7 +26,7 @@ class ArmorWearer : public NetElementSyncGroup { public: ArmorWearer(); - void setupHumanoidClothingDrawables(Humanoid& humanoid, bool forceNude) const; + void setupHumanoidClothingDrawables(Humanoid& humanoid, bool forceNude); void effects(EffectEmitter& effectEmitter); List statusEffects() const; @@ -82,6 +82,9 @@ private: NetElementData m_chestCosmeticItemDataNetState; NetElementData m_legsCosmeticItemDataNetState; NetElementData m_backCosmeticItemDataNetState; + + bool m_lastNude; + bool m_needsHumanoidSync; }; } diff --git a/source/game/StarDrawable.cpp b/source/game/StarDrawable.cpp index 5fd5071..aeb8cb6 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) + 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 613ed1a..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,29 +117,27 @@ RectU ImageMetadataDatabase::nonEmptyRegion(String const& path) const { return region; } -String ImageMetadataDatabase::filterProcessing(String const& path) { - AssetPath components = AssetPath::split(path); +AssetPath ImageMetadataDatabase::filterProcessing(AssetPath const& path) { + AssetPath newPath = { path.basePath, path.subPath, {} }; - components.directives.filter([](String const& directive) { - ImageOperation operation; - try { - operation = imageOperationFromString(directive); - } catch (StarException const&) { - return true; - } + List filtered; + path.directives.forEach([&](auto const& entry) { + ImageOperation const& operation = entry.operation; + if (!(operation.is() || + operation.is() || + operation.is() || + operation.is() || + operation.is() || + operation.is())) { + filtered.emplace_back(entry); + } + }); - return !(operation.is() || - operation.is() || - operation.is() || - operation.is() || - operation.is() || - operation.is()); - }); - - 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. @@ -150,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(); @@ -170,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; } } @@ -231,19 +228,13 @@ Vec2U ImageMetadataDatabase::calculateImageSize(String const& path) const { OperationSizeAdjust osa(imageSize); - for (auto const& directive : components.directives) { - ImageOperation operation; - try { - operation = imageOperationFromString(directive); - } catch (StarException const&) { - return fallback(); - } + bool complete = path.directives.forEachAbortable([&](auto const& entry) -> bool { + entry.operation.call(osa); + return !osa.hasError; + }); - operation.call(osa); - if (osa.hasError) { - return fallback(); - } - } + if (!complete) + return fallback(); return imageSize; } 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/StarItem.cpp b/source/game/StarItem.cpp index 68590b5..e25870b 100644 --- a/source/game/StarItem.cpp +++ b/source/game/StarItem.cpp @@ -285,4 +285,13 @@ ItemPtr GenericItem::clone() const { return make_shared(*this); } +bool Item::itemsEqual(ItemConstPtr const& a, ItemConstPtr const& b) { + if (!a && !b) // Both are null + return true; + if (a && b) // Both aren't null, compare + return a->stackableWith(b); + else // One is null, so not equal + return true; +} + } diff --git a/source/game/StarItem.hpp b/source/game/StarItem.hpp index 0dba854..ed13f82 100644 --- a/source/game/StarItem.hpp +++ b/source/game/StarItem.hpp @@ -114,6 +114,8 @@ public: // Returns just the dynamic parameters Json parameters() const; + static bool itemsEqual(ItemConstPtr const& a, ItemConstPtr const& b); + protected: void setMaxStack(uint64_t maxStack); void setDescription(String const& description); 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/StarParticle.cpp b/source/game/StarParticle.cpp index 250b2a4..ad0c168 100644 --- a/source/game/StarParticle.cpp +++ b/source/game/StarParticle.cpp @@ -45,7 +45,6 @@ Particle::Particle() { trail = false; flippable = true; flip = false; - directives = ""; } Particle::Particle(Json const& config, String const& path) { @@ -69,6 +68,12 @@ Particle::Particle(Json const& config, String const& path) { if (type == Type::Animated) initializeAnimation(); + auto pathEnd = string.find('?'); + if (pathEnd == NPos) + directives = ""; + else + directives.parse(string.substr(pathEnd)); + if (config.contains("color")) color = jsonToColor(config.get("color")); @@ -96,9 +101,11 @@ Particle::Particle(Json const& config, String const& path) { length = config.getFloat("length", 10.0f); destructionAction = DestructionActionNames.getLeft(config.getString("destructionAction", "None")); - destructionImage = config.getString("destructionImage", ""); + String destructionImagePath = config.getString("destructionImage", ""); if (destructionAction == DestructionAction::Image) - destructionImage = AssetPath::relativeTo(path, destructionImage); + destructionImagePath = AssetPath::relativeTo(path, destructionImagePath); + destructionImage = destructionImagePath; + destructionTime = config.getFloat("destructionTime", 0.0f); timeToLive = config.getFloat("timeToLive", 0.0f); @@ -113,7 +120,6 @@ Particle::Particle(Json const& config, String const& path) { ignoreWind = config.getBool("ignoreWind", true); trail = config.getBool("trail", false); - directives = ""; } Json Particle::toJson() const { @@ -134,7 +140,7 @@ Json Particle::toJson() const { {"angularVelocity", angularVelocity * 180.0f / Constants::pi}, {"length", length}, {"destructionAction", DestructionActionNames.getRight(destructionAction)}, - {"destructionImage", destructionImage}, + {"destructionImage", AssetPath::join(destructionImage)}, {"destructionTime", destructionTime}, {"timeToLive", timeToLive}, {"layer", LayerNames.getRight(layer)}, @@ -221,7 +227,7 @@ void Particle::destructionUpdate() { size = 1.0f; color = Color::White; type = Particle::Type::Textured; - string = destructionImage; + image = destructionImage; angularVelocity = 0.0f; length = 0.0f; rotation = 0.0f; @@ -232,7 +238,7 @@ void Particle::destructionUpdate() { void Particle::initializeAnimation() { if (!animation) { animation = Animation(AssetPath::removeDirectives(string)); - animation->addProcessing(AssetPath::getDirectives(string)); + animation->setProcessing(directives); } } diff --git a/source/game/StarParticle.hpp b/source/game/StarParticle.hpp index 5dba0fa..b35cb0a 100644 --- a/source/game/StarParticle.hpp +++ b/source/game/StarParticle.hpp @@ -5,6 +5,7 @@ #include "StarColor.hpp" #include "StarBiMap.hpp" #include "StarAnimation.hpp" +#include "StarAssetPath.hpp" namespace Star { @@ -75,6 +76,8 @@ struct Particle { // Used differently depending on the type of the particle. String string; + AssetPath image; + Directives directives; Color color; Color light; @@ -95,7 +98,7 @@ struct Particle { float length; DestructionAction destructionAction; - String destructionImage; + AssetPath destructionImage; float destructionTime; float timeToLive; @@ -110,7 +113,6 @@ struct Particle { bool trail; Maybe animation; - String directives; }; DataStream& operator<<(DataStream& ds, Particle const& particle); diff --git a/source/game/items/StarArmors.cpp b/source/game/items/StarArmors.cpp index 17714fb..71f95a2 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) 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..a19d684 100644 --- a/source/windowing/StarGuiContext.cpp +++ b/source/windowing/StarGuiContext.cpp @@ -135,7 +135,7 @@ void GuiContext::resetInterfaceScissorRect() { renderer()->setScissorRect({}); } -Vec2U GuiContext::textureSize(String const& texName) { +Vec2U GuiContext::textureSize(AssetPath const& texName) { return assetTextureGroup()->loadTexture(texName)->size(); } @@ -143,15 +143,15 @@ void GuiContext::drawQuad(RectF const& screenCoords, Vec4B const& color) { renderer()->render(renderFlatRect(screenCoords, color, 0.0f)); } -void GuiContext::drawQuad(String const& texName, RectF const& screenCoords, Vec4B const& color) { +void GuiContext::drawQuad(AssetPath const& texName, RectF const& screenCoords, Vec4B const& color) { renderer()->render(renderTexturedRect(assetTextureGroup()->loadTexture(texName), screenCoords, color, 0.0f)); } -void GuiContext::drawQuad(String const& texName, Vec2F const& screenPos, int pixelRatio, Vec4B const& color) { +void GuiContext::drawQuad(AssetPath const& texName, Vec2F const& screenPos, int pixelRatio, Vec4B const& color) { renderer()->render(renderTexturedRect(assetTextureGroup()->loadTexture(texName), screenPos, pixelRatio, color, 0.0f)); } -void GuiContext::drawQuad(String const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color) { +void GuiContext::drawQuad(AssetPath const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color) { renderer()->render(RenderQuad{assetTextureGroup()->loadTexture(texName), RenderVertex{Vec2F(screenCoords.xMin(), screenCoords.yMin()), Vec2F(texCoords.xMin(), texCoords.yMin()), color, 0.0f}, RenderVertex{Vec2F(screenCoords.xMax(), screenCoords.yMin()), Vec2F(texCoords.xMax(), texCoords.yMin()), color, 0.0f}, @@ -209,15 +209,15 @@ void GuiContext::drawInterfaceQuad(RectF const& screenCoords, Vec4B const& color drawQuad(screenCoords.scaled(interfaceScale()), color); } -void GuiContext::drawInterfaceQuad(String const& texName, Vec2F const& screenCoords, Vec4B const& color) { +void GuiContext::drawInterfaceQuad(AssetPath const& texName, Vec2F const& screenCoords, Vec4B const& color) { drawQuad(texName, screenCoords * interfaceScale(), interfaceScale(), color); } -void GuiContext::drawInterfaceQuad(String const& texName, Vec2F const& screenCoords, float scale, Vec4B const& color) { +void GuiContext::drawInterfaceQuad(AssetPath const& texName, Vec2F const& screenCoords, float scale, Vec4B const& color) { drawQuad(texName, screenCoords * interfaceScale(), interfaceScale() * scale, color); } -void GuiContext::drawInterfaceQuad(String const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color) { +void GuiContext::drawInterfaceQuad(AssetPath const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color) { drawQuad(texName, texCoords, screenCoords.scaled(interfaceScale()), color); } @@ -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/StarGuiContext.hpp b/source/windowing/StarGuiContext.hpp index 2f08394..bd46f65 100644 --- a/source/windowing/StarGuiContext.hpp +++ b/source/windowing/StarGuiContext.hpp @@ -62,12 +62,12 @@ public: void setInterfaceScissorRect(RectI const& scissor); void resetInterfaceScissorRect(); - Vec2U textureSize(String const& texName); + Vec2U textureSize(AssetPath const& texName); void drawQuad(RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255)); - void drawQuad(String const& texName, RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255)); - void drawQuad(String const& texName, Vec2F const& screenPos, int pixelRatio, Vec4B const& color = Vec4B::filled(255)); - void drawQuad(String const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255)); + void drawQuad(AssetPath const& texName, RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255)); + void drawQuad(AssetPath const& texName, Vec2F const& screenPos, int pixelRatio, Vec4B const& color = Vec4B::filled(255)); + void drawQuad(AssetPath const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255)); void drawDrawable(Drawable drawable, Vec2F const& screenPos, int pixelRatio, Vec4B const& color = Vec4B::filled(255)); @@ -84,9 +84,9 @@ public: void drawInterfaceTriangles(List> const& triangles, Vec4B const& color); void drawInterfaceQuad(RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255)); - void drawInterfaceQuad(String const& texName, Vec2F const& screenPos, Vec4B const& color = Vec4B::filled(255)); - void drawInterfaceQuad(String const& texName, Vec2F const& screenPos, float scale, Vec4B const& color = Vec4B::filled(255)); - void drawInterfaceQuad(String const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255)); + void drawInterfaceQuad(AssetPath const& texName, Vec2F const& screenPos, Vec4B const& color = Vec4B::filled(255)); + void drawInterfaceQuad(AssetPath const& texName, Vec2F const& screenPos, float scale, Vec4B const& color = Vec4B::filled(255)); + void drawInterfaceQuad(AssetPath const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255)); void drawImageStretchSet(ImageStretchSet const& imageSet, RectF const& screenPos, GuiDirection direction = GuiDirection::Horizontal, Vec4B const& color = Vec4B::filled(255)); 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) { diff --git a/source/windowing/StarItemSlotWidget.cpp b/source/windowing/StarItemSlotWidget.cpp index b317c8a..a828838 100644 --- a/source/windowing/StarItemSlotWidget.cpp +++ b/source/windowing/StarItemSlotWidget.cpp @@ -153,8 +153,7 @@ void ItemSlotWidget::renderImpl() { if (m_showLinkIndicator) { // TODO: Hardcoded - context()->drawInterfaceQuad("/interface/inventory/itemlinkindicator.png", - Vec2F(screenPosition() - Vec2I(1, 1))); + context()->drawInterfaceQuad(String("/interface/inventory/itemlinkindicator.png"), Vec2F(screenPosition() - Vec2I(1, 1))); } for (auto i : iconDrawables) @@ -179,7 +178,7 @@ void ItemSlotWidget::renderImpl() { } int frame = (int)roundf(m_progress * 18); // TODO: Hardcoded lol - context()->drawInterfaceQuad(strf("/interface/cooldown.png:%d", frame), Vec2F(screenPosition())); + context()->drawInterfaceQuad(String(strf("/interface/cooldown.png:%d", frame)), Vec2F(screenPosition())); if (m_item->count() > 1 && m_showCount) { // we don't need to tell people that there's only 1 of something context()->setFont(m_font); @@ -192,7 +191,7 @@ void ItemSlotWidget::renderImpl() { } else if (m_drawBackingImageWhenEmpty && m_backingImage != "") { context()->drawInterfaceQuad(m_backingImage, Vec2F(screenPosition())); int frame = (int)roundf(m_progress * 18); // TODO: Hardcoded lol - context()->drawInterfaceQuad(strf("/interface/cooldown.png:%d", frame), Vec2F(screenPosition())); + context()->drawInterfaceQuad(String(strf("/interface/cooldown.png:%d", frame)), Vec2F(screenPosition())); } if (m_highlightEnabled) { diff --git a/source/windowing/StarPortraitWidget.cpp b/source/windowing/StarPortraitWidget.cpp index 0966919..9341d55 100644 --- a/source/windowing/StarPortraitWidget.cpp +++ b/source/windowing/StarPortraitWidget.cpp @@ -38,7 +38,7 @@ void PortraitWidget::renderImpl() { } if (m_entity) { List portrait = m_entity->portrait(m_portraitMode); - for (auto i : portrait) { + for (auto& i : portrait) { i.scale(m_scale); context()->drawInterfaceDrawable(i, Vec2F(screenPosition() + offset)); } diff --git a/source/windowing/StarPortraitWidget.hpp b/source/windowing/StarPortraitWidget.hpp index a51aa20..c56f003 100644 --- a/source/windowing/StarPortraitWidget.hpp +++ b/source/windowing/StarPortraitWidget.hpp @@ -31,12 +31,12 @@ private: PortraitEntityPtr m_entity; PortraitMode m_portraitMode; - String m_noEntityImageFull; - String m_noEntityImagePart; + AssetPath m_noEntityImageFull; + AssetPath m_noEntityImagePart; float m_scale; bool m_iconMode; - String m_iconImage; + AssetPath m_iconImage; Vec2I m_iconOffset; };