diff --git a/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderFontIcon.cpp b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderFontIcon.cpp new file mode 100644 index 00000000..57dad330 --- /dev/null +++ b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderFontIcon.cpp @@ -0,0 +1,261 @@ +#include "AssetLoaderFontIcon.h" + +#include +#include +#include + +#include "Csv/CsvStream.h" +#include "Game/T6/CommonT6.h" +#include "Game/T6/T6.h" +#include "Pool/GlobalAssetPool.h" + +using namespace T6; + +void* AssetLoaderFontIcon::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) +{ + auto* fontIcon = memory->Create(); + memset(fontIcon, 0, sizeof(FontIcon)); + fontIcon->name = memory->Dup(assetName.c_str()); + return fontIcon; +} + +bool AssetLoaderFontIcon::CanLoadFromRaw() const +{ + return true; +} + +std::string AssetLoaderFontIcon::ErrorPrefix(const std::string& assetName, const unsigned rowIndex) +{ + std::ostringstream str; + str << "FontIcon \"" << assetName << "\" Row " << rowIndex << ": "; + return str.str(); +} + +void AssetLoaderFontIcon::PreprocessRow(std::vector& row) +{ + for (auto& cell : row) + { + for (auto c : cell) + { + if (isspace(c)) + continue; + if (c == '#') + cell = ""; + break; + } + } +} + +bool AssetLoaderFontIcon::RowIsEmpty(const std::vector& row) +{ + return std::all_of(row.begin(), row.end(), [](const std::string& cell) + { + return cell.empty(); + }); +} + +bool AssetLoaderFontIcon::ParseInt(int& value, const std::string& str) +{ + char* endPtr; + value = strtol(str.c_str(), &endPtr, 0); + if (endPtr != &str[str.size()]) + return false; + return true; +} + +bool AssetLoaderFontIcon::ParseFloat(float& value, const std::string& str) +{ + char* endPtr; + value = strtof(str.c_str(), &endPtr); + if (endPtr != &str[str.size()]) + return false; + return true; +} + +bool AssetLoaderFontIcon::ParseHashStr(int& value, const std::string& str) +{ + if (!str.empty() && str[0] == '@' && str.size() > 1) + { + char* endPtr; + value = strtol(&str[1], &endPtr, 16); + + if (endPtr != &str[str.size()]) + return false; + } + else + { + value = CommonT6::Com_HashString(str.c_str()); + } + + return true; +} + +bool AssetLoaderFontIcon::ReadIconRow(const std::vector& row, FontIconEntry& icon, const std::string& assetName, const unsigned rowIndex, MemoryManager* memory, + IAssetLoadingManager* manager, std::vector& dependencies) +{ + if (row.size() < COL_COUNT_ICON) + { + std::cout << ErrorPrefix(assetName, rowIndex) << "Column count lower than min column count for entries (" << COL_COUNT_ICON << ")" << std::endl; + return false; + } + + if (!ParseInt(icon.fontIconSize, row[ROW_ICON_SIZE])) + { + std::cout << ErrorPrefix(assetName, rowIndex) << "Failed to parse size" << std::endl; + return false; + } + + if (!ParseFloat(icon.xScale, row[ROW_ICON_XSCALE]) + || !ParseFloat(icon.yScale, row[ROW_ICON_YSCALE])) + { + std::cout << ErrorPrefix(assetName, rowIndex) << "Failed to parse scale" << std::endl; + return false; + } + + auto* materialDependency = manager->LoadDependency(ASSET_TYPE_MATERIAL, row[ROW_ICON_MATERIAL]); + if (materialDependency == nullptr) + { + std::cout << ErrorPrefix(assetName, rowIndex) << "Failed to load material \"" << row[ROW_ICON_MATERIAL] << "\"" << std::endl; + return false; + } + + icon.fontIconMaterialHandle = static_cast(materialDependency->m_ptr); + icon.fontIconName.string = memory->Dup(row[ROW_ICON_NAME].c_str()); + icon.fontIconName.hash = CommonT6::Com_HashString(icon.fontIconName.string); + + return true; +} + +bool AssetLoaderFontIcon::ReadAliasRow(const std::vector& row, FontIconAlias& alias, const std::string& assetName, const unsigned rowIndex, MemoryManager* memory, + IAssetLoadingManager* manager) +{ + if (row.size() < COL_COUNT_ALIAS) + { + std::cout << ErrorPrefix(assetName, rowIndex) << "Column count lower than min column count for aliases (" << COL_COUNT_ALIAS << ")" << std::endl; + return false; + } + + if (!ParseHashStr(alias.aliasHash, row[ROW_ALIAS_NAME])) + { + std::cout << ErrorPrefix(assetName, rowIndex) << "Failed to parse alias \"" << row[ROW_ALIAS_NAME] << "\"" << std::endl; + return false; + } + + if (!ParseHashStr(alias.buttonHash, row[ROW_ALIAS_BUTTON])) + { + std::cout << ErrorPrefix(assetName, rowIndex) << "Failed to parse button \"" << row[ROW_ALIAS_BUTTON] << "\"" << std::endl; + return false; + } + + return true; +} + +bool AssetLoaderFontIcon::LoadFromRaw(const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const +{ + const auto file = searchPath->Open(assetName); + if (!file.IsOpen()) + return false; + + auto* fontIcon = memory->Create(); + fontIcon->name = memory->Dup(assetName.c_str()); + + const CsvInputStream csv(*file.m_stream); + std::vector dependencies; + std::vector currentRow; + std::vector entries; + std::vector aliases; + auto currentRowIndex = 0u; + + while (csv.NextRow(currentRow)) + { + currentRowIndex++; + PreprocessRow(currentRow); + + if (RowIsEmpty(currentRow)) + continue; + + if (currentRow.size() < COL_COUNT_MIN) + { + std::cout << ErrorPrefix(assetName, currentRowIndex) << "Column count lower than min column count (" << COL_COUNT_MIN << ")" << std::endl; + return true; + } + + int index; + if (!ParseInt(index, currentRow[ROW_INDEX]) || index < 0) + { + std::cout << ErrorPrefix(assetName, currentRowIndex) << "Failed to parse index" << std::endl; + return true; + } + + if (currentRow[ROW_TYPE] == VALUE_TYPE_ICON) + { + FontIconEntry icon{}; + if (!ReadIconRow(currentRow, icon, assetName, currentRowIndex, memory, manager, dependencies)) + return true; + + if (static_cast(index) == entries.size()) + { + entries.push_back(icon); + } + else if (static_cast(index) > entries.size()) + { + entries.reserve(index + 1); + entries[index] = icon; + } + else + { + entries[index] = icon; + } + } + else if (currentRow[ROW_TYPE] == VALUE_TYPE_ALIAS) + { + FontIconAlias alias{}; + if (!ReadAliasRow(currentRow, alias, assetName, currentRowIndex, memory, manager)) + return true; + + if (static_cast(index) == aliases.size()) + { + aliases.push_back(alias); + } + else if (static_cast(index) > aliases.size()) + { + aliases.reserve(index + 1); + aliases[index] = alias; + } + else + { + aliases[index] = alias; + } + } + else + { + std::cout << ErrorPrefix(assetName, currentRowIndex) << "Unknown row type \"" << currentRow[ROW_TYPE] << "\"" << std::endl; + return true; + } + } + + fontIcon->numEntries = entries.size(); + fontIcon->numAliasEntries = aliases.size(); + + if (fontIcon->numEntries > 0) + { + fontIcon->fontIconEntry = static_cast(memory->Alloc(sizeof(FontIconEntry) * fontIcon->numEntries)); + for (auto i = 0u; i < entries.size(); i++) + fontIcon->fontIconEntry[i] = entries[i]; + } + else + fontIcon->fontIconEntry = nullptr; + + if (fontIcon->numAliasEntries > 0) + { + fontIcon->fontIconAlias = static_cast(memory->Alloc(sizeof(FontIconAlias) * fontIcon->numAliasEntries)); + for (auto i = 0u; i < aliases.size(); i++) + fontIcon->fontIconAlias[i] = aliases[i]; + } + else + fontIcon->fontIconAlias = nullptr; + + manager->AddAsset(ASSET_TYPE_FONTICON, assetName, fontIcon); + + return true; +} diff --git a/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderFontIcon.h b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderFontIcon.h new file mode 100644 index 00000000..0ec8f000 --- /dev/null +++ b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderFontIcon.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + +#include "Game/T6/T6.h" +#include "AssetLoading/BasicAssetLoader.h" +#include "AssetLoading/IAssetLoadingManager.h" +#include "SearchPath/ISearchPath.h" + +namespace T6 +{ + class AssetLoaderFontIcon final : public BasicAssetLoader + { + static constexpr unsigned ROW_INDEX = 0; + static constexpr unsigned ROW_TYPE = 1; + + static constexpr unsigned ROW_ICON_NAME = 2; + static constexpr unsigned ROW_ICON_MATERIAL = 3; + static constexpr unsigned ROW_ICON_SIZE = 4; + static constexpr unsigned ROW_ICON_XSCALE = 5; + static constexpr unsigned ROW_ICON_YSCALE = 6; + + static constexpr unsigned ROW_ALIAS_NAME = 2; + static constexpr unsigned ROW_ALIAS_BUTTON = 3; + + static constexpr const char* VALUE_TYPE_ICON = "icon"; + static constexpr const char* VALUE_TYPE_ALIAS = "alias"; + + static constexpr unsigned COL_COUNT_ICON = 7; + static constexpr unsigned COL_COUNT_ALIAS = 4; + static constexpr unsigned COL_COUNT_MIN = std::min(COL_COUNT_ICON, COL_COUNT_ALIAS); + + static std::string ErrorPrefix(const std::string& assetName, unsigned rowIndex); + static void PreprocessRow(std::vector& row); + static bool RowIsEmpty(const std::vector& row); + + static bool ParseInt(int& value, const std::string& str); + static bool ParseFloat(float& value, const std::string& str); + static bool ParseHashStr(int& value, const std::string& str); + + static bool ReadIconRow(const std::vector& row, FontIconEntry& icon, const std::string& assetName, unsigned rowIndex, MemoryManager* memory, IAssetLoadingManager* manager, + std::vector& dependencies); + static bool ReadAliasRow(const std::vector& row, FontIconAlias& alias, const std::string& assetName, unsigned rowIndex, MemoryManager* memory, IAssetLoadingManager* manager); + + public: + _NODISCARD void* CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) override; + _NODISCARD bool CanLoadFromRaw() const override; + bool LoadFromRaw(const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const override; + }; +} diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp index d023f715..7f62f18b 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp @@ -4,6 +4,7 @@ #include "Game/T6/GameAssetPoolT6.h" #include "ObjContainer/IPak/IPak.h" #include "ObjLoading.h" +#include "AssetLoaders/AssetLoaderFontIcon.h" #include "AssetLoaders/AssetLoaderLocalizeEntry.h" #include "AssetLoaders/AssetLoaderQdb.h" #include "AssetLoaders/AssetLoaderRawFile.h" @@ -44,7 +45,7 @@ namespace T6 REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_GFXWORLD, GfxWorld)) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_LIGHT_DEF, GfxLightDef)) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_FONT, Font_s)) - REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_FONTICON, FontIcon)) + REGISTER_ASSET_LOADER(AssetLoaderFontIcon) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_MENULIST, MenuList)) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_MENU, menuDef_t)) REGISTER_ASSET_LOADER(AssetLoaderLocalizeEntry)