diff --git a/src/ObjWriting/Game/IW4/Menu/AssetDumperMenuDef.cpp b/src/ObjWriting/Game/IW4/Menu/AssetDumperMenuDef.cpp deleted file mode 100644 index 4a90b9ad..00000000 --- a/src/ObjWriting/Game/IW4/Menu/AssetDumperMenuDef.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "AssetDumperMenuDef.h" - -#include "AssetDumperMenuList.h" -#include "Game/IW4/GameAssetPoolIW4.h" -#include "Game/IW4/Menu/MenuDumperIW4.h" -#include "Menu/AbstractMenuDumper.h" -#include "ObjWriting.h" - -#include -#include - -using namespace IW4; - -std::string AssetDumperMenuDef::GetPathForMenu(menu::MenuDumpingZoneState* zoneState, XAssetInfo* asset) -{ - const auto menuDumpingState = zoneState->m_menu_dumping_state_map.find(asset->Asset()); - - if (menuDumpingState == zoneState->m_menu_dumping_state_map.end()) - return "ui_mp/" + std::string(asset->Asset()->window.name) + ".menu"; - - return menuDumpingState->second.m_path; -} - -bool AssetDumperMenuDef::ShouldDump(XAssetInfo* asset) -{ - return true; -} - -void AssetDumperMenuDef::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) -{ - const auto* menu = asset->Asset(); - auto* zoneState = context.GetZoneAssetDumperState(); - - if (!ObjWriting::ShouldHandleAssetType(ASSET_TYPE_MENULIST)) - { - // Make sure menu paths based on menu lists are created - const auto* gameAssetPool = dynamic_cast(asset->m_zone->m_pools.get()); - for (auto* menuListAsset : *gameAssetPool->m_menu_list) - AssetDumperMenuList::CreateDumpingStateForMenuList(zoneState, menuListAsset->Asset()); - } - - const auto menuFilePath = GetPathForMenu(zoneState, asset); - const auto assetFile = context.OpenAssetFile(menuFilePath); - - if (!assetFile) - return; - - MenuDumper menuDumper(*assetFile); - - menuDumper.Start(); - menuDumper.WriteMenu(menu); - menuDumper.End(); -} diff --git a/src/ObjWriting/Game/IW4/Menu/AssetDumperMenuDef.h b/src/ObjWriting/Game/IW4/Menu/AssetDumperMenuDef.h deleted file mode 100644 index fbebc2c6..00000000 --- a/src/ObjWriting/Game/IW4/Menu/AssetDumperMenuDef.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "Dumping/AbstractAssetDumper.h" -#include "Game/IW4/IW4.h" -#include "Menu/MenuDumpingZoneState.h" - -namespace IW4 -{ - class AssetDumperMenuDef final : public AbstractAssetDumper - { - static std::string GetPathForMenu(menu::MenuDumpingZoneState* zoneState, XAssetInfo* asset); - - protected: - bool ShouldDump(XAssetInfo* asset) override; - void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; - }; -} // namespace IW4 diff --git a/src/ObjWriting/Game/IW4/Menu/AssetDumperMenuList.cpp b/src/ObjWriting/Game/IW4/Menu/AssetDumperMenuList.cpp deleted file mode 100644 index 2c4c690d..00000000 --- a/src/ObjWriting/Game/IW4/Menu/AssetDumperMenuList.cpp +++ /dev/null @@ -1,179 +0,0 @@ -#include "AssetDumperMenuList.h" - -#include "Game/IW4/Menu/MenuDumperIW4.h" -#include "Menu/AbstractMenuDumper.h" -#include "ObjWriting.h" - -#include -#include -#include -#include - -namespace fs = std::filesystem; - -using namespace IW4; - -std::vector AssetDumperMenuList::GetAllUniqueExpressionSupportingData(const MenuList* menuList) -{ - std::vector result; - std::set alreadyAddedSupportingData; - - if (menuList->menus == nullptr) - return result; - - for (auto i = 0; i < menuList->menuCount; i++) - { - if (menuList->menus[i] == nullptr) - continue; - - const auto* menu = menuList->menus[i]; - - if (menu->expressionData == nullptr) - continue; - - if (alreadyAddedSupportingData.find(menu->expressionData) == alreadyAddedSupportingData.end()) - { - result.push_back(menu->expressionData); - alreadyAddedSupportingData.emplace(menu->expressionData); - } - } - - return result; -} - -void AssetDumperMenuList::DumpFunctions(MenuDumper& menuDumper, const MenuList* menuList) -{ - const auto allSupportingData = GetAllUniqueExpressionSupportingData(menuList); - auto functionIndex = 0u; - - assert(allSupportingData.size() <= 1); - - for (const auto* supportingData : allSupportingData) - { - if (supportingData->uifunctions.functions == nullptr) - continue; - - for (auto i = 0; i < supportingData->uifunctions.totalFunctions; i++) - { - const auto* function = supportingData->uifunctions.functions[i]; - if (function != nullptr) - { - std::stringstream ss; - ss << "FUNC_" << functionIndex; - - menuDumper.WriteFunctionDef(ss.str(), function); - } - - functionIndex++; - } - } -} - -void AssetDumperMenuList::DumpMenus(MenuDumper& menuDumper, menu::MenuDumpingZoneState* zoneState, const MenuList* menuList) -{ - for (auto menuNum = 0; menuNum < menuList->menuCount; menuNum++) - { - const auto* menu = menuList->menus[menuNum]; - - const auto menuDumpingState = zoneState->m_menu_dumping_state_map.find(menu); - if (menuDumpingState == zoneState->m_menu_dumping_state_map.end()) - continue; - - // If the menu was embedded directly as menu list write its data in the menu list file - if (menuDumpingState->second.m_alias_menu_list == menuList) - menuDumper.WriteMenu(menu); - else - menuDumper.IncludeMenu(menuDumpingState->second.m_path); - } -} - -bool AssetDumperMenuList::ShouldDump(XAssetInfo* asset) -{ - return true; -} - -void AssetDumperMenuList::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) -{ - const auto* menuList = asset->Asset(); - const auto assetFile = context.OpenAssetFile(asset->m_name); - - if (!assetFile) - return; - - auto* zoneState = context.GetZoneAssetDumperState(); - - MenuDumper menuDumper(*assetFile); - - menuDumper.Start(); - - if (!ObjWriting::Configuration.MenuLegacyMode) - DumpFunctions(menuDumper, menuList); - - DumpMenus(menuDumper, zoneState, menuList); - - menuDumper.End(); -} - -std::string AssetDumperMenuList::PathForMenu(const std::string& menuListParentPath, const menuDef_t* menu) -{ - const auto* menuAssetName = menu->window.name; - - if (!menuAssetName) - return ""; - - if (menuAssetName[0] == ',') - menuAssetName = &menuAssetName[1]; - - std::ostringstream ss; - ss << menuListParentPath << menuAssetName << ".menu"; - - return ss.str(); -} - -void AssetDumperMenuList::CreateDumpingStateForMenuList(menu::MenuDumpingZoneState* zoneState, const MenuList* menuList) -{ - if (menuList->menuCount <= 0 || menuList->menus == nullptr || menuList->name == nullptr) - return; - - const std::string menuListName(menuList->name); - const fs::path p(menuListName); - std::string parentPath; - if (p.has_parent_path()) - parentPath = p.parent_path().string() + "/"; - - for (auto i = 0; i < menuList->menuCount; i++) - { - auto* menu = menuList->menus[i]; - - if (menu == nullptr) - continue; - - auto existingState = zoneState->m_menu_dumping_state_map.find(menu); - if (existingState == zoneState->m_menu_dumping_state_map.end()) - { - auto menuPath = PathForMenu(parentPath, menu); - const auto isTheSameAsMenuList = menuPath == menuListName; - zoneState->CreateMenuDumpingState(menu, std::move(menuPath), isTheSameAsMenuList ? menuList : nullptr); - } - else if (existingState->second.m_alias_menu_list == nullptr) - { - auto menuPath = PathForMenu(parentPath, menu); - const auto isTheSameAsMenuList = menuPath == menuListName; - if (isTheSameAsMenuList) - { - existingState->second.m_alias_menu_list = menuList; - existingState->second.m_path = std::move(menuPath); - } - } - } -} - -void AssetDumperMenuList::DumpPool(AssetDumpingContext& context, AssetPool* pool) -{ - auto* zoneState = context.GetZoneAssetDumperState(); - - for (auto* asset : *pool) - CreateDumpingStateForMenuList(zoneState, asset->Asset()); - - AbstractAssetDumper::DumpPool(context, pool); -} diff --git a/src/ObjWriting/Game/IW4/Menu/AssetDumperMenuList.h b/src/ObjWriting/Game/IW4/Menu/AssetDumperMenuList.h deleted file mode 100644 index 2e368d7f..00000000 --- a/src/ObjWriting/Game/IW4/Menu/AssetDumperMenuList.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include "Dumping/AbstractAssetDumper.h" -#include "Game/IW4/IW4.h" -#include "Game/IW4/Menu/MenuDumperIW4.h" -#include "Menu/MenuDumpingZoneState.h" - -namespace IW4 -{ - class AssetDumperMenuList final : public AbstractAssetDumper - { - static std::vector GetAllUniqueExpressionSupportingData(const MenuList* menuList); - - static void DumpFunctions(MenuDumper& menuDumper, const MenuList* menuList); - static void DumpMenus(MenuDumper& menuDumper, menu::MenuDumpingZoneState* zoneState, const MenuList* menuList); - - static std::string PathForMenu(const std::string& menuListParentPath, const menuDef_t* menu); - - protected: - bool ShouldDump(XAssetInfo* asset) override; - void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; - - public: - static void CreateDumpingStateForMenuList(menu::MenuDumpingZoneState* zoneState, const MenuList* menuList); - void DumpPool(AssetDumpingContext& context, AssetPool* pool) override; - }; -} // namespace IW4 diff --git a/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp b/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp index 5d65da59..995f1cf2 100644 --- a/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp +++ b/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp @@ -1,923 +1,60 @@ #include "MenuDumperIW4.h" -#include "Game/IW4/MenuConstantsIW4.h" +#include "Game/IW4/GameAssetPoolIW4.h" +#include "Game/IW4/Menu/MenuDumperIW4.h" +#include "MenuListDumperIW4.h" +#include "MenuWriterIW4.h" #include "ObjWriting.h" -#include -#include -#include +#include +#include using namespace IW4; +using namespace ::menu; -// Uncomment this macro to skip interpretative expression dumping -// #define DUMP_NAIVE - -#ifdef DUMP_NAIVE -#define DUMP_FUNC WriteStatementNaive -#else -#define DUMP_FUNC WriteStatementSkipInitialUnnecessaryParenthesis -#endif - -size_t MenuDumper::FindStatementClosingParenthesis(const Statement_s* statement, const size_t openingParenthesisPosition) +namespace { - assert(statement->numEntries >= 0); - assert(openingParenthesisPosition < static_cast(statement->numEntries)); - - const auto statementEnd = static_cast(statement->numEntries); - - // The openingParenthesisPosition does not necessarily point to an actual opening parenthesis operator. That's fine though. - // We will pretend it does since the game does sometimes leave out opening parenthesis from the entries. - auto currentParenthesisDepth = 1; - for (auto currentSearchPosition = openingParenthesisPosition + 1; currentSearchPosition < statementEnd; currentSearchPosition++) + std::string GetPathForMenu(MenuDumpingZoneState* zoneState, XAssetInfo* asset) { - const auto& expEntry = statement->entries[currentSearchPosition]; - if (expEntry.type != EET_OPERATOR) - continue; + const auto menuDumpingState = zoneState->m_menu_dumping_state_map.find(asset->Asset()); - // Any function means a "left out" left paren - if (expEntry.data.op == OP_LEFTPAREN || expEntry.data.op >= OP_COUNT) - { - currentParenthesisDepth++; - } - else if (expEntry.data.op == OP_RIGHTPAREN) - { - if (currentParenthesisDepth > 0) - currentParenthesisDepth--; - if (currentParenthesisDepth == 0) - return currentSearchPosition; - } + if (menuDumpingState == zoneState->m_menu_dumping_state_map.end()) + return "ui_mp/" + std::string(asset->Asset()->window.name) + ".menu"; + + return menuDumpingState->second.m_path; + } +} // namespace + +namespace IW4::menu +{ + bool MenuDumper::ShouldDump(XAssetInfo* asset) + { + return true; } - return statementEnd; -} - -void MenuDumper::WriteStatementOperator(const Statement_s* statement, size_t& currentPos, bool& spaceNext) const -{ - const auto& expEntry = statement->entries[currentPos]; - - if (spaceNext && expEntry.data.op != OP_COMMA) - m_stream << " "; - - if (expEntry.data.op == OP_LEFTPAREN) + void MenuDumper::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) { - const auto closingParenPos = FindStatementClosingParenthesis(statement, currentPos); - m_stream << "("; - WriteStatementEntryRange(statement, currentPos + 1, closingParenPos); - m_stream << ")"; + const auto* menu = asset->Asset(); + auto* zoneState = context.GetZoneAssetDumperState(); - currentPos = closingParenPos + 1; - spaceNext = true; - } - else if (expEntry.data.op >= EXP_FUNC_STATIC_DVAR_INT && expEntry.data.op <= EXP_FUNC_STATIC_DVAR_STRING) - { - switch (expEntry.data.op) + if (!ObjWriting::ShouldHandleAssetType(ASSET_TYPE_MENULIST)) { - case EXP_FUNC_STATIC_DVAR_INT: - m_stream << "dvarint"; - break; - - case EXP_FUNC_STATIC_DVAR_BOOL: - m_stream << "dvarbool"; - break; - - case EXP_FUNC_STATIC_DVAR_FLOAT: - m_stream << "dvarfloat"; - break; - - case EXP_FUNC_STATIC_DVAR_STRING: - m_stream << "dvarstring"; - break; - - default: - break; + // Make sure menu paths based on menu lists are created + const auto* gameAssetPool = dynamic_cast(asset->m_zone->m_pools.get()); + for (auto* menuListAsset : *gameAssetPool->m_menu_list) + CreateDumpingStateForMenuList(zoneState, menuListAsset->Asset()); } - // Functions do not have opening parenthesis in the entries. We can just pretend they do though - const auto closingParenPos = FindStatementClosingParenthesis(statement, currentPos); - m_stream << "("; + const auto menuFilePath = GetPathForMenu(zoneState, asset); + const auto assetFile = context.OpenAssetFile(menuFilePath); - if (closingParenPos - currentPos + 1 >= 1) - { - const auto& staticDvarEntry = statement->entries[currentPos + 1]; - if (staticDvarEntry.type == EET_OPERAND && staticDvarEntry.data.operand.dataType == VAL_INT) - { - if (statement->supportingData && statement->supportingData->staticDvarList.staticDvars && staticDvarEntry.data.operand.internals.intVal >= 0 - && staticDvarEntry.data.operand.internals.intVal < statement->supportingData->staticDvarList.numStaticDvars) - { - const auto* staticDvar = statement->supportingData->staticDvarList.staticDvars[staticDvarEntry.data.operand.internals.intVal]; - if (staticDvar && staticDvar->dvarName) - m_stream << staticDvar->dvarName; - } - else - { - m_stream << "#INVALID_DVAR_INDEX"; - } - } - else - { - m_stream << "#INVALID_DVAR_OPERAND"; - } - } + if (!assetFile) + return; - m_stream << ")"; - currentPos = closingParenPos + 1; - spaceNext = true; + auto menuWriter = CreateMenuWriter(*assetFile); + + menuWriter->Start(); + menuWriter->WriteMenu(*menu); + menuWriter->End(); } - else - { - if (expEntry.data.op >= 0 && static_cast(expEntry.data.op) < std::extent_v) - m_stream << g_expFunctionNames[expEntry.data.op]; - - if (expEntry.data.op >= OP_COUNT) - { - // Functions do not have opening parenthesis in the entries. We can just pretend they do though - const auto closingParenPos = FindStatementClosingParenthesis(statement, currentPos); - m_stream << "("; - WriteStatementEntryRange(statement, currentPos + 1, closingParenPos); - m_stream << ")"; - currentPos = closingParenPos + 1; - } - else - currentPos++; - - spaceNext = expEntry.data.op != OP_NOT; - } -} - -void MenuDumper::WriteStatementOperandFunction(const Statement_s* statement, const size_t currentPos) const -{ - const auto& operand = statement->entries[currentPos].data.operand; - - if (operand.internals.function == nullptr) - return; - - if (!ObjWriting::Configuration.MenuLegacyMode) - { - int functionIndex = -1; - if (statement->supportingData && statement->supportingData->uifunctions.functions) - { - for (auto supportingFunctionIndex = 0; supportingFunctionIndex < statement->supportingData->uifunctions.totalFunctions; supportingFunctionIndex++) - { - if (statement->supportingData->uifunctions.functions[supportingFunctionIndex] == operand.internals.function) - { - functionIndex = supportingFunctionIndex; - break; - } - } - } - - if (functionIndex >= 0) - m_stream << "FUNC_" << functionIndex; - else - m_stream << "INVALID_FUNC"; - m_stream << "()"; - } - else - { - m_stream << "("; - WriteStatementSkipInitialUnnecessaryParenthesis(operand.internals.function); - m_stream << ")"; - } -} - -void MenuDumper::WriteStatementOperand(const Statement_s* statement, size_t& currentPos, bool& spaceNext) const -{ - const auto& expEntry = statement->entries[currentPos]; - - if (spaceNext) - m_stream << " "; - - const auto& operand = expEntry.data.operand; - - switch (operand.dataType) - { - case VAL_FLOAT: - m_stream << operand.internals.floatVal; - break; - - case VAL_INT: - m_stream << operand.internals.intVal; - break; - - case VAL_STRING: - WriteEscapedString(operand.internals.stringVal.string); - break; - - case VAL_FUNCTION: - WriteStatementOperandFunction(statement, currentPos); - break; - - default: - break; - } - - currentPos++; - spaceNext = true; -} - -void MenuDumper::WriteStatementEntryRange(const Statement_s* statement, const size_t startOffset, const size_t endOffset) const -{ - assert(startOffset <= endOffset); - assert(endOffset <= static_cast(statement->numEntries)); - - auto currentPos = startOffset; - auto spaceNext = false; - while (currentPos < endOffset) - { - const auto& expEntry = statement->entries[currentPos]; - - if (expEntry.type == EET_OPERATOR) - { - WriteStatementOperator(statement, currentPos, spaceNext); - } - else - { - WriteStatementOperand(statement, currentPos, spaceNext); - } - } -} - -void MenuDumper::WriteStatement(const Statement_s* statement) const -{ - if (statement == nullptr || statement->numEntries < 0) - return; - - WriteStatementEntryRange(statement, 0, static_cast(statement->numEntries)); -} - -void MenuDumper::WriteStatementSkipInitialUnnecessaryParenthesis(const Statement_s* statementValue) const -{ - if (statementValue == nullptr || statementValue->numEntries < 0) - return; - - const auto statementEnd = static_cast(statementValue->numEntries); - - if (statementValue->numEntries >= 1 && statementValue->entries[0].type == EET_OPERATOR && statementValue->entries[0].data.op == OP_LEFTPAREN) - { - const auto parenthesisEnd = FindStatementClosingParenthesis(statementValue, 0); - - if (parenthesisEnd >= statementEnd) - WriteStatementEntryRange(statementValue, 1, statementEnd); - else if (parenthesisEnd == statementEnd - 1) - WriteStatementEntryRange(statementValue, 1, statementEnd - 1); - else - WriteStatementEntryRange(statementValue, 0, statementEnd); - } - else - { - WriteStatementEntryRange(statementValue, 0, statementEnd); - } -} - -void MenuDumper::WriteStatementNaive(const Statement_s* statement) const -{ - const auto entryCount = static_cast(statement->numEntries); - - const auto missingClosingParenthesis = statement->numEntries > 0 && statement->entries[0].type == EET_OPERATOR - && statement->entries[0].data.op == OP_LEFTPAREN - && FindStatementClosingParenthesis(statement, 0) >= static_cast(statement->numEntries); - - for (auto i = 0uz; i < entryCount; i++) - { - const auto& entry = statement->entries[i]; - if (entry.type == EET_OPERAND) - { - size_t pos = i; - bool discard = false; - WriteStatementOperand(statement, pos, discard); - } - else if (entry.data.op >= EXP_FUNC_STATIC_DVAR_INT && entry.data.op <= EXP_FUNC_STATIC_DVAR_STRING) - { - switch (entry.data.op) - { - case EXP_FUNC_STATIC_DVAR_INT: - m_stream << "dvarint"; - break; - - case EXP_FUNC_STATIC_DVAR_BOOL: - m_stream << "dvarbool"; - break; - - case EXP_FUNC_STATIC_DVAR_FLOAT: - m_stream << "dvarfloat"; - break; - - case EXP_FUNC_STATIC_DVAR_STRING: - m_stream << "dvarstring"; - break; - - default: - break; - } - - // Functions do not have opening parenthesis in the entries. We can just pretend they do though - const auto closingParenPos = FindStatementClosingParenthesis(statement, i); - m_stream << "("; - - if (closingParenPos - i + 1u >= 1u) - { - const auto& staticDvarEntry = statement->entries[i + 1]; - if (staticDvarEntry.type == EET_OPERAND && staticDvarEntry.data.operand.dataType == VAL_INT) - { - if (statement->supportingData && statement->supportingData->staticDvarList.staticDvars && staticDvarEntry.data.operand.internals.intVal >= 0 - && staticDvarEntry.data.operand.internals.intVal < statement->supportingData->staticDvarList.numStaticDvars) - { - const auto* staticDvar = statement->supportingData->staticDvarList.staticDvars[staticDvarEntry.data.operand.internals.intVal]; - if (staticDvar && staticDvar->dvarName) - m_stream << staticDvar->dvarName; - } - else - { - m_stream << "#INVALID_DVAR_INDEX"; - } - } - else - { - m_stream << "#INVALID_DVAR_OPERAND"; - } - } - - m_stream << ")"; - i = closingParenPos; - } - else - { - assert(entry.data.op >= 0 && static_cast(entry.data.op) < std::extent_v); - if (entry.data.op >= 0 && static_cast(entry.data.op) < std::extent_v) - m_stream << g_expFunctionNames[entry.data.op]; - if (entry.data.op >= OP_COUNT) - m_stream << "("; - } - } - - if (missingClosingParenthesis) - m_stream << ")"; -} - -void MenuDumper::WriteStatementProperty(const std::string& propertyKey, const Statement_s* statementValue, bool isBooleanStatement) const -{ - if (statementValue == nullptr || statementValue->numEntries < 0) - return; - - Indent(); - WriteKey(propertyKey); - - if (isBooleanStatement) - { - m_stream << "when("; - DUMP_FUNC(statementValue); - m_stream << ");\n"; - } - else - { - DUMP_FUNC(statementValue); - m_stream << ";\n"; - } -} - -void MenuDumper::WriteSetLocalVarData(const std::string& setFunction, const SetLocalVarData* setLocalVarData) const -{ - if (setLocalVarData == nullptr) - return; - - Indent(); - m_stream << setFunction << " " << setLocalVarData->localVarName << " "; - WriteStatement(setLocalVarData->expression); - m_stream << ";\n"; -} - -// #define WRITE_ORIGINAL_SCRIPT - -void MenuDumper::WriteUnconditionalScript(const char* script) const -{ -#ifdef WRITE_ORIGINAL_SCRIPT - Indent(); - m_stream << script << "\n"; - return; -#endif - - const auto tokenList = CreateScriptTokenList(script); - - auto isNewStatement = true; - for (const auto& token : tokenList) - { - if (isNewStatement) - { - if (token == ";") - continue; - - Indent(); - } - - if (token == ";") - { - m_stream << ";\n"; - isNewStatement = true; - continue; - } - - if (!isNewStatement) - m_stream << " "; - else - isNewStatement = false; - - if (DoesTokenNeedQuotationMarks(token)) - m_stream << "\"" << token << "\""; - else - m_stream << token; - } - - if (!isNewStatement) - m_stream << ";\n"; -} - -void MenuDumper::WriteMenuEventHandlerSet(const MenuEventHandlerSet* eventHandlerSet) -{ - Indent(); - m_stream << "{\n"; - IncIndent(); - - for (auto i = 0; i < eventHandlerSet->eventHandlerCount; i++) - { - const auto* eventHandler = eventHandlerSet->eventHandlers[i]; - if (eventHandler == nullptr) - continue; - - switch (eventHandler->eventType) - { - case EVENT_UNCONDITIONAL: - WriteUnconditionalScript(eventHandler->eventData.unconditionalScript); - break; - - case EVENT_IF: - if (eventHandler->eventData.conditionalScript == nullptr || eventHandler->eventData.conditionalScript->eventExpression == nullptr - || eventHandler->eventData.conditionalScript->eventHandlerSet == nullptr) - { - continue; - } - - Indent(); - m_stream << "if ("; - WriteStatementSkipInitialUnnecessaryParenthesis(eventHandler->eventData.conditionalScript->eventExpression); - m_stream << ")\n"; - WriteMenuEventHandlerSet(eventHandler->eventData.conditionalScript->eventHandlerSet); - break; - - case EVENT_ELSE: - if (eventHandler->eventData.elseScript == nullptr) - continue; - - Indent(); - m_stream << "else\n"; - WriteMenuEventHandlerSet(eventHandler->eventData.elseScript); - break; - - case EVENT_SET_LOCAL_VAR_BOOL: - WriteSetLocalVarData("setLocalVarBool", eventHandler->eventData.setLocalVarData); - break; - - case EVENT_SET_LOCAL_VAR_INT: - WriteSetLocalVarData("setLocalVarInt", eventHandler->eventData.setLocalVarData); - break; - - case EVENT_SET_LOCAL_VAR_FLOAT: - WriteSetLocalVarData("setLocalVarFloat", eventHandler->eventData.setLocalVarData); - break; - - case EVENT_SET_LOCAL_VAR_STRING: - WriteSetLocalVarData("setLocalVarString", eventHandler->eventData.setLocalVarData); - break; - - default: - break; - } - } - - DecIndent(); - Indent(); - m_stream << "}\n"; -} - -void MenuDumper::WriteMenuEventHandlerSetProperty(const std::string& propertyKey, const MenuEventHandlerSet* eventHandlerSetValue) -{ - if (eventHandlerSetValue == nullptr) - return; - - Indent(); - m_stream << propertyKey << "\n"; - WriteMenuEventHandlerSet(eventHandlerSetValue); -} - -void MenuDumper::WriteRectProperty(const std::string& propertyKey, const rectDef_s& rect) const -{ - Indent(); - WriteKey(propertyKey); - m_stream << rect.x << " " << rect.y << " " << rect.w << " " << rect.h << " " << static_cast(rect.horzAlign) << " " << static_cast(rect.vertAlign) - << "\n"; -} - -void MenuDumper::WriteMaterialProperty(const std::string& propertyKey, const Material* materialValue) const -{ - if (materialValue == nullptr || materialValue->info.name == nullptr) - return; - - if (materialValue->info.name[0] == ',') - WriteStringProperty(propertyKey, &materialValue->info.name[1]); - else - WriteStringProperty(propertyKey, materialValue->info.name); -} - -void MenuDumper::WriteSoundAliasProperty(const std::string& propertyKey, const snd_alias_list_t* soundAliasValue) const -{ - if (soundAliasValue == nullptr) - return; - - WriteStringProperty(propertyKey, soundAliasValue->aliasName); -} - -void MenuDumper::WriteDecodeEffectProperty(const std::string& propertyKey, const itemDef_s* item) const -{ - if (!item->decayActive) - return; - - Indent(); - WriteKey(propertyKey); - m_stream << item->fxLetterTime << " " << item->fxDecayStartTime << " " << item->fxDecayDuration << "\n"; -} - -void MenuDumper::WriteItemKeyHandlerProperty(const ItemKeyHandler* itemKeyHandlerValue) -{ - for (const auto* currentHandler = itemKeyHandlerValue; currentHandler; currentHandler = currentHandler->next) - { - if (currentHandler->key >= '!' && currentHandler->key <= '~' && currentHandler->key != '"') - { - std::ostringstream ss; - ss << "execKey \"" << static_cast(currentHandler->key) << "\""; - WriteMenuEventHandlerSetProperty(ss.str(), currentHandler->action); - } - else - { - std::ostringstream ss; - ss << "execKeyInt " << currentHandler->key; - WriteMenuEventHandlerSetProperty(ss.str(), currentHandler->action); - } - } -} - -void MenuDumper::WriteFloatExpressionsProperty(const ItemFloatExpression* floatExpressions, int floatExpressionCount) const -{ - if (!floatExpressions) - return; - - for (int i = 0; i < floatExpressionCount; i++) - { - const auto& floatExpression = floatExpressions[i]; - - if (floatExpression.target < 0 || floatExpression.target >= ITEM_FLOATEXP_TGT_COUNT) - continue; - - std::string propertyName = std::string("exp ") + floatExpressionTargetBindings[floatExpression.target].name + std::string(" ") - + floatExpressionTargetBindings[floatExpression.target].componentName; - - WriteStatementProperty(propertyName, floatExpression.expression, false); - } -} - -void MenuDumper::WriteMultiTokenStringProperty(const std::string& propertyKey, const char* value) const -{ - if (!value) - return; - - Indent(); - WriteKey(propertyKey); - - const auto tokenList = CreateScriptTokenList(value); - - auto firstToken = true; - m_stream << "{ "; - for (const auto& token : tokenList) - { - if (firstToken) - firstToken = false; - else - m_stream << ";"; - m_stream << "\"" << token << "\""; - } - if (!firstToken) - m_stream << " "; - m_stream << "}\n"; -} - -void MenuDumper::WriteColumnProperty(const std::string& propertyKey, const listBoxDef_s* listBox) const -{ - if (listBox->numColumns <= 0) - return; - - Indent(); - WriteKey(propertyKey); - m_stream << listBox->numColumns << "\n"; - - for (auto col = 0; col < listBox->numColumns; col++) - { - Indent(); - for (auto i = 0u; i < MENU_KEY_SPACING; i++) - m_stream << " "; - - m_stream << listBox->columnInfo[col].pos << " " << listBox->columnInfo[col].width << " " << listBox->columnInfo[col].maxChars << " " - << listBox->columnInfo[col].alignment << "\n"; - } -} - -void MenuDumper::WriteListBoxProperties(const itemDef_s* item) -{ - if (item->type != ITEM_TYPE_LISTBOX || item->typeData.listBox == nullptr) - return; - - const auto* listBox = item->typeData.listBox; - WriteKeywordProperty("notselectable", listBox->notselectable != 0); - WriteKeywordProperty("noscrollbars", listBox->noScrollBars != 0); - WriteKeywordProperty("usepaging", listBox->usePaging != 0); - WriteFloatProperty("elementwidth", listBox->elementWidth, 0.0f); - WriteFloatProperty("elementheight", listBox->elementHeight, 0.0f); - WriteFloatProperty("feeder", item->special, 0.0f); - WriteIntProperty("elementtype", listBox->elementStyle, 0); - WriteColumnProperty("columns", listBox); - WriteMenuEventHandlerSetProperty("doubleclick", listBox->onDoubleClick); - WriteColorProperty("selectBorder", listBox->selectBorder, COLOR_0000); - WriteMaterialProperty("selectIcon", listBox->selectIcon); -} - -void MenuDumper::WriteDvarFloatProperty(const std::string& propertyKey, const itemDef_s* item, const editFieldDef_s* editField) const -{ - if (item->dvar == nullptr) - return; - - Indent(); - WriteKey(propertyKey); - m_stream << "\"" << item->dvar << "\" " << editField->defVal << " " << editField->minVal << " " << editField->maxVal << "\n"; -} - -void MenuDumper::WriteEditFieldProperties(const itemDef_s* item) const -{ - switch (item->type) - { - case ITEM_TYPE_TEXT: - case ITEM_TYPE_EDITFIELD: - case ITEM_TYPE_NUMERICFIELD: - case ITEM_TYPE_SLIDER: - case ITEM_TYPE_YESNO: - case ITEM_TYPE_BIND: - case ITEM_TYPE_VALIDFILEFIELD: - case ITEM_TYPE_DECIMALFIELD: - case ITEM_TYPE_UPREDITFIELD: - case ITEM_TYPE_EMAILFIELD: - case ITEM_TYPE_PASSWORDFIELD: - break; - - default: - return; - } - - if (item->typeData.editField == nullptr) - return; - - const auto* editField = item->typeData.editField; - if (std::fabs(-1.0f - editField->defVal) >= std::numeric_limits::epsilon() - || std::fabs(-1.0f - editField->minVal) >= std::numeric_limits::epsilon() - || std::fabs(-1.0f - editField->maxVal) >= std::numeric_limits::epsilon()) - { - WriteDvarFloatProperty("dvarFloat", item, editField); - } - else - { - WriteStringProperty("dvar", item->dvar); - } - WriteStringProperty("localvar", item->localVar); - WriteIntProperty("maxChars", editField->maxChars, 0); - WriteKeywordProperty("maxCharsGotoNext", editField->maxCharsGotoNext != 0); - WriteIntProperty("maxPaintChars", editField->maxPaintChars, 0); -} - -void MenuDumper::WriteMultiValueProperty(const multiDef_s* multiDef) const -{ - Indent(); - if (multiDef->strDef) - WriteKey("dvarStrList"); - else - WriteKey("dvarFloatList"); - - m_stream << "{"; - for (auto i = 0; i < multiDef->count; i++) - { - if (multiDef->dvarList[i] == nullptr || multiDef->strDef && multiDef->dvarStr[i] == nullptr) - continue; - - m_stream << " \"" << multiDef->dvarList[i] << "\""; - - if (multiDef->strDef) - m_stream << " \"" << multiDef->dvarStr[i] << "\""; - else - m_stream << " " << multiDef->dvarValue[i] << ""; - } - m_stream << " }\n"; -} - -void MenuDumper::WriteMultiProperties(const itemDef_s* item) const -{ - if (item->type != ITEM_TYPE_MULTI || item->typeData.multi == nullptr) - return; - - const auto* multiDef = item->typeData.multi; - - if (multiDef->count <= 0) - return; - - WriteStringProperty("dvar", item->dvar); - WriteStringProperty("localvar", item->localVar); - WriteMultiValueProperty(multiDef); -} - -void MenuDumper::WriteEnumDvarProperties(const itemDef_s* item) const -{ - if (item->type != ITEM_TYPE_DVARENUM) - return; - - WriteStringProperty("dvar", item->dvar); - WriteStringProperty("localvar", item->localVar); - WriteStringProperty("dvarEnumList", item->typeData.enumDvarName); -} - -void MenuDumper::WriteTickerProperties(const itemDef_s* item) const -{ - if (item->type != ITEM_TYPE_NEWS_TICKER || item->typeData.ticker == nullptr) - return; - - const auto* newsTickerDef = item->typeData.ticker; - WriteIntProperty("spacing", newsTickerDef->spacing, 0); - WriteIntProperty("speed", newsTickerDef->speed, 0); - WriteIntProperty("newsfeed", newsTickerDef->feedId, 0); -} - -void MenuDumper::WriteItemData(const itemDef_s* item) -{ - WriteStringProperty("name", item->window.name); - WriteStringProperty("text", item->text); - WriteKeywordProperty("textsavegame", item->itemFlags & ITEM_FLAG_SAVE_GAME_INFO); - WriteKeywordProperty("textcinematicsubtitle", item->itemFlags & ITEM_FLAG_CINEMATIC_SUBTITLE); - WriteStringProperty("group", item->window.group); - WriteRectProperty("rect", item->window.rectClient); - WriteIntProperty("style", item->window.style, 0); - WriteKeywordProperty("decoration", item->window.staticFlags & WINDOW_FLAG_DECORATION); - WriteKeywordProperty("autowrapped", item->window.staticFlags & WINDOW_FLAG_AUTO_WRAPPED); - WriteKeywordProperty("horizontalscroll", item->window.staticFlags & WINDOW_FLAG_HORIZONTAL_SCROLL); - WriteIntProperty("type", item->type, ITEM_TYPE_TEXT); - WriteIntProperty("border", item->window.border, 0); - WriteFloatProperty("borderSize", item->window.borderSize, 0.0f); - - if (item->visibleExp) - WriteStatementProperty("visible", item->visibleExp, true); - else if (item->window.dynamicFlags[0] & WINDOW_FLAG_VISIBLE) - WriteIntProperty("visible", 1, 0); - - WriteStatementProperty("disabled", item->disabledExp, true); - WriteIntProperty("ownerdraw", item->window.ownerDraw, 0); - WriteFlagsProperty("ownerdrawFlag", item->window.ownerDrawFlags); - WriteIntProperty("align", item->alignment, 0); - WriteIntProperty("textalign", item->textAlignMode, 0); - WriteFloatProperty("textalignx", item->textalignx, 0.0f); - WriteFloatProperty("textaligny", item->textaligny, 0.0f); - WriteFloatProperty("textscale", item->textscale, 0.0f); - WriteIntProperty("textstyle", item->textStyle, 0); - WriteIntProperty("textfont", item->fontEnum, 0); - WriteColorProperty("backcolor", item->window.backColor, COLOR_0000); - WriteColorProperty("forecolor", item->window.foreColor, COLOR_1111); - WriteColorProperty("bordercolor", item->window.borderColor, COLOR_0000); - WriteColorProperty("outlinecolor", item->window.outlineColor, COLOR_0000); - WriteColorProperty("disablecolor", item->window.disableColor, COLOR_0000); - WriteColorProperty("glowcolor", item->glowColor, COLOR_0000); - WriteMaterialProperty("background", item->window.background); - WriteMenuEventHandlerSetProperty("onFocus", item->onFocus); - WriteMenuEventHandlerSetProperty("leaveFocus", item->leaveFocus); - WriteMenuEventHandlerSetProperty("mouseEnter", item->mouseEnter); - WriteMenuEventHandlerSetProperty("mouseExit", item->mouseExit); - WriteMenuEventHandlerSetProperty("mouseEnterText", item->mouseEnterText); - WriteMenuEventHandlerSetProperty("mouseExitText", item->mouseExitText); - WriteMenuEventHandlerSetProperty("action", item->action); - WriteMenuEventHandlerSetProperty("accept", item->accept); - // WriteFloatProperty("special", item->special, 0.0f); - WriteSoundAliasProperty("focusSound", item->focusSound); - WriteStringProperty("dvarTest", item->dvarTest); - - if (item->dvarFlags & ITEM_DVAR_FLAG_ENABLE) - WriteMultiTokenStringProperty("enableDvar", item->enableDvar); - else if (item->dvarFlags & ITEM_DVAR_FLAG_DISABLE) - WriteMultiTokenStringProperty("disableDvar", item->enableDvar); - else if (item->dvarFlags & ITEM_DVAR_FLAG_SHOW) - WriteMultiTokenStringProperty("showDvar", item->enableDvar); - else if (item->dvarFlags & ITEM_DVAR_FLAG_HIDE) - WriteMultiTokenStringProperty("hideDvar", item->enableDvar); - else if (item->dvarFlags & ITEM_DVAR_FLAG_FOCUS) - WriteMultiTokenStringProperty("focusDvar", item->enableDvar); - - WriteItemKeyHandlerProperty(item->onKey); - WriteStatementProperty("exp text", item->textExp, false); - WriteStatementProperty("exp material", item->materialExp, false); - WriteFloatExpressionsProperty(item->floatExpressions, item->floatExpressionCount); - WriteIntProperty("gamemsgwindowindex", item->gameMsgWindowIndex, 0); - WriteIntProperty("gamemsgwindowmode", item->gameMsgWindowMode, 0); - WriteDecodeEffectProperty("decodeEffect", item); - - WriteListBoxProperties(item); - WriteEditFieldProperties(item); - WriteMultiProperties(item); - WriteEnumDvarProperties(item); - WriteTickerProperties(item); -} - -void MenuDumper::WriteItemDefs(const itemDef_s* const* itemDefs, size_t itemCount) -{ - for (auto i = 0u; i < itemCount; i++) - { - StartItemDefScope(); - - WriteItemData(itemDefs[i]); - - EndScope(); - } -} - -void MenuDumper::WriteMenuData(const menuDef_t* menu) -{ - WriteStringProperty("name", menu->window.name); - WriteBoolProperty("fullscreen", menu->fullScreen, false); - WriteKeywordProperty("screenSpace", menu->window.staticFlags & WINDOW_FLAG_SCREEN_SPACE); - WriteKeywordProperty("decoration", menu->window.staticFlags & WINDOW_FLAG_DECORATION); - WriteRectProperty("rect", menu->window.rect); - WriteIntProperty("style", menu->window.style, 0); - WriteIntProperty("border", menu->window.border, 0); - WriteFloatProperty("borderSize", menu->window.borderSize, 0.0f); - WriteColorProperty("backcolor", menu->window.backColor, COLOR_0000); - WriteColorProperty("forecolor", menu->window.foreColor, COLOR_1111); - WriteColorProperty("bordercolor", menu->window.borderColor, COLOR_0000); - WriteColorProperty("focuscolor", menu->focusColor, COLOR_0000); - WriteColorProperty("outlinecolor", menu->window.outlineColor, COLOR_0000); - WriteMaterialProperty("background", menu->window.background); - WriteIntProperty("ownerdraw", menu->window.ownerDraw, 0); - WriteFlagsProperty("ownerdrawFlag", menu->window.ownerDrawFlags); - WriteKeywordProperty("outOfBoundsClick", menu->window.staticFlags & WINDOW_FLAG_OUT_OF_BOUNDS_CLICK); - WriteStringProperty("soundLoop", menu->soundName); - WriteKeywordProperty("popup", menu->window.staticFlags & WINDOW_FLAG_POPUP); - WriteFloatProperty("fadeClamp", menu->fadeClamp, 0.0f); - WriteIntProperty("fadeCycle", menu->fadeCycle, 0); - WriteFloatProperty("fadeAmount", menu->fadeAmount, 0.0f); - WriteFloatProperty("fadeInAmount", menu->fadeInAmount, 0.0f); - WriteFloatProperty("blurWorld", menu->blurRadius, 0.0f); - WriteKeywordProperty("legacySplitScreenScale", menu->window.staticFlags & WINDOW_FLAG_LEGACY_SPLIT_SCREEN_SCALE); - WriteKeywordProperty("hiddenDuringScope", menu->window.staticFlags & WINDOW_FLAG_HIDDEN_DURING_SCOPE); - WriteKeywordProperty("hiddenDuringFlashbang", menu->window.staticFlags & WINDOW_FLAG_HIDDEN_DURING_FLASH_BANG); - WriteKeywordProperty("hiddenDuringUI", menu->window.staticFlags & WINDOW_FLAG_HIDDEN_DURING_UI); - WriteStringProperty("allowedBinding", menu->allowedBinding); - WriteKeywordProperty("textOnlyFocus", menu->window.staticFlags & WINDOW_FLAG_TEXT_ONLY_FOCUS); - - if (menu->visibleExp) - WriteStatementProperty("visible", menu->visibleExp, true); - else if (menu->window.dynamicFlags[0] & WINDOW_FLAG_VISIBLE) - WriteIntProperty("visible", 1, 0); - - WriteStatementProperty("exp rect X", menu->rectXExp, false); - WriteStatementProperty("exp rect Y", menu->rectYExp, false); - WriteStatementProperty("exp rect W", menu->rectWExp, false); - WriteStatementProperty("exp rect H", menu->rectHExp, false); - WriteStatementProperty("exp openSound", menu->openSoundExp, false); - WriteStatementProperty("exp closeSound", menu->closeSoundExp, false); - WriteMenuEventHandlerSetProperty("onOpen", menu->onOpen); - WriteMenuEventHandlerSetProperty("onClose", menu->onClose); - WriteMenuEventHandlerSetProperty("onRequestClose", menu->onCloseRequest); - WriteMenuEventHandlerSetProperty("onESC", menu->onESC); - WriteItemKeyHandlerProperty(menu->onKey); - WriteItemDefs(menu->items, menu->itemCount); -} - -MenuDumper::MenuDumper(std::ostream& stream) - : AbstractMenuDumper(stream) -{ -} - -void MenuDumper::WriteFunctionDef(const std::string& functionName, const Statement_s* statement) -{ - StartFunctionDefScope(); - - WriteStringProperty("name", functionName); - WriteStatementProperty("value", statement, false); - - EndScope(); -} - -void MenuDumper::WriteMenu(const menuDef_t* menu) -{ - StartMenuDefScope(); - - WriteMenuData(menu); - - EndScope(); -} +} // namespace IW4::menu diff --git a/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.h b/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.h index 9baff905..5567b3f6 100644 --- a/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.h +++ b/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.h @@ -1,57 +1,15 @@ #pragma once +#include "Dumping/AbstractAssetDumper.h" #include "Game/IW4/IW4.h" -#include "Menu/AbstractMenuDumper.h" +#include "Menu/MenuDumpingZoneState.h" -#include - -namespace IW4 +namespace IW4::menu { - class MenuDumper : public AbstractMenuDumper + class MenuDumper final : public AbstractAssetDumper { - static size_t FindStatementClosingParenthesis(const Statement_s* statement, size_t openingParenthesisPosition); - - void WriteStatementNaive(const Statement_s* statement) const; - - void WriteStatementOperator(const Statement_s* statement, size_t& currentPos, bool& spaceNext) const; - void WriteStatementOperandFunction(const Statement_s* statement, size_t currentPos) const; - void WriteStatementOperand(const Statement_s* statement, size_t& currentPos, bool& spaceNext) const; - void WriteStatementEntryRange(const Statement_s* statement, size_t startOffset, size_t endOffset) const; - void WriteStatement(const Statement_s* statement) const; - void WriteStatementSkipInitialUnnecessaryParenthesis(const Statement_s* statementValue) const; - void WriteStatementProperty(const std::string& propertyKey, const Statement_s* statementValue, bool isBooleanStatement) const; - - void WriteSetLocalVarData(const std::string& setFunction, const SetLocalVarData* setLocalVarData) const; - void WriteUnconditionalScript(const char* script) const; - void WriteMenuEventHandlerSet(const MenuEventHandlerSet* eventHandlerSet); - void WriteMenuEventHandlerSetProperty(const std::string& propertyKey, const MenuEventHandlerSet* eventHandlerSetValue); - - void WriteRectProperty(const std::string& propertyKey, const rectDef_s& rect) const; - void WriteMaterialProperty(const std::string& propertyKey, const Material* materialValue) const; - void WriteSoundAliasProperty(const std::string& propertyKey, const snd_alias_list_t* soundAliasValue) const; - void WriteDecodeEffectProperty(const std::string& propertyKey, const itemDef_s* item) const; - void WriteItemKeyHandlerProperty(const ItemKeyHandler* itemKeyHandlerValue); - void WriteMultiTokenStringProperty(const std::string& propertyKey, const char* value) const; - void WriteFloatExpressionsProperty(const ItemFloatExpression* floatExpressions, int floatExpressionCount) const; - void WriteColumnProperty(const std::string& propertyKey, const listBoxDef_s* listBox) const; - - void WriteListBoxProperties(const itemDef_s* item); - void WriteDvarFloatProperty(const std::string& propertyKey, const itemDef_s* item, const editFieldDef_s* editField) const; - void WriteEditFieldProperties(const itemDef_s* item) const; - void WriteMultiValueProperty(const multiDef_s* multiDef) const; - void WriteMultiProperties(const itemDef_s* item) const; - void WriteEnumDvarProperties(const itemDef_s* item) const; - void WriteTickerProperties(const itemDef_s* item) const; - - void WriteItemData(const itemDef_s* item); - void WriteItemDefs(const itemDef_s* const* itemDefs, size_t itemCount); - - void WriteMenuData(const menuDef_t* menu); - - public: - explicit MenuDumper(std::ostream& stream); - - void WriteFunctionDef(const std::string& functionName, const Statement_s* statement); - void WriteMenu(const menuDef_t* menu); + protected: + bool ShouldDump(XAssetInfo* asset) override; + void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; }; -} // namespace IW4 +} // namespace IW4::menu diff --git a/src/ObjWriting/Game/IW4/Menu/MenuListDumperIW4.cpp b/src/ObjWriting/Game/IW4/Menu/MenuListDumperIW4.cpp new file mode 100644 index 00000000..2d2614af --- /dev/null +++ b/src/ObjWriting/Game/IW4/Menu/MenuListDumperIW4.cpp @@ -0,0 +1,187 @@ +#include "MenuListDumperIW4.h" + +#include "Game/IW4/Menu/MenuDumperIW4.h" +#include "Menu/AbstractMenuWriter.h" +#include "MenuWriterIW4.h" +#include "ObjWriting.h" + +#include +#include +#include +#include + +namespace fs = std::filesystem; + +using namespace IW4; +using namespace ::menu; + +namespace +{ + std::vector GetAllUniqueExpressionSupportingData(const MenuList* menuList) + { + std::vector result; + std::set alreadyAddedSupportingData; + + if (menuList->menus == nullptr) + return result; + + for (auto i = 0; i < menuList->menuCount; i++) + { + if (menuList->menus[i] == nullptr) + continue; + + const auto* menu = menuList->menus[i]; + + if (menu->expressionData == nullptr) + continue; + + if (alreadyAddedSupportingData.find(menu->expressionData) == alreadyAddedSupportingData.end()) + { + result.push_back(menu->expressionData); + alreadyAddedSupportingData.emplace(menu->expressionData); + } + } + + return result; + } + + void DumpFunctions(IW4::menu::IWriterIW4& menuDumper, const MenuList* menuList) + { + const auto allSupportingData = GetAllUniqueExpressionSupportingData(menuList); + auto functionIndex = 0u; + + assert(allSupportingData.size() <= 1); + + for (const auto* supportingData : allSupportingData) + { + if (supportingData->uifunctions.functions == nullptr) + continue; + + for (auto i = 0; i < supportingData->uifunctions.totalFunctions; i++) + { + const auto* function = supportingData->uifunctions.functions[i]; + if (function != nullptr) + { + std::stringstream ss; + ss << "FUNC_" << functionIndex; + + menuDumper.WriteFunctionDef(ss.str(), function); + } + + functionIndex++; + } + } + } + + void DumpMenus(IW4::menu::IWriterIW4& menuDumper, ::menu::MenuDumpingZoneState* zoneState, const MenuList* menuList) + { + for (auto menuNum = 0; menuNum < menuList->menuCount; menuNum++) + { + const auto* menu = menuList->menus[menuNum]; + + const auto menuDumpingState = zoneState->m_menu_dumping_state_map.find(menu); + if (menuDumpingState == zoneState->m_menu_dumping_state_map.end()) + continue; + + // If the menu was embedded directly as menu list write its data in the menu list file + if (menuDumpingState->second.m_alias_menu_list == menuList) + menuDumper.WriteMenu(*menu); + else + menuDumper.IncludeMenu(menuDumpingState->second.m_path); + } + } + + std::string PathForMenu(const std::string& menuListParentPath, const menuDef_t* menu) + { + const auto* menuAssetName = menu->window.name; + + if (!menuAssetName) + return ""; + + if (menuAssetName[0] == ',') + menuAssetName = &menuAssetName[1]; + + std::ostringstream ss; + ss << menuListParentPath << menuAssetName << ".menu"; + + return ss.str(); + } +} // namespace + +namespace IW4::menu +{ + void CreateDumpingStateForMenuList(::menu::MenuDumpingZoneState* zoneState, const MenuList* menuList) + { + if (menuList->menuCount <= 0 || menuList->menus == nullptr || menuList->name == nullptr) + return; + + const std::string menuListName(menuList->name); + const fs::path p(menuListName); + std::string parentPath; + if (p.has_parent_path()) + parentPath = p.parent_path().string() + "/"; + + for (auto i = 0; i < menuList->menuCount; i++) + { + auto* menu = menuList->menus[i]; + + if (menu == nullptr) + continue; + + auto existingState = zoneState->m_menu_dumping_state_map.find(menu); + if (existingState == zoneState->m_menu_dumping_state_map.end()) + { + auto menuPath = PathForMenu(parentPath, menu); + const auto isTheSameAsMenuList = menuPath == menuListName; + zoneState->CreateMenuDumpingState(menu, std::move(menuPath), isTheSameAsMenuList ? menuList : nullptr); + } + else if (existingState->second.m_alias_menu_list == nullptr) + { + auto menuPath = PathForMenu(parentPath, menu); + const auto isTheSameAsMenuList = menuPath == menuListName; + if (isTheSameAsMenuList) + { + existingState->second.m_alias_menu_list = menuList; + existingState->second.m_path = std::move(menuPath); + } + } + } + } + + bool MenuListDumper::ShouldDump(XAssetInfo* asset) + { + return true; + } + + void MenuListDumper::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) + { + const auto* menuList = asset->Asset(); + const auto assetFile = context.OpenAssetFile(asset->m_name); + + if (!assetFile) + return; + + auto* zoneState = context.GetZoneAssetDumperState(); + + auto menuWriter = CreateMenuWriter(*assetFile); + + menuWriter->Start(); + + if (!ObjWriting::Configuration.MenuLegacyMode) + DumpFunctions(*menuWriter, menuList); + + DumpMenus(*menuWriter, zoneState, menuList); + + menuWriter->End(); + } + + void MenuListDumper::DumpPool(AssetDumpingContext& context, AssetPool* pool) + { + auto* zoneState = context.GetZoneAssetDumperState(); + + for (auto* asset : *pool) + CreateDumpingStateForMenuList(zoneState, asset->Asset()); + + AbstractAssetDumper::DumpPool(context, pool); + } +} // namespace IW4::menu diff --git a/src/ObjWriting/Game/IW4/Menu/MenuListDumperIW4.h b/src/ObjWriting/Game/IW4/Menu/MenuListDumperIW4.h new file mode 100644 index 00000000..891ba458 --- /dev/null +++ b/src/ObjWriting/Game/IW4/Menu/MenuListDumperIW4.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Dumping/AbstractAssetDumper.h" +#include "Game/IW4/IW4.h" +#include "Game/IW4/Menu/MenuDumperIW4.h" +#include "Menu/MenuDumpingZoneState.h" + +namespace IW4::menu +{ + void CreateDumpingStateForMenuList(::menu::MenuDumpingZoneState* zoneState, const MenuList* menuList); + + class MenuListDumper final : public AbstractAssetDumper + { + protected: + bool ShouldDump(XAssetInfo* asset) override; + void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; + + public: + void DumpPool(AssetDumpingContext& context, AssetPool* pool) override; + }; +} // namespace IW4::menu diff --git a/src/ObjWriting/Game/IW4/Menu/MenuWriterIW4.cpp b/src/ObjWriting/Game/IW4/Menu/MenuWriterIW4.cpp new file mode 100644 index 00000000..ad19833b --- /dev/null +++ b/src/ObjWriting/Game/IW4/Menu/MenuWriterIW4.cpp @@ -0,0 +1,957 @@ +#include "MenuWriterIW4.h" + +#include "Game/IW4/MenuConstantsIW4.h" +#include "Menu/AbstractMenuWriter.h" +#include "ObjWriting.h" + +#include +#include +#include + +using namespace IW4; + +// Uncomment this macro to skip interpretative expression dumping +// #define DUMP_NAIVE + +#ifdef DUMP_NAIVE +#define DUMP_FUNC WriteStatementNaive +#else +#define DUMP_FUNC WriteStatementSkipInitialUnnecessaryParenthesis +#endif + +namespace +{ + size_t FindStatementClosingParenthesis(const Statement_s* statement, const size_t openingParenthesisPosition) + { + assert(statement->numEntries >= 0); + assert(openingParenthesisPosition < static_cast(statement->numEntries)); + + const auto statementEnd = static_cast(statement->numEntries); + + // The openingParenthesisPosition does not necessarily point to an actual opening parenthesis operator. That's fine though. + // We will pretend it does since the game does sometimes leave out opening parenthesis from the entries. + auto currentParenthesisDepth = 1; + for (auto currentSearchPosition = openingParenthesisPosition + 1; currentSearchPosition < statementEnd; currentSearchPosition++) + { + const auto& expEntry = statement->entries[currentSearchPosition]; + if (expEntry.type != EET_OPERATOR) + continue; + + // Any function means a "left out" left paren + if (expEntry.data.op == OP_LEFTPAREN || expEntry.data.op >= OP_COUNT) + { + currentParenthesisDepth++; + } + else if (expEntry.data.op == OP_RIGHTPAREN) + { + if (currentParenthesisDepth > 0) + currentParenthesisDepth--; + if (currentParenthesisDepth == 0) + return currentSearchPosition; + } + } + + return statementEnd; + } + + class MenuWriter final : public ::menu::AbstractBaseWriter, public IW4::menu::IWriterIW4 + { + public: + explicit MenuWriter(std::ostream& stream) + : AbstractBaseWriter(stream) + { + } + + void WriteFunctionDef(const std::string& functionName, const Statement_s* statement) override + { + StartFunctionDefScope(); + + WriteStringProperty("name", functionName); + WriteStatementProperty("value", statement, false); + + EndScope(); + } + + void WriteMenu(const menuDef_t& menu) override + { + StartMenuDefScope(); + + WriteMenuData(&menu); + + EndScope(); + } + + void Start() override + { + AbstractBaseWriter::Start(); + } + + void End() override + { + AbstractBaseWriter::End(); + } + + void IncludeMenu(const std::string& menuPath) const override + { + AbstractBaseWriter::IncludeMenu(menuPath); + } + + private: + void WriteStatementNaive(const Statement_s* statement) const + { + const auto entryCount = static_cast(statement->numEntries); + + const auto missingClosingParenthesis = statement->numEntries > 0 && statement->entries[0].type == EET_OPERATOR + && statement->entries[0].data.op == OP_LEFTPAREN + && FindStatementClosingParenthesis(statement, 0) >= static_cast(statement->numEntries); + + for (auto i = 0uz; i < entryCount; i++) + { + const auto& entry = statement->entries[i]; + if (entry.type == EET_OPERAND) + { + size_t pos = i; + bool discard = false; + WriteStatementOperand(statement, pos, discard); + } + else if (entry.data.op >= EXP_FUNC_STATIC_DVAR_INT && entry.data.op <= EXP_FUNC_STATIC_DVAR_STRING) + { + switch (entry.data.op) + { + case EXP_FUNC_STATIC_DVAR_INT: + m_stream << "dvarint"; + break; + + case EXP_FUNC_STATIC_DVAR_BOOL: + m_stream << "dvarbool"; + break; + + case EXP_FUNC_STATIC_DVAR_FLOAT: + m_stream << "dvarfloat"; + break; + + case EXP_FUNC_STATIC_DVAR_STRING: + m_stream << "dvarstring"; + break; + + default: + break; + } + + // Functions do not have opening parenthesis in the entries. We can just pretend they do though + const auto closingParenPos = FindStatementClosingParenthesis(statement, i); + m_stream << "("; + + if (closingParenPos - i + 1u >= 1u) + { + const auto& staticDvarEntry = statement->entries[i + 1]; + if (staticDvarEntry.type == EET_OPERAND && staticDvarEntry.data.operand.dataType == VAL_INT) + { + if (statement->supportingData && statement->supportingData->staticDvarList.staticDvars + && staticDvarEntry.data.operand.internals.intVal >= 0 + && staticDvarEntry.data.operand.internals.intVal < statement->supportingData->staticDvarList.numStaticDvars) + { + const auto* staticDvar = statement->supportingData->staticDvarList.staticDvars[staticDvarEntry.data.operand.internals.intVal]; + if (staticDvar && staticDvar->dvarName) + m_stream << staticDvar->dvarName; + } + else + { + m_stream << "#INVALID_DVAR_INDEX"; + } + } + else + { + m_stream << "#INVALID_DVAR_OPERAND"; + } + } + + m_stream << ")"; + i = closingParenPos; + } + else + { + assert(entry.data.op >= 0 && static_cast(entry.data.op) < std::extent_v); + if (entry.data.op >= 0 && static_cast(entry.data.op) < std::extent_v) + m_stream << g_expFunctionNames[entry.data.op]; + if (entry.data.op >= OP_COUNT) + m_stream << "("; + } + } + + if (missingClosingParenthesis) + m_stream << ")"; + } + + void WriteStatementOperator(const Statement_s* statement, size_t& currentPos, bool& spaceNext) const + { + const auto& expEntry = statement->entries[currentPos]; + + if (spaceNext && expEntry.data.op != OP_COMMA) + m_stream << " "; + + if (expEntry.data.op == OP_LEFTPAREN) + { + const auto closingParenPos = FindStatementClosingParenthesis(statement, currentPos); + m_stream << "("; + WriteStatementEntryRange(statement, currentPos + 1, closingParenPos); + m_stream << ")"; + + currentPos = closingParenPos + 1; + spaceNext = true; + } + else if (expEntry.data.op >= EXP_FUNC_STATIC_DVAR_INT && expEntry.data.op <= EXP_FUNC_STATIC_DVAR_STRING) + { + switch (expEntry.data.op) + { + case EXP_FUNC_STATIC_DVAR_INT: + m_stream << "dvarint"; + break; + + case EXP_FUNC_STATIC_DVAR_BOOL: + m_stream << "dvarbool"; + break; + + case EXP_FUNC_STATIC_DVAR_FLOAT: + m_stream << "dvarfloat"; + break; + + case EXP_FUNC_STATIC_DVAR_STRING: + m_stream << "dvarstring"; + break; + + default: + break; + } + + // Functions do not have opening parenthesis in the entries. We can just pretend they do though + const auto closingParenPos = FindStatementClosingParenthesis(statement, currentPos); + m_stream << "("; + + if (closingParenPos - currentPos + 1 >= 1) + { + const auto& staticDvarEntry = statement->entries[currentPos + 1]; + if (staticDvarEntry.type == EET_OPERAND && staticDvarEntry.data.operand.dataType == VAL_INT) + { + if (statement->supportingData && statement->supportingData->staticDvarList.staticDvars + && staticDvarEntry.data.operand.internals.intVal >= 0 + && staticDvarEntry.data.operand.internals.intVal < statement->supportingData->staticDvarList.numStaticDvars) + { + const auto* staticDvar = statement->supportingData->staticDvarList.staticDvars[staticDvarEntry.data.operand.internals.intVal]; + if (staticDvar && staticDvar->dvarName) + m_stream << staticDvar->dvarName; + } + else + { + m_stream << "#INVALID_DVAR_INDEX"; + } + } + else + { + m_stream << "#INVALID_DVAR_OPERAND"; + } + } + + m_stream << ")"; + currentPos = closingParenPos + 1; + spaceNext = true; + } + else + { + if (expEntry.data.op >= 0 && static_cast(expEntry.data.op) < std::extent_v) + m_stream << g_expFunctionNames[expEntry.data.op]; + + if (expEntry.data.op >= OP_COUNT) + { + // Functions do not have opening parenthesis in the entries. We can just pretend they do though + const auto closingParenPos = FindStatementClosingParenthesis(statement, currentPos); + m_stream << "("; + WriteStatementEntryRange(statement, currentPos + 1, closingParenPos); + m_stream << ")"; + currentPos = closingParenPos + 1; + } + else + currentPos++; + + spaceNext = expEntry.data.op != OP_NOT; + } + } + + void WriteStatementOperandFunction(const Statement_s* statement, size_t currentPos) const + { + const auto& operand = statement->entries[currentPos].data.operand; + + if (operand.internals.function == nullptr) + return; + + if (!ObjWriting::Configuration.MenuLegacyMode) + { + int functionIndex = -1; + if (statement->supportingData && statement->supportingData->uifunctions.functions) + { + for (auto supportingFunctionIndex = 0; supportingFunctionIndex < statement->supportingData->uifunctions.totalFunctions; + supportingFunctionIndex++) + { + if (statement->supportingData->uifunctions.functions[supportingFunctionIndex] == operand.internals.function) + { + functionIndex = supportingFunctionIndex; + break; + } + } + } + + if (functionIndex >= 0) + m_stream << "FUNC_" << functionIndex; + else + m_stream << "INVALID_FUNC"; + m_stream << "()"; + } + else + { + m_stream << "("; + WriteStatementSkipInitialUnnecessaryParenthesis(operand.internals.function); + m_stream << ")"; + } + } + + void WriteStatementOperand(const Statement_s* statement, size_t& currentPos, bool& spaceNext) const + { + const auto& expEntry = statement->entries[currentPos]; + + if (spaceNext) + m_stream << " "; + + const auto& operand = expEntry.data.operand; + + switch (operand.dataType) + { + case VAL_FLOAT: + m_stream << operand.internals.floatVal; + break; + + case VAL_INT: + m_stream << operand.internals.intVal; + break; + + case VAL_STRING: + WriteEscapedString(operand.internals.stringVal.string); + break; + + case VAL_FUNCTION: + WriteStatementOperandFunction(statement, currentPos); + break; + + default: + break; + } + + currentPos++; + spaceNext = true; + } + + void WriteStatementEntryRange(const Statement_s* statement, size_t startOffset, size_t endOffset) const + { + assert(startOffset <= endOffset); + assert(endOffset <= static_cast(statement->numEntries)); + + auto currentPos = startOffset; + auto spaceNext = false; + while (currentPos < endOffset) + { + const auto& expEntry = statement->entries[currentPos]; + + if (expEntry.type == EET_OPERATOR) + { + WriteStatementOperator(statement, currentPos, spaceNext); + } + else + { + WriteStatementOperand(statement, currentPos, spaceNext); + } + } + } + + void WriteStatement(const Statement_s* statement) const + { + if (statement == nullptr || statement->numEntries < 0) + return; + + WriteStatementEntryRange(statement, 0, static_cast(statement->numEntries)); + } + + void WriteStatementSkipInitialUnnecessaryParenthesis(const Statement_s* statementValue) const + { + if (statementValue == nullptr || statementValue->numEntries < 0) + return; + + const auto statementEnd = static_cast(statementValue->numEntries); + + if (statementValue->numEntries >= 1 && statementValue->entries[0].type == EET_OPERATOR && statementValue->entries[0].data.op == OP_LEFTPAREN) + { + const auto parenthesisEnd = FindStatementClosingParenthesis(statementValue, 0); + + if (parenthesisEnd >= statementEnd) + WriteStatementEntryRange(statementValue, 1, statementEnd); + else if (parenthesisEnd == statementEnd - 1) + WriteStatementEntryRange(statementValue, 1, statementEnd - 1); + else + WriteStatementEntryRange(statementValue, 0, statementEnd); + } + else + { + WriteStatementEntryRange(statementValue, 0, statementEnd); + } + } + + void WriteStatementProperty(const std::string& propertyKey, const Statement_s* statementValue, bool isBooleanStatement) const + { + if (statementValue == nullptr || statementValue->numEntries < 0) + return; + + Indent(); + WriteKey(propertyKey); + + if (isBooleanStatement) + { + m_stream << "when("; + DUMP_FUNC(statementValue); + m_stream << ");\n"; + } + else + { + DUMP_FUNC(statementValue); + m_stream << ";\n"; + } + } + + void WriteSetLocalVarData(const std::string& setFunction, const SetLocalVarData* setLocalVarData) const + { + if (setLocalVarData == nullptr) + return; + + Indent(); + m_stream << setFunction << " " << setLocalVarData->localVarName << " "; + WriteStatement(setLocalVarData->expression); + m_stream << ";\n"; + } + + // #define WRITE_ORIGINAL_SCRIPT + void WriteUnconditionalScript(const char* script) const + { +#ifdef WRITE_ORIGINAL_SCRIPT + Indent(); + m_stream << script << "\n"; + return; +#endif + + const auto tokenList = CreateScriptTokenList(script); + + auto isNewStatement = true; + for (const auto& token : tokenList) + { + if (isNewStatement) + { + if (token == ";") + continue; + + Indent(); + } + + if (token == ";") + { + m_stream << ";\n"; + isNewStatement = true; + continue; + } + + if (!isNewStatement) + m_stream << " "; + else + isNewStatement = false; + + if (DoesTokenNeedQuotationMarks(token)) + m_stream << "\"" << token << "\""; + else + m_stream << token; + } + + if (!isNewStatement) + m_stream << ";\n"; + } + + void WriteMenuEventHandlerSet(const MenuEventHandlerSet* eventHandlerSet) + { + Indent(); + m_stream << "{\n"; + IncIndent(); + + for (auto i = 0; i < eventHandlerSet->eventHandlerCount; i++) + { + const auto* eventHandler = eventHandlerSet->eventHandlers[i]; + if (eventHandler == nullptr) + continue; + + switch (eventHandler->eventType) + { + case EVENT_UNCONDITIONAL: + WriteUnconditionalScript(eventHandler->eventData.unconditionalScript); + break; + + case EVENT_IF: + if (eventHandler->eventData.conditionalScript == nullptr || eventHandler->eventData.conditionalScript->eventExpression == nullptr + || eventHandler->eventData.conditionalScript->eventHandlerSet == nullptr) + { + continue; + } + + Indent(); + m_stream << "if ("; + WriteStatementSkipInitialUnnecessaryParenthesis(eventHandler->eventData.conditionalScript->eventExpression); + m_stream << ")\n"; + WriteMenuEventHandlerSet(eventHandler->eventData.conditionalScript->eventHandlerSet); + break; + + case EVENT_ELSE: + if (eventHandler->eventData.elseScript == nullptr) + continue; + + Indent(); + m_stream << "else\n"; + WriteMenuEventHandlerSet(eventHandler->eventData.elseScript); + break; + + case EVENT_SET_LOCAL_VAR_BOOL: + WriteSetLocalVarData("setLocalVarBool", eventHandler->eventData.setLocalVarData); + break; + + case EVENT_SET_LOCAL_VAR_INT: + WriteSetLocalVarData("setLocalVarInt", eventHandler->eventData.setLocalVarData); + break; + + case EVENT_SET_LOCAL_VAR_FLOAT: + WriteSetLocalVarData("setLocalVarFloat", eventHandler->eventData.setLocalVarData); + break; + + case EVENT_SET_LOCAL_VAR_STRING: + WriteSetLocalVarData("setLocalVarString", eventHandler->eventData.setLocalVarData); + break; + + default: + break; + } + } + + DecIndent(); + Indent(); + m_stream << "}\n"; + } + + void WriteMenuEventHandlerSetProperty(const std::string& propertyKey, const MenuEventHandlerSet* eventHandlerSetValue) + { + if (eventHandlerSetValue == nullptr) + return; + + Indent(); + m_stream << propertyKey << "\n"; + WriteMenuEventHandlerSet(eventHandlerSetValue); + } + + void WriteRectProperty(const std::string& propertyKey, const rectDef_s& rect) const + { + Indent(); + WriteKey(propertyKey); + m_stream << rect.x << " " << rect.y << " " << rect.w << " " << rect.h << " " << static_cast(rect.horzAlign) << " " + << static_cast(rect.vertAlign) << "\n"; + } + + void WriteMaterialProperty(const std::string& propertyKey, const Material* materialValue) const + { + if (materialValue == nullptr || materialValue->info.name == nullptr) + return; + + if (materialValue->info.name[0] == ',') + WriteStringProperty(propertyKey, &materialValue->info.name[1]); + else + WriteStringProperty(propertyKey, materialValue->info.name); + } + + void WriteSoundAliasProperty(const std::string& propertyKey, const snd_alias_list_t* soundAliasValue) const + { + if (soundAliasValue == nullptr) + return; + + WriteStringProperty(propertyKey, soundAliasValue->aliasName); + } + + void WriteDecodeEffectProperty(const std::string& propertyKey, const itemDef_s* item) const + { + if (!item->decayActive) + return; + + Indent(); + WriteKey(propertyKey); + m_stream << item->fxLetterTime << " " << item->fxDecayStartTime << " " << item->fxDecayDuration << "\n"; + } + + void WriteItemKeyHandlerProperty(const ItemKeyHandler* itemKeyHandlerValue) + { + for (const auto* currentHandler = itemKeyHandlerValue; currentHandler; currentHandler = currentHandler->next) + { + if (currentHandler->key >= '!' && currentHandler->key <= '~' && currentHandler->key != '"') + { + std::ostringstream ss; + ss << "execKey \"" << static_cast(currentHandler->key) << "\""; + WriteMenuEventHandlerSetProperty(ss.str(), currentHandler->action); + } + else + { + std::ostringstream ss; + ss << "execKeyInt " << currentHandler->key; + WriteMenuEventHandlerSetProperty(ss.str(), currentHandler->action); + } + } + } + + void WriteMultiTokenStringProperty(const std::string& propertyKey, const char* value) const + { + if (!value) + return; + + Indent(); + WriteKey(propertyKey); + + const auto tokenList = CreateScriptTokenList(value); + + auto firstToken = true; + m_stream << "{ "; + for (const auto& token : tokenList) + { + if (firstToken) + firstToken = false; + else + m_stream << ";"; + m_stream << "\"" << token << "\""; + } + if (!firstToken) + m_stream << " "; + m_stream << "}\n"; + } + + void WriteFloatExpressionsProperty(const ItemFloatExpression* floatExpressions, int floatExpressionCount) const + { + if (!floatExpressions) + return; + + for (int i = 0; i < floatExpressionCount; i++) + { + const auto& floatExpression = floatExpressions[i]; + + if (floatExpression.target < 0 || floatExpression.target >= ITEM_FLOATEXP_TGT_COUNT) + continue; + + std::string propertyName = std::string("exp ") + floatExpressionTargetBindings[floatExpression.target].name + std::string(" ") + + floatExpressionTargetBindings[floatExpression.target].componentName; + + WriteStatementProperty(propertyName, floatExpression.expression, false); + } + } + + void WriteColumnProperty(const std::string& propertyKey, const listBoxDef_s* listBox) const + { + if (listBox->numColumns <= 0) + return; + + Indent(); + WriteKey(propertyKey); + m_stream << listBox->numColumns << "\n"; + + for (auto col = 0; col < listBox->numColumns; col++) + { + Indent(); + for (auto i = 0u; i < MENU_KEY_SPACING; i++) + m_stream << " "; + + m_stream << listBox->columnInfo[col].pos << " " << listBox->columnInfo[col].width << " " << listBox->columnInfo[col].maxChars << " " + << listBox->columnInfo[col].alignment << "\n"; + } + } + + void WriteListBoxProperties(const itemDef_s* item) + { + if (item->type != ITEM_TYPE_LISTBOX || item->typeData.listBox == nullptr) + return; + + const auto* listBox = item->typeData.listBox; + WriteKeywordProperty("notselectable", listBox->notselectable != 0); + WriteKeywordProperty("noscrollbars", listBox->noScrollBars != 0); + WriteKeywordProperty("usepaging", listBox->usePaging != 0); + WriteFloatProperty("elementwidth", listBox->elementWidth, 0.0f); + WriteFloatProperty("elementheight", listBox->elementHeight, 0.0f); + WriteFloatProperty("feeder", item->special, 0.0f); + WriteIntProperty("elementtype", listBox->elementStyle, 0); + WriteColumnProperty("columns", listBox); + WriteMenuEventHandlerSetProperty("doubleclick", listBox->onDoubleClick); + WriteColorProperty("selectBorder", listBox->selectBorder, COLOR_0000); + WriteMaterialProperty("selectIcon", listBox->selectIcon); + } + + void WriteDvarFloatProperty(const std::string& propertyKey, const itemDef_s* item, const editFieldDef_s* editField) const + { + if (item->dvar == nullptr) + return; + + Indent(); + WriteKey(propertyKey); + m_stream << "\"" << item->dvar << "\" " << editField->defVal << " " << editField->minVal << " " << editField->maxVal << "\n"; + } + + void WriteEditFieldProperties(const itemDef_s* item) const + { + switch (item->type) + { + case ITEM_TYPE_TEXT: + case ITEM_TYPE_EDITFIELD: + case ITEM_TYPE_NUMERICFIELD: + case ITEM_TYPE_SLIDER: + case ITEM_TYPE_YESNO: + case ITEM_TYPE_BIND: + case ITEM_TYPE_VALIDFILEFIELD: + case ITEM_TYPE_DECIMALFIELD: + case ITEM_TYPE_UPREDITFIELD: + case ITEM_TYPE_EMAILFIELD: + case ITEM_TYPE_PASSWORDFIELD: + break; + + default: + return; + } + + if (item->typeData.editField == nullptr) + return; + + const auto* editField = item->typeData.editField; + if (std::fabs(-1.0f - editField->defVal) >= std::numeric_limits::epsilon() + || std::fabs(-1.0f - editField->minVal) >= std::numeric_limits::epsilon() + || std::fabs(-1.0f - editField->maxVal) >= std::numeric_limits::epsilon()) + { + WriteDvarFloatProperty("dvarFloat", item, editField); + } + else + { + WriteStringProperty("dvar", item->dvar); + } + WriteStringProperty("localvar", item->localVar); + WriteIntProperty("maxChars", editField->maxChars, 0); + WriteKeywordProperty("maxCharsGotoNext", editField->maxCharsGotoNext != 0); + WriteIntProperty("maxPaintChars", editField->maxPaintChars, 0); + } + + void WriteMultiValueProperty(const multiDef_s* multiDef) const + { + Indent(); + if (multiDef->strDef) + WriteKey("dvarStrList"); + else + WriteKey("dvarFloatList"); + + m_stream << "{"; + for (auto i = 0; i < multiDef->count; i++) + { + if (multiDef->dvarList[i] == nullptr || multiDef->strDef && multiDef->dvarStr[i] == nullptr) + continue; + + m_stream << " \"" << multiDef->dvarList[i] << "\""; + + if (multiDef->strDef) + m_stream << " \"" << multiDef->dvarStr[i] << "\""; + else + m_stream << " " << multiDef->dvarValue[i] << ""; + } + m_stream << " }\n"; + } + + void WriteMultiProperties(const itemDef_s* item) const + { + if (item->type != ITEM_TYPE_MULTI || item->typeData.multi == nullptr) + return; + + const auto* multiDef = item->typeData.multi; + + if (multiDef->count <= 0) + return; + + WriteStringProperty("dvar", item->dvar); + WriteStringProperty("localvar", item->localVar); + WriteMultiValueProperty(multiDef); + } + + void WriteEnumDvarProperties(const itemDef_s* item) const + { + if (item->type != ITEM_TYPE_DVARENUM) + return; + + WriteStringProperty("dvar", item->dvar); + WriteStringProperty("localvar", item->localVar); + WriteStringProperty("dvarEnumList", item->typeData.enumDvarName); + } + + void WriteTickerProperties(const itemDef_s* item) const + { + if (item->type != ITEM_TYPE_NEWS_TICKER || item->typeData.ticker == nullptr) + return; + + const auto* newsTickerDef = item->typeData.ticker; + WriteIntProperty("spacing", newsTickerDef->spacing, 0); + WriteIntProperty("speed", newsTickerDef->speed, 0); + WriteIntProperty("newsfeed", newsTickerDef->feedId, 0); + } + + void WriteItemData(const itemDef_s* item) + { + WriteStringProperty("name", item->window.name); + WriteStringProperty("text", item->text); + WriteKeywordProperty("textsavegame", item->itemFlags & ITEM_FLAG_SAVE_GAME_INFO); + WriteKeywordProperty("textcinematicsubtitle", item->itemFlags & ITEM_FLAG_CINEMATIC_SUBTITLE); + WriteStringProperty("group", item->window.group); + WriteRectProperty("rect", item->window.rectClient); + WriteIntProperty("style", item->window.style, 0); + WriteKeywordProperty("decoration", item->window.staticFlags & WINDOW_FLAG_DECORATION); + WriteKeywordProperty("autowrapped", item->window.staticFlags & WINDOW_FLAG_AUTO_WRAPPED); + WriteKeywordProperty("horizontalscroll", item->window.staticFlags & WINDOW_FLAG_HORIZONTAL_SCROLL); + WriteIntProperty("type", item->type, ITEM_TYPE_TEXT); + WriteIntProperty("border", item->window.border, 0); + WriteFloatProperty("borderSize", item->window.borderSize, 0.0f); + + if (item->visibleExp) + WriteStatementProperty("visible", item->visibleExp, true); + else if (item->window.dynamicFlags[0] & WINDOW_FLAG_VISIBLE) + WriteIntProperty("visible", 1, 0); + + WriteStatementProperty("disabled", item->disabledExp, true); + WriteIntProperty("ownerdraw", item->window.ownerDraw, 0); + WriteFlagsProperty("ownerdrawFlag", item->window.ownerDrawFlags); + WriteIntProperty("align", item->alignment, 0); + WriteIntProperty("textalign", item->textAlignMode, 0); + WriteFloatProperty("textalignx", item->textalignx, 0.0f); + WriteFloatProperty("textaligny", item->textaligny, 0.0f); + WriteFloatProperty("textscale", item->textscale, 0.0f); + WriteIntProperty("textstyle", item->textStyle, 0); + WriteIntProperty("textfont", item->fontEnum, 0); + WriteColorProperty("backcolor", item->window.backColor, COLOR_0000); + WriteColorProperty("forecolor", item->window.foreColor, COLOR_1111); + WriteColorProperty("bordercolor", item->window.borderColor, COLOR_0000); + WriteColorProperty("outlinecolor", item->window.outlineColor, COLOR_0000); + WriteColorProperty("disablecolor", item->window.disableColor, COLOR_0000); + WriteColorProperty("glowcolor", item->glowColor, COLOR_0000); + WriteMaterialProperty("background", item->window.background); + WriteMenuEventHandlerSetProperty("onFocus", item->onFocus); + WriteMenuEventHandlerSetProperty("leaveFocus", item->leaveFocus); + WriteMenuEventHandlerSetProperty("mouseEnter", item->mouseEnter); + WriteMenuEventHandlerSetProperty("mouseExit", item->mouseExit); + WriteMenuEventHandlerSetProperty("mouseEnterText", item->mouseEnterText); + WriteMenuEventHandlerSetProperty("mouseExitText", item->mouseExitText); + WriteMenuEventHandlerSetProperty("action", item->action); + WriteMenuEventHandlerSetProperty("accept", item->accept); + // WriteFloatProperty("special", item->special, 0.0f); + WriteSoundAliasProperty("focusSound", item->focusSound); + WriteStringProperty("dvarTest", item->dvarTest); + + if (item->dvarFlags & ITEM_DVAR_FLAG_ENABLE) + WriteMultiTokenStringProperty("enableDvar", item->enableDvar); + else if (item->dvarFlags & ITEM_DVAR_FLAG_DISABLE) + WriteMultiTokenStringProperty("disableDvar", item->enableDvar); + else if (item->dvarFlags & ITEM_DVAR_FLAG_SHOW) + WriteMultiTokenStringProperty("showDvar", item->enableDvar); + else if (item->dvarFlags & ITEM_DVAR_FLAG_HIDE) + WriteMultiTokenStringProperty("hideDvar", item->enableDvar); + else if (item->dvarFlags & ITEM_DVAR_FLAG_FOCUS) + WriteMultiTokenStringProperty("focusDvar", item->enableDvar); + + WriteItemKeyHandlerProperty(item->onKey); + WriteStatementProperty("exp text", item->textExp, false); + WriteStatementProperty("exp material", item->materialExp, false); + WriteFloatExpressionsProperty(item->floatExpressions, item->floatExpressionCount); + WriteIntProperty("gamemsgwindowindex", item->gameMsgWindowIndex, 0); + WriteIntProperty("gamemsgwindowmode", item->gameMsgWindowMode, 0); + WriteDecodeEffectProperty("decodeEffect", item); + + WriteListBoxProperties(item); + WriteEditFieldProperties(item); + WriteMultiProperties(item); + WriteEnumDvarProperties(item); + WriteTickerProperties(item); + } + + void WriteItemDefs(const itemDef_s* const* itemDefs, size_t itemCount) + { + for (auto i = 0u; i < itemCount; i++) + { + StartItemDefScope(); + + WriteItemData(itemDefs[i]); + + EndScope(); + } + } + + void WriteMenuData(const menuDef_t* menu) + { + WriteStringProperty("name", menu->window.name); + WriteBoolProperty("fullscreen", menu->fullScreen, false); + WriteKeywordProperty("screenSpace", menu->window.staticFlags & WINDOW_FLAG_SCREEN_SPACE); + WriteKeywordProperty("decoration", menu->window.staticFlags & WINDOW_FLAG_DECORATION); + WriteRectProperty("rect", menu->window.rect); + WriteIntProperty("style", menu->window.style, 0); + WriteIntProperty("border", menu->window.border, 0); + WriteFloatProperty("borderSize", menu->window.borderSize, 0.0f); + WriteColorProperty("backcolor", menu->window.backColor, COLOR_0000); + WriteColorProperty("forecolor", menu->window.foreColor, COLOR_1111); + WriteColorProperty("bordercolor", menu->window.borderColor, COLOR_0000); + WriteColorProperty("focuscolor", menu->focusColor, COLOR_0000); + WriteColorProperty("outlinecolor", menu->window.outlineColor, COLOR_0000); + WriteMaterialProperty("background", menu->window.background); + WriteIntProperty("ownerdraw", menu->window.ownerDraw, 0); + WriteFlagsProperty("ownerdrawFlag", menu->window.ownerDrawFlags); + WriteKeywordProperty("outOfBoundsClick", menu->window.staticFlags & WINDOW_FLAG_OUT_OF_BOUNDS_CLICK); + WriteStringProperty("soundLoop", menu->soundName); + WriteKeywordProperty("popup", menu->window.staticFlags & WINDOW_FLAG_POPUP); + WriteFloatProperty("fadeClamp", menu->fadeClamp, 0.0f); + WriteIntProperty("fadeCycle", menu->fadeCycle, 0); + WriteFloatProperty("fadeAmount", menu->fadeAmount, 0.0f); + WriteFloatProperty("fadeInAmount", menu->fadeInAmount, 0.0f); + WriteFloatProperty("blurWorld", menu->blurRadius, 0.0f); + WriteKeywordProperty("legacySplitScreenScale", menu->window.staticFlags & WINDOW_FLAG_LEGACY_SPLIT_SCREEN_SCALE); + WriteKeywordProperty("hiddenDuringScope", menu->window.staticFlags & WINDOW_FLAG_HIDDEN_DURING_SCOPE); + WriteKeywordProperty("hiddenDuringFlashbang", menu->window.staticFlags & WINDOW_FLAG_HIDDEN_DURING_FLASH_BANG); + WriteKeywordProperty("hiddenDuringUI", menu->window.staticFlags & WINDOW_FLAG_HIDDEN_DURING_UI); + WriteStringProperty("allowedBinding", menu->allowedBinding); + WriteKeywordProperty("textOnlyFocus", menu->window.staticFlags & WINDOW_FLAG_TEXT_ONLY_FOCUS); + + if (menu->visibleExp) + WriteStatementProperty("visible", menu->visibleExp, true); + else if (menu->window.dynamicFlags[0] & WINDOW_FLAG_VISIBLE) + WriteIntProperty("visible", 1, 0); + + WriteStatementProperty("exp rect X", menu->rectXExp, false); + WriteStatementProperty("exp rect Y", menu->rectYExp, false); + WriteStatementProperty("exp rect W", menu->rectWExp, false); + WriteStatementProperty("exp rect H", menu->rectHExp, false); + WriteStatementProperty("exp openSound", menu->openSoundExp, false); + WriteStatementProperty("exp closeSound", menu->closeSoundExp, false); + WriteMenuEventHandlerSetProperty("onOpen", menu->onOpen); + WriteMenuEventHandlerSetProperty("onClose", menu->onClose); + WriteMenuEventHandlerSetProperty("onRequestClose", menu->onCloseRequest); + WriteMenuEventHandlerSetProperty("onESC", menu->onESC); + WriteItemKeyHandlerProperty(menu->onKey); + WriteItemDefs(menu->items, menu->itemCount); + } + }; +} // namespace + +namespace IW4::menu +{ + std::unique_ptr CreateMenuWriter(std::ostream& stream) + { + return std::make_unique(stream); + } +} // namespace IW4::menu diff --git a/src/ObjWriting/Game/IW4/Menu/MenuWriterIW4.h b/src/ObjWriting/Game/IW4/Menu/MenuWriterIW4.h new file mode 100644 index 00000000..5c198f80 --- /dev/null +++ b/src/ObjWriting/Game/IW4/Menu/MenuWriterIW4.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Game/IW4/IW4.h" +#include "Menu/IMenuWriter.h" + +#include +#include + +namespace IW4::menu +{ + class IWriterIW4 : public ::menu::IWriter + { + public: + virtual void WriteFunctionDef(const std::string& functionName, const Statement_s* statement) = 0; + virtual void WriteMenu(const menuDef_t& menu) = 0; + }; + + std::unique_ptr CreateMenuWriter(std::ostream& stream); +} // namespace IW4::menu diff --git a/src/ObjWriting/Game/IW4/ObjWriterIW4.cpp b/src/ObjWriting/Game/IW4/ObjWriterIW4.cpp index 6fbe38da..727ab8d8 100644 --- a/src/ObjWriting/Game/IW4/ObjWriterIW4.cpp +++ b/src/ObjWriting/Game/IW4/ObjWriterIW4.cpp @@ -9,8 +9,8 @@ #include "Localize/LocalizeDumperIW4.h" #include "Maps/AddonMapEntsDumperIW4.h" #include "Material/MaterialDecompilingDumperIW4.h" -#include "Menu/AssetDumperMenuDef.h" -#include "Menu/AssetDumperMenuList.h" +#include "Menu/MenuDumperIW4.h" +#include "Menu/MenuListDumperIW4.h" #include "ObjWriting.h" #include "PhysCollmap/AssetDumperPhysCollmap.h" #include "PhysPreset/PhysPresetInfoStringDumperIW4.h" @@ -63,8 +63,8 @@ bool ObjWriter::DumpZone(AssetDumpingContext& context) const // DUMP_ASSET_POOL(AssetDumperGfxWorld, m_gfx_world, ASSET_TYPE_GFXWORLD) DUMP_ASSET_POOL(light_def::Dumper, m_gfx_light_def, ASSET_TYPE_LIGHT_DEF) // DUMP_ASSET_POOL(AssetDumperFont_s, m_font, ASSET_TYPE_FONT) - DUMP_ASSET_POOL(AssetDumperMenuList, m_menu_list, ASSET_TYPE_MENULIST) - DUMP_ASSET_POOL(AssetDumperMenuDef, m_menu_def, ASSET_TYPE_MENU) + DUMP_ASSET_POOL(menu::MenuListDumper, m_menu_list, ASSET_TYPE_MENULIST) + DUMP_ASSET_POOL(menu::MenuDumper, m_menu_def, ASSET_TYPE_MENU) DUMP_ASSET_POOL(localize::Dumper, m_localize, ASSET_TYPE_LOCALIZE_ENTRY) DUMP_ASSET_POOL(weapon::Dumper, m_weapon, ASSET_TYPE_WEAPON) // DUMP_ASSET_POOL(AssetDumperSndDriverGlobals, m_snd_driver_globals, ASSET_TYPE_SNDDRIVER_GLOBALS) diff --git a/src/ObjWriting/Game/IW5/Menu/AssetDumperMenuDef.cpp b/src/ObjWriting/Game/IW5/Menu/AssetDumperMenuDef.cpp deleted file mode 100644 index b14b5dbe..00000000 --- a/src/ObjWriting/Game/IW5/Menu/AssetDumperMenuDef.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include "AssetDumperMenuDef.h" - -#include "Game/IW5/GameAssetPoolIW5.h" -#include "Game/IW5/Menu/MenuDumperIW5.h" -#include "Menu/AbstractMenuDumper.h" -#include "ObjWriting.h" - -#include -#include - -namespace fs = std::filesystem; - -using namespace IW5; - -const MenuList* AssetDumperMenuDef::GetParentMenuList(XAssetInfo* asset) -{ - const auto* menu = asset->Asset(); - const auto* gameAssetPool = dynamic_cast(asset->m_zone->m_pools.get()); - for (const auto* menuList : *gameAssetPool->m_menu_list) - { - const auto* menuListAsset = menuList->Asset(); - - for (auto menuIndex = 0; menuIndex < menuListAsset->menuCount; menuIndex++) - { - if (menuListAsset->menus[menuIndex] == menu) - return menuListAsset; - } - } - - return nullptr; -} - -std::string AssetDumperMenuDef::GetPathForMenu(XAssetInfo* asset) -{ - const auto* list = GetParentMenuList(asset); - - if (!list) - return "ui_mp/" + std::string(asset->Asset()->window.name) + ".menu"; - - const fs::path p(list->name); - std::string parentPath; - if (p.has_parent_path()) - parentPath = p.parent_path().string() + "/"; - - return parentPath + std::string(asset->Asset()->window.name) + ".menu"; -} - -bool AssetDumperMenuDef::ShouldDump(XAssetInfo* asset) -{ - return true; -} - -void AssetDumperMenuDef::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) -{ - const auto* menu = asset->Asset(); - const auto menuFilePath = GetPathForMenu(asset); - - if (ObjWriting::ShouldHandleAssetType(ASSET_TYPE_MENULIST)) - { - // Don't dump menu file separately if the name matches the menu list - const auto* menuListParent = GetParentMenuList(asset); - if (menuListParent && menuFilePath == menuListParent->name) - return; - } - - const auto assetFile = context.OpenAssetFile(menuFilePath); - - if (!assetFile) - return; - - MenuDumper menuDumper(*assetFile); - - menuDumper.Start(); - menuDumper.WriteMenu(menu); - menuDumper.End(); -} diff --git a/src/ObjWriting/Game/IW5/Menu/AssetDumperMenuDef.h b/src/ObjWriting/Game/IW5/Menu/AssetDumperMenuDef.h deleted file mode 100644 index 696da199..00000000 --- a/src/ObjWriting/Game/IW5/Menu/AssetDumperMenuDef.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "Dumping/AbstractAssetDumper.h" -#include "Game/IW5/IW5.h" - -namespace IW5 -{ - class AssetDumperMenuDef final : public AbstractAssetDumper - { - static const MenuList* GetParentMenuList(XAssetInfo* asset); - static std::string GetPathForMenu(XAssetInfo* asset); - - protected: - bool ShouldDump(XAssetInfo* asset) override; - void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; - }; -} // namespace IW5 diff --git a/src/ObjWriting/Game/IW5/Menu/AssetDumperMenuList.cpp b/src/ObjWriting/Game/IW5/Menu/AssetDumperMenuList.cpp deleted file mode 100644 index c3b9bb44..00000000 --- a/src/ObjWriting/Game/IW5/Menu/AssetDumperMenuList.cpp +++ /dev/null @@ -1,128 +0,0 @@ -#include "AssetDumperMenuList.h" - -#include "Game/IW5/Menu/MenuDumperIW5.h" -#include "Menu/AbstractMenuDumper.h" -#include "ObjWriting.h" - -#include -#include -#include -#include - -namespace fs = std::filesystem; - -using namespace IW5; - -std::vector AssetDumperMenuList::GetAllUniqueExpressionSupportingData(const MenuList* menuList) -{ - std::vector result; - std::set alreadyAddedSupportingData; - - if (menuList->menus == nullptr) - return result; - - for (auto i = 0; i < menuList->menuCount; i++) - { - if (menuList->menus[i] == nullptr) - continue; - - const auto* menu = menuList->menus[i]; - - if (menu->data == nullptr || menu->data->expressionData == nullptr) - continue; - - if (alreadyAddedSupportingData.find(menu->data->expressionData) == alreadyAddedSupportingData.end()) - { - result.push_back(menu->data->expressionData); - alreadyAddedSupportingData.emplace(menu->data->expressionData); - } - } - - return result; -} - -void AssetDumperMenuList::DumpFunctions(MenuDumper& menuDumper, const MenuList* menuList) -{ - const auto allSupportingData = GetAllUniqueExpressionSupportingData(menuList); - auto functionIndex = 0u; - - assert(allSupportingData.size() <= 1); - - for (const auto* supportingData : allSupportingData) - { - if (supportingData->uifunctions.functions == nullptr) - continue; - - for (auto i = 0; i < supportingData->uifunctions.totalFunctions; i++) - { - const auto* function = supportingData->uifunctions.functions[i]; - if (function == nullptr) - continue; - - std::stringstream ss; - ss << "FUNC_" << functionIndex; - - menuDumper.WriteFunctionDef(ss.str(), function); - - functionIndex++; - } - } -} - -void AssetDumperMenuList::DumpMenus(MenuDumper& menuDumper, const MenuList* menuList) -{ - const fs::path p(menuList->name); - - std::string parentPath; - if (p.has_parent_path()) - parentPath = p.parent_path().string() + "/"; - - for (auto menuNum = 0; menuNum < menuList->menuCount; menuNum++) - { - const auto* menu = menuList->menus[menuNum]; - const auto* menuAssetName = menu->window.name; - - bool isReference = false; - if (menuAssetName && menuAssetName[0] == ',') - { - menuAssetName = &menuAssetName[1]; - isReference = true; - } - - std::ostringstream ss; - ss << parentPath << menuAssetName << ".menu"; - - const auto menuName = ss.str(); - - // If the menu was embedded directly as menu list write its data in the menu list file - if (!isReference && menuName == menuList->name) - menuDumper.WriteMenu(menu); - else - menuDumper.IncludeMenu(ss.str()); - } -} - -bool AssetDumperMenuList::ShouldDump(XAssetInfo* asset) -{ - return true; -} - -void AssetDumperMenuList::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) -{ - const auto* menuList = asset->Asset(); - const auto assetFile = context.OpenAssetFile(asset->m_name); - - if (!assetFile) - return; - - MenuDumper menuDumper(*assetFile); - - menuDumper.Start(); - - if (!ObjWriting::Configuration.MenuLegacyMode) - DumpFunctions(menuDumper, menuList); - - DumpMenus(menuDumper, menuList); - - menuDumper.End(); -} diff --git a/src/ObjWriting/Game/IW5/Menu/AssetDumperMenuList.h b/src/ObjWriting/Game/IW5/Menu/AssetDumperMenuList.h deleted file mode 100644 index 58a83904..00000000 --- a/src/ObjWriting/Game/IW5/Menu/AssetDumperMenuList.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "Dumping/AbstractAssetDumper.h" -#include "Game/IW5/IW5.h" -#include "Game/IW5/Menu/MenuDumperIW5.h" - -namespace IW5 -{ - class AssetDumperMenuList final : public AbstractAssetDumper - { - static std::vector GetAllUniqueExpressionSupportingData(const MenuList* menuList); - - static void DumpFunctions(MenuDumper& menuDumper, const MenuList* menuList); - static void DumpMenus(MenuDumper& menuDumper, const MenuList* menuList); - - protected: - bool ShouldDump(XAssetInfo* asset) override; - void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; - }; -} // namespace IW5 diff --git a/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp b/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp index d96e3c0b..713006b4 100644 --- a/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp +++ b/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp @@ -1,920 +1,83 @@ #include "MenuDumperIW5.h" -#include "Game/IW5/MenuConstantsIW5.h" +#include "Game/IW5/GameAssetPoolIW5.h" +#include "Game/IW5/Menu/MenuDumperIW5.h" +#include "MenuWriterIW5.h" #include "ObjWriting.h" -#include -#include -#include +#include +#include +#include + +namespace fs = std::filesystem; using namespace IW5; -// Uncomment this macro to skip interpretative expression dumping -// #define DUMP_NAIVE - -#ifdef DUMP_NAIVE -#define DUMP_FUNC WriteStatementNaive -#else -#define DUMP_FUNC WriteStatementSkipInitialUnnecessaryParenthesis -#endif - -size_t MenuDumper::FindStatementClosingParenthesis(const Statement_s* statement, size_t openingParenthesisPosition) +namespace { - assert(statement->numEntries >= 0); - assert(openingParenthesisPosition < static_cast(statement->numEntries)); - - const auto statementEnd = static_cast(statement->numEntries); - - // The openingParenthesisPosition does not necessarily point to an actual opening parenthesis operator. That's fine though. - // We will pretend it does since the game does sometimes leave out opening parenthesis from the entries. - auto currentParenthesisDepth = 1; - for (auto currentSearchPosition = openingParenthesisPosition + 1; currentSearchPosition < statementEnd; currentSearchPosition++) + const MenuList* GetParentMenuList(XAssetInfo* asset) { - const auto& expEntry = statement->entries[currentSearchPosition]; - if (expEntry.type != EET_OPERATOR) - continue; - - // Any function means a "left out" left paren - if (expEntry.data.op == OP_LEFTPAREN || expEntry.data.op >= OP_COUNT) + const auto* menu = asset->Asset(); + const auto* gameAssetPool = dynamic_cast(asset->m_zone->m_pools.get()); + for (const auto* menuList : *gameAssetPool->m_menu_list) { - currentParenthesisDepth++; - } - else if (expEntry.data.op == OP_RIGHTPAREN) - { - if (currentParenthesisDepth > 0) - currentParenthesisDepth--; - if (currentParenthesisDepth == 0) - return currentSearchPosition; - } - } + const auto* menuListAsset = menuList->Asset(); - return statementEnd; -} - -void MenuDumper::WriteStatementOperator(const Statement_s* statement, size_t& currentPos, bool& spaceNext) const -{ - const auto& expEntry = statement->entries[currentPos]; - - if (spaceNext && expEntry.data.op != OP_COMMA) - m_stream << " "; - - if (expEntry.data.op == OP_LEFTPAREN) - { - const auto closingParenPos = FindStatementClosingParenthesis(statement, currentPos); - m_stream << "("; - WriteStatementEntryRange(statement, currentPos + 1, closingParenPos); - m_stream << ")"; - - currentPos = closingParenPos + 1; - spaceNext = true; - } - else if (expEntry.data.op >= EXP_FUNC_STATIC_DVAR_INT && expEntry.data.op <= EXP_FUNC_STATIC_DVAR_STRING) - { - switch (expEntry.data.op) - { - case EXP_FUNC_STATIC_DVAR_INT: - m_stream << "dvarint"; - break; - - case EXP_FUNC_STATIC_DVAR_BOOL: - m_stream << "dvarbool"; - break; - - case EXP_FUNC_STATIC_DVAR_FLOAT: - m_stream << "dvarfloat"; - break; - - case EXP_FUNC_STATIC_DVAR_STRING: - m_stream << "dvarstring"; - break; - - default: - break; - } - - // Functions do not have opening parenthesis in the entries. We can just pretend they do though - const auto closingParenPos = FindStatementClosingParenthesis(statement, currentPos); - m_stream << "("; - - if (closingParenPos - currentPos + 1 >= 1) - { - const auto& staticDvarEntry = statement->entries[currentPos + 1]; - if (staticDvarEntry.type == EET_OPERAND && staticDvarEntry.data.operand.dataType == VAL_INT) + for (auto menuIndex = 0; menuIndex < menuListAsset->menuCount; menuIndex++) { - if (statement->supportingData && statement->supportingData->staticDvarList.staticDvars && staticDvarEntry.data.operand.internals.intVal >= 0 - && staticDvarEntry.data.operand.internals.intVal < statement->supportingData->staticDvarList.numStaticDvars) - { - const auto* staticDvar = statement->supportingData->staticDvarList.staticDvars[staticDvarEntry.data.operand.internals.intVal]; - if (staticDvar && staticDvar->dvarName) - m_stream << staticDvar->dvarName; - } - else - { - m_stream << "#INVALID_DVAR_INDEX"; - } - } - else - { - m_stream << "#INVALID_DVAR_OPERAND"; + if (menuListAsset->menus[menuIndex] == menu) + return menuListAsset; } } - m_stream << ")"; - currentPos = closingParenPos + 1; - spaceNext = true; + return nullptr; } - else + + std::string GetPathForMenu(XAssetInfo* asset) { - if (expEntry.data.op >= 0 && static_cast(expEntry.data.op) < std::extent_v) - m_stream << g_expFunctionNames[expEntry.data.op]; + const auto* list = GetParentMenuList(asset); - if (expEntry.data.op >= OP_COUNT) - { - // Functions do not have opening parenthesis in the entries. We can just pretend they do though - const auto closingParenPos = FindStatementClosingParenthesis(statement, currentPos); - m_stream << "("; - WriteStatementEntryRange(statement, currentPos + 1, closingParenPos); - m_stream << ")"; - currentPos = closingParenPos + 1; - } - else - currentPos++; + if (!list) + return std::format("ui_mp/{}.menu", asset->Asset()->window.name); - spaceNext = expEntry.data.op != OP_NOT; + const fs::path p(list->name); + std::string parentPath; + if (p.has_parent_path()) + parentPath = p.parent_path().string() + "/"; + + return std::format("{}{}.menu", parentPath, asset->Asset()->window.name); } -} +} // namespace -void MenuDumper::WriteStatementOperandFunction(const Statement_s* statement, const size_t currentPos) const +namespace IW5::menu { - const auto& operand = statement->entries[currentPos].data.operand; - - if (operand.internals.function == nullptr) - return; - - if (!ObjWriting::Configuration.MenuLegacyMode) + bool MenuDumper::ShouldDump(XAssetInfo* asset) { - int functionIndex = -1; - if (statement->supportingData && statement->supportingData->uifunctions.functions) + return true; + } + + void MenuDumper::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) + { + const auto* menu = asset->Asset(); + const auto menuFilePath = GetPathForMenu(asset); + + if (ObjWriting::ShouldHandleAssetType(ASSET_TYPE_MENULIST)) { - for (auto supportingFunctionIndex = 0; supportingFunctionIndex < statement->supportingData->uifunctions.totalFunctions; supportingFunctionIndex++) - { - if (statement->supportingData->uifunctions.functions[supportingFunctionIndex] == operand.internals.function) - { - functionIndex = supportingFunctionIndex; - break; - } - } + // Don't dump menu file separately if the name matches the menu list + const auto* menuListParent = GetParentMenuList(asset); + if (menuListParent && menuFilePath == menuListParent->name) + return; } - if (functionIndex >= 0) - m_stream << "FUNC_" << functionIndex; - else - m_stream << "INVALID_FUNC"; - m_stream << "()"; + const auto assetFile = context.OpenAssetFile(menuFilePath); + + if (!assetFile) + return; + + auto menuWriter = CreateMenuWriter(*assetFile); + + menuWriter->Start(); + menuWriter->WriteMenu(*menu); + menuWriter->End(); } - else - { - m_stream << "("; - WriteStatementSkipInitialUnnecessaryParenthesis(operand.internals.function); - m_stream << ")"; - } -} - -void MenuDumper::WriteStatementOperand(const Statement_s* statement, size_t& currentPos, bool& spaceNext) const -{ - const auto& expEntry = statement->entries[currentPos]; - - if (spaceNext) - m_stream << " "; - - const auto& operand = expEntry.data.operand; - - switch (operand.dataType) - { - case VAL_FLOAT: - m_stream << operand.internals.floatVal; - break; - - case VAL_INT: - m_stream << operand.internals.intVal; - break; - - case VAL_STRING: - WriteEscapedString(operand.internals.stringVal.string); - break; - - case VAL_FUNCTION: - WriteStatementOperandFunction(statement, currentPos); - break; - - default: - break; - } - - currentPos++; - spaceNext = true; -} - -void MenuDumper::WriteStatementEntryRange(const Statement_s* statement, size_t startOffset, size_t endOffset) const -{ - assert(startOffset <= endOffset); - assert(endOffset <= static_cast(statement->numEntries)); - - auto currentPos = startOffset; - auto spaceNext = false; - while (currentPos < endOffset) - { - const auto& expEntry = statement->entries[currentPos]; - - if (expEntry.type == EET_OPERATOR) - { - WriteStatementOperator(statement, currentPos, spaceNext); - } - else - { - WriteStatementOperand(statement, currentPos, spaceNext); - } - } -} - -void MenuDumper::WriteStatementNaive(const Statement_s* statement) const -{ - const auto entryCount = static_cast(statement->numEntries); - for (auto i = 0uz; i < entryCount; i++) - { - const auto& entry = statement->entries[i]; - if (entry.type == EET_OPERAND) - { - size_t pos = i; - bool discard = false; - WriteStatementOperand(statement, pos, discard); - } - else if (entry.data.op >= EXP_FUNC_STATIC_DVAR_INT && entry.data.op <= EXP_FUNC_STATIC_DVAR_STRING) - { - switch (entry.data.op) - { - case EXP_FUNC_STATIC_DVAR_INT: - m_stream << "dvarint"; - break; - - case EXP_FUNC_STATIC_DVAR_BOOL: - m_stream << "dvarbool"; - break; - - case EXP_FUNC_STATIC_DVAR_FLOAT: - m_stream << "dvarfloat"; - break; - - case EXP_FUNC_STATIC_DVAR_STRING: - m_stream << "dvarstring"; - break; - - default: - break; - } - - // Functions do not have opening parenthesis in the entries. We can just pretend they do though - const auto closingParenPos = FindStatementClosingParenthesis(statement, i); - m_stream << "("; - - if (closingParenPos - i + 1u >= 1u) - { - const auto& staticDvarEntry = statement->entries[i + 1u]; - if (staticDvarEntry.type == EET_OPERAND && staticDvarEntry.data.operand.dataType == VAL_INT) - { - if (statement->supportingData && statement->supportingData->staticDvarList.staticDvars && staticDvarEntry.data.operand.internals.intVal >= 0 - && staticDvarEntry.data.operand.internals.intVal < statement->supportingData->staticDvarList.numStaticDvars) - { - const auto* staticDvar = statement->supportingData->staticDvarList.staticDvars[staticDvarEntry.data.operand.internals.intVal]; - if (staticDvar && staticDvar->dvarName) - m_stream << staticDvar->dvarName; - } - else - { - m_stream << "#INVALID_DVAR_INDEX"; - } - } - else - { - m_stream << "#INVALID_DVAR_OPERAND"; - } - } - - m_stream << ")"; - i = closingParenPos; - } - else - { - assert(entry.data.op >= 0 && static_cast(entry.data.op) < std::extent_v); - if (entry.data.op >= 0 && static_cast(entry.data.op) < std::extent_v) - m_stream << g_expFunctionNames[entry.data.op]; - if (entry.data.op >= OP_COUNT) - m_stream << "("; - } - } -} - -void MenuDumper::WriteStatement(const Statement_s* statement) const -{ - if (statement == nullptr || statement->numEntries < 0) - return; - - WriteStatementEntryRange(statement, 0, static_cast(statement->numEntries)); -} - -void MenuDumper::WriteStatementSkipInitialUnnecessaryParenthesis(const Statement_s* statementValue) const -{ - if (statementValue == nullptr || statementValue->numEntries < 0) - return; - - const auto statementEnd = static_cast(statementValue->numEntries); - - if (statementValue->numEntries >= 1 && statementValue->entries[0].type == EET_OPERATOR && statementValue->entries[0].data.op == OP_LEFTPAREN) - { - const auto parenthesisEnd = FindStatementClosingParenthesis(statementValue, 0); - - if (parenthesisEnd >= statementEnd) - WriteStatementEntryRange(statementValue, 1, statementEnd); - else if (parenthesisEnd == statementEnd - 1) - WriteStatementEntryRange(statementValue, 1, statementEnd - 1); - else - WriteStatementEntryRange(statementValue, 0, statementEnd); - } - else - { - WriteStatementEntryRange(statementValue, 0, statementEnd); - } -} - -void MenuDumper::WriteStatementProperty(const std::string& propertyKey, const Statement_s* statementValue, bool isBooleanStatement) const -{ - if (statementValue == nullptr || statementValue->numEntries < 0) - return; - - Indent(); - WriteKey(propertyKey); - - if (isBooleanStatement) - { - m_stream << "when("; - DUMP_FUNC(statementValue); - m_stream << ");\n"; - } - else - { - DUMP_FUNC(statementValue); - m_stream << ";\n"; - } -} - -void MenuDumper::WriteSetLocalVarData(const std::string& setFunction, const SetLocalVarData* setLocalVarData) const -{ - if (setLocalVarData == nullptr) - return; - - Indent(); - m_stream << setFunction << " " << setLocalVarData->localVarName << " "; - WriteStatement(setLocalVarData->expression); - m_stream << ";\n"; -} - -// #define WRITE_ORIGINAL_SCRIPT - -void MenuDumper::WriteUnconditionalScript(const char* script) const -{ -#ifdef WRITE_ORIGINAL_SCRIPT - Indent(); - m_stream << script << "\n"; - return; -#endif - - const auto tokenList = CreateScriptTokenList(script); - - auto isNewStatement = true; - for (const auto& token : tokenList) - { - if (isNewStatement) - { - if (token == ";") - continue; - - Indent(); - } - - if (token == ";") - { - m_stream << ";\n"; - isNewStatement = true; - continue; - } - - if (!isNewStatement) - m_stream << " "; - else - isNewStatement = false; - - if (DoesTokenNeedQuotationMarks(token)) - m_stream << "\"" << token << "\""; - else - m_stream << token; - } - - if (!isNewStatement) - m_stream << ";\n"; -} - -void MenuDumper::WriteMenuEventHandlerSet(const MenuEventHandlerSet* eventHandlerSet) -{ - Indent(); - m_stream << "{\n"; - IncIndent(); - - for (auto i = 0; i < eventHandlerSet->eventHandlerCount; i++) - { - const auto* eventHandler = eventHandlerSet->eventHandlers[i]; - if (eventHandler == nullptr) - continue; - - switch (eventHandler->eventType) - { - case EVENT_UNCONDITIONAL: - WriteUnconditionalScript(eventHandler->eventData.unconditionalScript); - break; - - case EVENT_IF: - if (eventHandler->eventData.conditionalScript == nullptr || eventHandler->eventData.conditionalScript->eventExpression == nullptr - || eventHandler->eventData.conditionalScript->eventHandlerSet == nullptr) - { - continue; - } - - Indent(); - m_stream << "if ("; - WriteStatementSkipInitialUnnecessaryParenthesis(eventHandler->eventData.conditionalScript->eventExpression); - m_stream << ")\n"; - WriteMenuEventHandlerSet(eventHandler->eventData.conditionalScript->eventHandlerSet); - break; - - case EVENT_ELSE: - if (eventHandler->eventData.elseScript == nullptr) - continue; - - Indent(); - m_stream << "else\n"; - WriteMenuEventHandlerSet(eventHandler->eventData.elseScript); - break; - - case EVENT_SET_LOCAL_VAR_BOOL: - WriteSetLocalVarData("setLocalVarBool", eventHandler->eventData.setLocalVarData); - break; - - case EVENT_SET_LOCAL_VAR_INT: - WriteSetLocalVarData("setLocalVarInt", eventHandler->eventData.setLocalVarData); - break; - - case EVENT_SET_LOCAL_VAR_FLOAT: - WriteSetLocalVarData("setLocalVarFloat", eventHandler->eventData.setLocalVarData); - break; - - case EVENT_SET_LOCAL_VAR_STRING: - WriteSetLocalVarData("setLocalVarString", eventHandler->eventData.setLocalVarData); - break; - - default: - break; - } - } - - DecIndent(); - Indent(); - m_stream << "}\n"; -} - -void MenuDumper::WriteMenuEventHandlerSetProperty(const std::string& propertyKey, const MenuEventHandlerSet* eventHandlerSetValue) -{ - if (eventHandlerSetValue == nullptr) - return; - - Indent(); - m_stream << propertyKey << "\n"; - WriteMenuEventHandlerSet(eventHandlerSetValue); -} - -void MenuDumper::WriteRectProperty(const std::string& propertyKey, const rectDef_s& rect) const -{ - Indent(); - WriteKey(propertyKey); - m_stream << rect.x << " " << rect.y << " " << rect.w << " " << rect.h << " " << static_cast(rect.horzAlign) << " " << static_cast(rect.vertAlign) - << "\n"; -} - -void MenuDumper::WriteMaterialProperty(const std::string& propertyKey, const Material* materialValue) const -{ - if (materialValue == nullptr || materialValue->info.name == nullptr) - return; - - if (materialValue->info.name[0] == ',') - WriteStringProperty(propertyKey, &materialValue->info.name[1]); - else - WriteStringProperty(propertyKey, materialValue->info.name); -} - -void MenuDumper::WriteSoundAliasProperty(const std::string& propertyKey, const snd_alias_list_t* soundAliasValue) const -{ - if (soundAliasValue == nullptr) - return; - - WriteStringProperty(propertyKey, soundAliasValue->aliasName); -} - -void MenuDumper::WriteDecodeEffectProperty(const std::string& propertyKey, const itemDef_s* item) const -{ - if (!item->decayActive) - return; - - Indent(); - WriteKey(propertyKey); - m_stream << item->fxLetterTime << " " << item->fxDecayStartTime << " " << item->fxDecayDuration << "\n"; -} - -void MenuDumper::WriteItemKeyHandlerProperty(const ItemKeyHandler* itemKeyHandlerValue) -{ - for (const auto* currentHandler = itemKeyHandlerValue; currentHandler; currentHandler = currentHandler->next) - { - if (currentHandler->key >= '!' && currentHandler->key <= '~' && currentHandler->key != '"') - { - std::ostringstream ss; - ss << "execKey \"" << static_cast(currentHandler->key) << "\""; - WriteMenuEventHandlerSetProperty(ss.str(), currentHandler->action); - } - else - { - std::ostringstream ss; - ss << "execKeyInt " << currentHandler->key; - WriteMenuEventHandlerSetProperty(ss.str(), currentHandler->action); - } - } -} - -void MenuDumper::WriteFloatExpressionsProperty(const ItemFloatExpression* floatExpressions, int floatExpressionCount) const -{ - if (!floatExpressions) - return; - - for (int i = 0; i < floatExpressionCount; i++) - { - const auto& floatExpression = floatExpressions[i]; - - if (floatExpression.target < 0 || floatExpression.target >= ITEM_FLOATEXP_TGT_COUNT) - continue; - - std::string propertyName = std::string("exp ") + floatExpressionTargetBindings[floatExpression.target].name + std::string(" ") - + floatExpressionTargetBindings[floatExpression.target].componentName; - - WriteStatementProperty(propertyName, floatExpression.expression, false); - } -} - -void MenuDumper::WriteMultiTokenStringProperty(const std::string& propertyKey, const char* value) const -{ - if (!value) - return; - - Indent(); - WriteKey(propertyKey); - - const auto tokenList = CreateScriptTokenList(value); - - auto firstToken = true; - m_stream << "{ "; - for (const auto& token : tokenList) - { - if (firstToken) - firstToken = false; - else - m_stream << ";"; - m_stream << "\"" << token << "\""; - } - if (!firstToken) - m_stream << " "; - m_stream << "}\n"; -} - -void MenuDumper::WriteColumnProperty(const std::string& propertyKey, const listBoxDef_s* listBox) const -{ - if (listBox->numColumns <= 0) - return; - - Indent(); - WriteKey(propertyKey); - m_stream << listBox->numColumns << "\n"; - - for (auto col = 0; col < listBox->numColumns; col++) - { - Indent(); - for (auto i = 0u; i < MENU_KEY_SPACING; i++) - m_stream << " "; - - m_stream << listBox->columnInfo[col].xpos << " " << listBox->columnInfo[col].ypos << " " << listBox->columnInfo[col].width << " " - << listBox->columnInfo[col].height << " " << listBox->columnInfo[col].maxChars << " " << listBox->columnInfo[col].alignment << "\n"; - } -} - -void MenuDumper::WriteListBoxProperties(const itemDef_s* item) -{ - if (item->type != ITEM_TYPE_LISTBOX || item->typeData.listBox == nullptr) - return; - - const auto* listBox = item->typeData.listBox; - WriteKeywordProperty("notselectable", listBox->notselectable != 0); - WriteKeywordProperty("noscrollbars", listBox->noScrollBars != 0); - WriteKeywordProperty("usepaging", listBox->usePaging != 0); - WriteFloatProperty("elementwidth", listBox->elementWidth, 0.0f); - WriteFloatProperty("elementheight", listBox->elementHeight, 0.0f); - WriteFloatProperty("feeder", item->special, 0.0f); - WriteIntProperty("elementtype", listBox->elementStyle, 0); - WriteColumnProperty("columns", listBox); - WriteMenuEventHandlerSetProperty("doubleclick", listBox->onDoubleClick); - WriteColorProperty("selectBorder", listBox->selectBorder, COLOR_0000); - WriteMaterialProperty("selectIcon", listBox->selectIcon); - WriteStatementProperty("exp elementheight", listBox->elementHeightExp, false); -} - -void MenuDumper::WriteDvarFloatProperty(const std::string& propertyKey, const itemDef_s* item, const editFieldDef_s* editField) const -{ - if (item->dvar == nullptr) - return; - - Indent(); - WriteKey(propertyKey); - m_stream << "\"" << item->dvar << "\" " << editField->stepVal << " " << editField->minVal << " " << editField->maxVal << "\n"; -} - -void MenuDumper::WriteEditFieldProperties(const itemDef_s* item) const -{ - switch (item->type) - { - case ITEM_TYPE_TEXT: - case ITEM_TYPE_EDITFIELD: - case ITEM_TYPE_NUMERICFIELD: - case ITEM_TYPE_SLIDER: - case ITEM_TYPE_YESNO: - case ITEM_TYPE_BIND: - case ITEM_TYPE_VALIDFILEFIELD: - case ITEM_TYPE_DECIMALFIELD: - case ITEM_TYPE_UPREDITFIELD: - case ITEM_TYPE_EMAILFIELD: - case ITEM_TYPE_PASSWORDFIELD: - break; - - default: - return; - } - - if (item->typeData.editField == nullptr) - return; - - const auto* editField = item->typeData.editField; - if (std::fabs(-1.0f - editField->stepVal) >= std::numeric_limits::epsilon() - || std::fabs(-1.0f - editField->minVal) >= std::numeric_limits::epsilon() - || std::fabs(-1.0f - editField->maxVal) >= std::numeric_limits::epsilon()) - { - WriteDvarFloatProperty("dvarFloat", item, editField); - } - else - { - WriteStringProperty("dvar", item->dvar); - } - WriteStringProperty("localvar", item->localVar); - WriteIntProperty("maxChars", editField->maxChars, 0); - WriteKeywordProperty("maxCharsGotoNext", editField->maxCharsGotoNext != 0); - WriteIntProperty("maxPaintChars", editField->maxPaintChars, 0); -} - -void MenuDumper::WriteMultiValueProperty(const multiDef_s* multiDef) const -{ - Indent(); - if (multiDef->strDef) - WriteKey("dvarStrList"); - else - WriteKey("dvarFloatList"); - - m_stream << "{"; - for (auto i = 0; i < multiDef->count; i++) - { - if (multiDef->dvarList[i] == nullptr || multiDef->strDef && multiDef->dvarStr[i] == nullptr) - continue; - - m_stream << " \"" << multiDef->dvarList[i] << "\""; - - if (multiDef->strDef) - m_stream << " \"" << multiDef->dvarStr[i] << "\""; - else - m_stream << " " << multiDef->dvarValue[i] << ""; - } - m_stream << " }\n"; -} - -void MenuDumper::WriteMultiProperties(const itemDef_s* item) const -{ - if (item->type != ITEM_TYPE_MULTI || item->typeData.multi == nullptr) - return; - - const auto* multiDef = item->typeData.multi; - - if (multiDef->count <= 0) - return; - - WriteStringProperty("dvar", item->dvar); - WriteStringProperty("localvar", item->localVar); - WriteMultiValueProperty(multiDef); -} - -void MenuDumper::WriteEnumDvarProperties(const itemDef_s* item) const -{ - if (item->type != ITEM_TYPE_DVARENUM) - return; - - WriteStringProperty("dvar", item->dvar); - WriteStringProperty("localvar", item->localVar); - WriteStringProperty("dvarEnumList", item->typeData.enumDvarName); -} - -void MenuDumper::WriteTickerProperties(const itemDef_s* item) const -{ - if (item->type != ITEM_TYPE_NEWS_TICKER || item->typeData.ticker == nullptr) - return; - - const auto* newsTickerDef = item->typeData.ticker; - WriteIntProperty("spacing", newsTickerDef->spacing, 0); - WriteIntProperty("speed", newsTickerDef->speed, 0); - WriteIntProperty("newsfeed", newsTickerDef->feedId, 0); -} - -void MenuDumper::WriteItemData(const itemDef_s* item) -{ - WriteStringProperty("name", item->window.name); - WriteStringProperty("text", item->text); - WriteKeywordProperty("textsavegame", item->itemFlags & ITEM_FLAG_SAVE_GAME_INFO); - WriteKeywordProperty("textcinematicsubtitle", item->itemFlags & ITEM_FLAG_CINEMATIC_SUBTITLE); - WriteStringProperty("group", item->window.group); - WriteRectProperty("rect", item->window.rectClient); - WriteIntProperty("style", item->window.style, 0); - WriteKeywordProperty("decoration", item->window.staticFlags & WINDOW_FLAG_DECORATION); - WriteKeywordProperty("autowrapped", item->window.staticFlags & WINDOW_FLAG_AUTO_WRAPPED); - WriteKeywordProperty("horizontalscroll", item->window.staticFlags & WINDOW_FLAG_HORIZONTAL_SCROLL); - WriteIntProperty("type", item->type, ITEM_TYPE_TEXT); - WriteIntProperty("border", item->window.border, 0); - WriteFloatProperty("borderSize", item->window.borderSize, 0.0f); - - if (item->visibleExp) - WriteStatementProperty("visible", item->visibleExp, true); - else if (item->window.dynamicFlags[0] & WINDOW_FLAG_VISIBLE) - WriteIntProperty("visible", 1, 0); - - WriteStatementProperty("disabled", item->disabledExp, true); - WriteIntProperty("ownerdraw", item->window.ownerDraw, 0); - WriteFlagsProperty("ownerdrawFlag", item->window.ownerDrawFlags); - WriteIntProperty("align", item->alignment, 0); - WriteIntProperty("textalign", item->textAlignMode, 0); - WriteFloatProperty("textalignx", item->textalignx, 0.0f); - WriteFloatProperty("textaligny", item->textaligny, 0.0f); - WriteFloatProperty("textscale", item->textscale, 0.0f); - WriteIntProperty("textstyle", item->textStyle, 0); - WriteIntProperty("textfont", item->fontEnum, 0); - WriteColorProperty("backcolor", item->window.backColor, COLOR_0000); - WriteColorProperty("forecolor", item->window.foreColor, COLOR_1111); - WriteColorProperty("bordercolor", item->window.borderColor, COLOR_0000); - WriteColorProperty("outlinecolor", item->window.outlineColor, COLOR_0000); - WriteColorProperty("disablecolor", item->window.disableColor, COLOR_0000); - WriteColorProperty("glowcolor", item->glowColor, COLOR_0000); - WriteMaterialProperty("background", item->window.background); - WriteMenuEventHandlerSetProperty("onFocus", item->onFocus); - WriteMenuEventHandlerSetProperty("hasFocus", item->hasFocus); - WriteMenuEventHandlerSetProperty("leaveFocus", item->leaveFocus); - WriteMenuEventHandlerSetProperty("mouseEnter", item->mouseEnter); - WriteMenuEventHandlerSetProperty("mouseExit", item->mouseExit); - WriteMenuEventHandlerSetProperty("mouseEnterText", item->mouseEnterText); - WriteMenuEventHandlerSetProperty("mouseExitText", item->mouseExitText); - WriteMenuEventHandlerSetProperty("action", item->action); - WriteMenuEventHandlerSetProperty("accept", item->accept); - // WriteFloatProperty("special", item->special, 0.0f); - WriteSoundAliasProperty("focusSound", item->focusSound); - WriteStringProperty("dvarTest", item->dvarTest); - - if (item->dvarFlags & ITEM_DVAR_FLAG_ENABLE) - WriteMultiTokenStringProperty("enableDvar", item->enableDvar); - else if (item->dvarFlags & ITEM_DVAR_FLAG_DISABLE) - WriteMultiTokenStringProperty("disableDvar", item->enableDvar); - else if (item->dvarFlags & ITEM_DVAR_FLAG_SHOW) - WriteMultiTokenStringProperty("showDvar", item->enableDvar); - else if (item->dvarFlags & ITEM_DVAR_FLAG_HIDE) - WriteMultiTokenStringProperty("hideDvar", item->enableDvar); - else if (item->dvarFlags & ITEM_DVAR_FLAG_FOCUS) - WriteMultiTokenStringProperty("focusDvar", item->enableDvar); - - WriteItemKeyHandlerProperty(item->onKey); - WriteStatementProperty("exp text", item->textExp, false); - WriteStatementProperty("exp textaligny", item->textAlignYExp, false); - WriteStatementProperty("exp material", item->materialExp, false); - WriteFloatExpressionsProperty(item->floatExpressions, item->floatExpressionCount); - WriteIntProperty("gamemsgwindowindex", item->gameMsgWindowIndex, 0); - WriteIntProperty("gamemsgwindowmode", item->gameMsgWindowMode, 0); - WriteDecodeEffectProperty("decodeEffect", item); - - WriteListBoxProperties(item); - WriteEditFieldProperties(item); - WriteMultiProperties(item); - WriteEnumDvarProperties(item); - WriteTickerProperties(item); -} - -void MenuDumper::WriteItemDefs(const itemDef_s* const* itemDefs, size_t itemCount) -{ - for (auto i = 0u; i < itemCount; i++) - { - StartItemDefScope(); - - WriteItemData(itemDefs[i]); - - EndScope(); - } -} - -void MenuDumper::WriteMenuData(const menuDef_t* menu) -{ - WriteStringProperty("name", menu->window.name); - WriteBoolProperty("fullscreen", menu->data->fullScreen, false); - WriteKeywordProperty("screenSpace", menu->window.staticFlags & WINDOW_FLAG_SCREEN_SPACE); - WriteKeywordProperty("decoration", menu->window.staticFlags & WINDOW_FLAG_DECORATION); - WriteRectProperty("rect", menu->window.rect); - WriteIntProperty("style", menu->window.style, 0); - WriteIntProperty("border", menu->window.border, 0); - WriteFloatProperty("borderSize", menu->window.borderSize, 0.0f); - WriteColorProperty("backcolor", menu->window.backColor, COLOR_0000); - WriteColorProperty("forecolor", menu->window.foreColor, COLOR_1111); - WriteColorProperty("bordercolor", menu->window.borderColor, COLOR_0000); - WriteColorProperty("focuscolor", menu->data->focusColor, COLOR_0000); - WriteColorProperty("outlinecolor", menu->window.outlineColor, COLOR_0000); - WriteMaterialProperty("background", menu->window.background); - WriteIntProperty("ownerdraw", menu->window.ownerDraw, 0); - WriteFlagsProperty("ownerdrawFlag", menu->window.ownerDrawFlags); - WriteKeywordProperty("outOfBoundsClick", menu->window.staticFlags & WINDOW_FLAG_OUT_OF_BOUNDS_CLICK); - WriteStringProperty("soundLoop", menu->data->soundName); - WriteKeywordProperty("popup", menu->window.staticFlags & WINDOW_FLAG_POPUP); - WriteFloatProperty("fadeClamp", menu->data->fadeClamp, 0.0f); - WriteIntProperty("fadeCycle", menu->data->fadeCycle, 0); - WriteFloatProperty("fadeAmount", menu->data->fadeAmount, 0.0f); - WriteFloatProperty("fadeInAmount", menu->data->fadeInAmount, 0.0f); - WriteFloatProperty("blurWorld", menu->data->blurRadius, 0.0f); - WriteKeywordProperty("legacySplitScreenScale", menu->window.staticFlags & WINDOW_FLAG_LEGACY_SPLIT_SCREEN_SCALE); - WriteKeywordProperty("hiddenDuringScope", menu->window.staticFlags & WINDOW_FLAG_HIDDEN_DURING_SCOPE); - WriteKeywordProperty("hiddenDuringFlashbang", menu->window.staticFlags & WINDOW_FLAG_HIDDEN_DURING_FLASH_BANG); - WriteKeywordProperty("hiddenDuringUI", menu->window.staticFlags & WINDOW_FLAG_HIDDEN_DURING_UI); - WriteStringProperty("allowedBinding", menu->data->allowedBinding); - WriteKeywordProperty("textOnlyFocus", menu->window.staticFlags & WINDOW_FLAG_TEXT_ONLY_FOCUS); - - if (menu->data->visibleExp) - WriteStatementProperty("visible", menu->data->visibleExp, true); - else if (menu->window.dynamicFlags[0] & WINDOW_FLAG_VISIBLE) - WriteIntProperty("visible", 1, 0); - - WriteStatementProperty("exp rect X", menu->data->rectXExp, false); - WriteStatementProperty("exp rect Y", menu->data->rectYExp, false); - WriteStatementProperty("exp rect W", menu->data->rectWExp, false); - WriteStatementProperty("exp rect H", menu->data->rectHExp, false); - WriteStatementProperty("exp openSound", menu->data->openSoundExp, false); - WriteStatementProperty("exp closeSound", menu->data->closeSoundExp, false); - WriteStatementProperty("exp soundLoop", menu->data->soundLoopExp, false); - WriteMenuEventHandlerSetProperty("onOpen", menu->data->onOpen); - WriteMenuEventHandlerSetProperty("onClose", menu->data->onClose); - WriteMenuEventHandlerSetProperty("onRequestClose", menu->data->onCloseRequest); - WriteMenuEventHandlerSetProperty("onESC", menu->data->onESC); - WriteMenuEventHandlerSetProperty("onFocusDueToClose", menu->data->onFocusDueToClose); - WriteItemKeyHandlerProperty(menu->data->onKey); - WriteItemDefs(menu->items, menu->itemCount); -} - -MenuDumper::MenuDumper(std::ostream& stream) - : AbstractMenuDumper(stream) -{ -} - -void MenuDumper::WriteFunctionDef(const std::string& functionName, const Statement_s* statement) -{ - StartFunctionDefScope(); - - WriteStringProperty("name", functionName); - WriteStatementProperty("value", statement, false); - - EndScope(); -} - -void MenuDumper::WriteMenu(const menuDef_t* menu) -{ - StartMenuDefScope(); - - WriteMenuData(menu); - - EndScope(); -} +} // namespace IW5::menu diff --git a/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.h b/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.h index 9e3e3000..e5bd12ad 100644 --- a/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.h +++ b/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.h @@ -1,57 +1,15 @@ #pragma once +#include "Dumping/AbstractAssetDumper.h" #include "Game/IW5/IW5.h" -#include "Menu/AbstractMenuDumper.h" +#include "Game/IW5/Menu/MenuDumperIW5.h" -#include - -namespace IW5 +namespace IW5::menu { - class MenuDumper : public AbstractMenuDumper + class MenuDumper final : public AbstractAssetDumper { - static size_t FindStatementClosingParenthesis(const Statement_s* statement, size_t openingParenthesisPosition); - - void WriteStatementNaive(const Statement_s* statement) const; - - void WriteStatementOperator(const Statement_s* statement, size_t& currentPos, bool& spaceNext) const; - void WriteStatementOperandFunction(const Statement_s* statement, size_t currentPos) const; - void WriteStatementOperand(const Statement_s* statement, size_t& currentPos, bool& spaceNext) const; - void WriteStatementEntryRange(const Statement_s* statement, size_t startOffset, size_t endOffset) const; - void WriteStatement(const Statement_s* statement) const; - void WriteStatementSkipInitialUnnecessaryParenthesis(const Statement_s* statementValue) const; - void WriteStatementProperty(const std::string& propertyKey, const Statement_s* statementValue, bool isBooleanStatement) const; - - void WriteSetLocalVarData(const std::string& setFunction, const SetLocalVarData* setLocalVarData) const; - void WriteUnconditionalScript(const char* script) const; - void WriteMenuEventHandlerSet(const MenuEventHandlerSet* eventHandlerSet); - void WriteMenuEventHandlerSetProperty(const std::string& propertyKey, const MenuEventHandlerSet* eventHandlerSetValue); - - void WriteRectProperty(const std::string& propertyKey, const rectDef_s& rect) const; - void WriteMaterialProperty(const std::string& propertyKey, const Material* materialValue) const; - void WriteSoundAliasProperty(const std::string& propertyKey, const snd_alias_list_t* soundAliasValue) const; - void WriteDecodeEffectProperty(const std::string& propertyKey, const itemDef_s* item) const; - void WriteItemKeyHandlerProperty(const ItemKeyHandler* itemKeyHandlerValue); - void WriteMultiTokenStringProperty(const std::string& propertyKey, const char* value) const; - void WriteFloatExpressionsProperty(const ItemFloatExpression* floatExpressions, int floatExpressionCount) const; - void WriteColumnProperty(const std::string& propertyKey, const listBoxDef_s* listBox) const; - - void WriteListBoxProperties(const itemDef_s* item); - void WriteDvarFloatProperty(const std::string& propertyKey, const itemDef_s* item, const editFieldDef_s* editField) const; - void WriteEditFieldProperties(const itemDef_s* item) const; - void WriteMultiValueProperty(const multiDef_s* multiDef) const; - void WriteMultiProperties(const itemDef_s* item) const; - void WriteEnumDvarProperties(const itemDef_s* item) const; - void WriteTickerProperties(const itemDef_s* item) const; - - void WriteItemData(const itemDef_s* item); - void WriteItemDefs(const itemDef_s* const* itemDefs, size_t itemCount); - - void WriteMenuData(const menuDef_t* menu); - - public: - explicit MenuDumper(std::ostream& stream); - - void WriteFunctionDef(const std::string& functionName, const Statement_s* statement); - void WriteMenu(const menuDef_t* menu); + protected: + bool ShouldDump(XAssetInfo* asset) override; + void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; }; -} // namespace IW5 +} // namespace IW5::menu diff --git a/src/ObjWriting/Game/IW5/Menu/MenuListDumperIW5.cpp b/src/ObjWriting/Game/IW5/Menu/MenuListDumperIW5.cpp new file mode 100644 index 00000000..8806de3b --- /dev/null +++ b/src/ObjWriting/Game/IW5/Menu/MenuListDumperIW5.cpp @@ -0,0 +1,133 @@ +#include "MenuListDumperIW5.h" + +#include "Game/IW5/Menu/MenuWriterIW5.h" +#include "Menu/AbstractMenuWriter.h" +#include "MenuWriterIW5.h" +#include "ObjWriting.h" + +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +using namespace IW5; + +namespace +{ + std::vector GetAllUniqueExpressionSupportingData(const MenuList* menuList) + { + std::vector result; + std::set alreadyAddedSupportingData; + + if (menuList->menus == nullptr) + return result; + + for (auto i = 0; i < menuList->menuCount; i++) + { + if (menuList->menus[i] == nullptr) + continue; + + const auto* menu = menuList->menus[i]; + + if (menu->data == nullptr || menu->data->expressionData == nullptr) + continue; + + if (alreadyAddedSupportingData.find(menu->data->expressionData) == alreadyAddedSupportingData.end()) + { + result.push_back(menu->data->expressionData); + alreadyAddedSupportingData.emplace(menu->data->expressionData); + } + } + + return result; + } + + void DumpFunctions(IW5::menu::IWriterIW5& menuDumper, const MenuList* menuList) + { + const auto allSupportingData = GetAllUniqueExpressionSupportingData(menuList); + auto functionIndex = 0u; + + assert(allSupportingData.size() <= 1); + + for (const auto* supportingData : allSupportingData) + { + if (supportingData->uifunctions.functions == nullptr) + continue; + + for (auto i = 0; i < supportingData->uifunctions.totalFunctions; i++) + { + const auto* function = supportingData->uifunctions.functions[i]; + if (function == nullptr) + continue; + + menuDumper.WriteFunctionDef(std::format("FUNC_{}", functionIndex), function); + + functionIndex++; + } + } + } + + void DumpMenus(IW5::menu::IWriterIW5& menuDumper, const MenuList* menuList) + { + const fs::path p(menuList->name); + + std::string parentPath; + if (p.has_parent_path()) + parentPath = p.parent_path().string() + "/"; + + for (auto menuNum = 0; menuNum < menuList->menuCount; menuNum++) + { + const auto* menu = menuList->menus[menuNum]; + const auto* menuAssetName = menu->window.name; + + bool isReference = false; + if (menuAssetName && menuAssetName[0] == ',') + { + menuAssetName = &menuAssetName[1]; + isReference = true; + } + + std::ostringstream ss; + ss << parentPath << menuAssetName << ".menu"; + + const auto menuName = ss.str(); + + // If the menu was embedded directly as menu list write its data in the menu list file + if (!isReference && menuName == menuList->name) + menuDumper.WriteMenu(*menu); + else + menuDumper.IncludeMenu(ss.str()); + } + } +} // namespace + +namespace IW5::menu +{ + bool MenuListDumper::ShouldDump(XAssetInfo* asset) + { + return true; + } + + void MenuListDumper::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) + { + const auto* menuList = asset->Asset(); + const auto assetFile = context.OpenAssetFile(asset->m_name); + + if (!assetFile) + return; + + auto menuWriter = CreateMenuWriter(*assetFile); + + menuWriter->Start(); + + if (!ObjWriting::Configuration.MenuLegacyMode) + DumpFunctions(*menuWriter, menuList); + + DumpMenus(*menuWriter, menuList); + + menuWriter->End(); + } +} // namespace IW5::menu diff --git a/src/ObjWriting/Game/IW5/Menu/MenuListDumperIW5.h b/src/ObjWriting/Game/IW5/Menu/MenuListDumperIW5.h new file mode 100644 index 00000000..29daedb6 --- /dev/null +++ b/src/ObjWriting/Game/IW5/Menu/MenuListDumperIW5.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Dumping/AbstractAssetDumper.h" +#include "Game/IW5/IW5.h" + +namespace IW5::menu +{ + class MenuListDumper final : public AbstractAssetDumper + { + protected: + bool ShouldDump(XAssetInfo* asset) override; + void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; + }; +} // namespace IW5::menu diff --git a/src/ObjWriting/Game/IW5/Menu/MenuWriterIW5.cpp b/src/ObjWriting/Game/IW5/Menu/MenuWriterIW5.cpp new file mode 100644 index 00000000..1ef3c4cb --- /dev/null +++ b/src/ObjWriting/Game/IW5/Menu/MenuWriterIW5.cpp @@ -0,0 +1,957 @@ +#include "MenuWriterIW5.h" + +#include "Game/IW5/MenuConstantsIW5.h" +#include "Menu/AbstractMenuWriter.h" +#include "ObjWriting.h" + +#include +#include +#include + +using namespace IW5; + +// Uncomment this macro to skip interpretative expression dumping +// #define DUMP_NAIVE + +#ifdef DUMP_NAIVE +#define DUMP_FUNC WriteStatementNaive +#else +#define DUMP_FUNC WriteStatementSkipInitialUnnecessaryParenthesis +#endif + +namespace +{ + size_t FindStatementClosingParenthesis(const Statement_s* statement, const size_t openingParenthesisPosition) + { + assert(statement->numEntries >= 0); + assert(openingParenthesisPosition < static_cast(statement->numEntries)); + + const auto statementEnd = static_cast(statement->numEntries); + + // The openingParenthesisPosition does not necessarily point to an actual opening parenthesis operator. That's fine though. + // We will pretend it does since the game does sometimes leave out opening parenthesis from the entries. + auto currentParenthesisDepth = 1; + for (auto currentSearchPosition = openingParenthesisPosition + 1; currentSearchPosition < statementEnd; currentSearchPosition++) + { + const auto& expEntry = statement->entries[currentSearchPosition]; + if (expEntry.type != EET_OPERATOR) + continue; + + // Any function means a "left out" left paren + if (expEntry.data.op == OP_LEFTPAREN || expEntry.data.op >= OP_COUNT) + { + currentParenthesisDepth++; + } + else if (expEntry.data.op == OP_RIGHTPAREN) + { + if (currentParenthesisDepth > 0) + currentParenthesisDepth--; + if (currentParenthesisDepth == 0) + return currentSearchPosition; + } + } + + return statementEnd; + } +} // namespace + +namespace +{ + class MenuWriter final : public ::menu::AbstractBaseWriter, public IW5::menu::IWriterIW5 + { + public: + explicit MenuWriter(std::ostream& stream) + : AbstractBaseWriter(stream) + { + } + + void WriteFunctionDef(const std::string& functionName, const Statement_s* statement) override + { + StartFunctionDefScope(); + + WriteStringProperty("name", functionName); + WriteStatementProperty("value", statement, false); + + EndScope(); + } + + void WriteMenu(const menuDef_t& menu) override + { + StartMenuDefScope(); + + WriteMenuData(&menu); + + EndScope(); + } + + void Start() override + { + AbstractBaseWriter::Start(); + } + + void End() override + { + AbstractBaseWriter::End(); + } + + void IncludeMenu(const std::string& menuPath) const override + { + AbstractBaseWriter::IncludeMenu(menuPath); + } + + private: + void WriteStatementNaive(const Statement_s* statement) const + { + const auto entryCount = static_cast(statement->numEntries); + for (auto i = 0uz; i < entryCount; i++) + { + const auto& entry = statement->entries[i]; + if (entry.type == EET_OPERAND) + { + size_t pos = i; + bool discard = false; + WriteStatementOperand(statement, pos, discard); + } + else if (entry.data.op >= EXP_FUNC_STATIC_DVAR_INT && entry.data.op <= EXP_FUNC_STATIC_DVAR_STRING) + { + switch (entry.data.op) + { + case EXP_FUNC_STATIC_DVAR_INT: + m_stream << "dvarint"; + break; + + case EXP_FUNC_STATIC_DVAR_BOOL: + m_stream << "dvarbool"; + break; + + case EXP_FUNC_STATIC_DVAR_FLOAT: + m_stream << "dvarfloat"; + break; + + case EXP_FUNC_STATIC_DVAR_STRING: + m_stream << "dvarstring"; + break; + + default: + break; + } + + // Functions do not have opening parenthesis in the entries. We can just pretend they do though + const auto closingParenPos = FindStatementClosingParenthesis(statement, i); + m_stream << "("; + + if (closingParenPos - i + 1u >= 1u) + { + const auto& staticDvarEntry = statement->entries[i + 1u]; + if (staticDvarEntry.type == EET_OPERAND && staticDvarEntry.data.operand.dataType == VAL_INT) + { + if (statement->supportingData && statement->supportingData->staticDvarList.staticDvars + && staticDvarEntry.data.operand.internals.intVal >= 0 + && staticDvarEntry.data.operand.internals.intVal < statement->supportingData->staticDvarList.numStaticDvars) + { + const auto* staticDvar = statement->supportingData->staticDvarList.staticDvars[staticDvarEntry.data.operand.internals.intVal]; + if (staticDvar && staticDvar->dvarName) + m_stream << staticDvar->dvarName; + } + else + { + m_stream << "#INVALID_DVAR_INDEX"; + } + } + else + { + m_stream << "#INVALID_DVAR_OPERAND"; + } + } + + m_stream << ")"; + i = closingParenPos; + } + else + { + assert(entry.data.op >= 0 && static_cast(entry.data.op) < std::extent_v); + if (entry.data.op >= 0 && static_cast(entry.data.op) < std::extent_v) + m_stream << g_expFunctionNames[entry.data.op]; + if (entry.data.op >= OP_COUNT) + m_stream << "("; + } + } + } + + void WriteStatementOperator(const Statement_s* statement, size_t& currentPos, bool& spaceNext) const + { + const auto& expEntry = statement->entries[currentPos]; + + if (spaceNext && expEntry.data.op != OP_COMMA) + m_stream << " "; + + if (expEntry.data.op == OP_LEFTPAREN) + { + const auto closingParenPos = FindStatementClosingParenthesis(statement, currentPos); + m_stream << "("; + WriteStatementEntryRange(statement, currentPos + 1, closingParenPos); + m_stream << ")"; + + currentPos = closingParenPos + 1; + spaceNext = true; + } + else if (expEntry.data.op >= EXP_FUNC_STATIC_DVAR_INT && expEntry.data.op <= EXP_FUNC_STATIC_DVAR_STRING) + { + switch (expEntry.data.op) + { + case EXP_FUNC_STATIC_DVAR_INT: + m_stream << "dvarint"; + break; + + case EXP_FUNC_STATIC_DVAR_BOOL: + m_stream << "dvarbool"; + break; + + case EXP_FUNC_STATIC_DVAR_FLOAT: + m_stream << "dvarfloat"; + break; + + case EXP_FUNC_STATIC_DVAR_STRING: + m_stream << "dvarstring"; + break; + + default: + break; + } + + // Functions do not have opening parenthesis in the entries. We can just pretend they do though + const auto closingParenPos = FindStatementClosingParenthesis(statement, currentPos); + m_stream << "("; + + if (closingParenPos - currentPos + 1 >= 1) + { + const auto& staticDvarEntry = statement->entries[currentPos + 1]; + if (staticDvarEntry.type == EET_OPERAND && staticDvarEntry.data.operand.dataType == VAL_INT) + { + if (statement->supportingData && statement->supportingData->staticDvarList.staticDvars + && staticDvarEntry.data.operand.internals.intVal >= 0 + && staticDvarEntry.data.operand.internals.intVal < statement->supportingData->staticDvarList.numStaticDvars) + { + const auto* staticDvar = statement->supportingData->staticDvarList.staticDvars[staticDvarEntry.data.operand.internals.intVal]; + if (staticDvar && staticDvar->dvarName) + m_stream << staticDvar->dvarName; + } + else + { + m_stream << "#INVALID_DVAR_INDEX"; + } + } + else + { + m_stream << "#INVALID_DVAR_OPERAND"; + } + } + + m_stream << ")"; + currentPos = closingParenPos + 1; + spaceNext = true; + } + else + { + if (expEntry.data.op >= 0 && static_cast(expEntry.data.op) < std::extent_v) + m_stream << g_expFunctionNames[expEntry.data.op]; + + if (expEntry.data.op >= OP_COUNT) + { + // Functions do not have opening parenthesis in the entries. We can just pretend they do though + const auto closingParenPos = FindStatementClosingParenthesis(statement, currentPos); + m_stream << "("; + WriteStatementEntryRange(statement, currentPos + 1, closingParenPos); + m_stream << ")"; + currentPos = closingParenPos + 1; + } + else + currentPos++; + + spaceNext = expEntry.data.op != OP_NOT; + } + } + + void WriteStatementOperandFunction(const Statement_s* statement, size_t currentPos) const + { + const auto& operand = statement->entries[currentPos].data.operand; + + if (operand.internals.function == nullptr) + return; + + if (!ObjWriting::Configuration.MenuLegacyMode) + { + int functionIndex = -1; + if (statement->supportingData && statement->supportingData->uifunctions.functions) + { + for (auto supportingFunctionIndex = 0; supportingFunctionIndex < statement->supportingData->uifunctions.totalFunctions; + supportingFunctionIndex++) + { + if (statement->supportingData->uifunctions.functions[supportingFunctionIndex] == operand.internals.function) + { + functionIndex = supportingFunctionIndex; + break; + } + } + } + + if (functionIndex >= 0) + m_stream << "FUNC_" << functionIndex; + else + m_stream << "INVALID_FUNC"; + m_stream << "()"; + } + else + { + m_stream << "("; + WriteStatementSkipInitialUnnecessaryParenthesis(operand.internals.function); + m_stream << ")"; + } + } + + void WriteStatementOperand(const Statement_s* statement, size_t& currentPos, bool& spaceNext) const + { + const auto& expEntry = statement->entries[currentPos]; + + if (spaceNext) + m_stream << " "; + + const auto& operand = expEntry.data.operand; + + switch (operand.dataType) + { + case VAL_FLOAT: + m_stream << operand.internals.floatVal; + break; + + case VAL_INT: + m_stream << operand.internals.intVal; + break; + + case VAL_STRING: + WriteEscapedString(operand.internals.stringVal.string); + break; + + case VAL_FUNCTION: + WriteStatementOperandFunction(statement, currentPos); + break; + + default: + break; + } + + currentPos++; + spaceNext = true; + } + + void WriteStatementEntryRange(const Statement_s* statement, size_t startOffset, size_t endOffset) const + { + assert(startOffset <= endOffset); + assert(endOffset <= static_cast(statement->numEntries)); + + auto currentPos = startOffset; + auto spaceNext = false; + while (currentPos < endOffset) + { + const auto& expEntry = statement->entries[currentPos]; + + if (expEntry.type == EET_OPERATOR) + { + WriteStatementOperator(statement, currentPos, spaceNext); + } + else + { + WriteStatementOperand(statement, currentPos, spaceNext); + } + } + } + + void WriteStatement(const Statement_s* statement) const + { + if (statement == nullptr || statement->numEntries < 0) + return; + + WriteStatementEntryRange(statement, 0, static_cast(statement->numEntries)); + } + + void WriteStatementSkipInitialUnnecessaryParenthesis(const Statement_s* statementValue) const + { + if (statementValue == nullptr || statementValue->numEntries < 0) + return; + + const auto statementEnd = static_cast(statementValue->numEntries); + + if (statementValue->numEntries >= 1 && statementValue->entries[0].type == EET_OPERATOR && statementValue->entries[0].data.op == OP_LEFTPAREN) + { + const auto parenthesisEnd = FindStatementClosingParenthesis(statementValue, 0); + + if (parenthesisEnd >= statementEnd) + WriteStatementEntryRange(statementValue, 1, statementEnd); + else if (parenthesisEnd == statementEnd - 1) + WriteStatementEntryRange(statementValue, 1, statementEnd - 1); + else + WriteStatementEntryRange(statementValue, 0, statementEnd); + } + else + { + WriteStatementEntryRange(statementValue, 0, statementEnd); + } + } + + void WriteStatementProperty(const std::string& propertyKey, const Statement_s* statementValue, bool isBooleanStatement) const + { + if (statementValue == nullptr || statementValue->numEntries < 0) + return; + + Indent(); + WriteKey(propertyKey); + + if (isBooleanStatement) + { + m_stream << "when("; + DUMP_FUNC(statementValue); + m_stream << ");\n"; + } + else + { + DUMP_FUNC(statementValue); + m_stream << ";\n"; + } + } + + void WriteSetLocalVarData(const std::string& setFunction, const SetLocalVarData* setLocalVarData) const + { + if (setLocalVarData == nullptr) + return; + + Indent(); + m_stream << setFunction << " " << setLocalVarData->localVarName << " "; + WriteStatement(setLocalVarData->expression); + m_stream << ";\n"; + } + + // #define WRITE_ORIGINAL_SCRIPT + void WriteUnconditionalScript(const char* script) const + { +#ifdef WRITE_ORIGINAL_SCRIPT + Indent(); + m_stream << script << "\n"; + return; +#endif + + const auto tokenList = CreateScriptTokenList(script); + + auto isNewStatement = true; + for (const auto& token : tokenList) + { + if (isNewStatement) + { + if (token == ";") + continue; + + Indent(); + } + + if (token == ";") + { + m_stream << ";\n"; + isNewStatement = true; + continue; + } + + if (!isNewStatement) + m_stream << " "; + else + isNewStatement = false; + + if (DoesTokenNeedQuotationMarks(token)) + m_stream << "\"" << token << "\""; + else + m_stream << token; + } + + if (!isNewStatement) + m_stream << ";\n"; + } + + void WriteMenuEventHandlerSet(const MenuEventHandlerSet* eventHandlerSet) + { + Indent(); + m_stream << "{\n"; + IncIndent(); + + for (auto i = 0; i < eventHandlerSet->eventHandlerCount; i++) + { + const auto* eventHandler = eventHandlerSet->eventHandlers[i]; + if (eventHandler == nullptr) + continue; + + switch (eventHandler->eventType) + { + case EVENT_UNCONDITIONAL: + WriteUnconditionalScript(eventHandler->eventData.unconditionalScript); + break; + + case EVENT_IF: + if (eventHandler->eventData.conditionalScript == nullptr || eventHandler->eventData.conditionalScript->eventExpression == nullptr + || eventHandler->eventData.conditionalScript->eventHandlerSet == nullptr) + { + continue; + } + + Indent(); + m_stream << "if ("; + WriteStatementSkipInitialUnnecessaryParenthesis(eventHandler->eventData.conditionalScript->eventExpression); + m_stream << ")\n"; + WriteMenuEventHandlerSet(eventHandler->eventData.conditionalScript->eventHandlerSet); + break; + + case EVENT_ELSE: + if (eventHandler->eventData.elseScript == nullptr) + continue; + + Indent(); + m_stream << "else\n"; + WriteMenuEventHandlerSet(eventHandler->eventData.elseScript); + break; + + case EVENT_SET_LOCAL_VAR_BOOL: + WriteSetLocalVarData("setLocalVarBool", eventHandler->eventData.setLocalVarData); + break; + + case EVENT_SET_LOCAL_VAR_INT: + WriteSetLocalVarData("setLocalVarInt", eventHandler->eventData.setLocalVarData); + break; + + case EVENT_SET_LOCAL_VAR_FLOAT: + WriteSetLocalVarData("setLocalVarFloat", eventHandler->eventData.setLocalVarData); + break; + + case EVENT_SET_LOCAL_VAR_STRING: + WriteSetLocalVarData("setLocalVarString", eventHandler->eventData.setLocalVarData); + break; + + default: + break; + } + } + + DecIndent(); + Indent(); + m_stream << "}\n"; + } + + void WriteMenuEventHandlerSetProperty(const std::string& propertyKey, const MenuEventHandlerSet* eventHandlerSetValue) + { + if (eventHandlerSetValue == nullptr) + return; + + Indent(); + m_stream << propertyKey << "\n"; + WriteMenuEventHandlerSet(eventHandlerSetValue); + } + + void WriteRectProperty(const std::string& propertyKey, const rectDef_s& rect) const + { + Indent(); + WriteKey(propertyKey); + m_stream << rect.x << " " << rect.y << " " << rect.w << " " << rect.h << " " << static_cast(rect.horzAlign) << " " + << static_cast(rect.vertAlign) << "\n"; + } + + void WriteMaterialProperty(const std::string& propertyKey, const Material* materialValue) const + { + if (materialValue == nullptr || materialValue->info.name == nullptr) + return; + + if (materialValue->info.name[0] == ',') + WriteStringProperty(propertyKey, &materialValue->info.name[1]); + else + WriteStringProperty(propertyKey, materialValue->info.name); + } + + void WriteSoundAliasProperty(const std::string& propertyKey, const snd_alias_list_t* soundAliasValue) const + { + if (soundAliasValue == nullptr) + return; + + WriteStringProperty(propertyKey, soundAliasValue->aliasName); + } + + void WriteDecodeEffectProperty(const std::string& propertyKey, const itemDef_s* item) const + { + if (!item->decayActive) + return; + + Indent(); + WriteKey(propertyKey); + m_stream << item->fxLetterTime << " " << item->fxDecayStartTime << " " << item->fxDecayDuration << "\n"; + } + + void WriteItemKeyHandlerProperty(const ItemKeyHandler* itemKeyHandlerValue) + { + for (const auto* currentHandler = itemKeyHandlerValue; currentHandler; currentHandler = currentHandler->next) + { + if (currentHandler->key >= '!' && currentHandler->key <= '~' && currentHandler->key != '"') + { + std::ostringstream ss; + ss << "execKey \"" << static_cast(currentHandler->key) << "\""; + WriteMenuEventHandlerSetProperty(ss.str(), currentHandler->action); + } + else + { + std::ostringstream ss; + ss << "execKeyInt " << currentHandler->key; + WriteMenuEventHandlerSetProperty(ss.str(), currentHandler->action); + } + } + } + + void WriteMultiTokenStringProperty(const std::string& propertyKey, const char* value) const + { + if (!value) + return; + + Indent(); + WriteKey(propertyKey); + + const auto tokenList = CreateScriptTokenList(value); + + auto firstToken = true; + m_stream << "{ "; + for (const auto& token : tokenList) + { + if (firstToken) + firstToken = false; + else + m_stream << ";"; + m_stream << "\"" << token << "\""; + } + if (!firstToken) + m_stream << " "; + m_stream << "}\n"; + } + + void WriteFloatExpressionsProperty(const ItemFloatExpression* floatExpressions, int floatExpressionCount) const + { + if (!floatExpressions) + return; + + for (int i = 0; i < floatExpressionCount; i++) + { + const auto& floatExpression = floatExpressions[i]; + + if (floatExpression.target < 0 || floatExpression.target >= ITEM_FLOATEXP_TGT_COUNT) + continue; + + std::string propertyName = std::string("exp ") + floatExpressionTargetBindings[floatExpression.target].name + std::string(" ") + + floatExpressionTargetBindings[floatExpression.target].componentName; + + WriteStatementProperty(propertyName, floatExpression.expression, false); + } + } + + void WriteColumnProperty(const std::string& propertyKey, const listBoxDef_s* listBox) const + { + if (listBox->numColumns <= 0) + return; + + Indent(); + WriteKey(propertyKey); + m_stream << listBox->numColumns << "\n"; + + for (auto col = 0; col < listBox->numColumns; col++) + { + Indent(); + for (auto i = 0u; i < MENU_KEY_SPACING; i++) + m_stream << " "; + + m_stream << listBox->columnInfo[col].xpos << " " << listBox->columnInfo[col].ypos << " " << listBox->columnInfo[col].width << " " + << listBox->columnInfo[col].height << " " << listBox->columnInfo[col].maxChars << " " << listBox->columnInfo[col].alignment << "\n"; + } + } + + void WriteListBoxProperties(const itemDef_s* item) + { + if (item->type != ITEM_TYPE_LISTBOX || item->typeData.listBox == nullptr) + return; + + const auto* listBox = item->typeData.listBox; + WriteKeywordProperty("notselectable", listBox->notselectable != 0); + WriteKeywordProperty("noscrollbars", listBox->noScrollBars != 0); + WriteKeywordProperty("usepaging", listBox->usePaging != 0); + WriteFloatProperty("elementwidth", listBox->elementWidth, 0.0f); + WriteFloatProperty("elementheight", listBox->elementHeight, 0.0f); + WriteFloatProperty("feeder", item->special, 0.0f); + WriteIntProperty("elementtype", listBox->elementStyle, 0); + WriteColumnProperty("columns", listBox); + WriteMenuEventHandlerSetProperty("doubleclick", listBox->onDoubleClick); + WriteColorProperty("selectBorder", listBox->selectBorder, COLOR_0000); + WriteMaterialProperty("selectIcon", listBox->selectIcon); + WriteStatementProperty("exp elementheight", listBox->elementHeightExp, false); + } + + void WriteDvarFloatProperty(const std::string& propertyKey, const itemDef_s* item, const editFieldDef_s* editField) const + { + if (item->dvar == nullptr) + return; + + Indent(); + WriteKey(propertyKey); + m_stream << "\"" << item->dvar << "\" " << editField->stepVal << " " << editField->minVal << " " << editField->maxVal << "\n"; + } + + void WriteEditFieldProperties(const itemDef_s* item) const + { + switch (item->type) + { + case ITEM_TYPE_TEXT: + case ITEM_TYPE_EDITFIELD: + case ITEM_TYPE_NUMERICFIELD: + case ITEM_TYPE_SLIDER: + case ITEM_TYPE_YESNO: + case ITEM_TYPE_BIND: + case ITEM_TYPE_VALIDFILEFIELD: + case ITEM_TYPE_DECIMALFIELD: + case ITEM_TYPE_UPREDITFIELD: + case ITEM_TYPE_EMAILFIELD: + case ITEM_TYPE_PASSWORDFIELD: + break; + + default: + return; + } + + if (item->typeData.editField == nullptr) + return; + + const auto* editField = item->typeData.editField; + if (std::fabs(-1.0f - editField->stepVal) >= std::numeric_limits::epsilon() + || std::fabs(-1.0f - editField->minVal) >= std::numeric_limits::epsilon() + || std::fabs(-1.0f - editField->maxVal) >= std::numeric_limits::epsilon()) + { + WriteDvarFloatProperty("dvarFloat", item, editField); + } + else + { + WriteStringProperty("dvar", item->dvar); + } + WriteStringProperty("localvar", item->localVar); + WriteIntProperty("maxChars", editField->maxChars, 0); + WriteKeywordProperty("maxCharsGotoNext", editField->maxCharsGotoNext != 0); + WriteIntProperty("maxPaintChars", editField->maxPaintChars, 0); + } + + void WriteMultiValueProperty(const multiDef_s* multiDef) const + { + Indent(); + if (multiDef->strDef) + WriteKey("dvarStrList"); + else + WriteKey("dvarFloatList"); + + m_stream << "{"; + for (auto i = 0; i < multiDef->count; i++) + { + if (multiDef->dvarList[i] == nullptr || multiDef->strDef && multiDef->dvarStr[i] == nullptr) + continue; + + m_stream << " \"" << multiDef->dvarList[i] << "\""; + + if (multiDef->strDef) + m_stream << " \"" << multiDef->dvarStr[i] << "\""; + else + m_stream << " " << multiDef->dvarValue[i] << ""; + } + m_stream << " }\n"; + } + + void WriteMultiProperties(const itemDef_s* item) const + { + if (item->type != ITEM_TYPE_MULTI || item->typeData.multi == nullptr) + return; + + const auto* multiDef = item->typeData.multi; + + if (multiDef->count <= 0) + return; + + WriteStringProperty("dvar", item->dvar); + WriteStringProperty("localvar", item->localVar); + WriteMultiValueProperty(multiDef); + } + + void WriteEnumDvarProperties(const itemDef_s* item) const + { + if (item->type != ITEM_TYPE_DVARENUM) + return; + + WriteStringProperty("dvar", item->dvar); + WriteStringProperty("localvar", item->localVar); + WriteStringProperty("dvarEnumList", item->typeData.enumDvarName); + } + + void WriteTickerProperties(const itemDef_s* item) const + { + if (item->type != ITEM_TYPE_NEWS_TICKER || item->typeData.ticker == nullptr) + return; + + const auto* newsTickerDef = item->typeData.ticker; + WriteIntProperty("spacing", newsTickerDef->spacing, 0); + WriteIntProperty("speed", newsTickerDef->speed, 0); + WriteIntProperty("newsfeed", newsTickerDef->feedId, 0); + } + + void WriteItemData(const itemDef_s* item) + { + WriteStringProperty("name", item->window.name); + WriteStringProperty("text", item->text); + WriteKeywordProperty("textsavegame", item->itemFlags & ITEM_FLAG_SAVE_GAME_INFO); + WriteKeywordProperty("textcinematicsubtitle", item->itemFlags & ITEM_FLAG_CINEMATIC_SUBTITLE); + WriteStringProperty("group", item->window.group); + WriteRectProperty("rect", item->window.rectClient); + WriteIntProperty("style", item->window.style, 0); + WriteKeywordProperty("decoration", item->window.staticFlags & WINDOW_FLAG_DECORATION); + WriteKeywordProperty("autowrapped", item->window.staticFlags & WINDOW_FLAG_AUTO_WRAPPED); + WriteKeywordProperty("horizontalscroll", item->window.staticFlags & WINDOW_FLAG_HORIZONTAL_SCROLL); + WriteIntProperty("type", item->type, ITEM_TYPE_TEXT); + WriteIntProperty("border", item->window.border, 0); + WriteFloatProperty("borderSize", item->window.borderSize, 0.0f); + + if (item->visibleExp) + WriteStatementProperty("visible", item->visibleExp, true); + else if (item->window.dynamicFlags[0] & WINDOW_FLAG_VISIBLE) + WriteIntProperty("visible", 1, 0); + + WriteStatementProperty("disabled", item->disabledExp, true); + WriteIntProperty("ownerdraw", item->window.ownerDraw, 0); + WriteFlagsProperty("ownerdrawFlag", item->window.ownerDrawFlags); + WriteIntProperty("align", item->alignment, 0); + WriteIntProperty("textalign", item->textAlignMode, 0); + WriteFloatProperty("textalignx", item->textalignx, 0.0f); + WriteFloatProperty("textaligny", item->textaligny, 0.0f); + WriteFloatProperty("textscale", item->textscale, 0.0f); + WriteIntProperty("textstyle", item->textStyle, 0); + WriteIntProperty("textfont", item->fontEnum, 0); + WriteColorProperty("backcolor", item->window.backColor, COLOR_0000); + WriteColorProperty("forecolor", item->window.foreColor, COLOR_1111); + WriteColorProperty("bordercolor", item->window.borderColor, COLOR_0000); + WriteColorProperty("outlinecolor", item->window.outlineColor, COLOR_0000); + WriteColorProperty("disablecolor", item->window.disableColor, COLOR_0000); + WriteColorProperty("glowcolor", item->glowColor, COLOR_0000); + WriteMaterialProperty("background", item->window.background); + WriteMenuEventHandlerSetProperty("onFocus", item->onFocus); + WriteMenuEventHandlerSetProperty("hasFocus", item->hasFocus); + WriteMenuEventHandlerSetProperty("leaveFocus", item->leaveFocus); + WriteMenuEventHandlerSetProperty("mouseEnter", item->mouseEnter); + WriteMenuEventHandlerSetProperty("mouseExit", item->mouseExit); + WriteMenuEventHandlerSetProperty("mouseEnterText", item->mouseEnterText); + WriteMenuEventHandlerSetProperty("mouseExitText", item->mouseExitText); + WriteMenuEventHandlerSetProperty("action", item->action); + WriteMenuEventHandlerSetProperty("accept", item->accept); + // WriteFloatProperty("special", item->special, 0.0f); + WriteSoundAliasProperty("focusSound", item->focusSound); + WriteStringProperty("dvarTest", item->dvarTest); + + if (item->dvarFlags & ITEM_DVAR_FLAG_ENABLE) + WriteMultiTokenStringProperty("enableDvar", item->enableDvar); + else if (item->dvarFlags & ITEM_DVAR_FLAG_DISABLE) + WriteMultiTokenStringProperty("disableDvar", item->enableDvar); + else if (item->dvarFlags & ITEM_DVAR_FLAG_SHOW) + WriteMultiTokenStringProperty("showDvar", item->enableDvar); + else if (item->dvarFlags & ITEM_DVAR_FLAG_HIDE) + WriteMultiTokenStringProperty("hideDvar", item->enableDvar); + else if (item->dvarFlags & ITEM_DVAR_FLAG_FOCUS) + WriteMultiTokenStringProperty("focusDvar", item->enableDvar); + + WriteItemKeyHandlerProperty(item->onKey); + WriteStatementProperty("exp text", item->textExp, false); + WriteStatementProperty("exp textaligny", item->textAlignYExp, false); + WriteStatementProperty("exp material", item->materialExp, false); + WriteFloatExpressionsProperty(item->floatExpressions, item->floatExpressionCount); + WriteIntProperty("gamemsgwindowindex", item->gameMsgWindowIndex, 0); + WriteIntProperty("gamemsgwindowmode", item->gameMsgWindowMode, 0); + WriteDecodeEffectProperty("decodeEffect", item); + + WriteListBoxProperties(item); + WriteEditFieldProperties(item); + WriteMultiProperties(item); + WriteEnumDvarProperties(item); + WriteTickerProperties(item); + } + + void WriteItemDefs(const itemDef_s* const* itemDefs, size_t itemCount) + { + for (auto i = 0u; i < itemCount; i++) + { + StartItemDefScope(); + + WriteItemData(itemDefs[i]); + + EndScope(); + } + } + + void WriteMenuData(const menuDef_t* menu) + { + WriteStringProperty("name", menu->window.name); + WriteBoolProperty("fullscreen", menu->data->fullScreen, false); + WriteKeywordProperty("screenSpace", menu->window.staticFlags & WINDOW_FLAG_SCREEN_SPACE); + WriteKeywordProperty("decoration", menu->window.staticFlags & WINDOW_FLAG_DECORATION); + WriteRectProperty("rect", menu->window.rect); + WriteIntProperty("style", menu->window.style, 0); + WriteIntProperty("border", menu->window.border, 0); + WriteFloatProperty("borderSize", menu->window.borderSize, 0.0f); + WriteColorProperty("backcolor", menu->window.backColor, COLOR_0000); + WriteColorProperty("forecolor", menu->window.foreColor, COLOR_1111); + WriteColorProperty("bordercolor", menu->window.borderColor, COLOR_0000); + WriteColorProperty("focuscolor", menu->data->focusColor, COLOR_0000); + WriteColorProperty("outlinecolor", menu->window.outlineColor, COLOR_0000); + WriteMaterialProperty("background", menu->window.background); + WriteIntProperty("ownerdraw", menu->window.ownerDraw, 0); + WriteFlagsProperty("ownerdrawFlag", menu->window.ownerDrawFlags); + WriteKeywordProperty("outOfBoundsClick", menu->window.staticFlags & WINDOW_FLAG_OUT_OF_BOUNDS_CLICK); + WriteStringProperty("soundLoop", menu->data->soundName); + WriteKeywordProperty("popup", menu->window.staticFlags & WINDOW_FLAG_POPUP); + WriteFloatProperty("fadeClamp", menu->data->fadeClamp, 0.0f); + WriteIntProperty("fadeCycle", menu->data->fadeCycle, 0); + WriteFloatProperty("fadeAmount", menu->data->fadeAmount, 0.0f); + WriteFloatProperty("fadeInAmount", menu->data->fadeInAmount, 0.0f); + WriteFloatProperty("blurWorld", menu->data->blurRadius, 0.0f); + WriteKeywordProperty("legacySplitScreenScale", menu->window.staticFlags & WINDOW_FLAG_LEGACY_SPLIT_SCREEN_SCALE); + WriteKeywordProperty("hiddenDuringScope", menu->window.staticFlags & WINDOW_FLAG_HIDDEN_DURING_SCOPE); + WriteKeywordProperty("hiddenDuringFlashbang", menu->window.staticFlags & WINDOW_FLAG_HIDDEN_DURING_FLASH_BANG); + WriteKeywordProperty("hiddenDuringUI", menu->window.staticFlags & WINDOW_FLAG_HIDDEN_DURING_UI); + WriteStringProperty("allowedBinding", menu->data->allowedBinding); + WriteKeywordProperty("textOnlyFocus", menu->window.staticFlags & WINDOW_FLAG_TEXT_ONLY_FOCUS); + + if (menu->data->visibleExp) + WriteStatementProperty("visible", menu->data->visibleExp, true); + else if (menu->window.dynamicFlags[0] & WINDOW_FLAG_VISIBLE) + WriteIntProperty("visible", 1, 0); + + WriteStatementProperty("exp rect X", menu->data->rectXExp, false); + WriteStatementProperty("exp rect Y", menu->data->rectYExp, false); + WriteStatementProperty("exp rect W", menu->data->rectWExp, false); + WriteStatementProperty("exp rect H", menu->data->rectHExp, false); + WriteStatementProperty("exp openSound", menu->data->openSoundExp, false); + WriteStatementProperty("exp closeSound", menu->data->closeSoundExp, false); + WriteStatementProperty("exp soundLoop", menu->data->soundLoopExp, false); + WriteMenuEventHandlerSetProperty("onOpen", menu->data->onOpen); + WriteMenuEventHandlerSetProperty("onClose", menu->data->onClose); + WriteMenuEventHandlerSetProperty("onRequestClose", menu->data->onCloseRequest); + WriteMenuEventHandlerSetProperty("onESC", menu->data->onESC); + WriteMenuEventHandlerSetProperty("onFocusDueToClose", menu->data->onFocusDueToClose); + WriteItemKeyHandlerProperty(menu->data->onKey); + WriteItemDefs(menu->items, menu->itemCount); + } + }; +} // namespace + +namespace IW5::menu +{ + std::unique_ptr CreateMenuWriter(std::ostream& stream) + { + return std::make_unique(stream); + } +} // namespace IW5::menu diff --git a/src/ObjWriting/Game/IW5/Menu/MenuWriterIW5.h b/src/ObjWriting/Game/IW5/Menu/MenuWriterIW5.h new file mode 100644 index 00000000..eb9fe690 --- /dev/null +++ b/src/ObjWriting/Game/IW5/Menu/MenuWriterIW5.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Game/IW5/IW5.h" +#include "Menu/IMenuWriter.h" + +#include +#include + +namespace IW5::menu +{ + class IWriterIW5 : public ::menu::IWriter + { + public: + virtual void WriteFunctionDef(const std::string& functionName, const Statement_s* statement) = 0; + virtual void WriteMenu(const menuDef_t& menu) = 0; + }; + + std::unique_ptr CreateMenuWriter(std::ostream& stream); +} // namespace IW5::menu diff --git a/src/ObjWriting/Game/IW5/ObjWriterIW5.cpp b/src/ObjWriting/Game/IW5/ObjWriterIW5.cpp index 28ea516a..536f7935 100644 --- a/src/ObjWriting/Game/IW5/ObjWriterIW5.cpp +++ b/src/ObjWriting/Game/IW5/ObjWriterIW5.cpp @@ -7,8 +7,8 @@ #include "Leaderboard/LeaderboardJsonDumperIW5.h" #include "Localize/LocalizeDumperIW5.h" #include "Maps/AddonMapEntsDumperIW5.h" -#include "Menu/AssetDumperMenuDef.h" -#include "Menu/AssetDumperMenuList.h" +#include "Menu/MenuDumperIW5.h" +#include "Menu/MenuListDumperIW5.h" #include "ObjWriting.h" #include "RawFile/RawFileDumperIW5.h" #include "Script/ScriptDumperIW5.h" @@ -53,8 +53,8 @@ bool ObjWriter::DumpZone(AssetDumpingContext& context) const // DUMP_ASSET_POOL(AssetDumperGfxWorld, m_gfx_world, ASSET_TYPE_GFXWORLD) // DUMP_ASSET_POOL(AssetDumperGfxLightDef, m_gfx_light_def, ASSET_TYPE_LIGHT_DEF) // DUMP_ASSET_POOL(AssetDumperFont_s, m_font, ASSET_TYPE_FONT) - DUMP_ASSET_POOL(AssetDumperMenuList, m_menu_list, ASSET_TYPE_MENULIST) - DUMP_ASSET_POOL(AssetDumperMenuDef, m_menu_def, ASSET_TYPE_MENU) + DUMP_ASSET_POOL(menu::MenuListDumper, m_menu_list, ASSET_TYPE_MENULIST) + DUMP_ASSET_POOL(menu::MenuDumper, m_menu_def, ASSET_TYPE_MENU) DUMP_ASSET_POOL(localize::Dumper, m_localize, ASSET_TYPE_LOCALIZE_ENTRY) DUMP_ASSET_POOL(attachment::JsonDumper, m_attachment, ASSET_TYPE_ATTACHMENT) DUMP_ASSET_POOL(weapon::Dumper, m_weapon, ASSET_TYPE_WEAPON) diff --git a/src/ObjWriting/Menu/AbstractMenuDumper.cpp b/src/ObjWriting/Menu/AbstractMenuDumper.cpp deleted file mode 100644 index 5b00b40b..00000000 --- a/src/ObjWriting/Menu/AbstractMenuDumper.cpp +++ /dev/null @@ -1,298 +0,0 @@ -#include "AbstractMenuDumper.h" - -#include "Parsing/Impl/ParserSingleInputStream.h" -#include "Parsing/Simple/SimpleLexer.h" - -#include -#include -#include - -AbstractMenuDumper::AbstractMenuDumper(std::ostream& stream) - : m_stream(stream), - m_indent(0u) -{ -} - -void AbstractMenuDumper::IncIndent() -{ - m_indent++; -} - -void AbstractMenuDumper::DecIndent() -{ - if (m_indent > 0) - m_indent--; -} - -void AbstractMenuDumper::Indent() const -{ - for (auto i = 0u; i < m_indent; i++) - m_stream << " "; -} - -void AbstractMenuDumper::StartScope(const std::string& scopeName) -{ - Indent(); - m_stream << scopeName << "\n"; - Indent(); - m_stream << "{\n"; - IncIndent(); -} - -void AbstractMenuDumper::StartMenuDefScope() -{ - StartScope("menuDef"); -} - -void AbstractMenuDumper::StartItemDefScope() -{ - StartScope("itemDef"); -} - -void AbstractMenuDumper::StartFunctionDefScope() -{ - StartScope("functionDef"); -} - -void AbstractMenuDumper::EndScope() -{ - DecIndent(); - Indent(); - m_stream << "}\n"; -} - -std::vector AbstractMenuDumper::CreateScriptTokenList(const char* script) -{ - const std::string scriptString(script); - std::istringstream stringStream(scriptString); - ParserSingleInputStream inputStream(stringStream, "MenuScript"); - - SimpleLexer::Config lexerConfig; - lexerConfig.m_emit_new_line_tokens = false; - lexerConfig.m_read_strings = true; - lexerConfig.m_string_escape_sequences = true; - lexerConfig.m_read_integer_numbers = false; - lexerConfig.m_read_floating_point_numbers = false; - SimpleLexer lexer(&inputStream, std::move(lexerConfig)); - - std::vector result; - auto hasLexerTokens = true; - while (hasLexerTokens) - { - const auto& token = lexer.GetToken(0); - switch (token.m_type) - { - case SimpleParserValueType::IDENTIFIER: - result.emplace_back(token.IdentifierValue()); - break; - - case SimpleParserValueType::STRING: - result.emplace_back(token.StringValue()); - break; - - case SimpleParserValueType::CHARACTER: - result.emplace_back(1, token.CharacterValue()); - break; - - case SimpleParserValueType::INVALID: - case SimpleParserValueType::END_OF_FILE: - hasLexerTokens = false; - break; - - default: - assert(false); - break; - } - - lexer.PopTokens(1); - } - - return result; -} - -bool AbstractMenuDumper::DoesTokenNeedQuotationMarks(const std::string& token) -{ - if (token.empty()) - return true; - - const auto hasAlNumCharacter = std::ranges::any_of(token, - [](const char& c) - { - return isalnum(c); - }); - - if (!hasAlNumCharacter) - return false; - - const auto hasNonIdentifierCharacter = std::ranges::any_of(token, - [](const char& c) - { - return !isalnum(c) && c != '_'; - }); - - return hasNonIdentifierCharacter; -} - -void AbstractMenuDumper::WriteEscapedString(const std::string_view& str) const -{ - m_stream << "\""; - - for (const auto& c : str) - { - switch (c) - { - case '\r': - m_stream << "\\r"; - break; - case '\n': - m_stream << "\\n"; - break; - case '\t': - m_stream << "\\t"; - break; - case '\f': - m_stream << "\\f"; - break; - case '"': - m_stream << "\\\""; - break; - default: - m_stream << c; - break; - } - } - - m_stream << "\""; -} - -const std::string& AbstractMenuDumper::BoolValue(const bool value) -{ - return value ? BOOL_VALUE_TRUE : BOOL_VALUE_FALSE; -} - -void AbstractMenuDumper::WriteKey(const std::string& keyName) const -{ - m_stream << keyName; - - if (keyName.size() < MENU_KEY_SPACING) - { - const auto spacingLength = MENU_KEY_SPACING - keyName.size(); - for (auto i = 0u; i < spacingLength; i++) - m_stream << " "; - } -} - -void AbstractMenuDumper::WriteStringProperty(const std::string& propertyKey, const std::string& propertyValue) const -{ - if (propertyValue.empty()) - return; - - Indent(); - WriteKey(propertyKey); - - WriteEscapedString(propertyValue); - m_stream << "\n"; -} - -void AbstractMenuDumper::WriteStringProperty(const std::string& propertyKey, const char* propertyValue) const -{ - if (propertyValue == nullptr || propertyValue[0] == '\0') - return; - - Indent(); - WriteKey(propertyKey); - - WriteEscapedString(propertyValue); - m_stream << "\n"; -} - -void AbstractMenuDumper::WriteBoolProperty(const std::string& propertyKey, const bool propertyValue, const bool defaultValue) const -{ - if (propertyValue == defaultValue) - return; - - Indent(); - WriteKey(propertyKey); - m_stream << BoolValue(propertyValue) << "\n"; -} - -void AbstractMenuDumper::WriteIntProperty(const std::string& propertyKey, const int propertyValue, const int defaultValue) const -{ - if (propertyValue == defaultValue) - return; - - Indent(); - WriteKey(propertyKey); - m_stream << propertyValue << "\n"; -} - -void AbstractMenuDumper::WriteFloatProperty(const std::string& propertyKey, const float propertyValue, const float defaultValue) const -{ - if (std::fabs(propertyValue - defaultValue) < std::numeric_limits::epsilon()) - return; - - Indent(); - WriteKey(propertyKey); - m_stream << propertyValue << "\n"; -} - -void AbstractMenuDumper::WriteColorProperty(const std::string& propertyKey, const float (&propertyValue)[4], const float (&defaultValue)[4]) const -{ - if (std::fabs(propertyValue[0] - defaultValue[0]) < std::numeric_limits::epsilon() - && std::fabs(propertyValue[1] - defaultValue[1]) < std::numeric_limits::epsilon() - && std::fabs(propertyValue[2] - defaultValue[2]) < std::numeric_limits::epsilon() - && std::fabs(propertyValue[3] - defaultValue[3]) < std::numeric_limits::epsilon()) - { - return; - } - - Indent(); - WriteKey(propertyKey); - m_stream << propertyValue[0] << " " << propertyValue[1] << " " << propertyValue[2] << " " << propertyValue[3] << "\n"; -} - -void AbstractMenuDumper::WriteKeywordProperty(const std::string& propertyKey, const bool shouldWrite) const -{ - if (!shouldWrite) - return; - - Indent(); - WriteKey(propertyKey); - m_stream << "\n"; -} - -void AbstractMenuDumper::WriteFlagsProperty(const std::string& propertyKey, const int flagsValue) const -{ - for (auto i = 0u; i < sizeof(flagsValue) * 8; i++) - { - if (flagsValue & (1 << i)) - { - Indent(); - WriteKey(propertyKey); - m_stream << i << "\n"; - } - } -} - -void AbstractMenuDumper::Start() -{ - Indent(); - m_stream << "{\n"; - IncIndent(); -} - -void AbstractMenuDumper::End() -{ - for (auto i = 0u; i < m_indent; i++) - { - DecIndent(); - Indent(); - m_stream << "}\n"; - } -} - -void AbstractMenuDumper::IncludeMenu(const std::string& menuPath) const -{ - Indent(); - m_stream << "loadMenu { \"" << menuPath << "\" }\n"; -} diff --git a/src/ObjWriting/Menu/AbstractMenuDumper.h b/src/ObjWriting/Menu/AbstractMenuDumper.h deleted file mode 100644 index 10494684..00000000 --- a/src/ObjWriting/Menu/AbstractMenuDumper.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -class AbstractMenuDumper -{ -protected: - static constexpr auto MENU_KEY_SPACING = 28u; - static const inline std::string BOOL_VALUE_TRUE = "1"; - static const inline std::string BOOL_VALUE_FALSE = "0"; - static constexpr inline float COLOR_0000[4]{0.0f, 0.0f, 0.0f, 0.0f}; - static constexpr inline float COLOR_1111[4]{1.0f, 1.0f, 1.0f, 1.0f}; - - std::ostream& m_stream; - size_t m_indent; - - void IncIndent(); - void DecIndent(); - void Indent() const; - - void StartScope(const std::string& scopeName); - void StartMenuDefScope(); - void StartItemDefScope(); - void StartFunctionDefScope(); - void EndScope(); - - static std::vector CreateScriptTokenList(const char* script); - static bool DoesTokenNeedQuotationMarks(const std::string& token); - - void WriteEscapedString(const std::string_view& str) const; - - static const std::string& BoolValue(bool value); - void WriteKey(const std::string& keyName) const; - void WriteStringProperty(const std::string& propertyKey, const std::string& propertyValue) const; - void WriteStringProperty(const std::string& propertyKey, const char* propertyValue) const; - void WriteBoolProperty(const std::string& propertyKey, bool propertyValue, bool defaultValue) const; - void WriteIntProperty(const std::string& propertyKey, int propertyValue, int defaultValue) const; - void WriteFloatProperty(const std::string& propertyKey, float propertyValue, float defaultValue) const; - void WriteColorProperty(const std::string& propertyKey, const float (&propertyValue)[4], const float (&defaultValue)[4]) const; - void WriteKeywordProperty(const std::string& propertyKey, bool shouldWrite) const; - void WriteFlagsProperty(const std::string& propertyKey, int flagsValue) const; - - explicit AbstractMenuDumper(std::ostream& stream); - -public: - void Start(); - void End(); - - void IncludeMenu(const std::string& menuPath) const; -}; diff --git a/src/ObjWriting/Menu/AbstractMenuWriter.cpp b/src/ObjWriting/Menu/AbstractMenuWriter.cpp new file mode 100644 index 00000000..34c1b567 --- /dev/null +++ b/src/ObjWriting/Menu/AbstractMenuWriter.cpp @@ -0,0 +1,301 @@ +#include "AbstractMenuWriter.h" + +#include "Parsing/Impl/ParserSingleInputStream.h" +#include "Parsing/Simple/SimpleLexer.h" + +#include +#include +#include + +namespace menu +{ + AbstractBaseWriter::AbstractBaseWriter(std::ostream& stream) + : m_stream(stream), + m_indent(0u) + { + } + + void AbstractBaseWriter::IncIndent() + { + m_indent++; + } + + void AbstractBaseWriter::DecIndent() + { + if (m_indent > 0) + m_indent--; + } + + void AbstractBaseWriter::Indent() const + { + for (auto i = 0u; i < m_indent; i++) + m_stream << " "; + } + + void AbstractBaseWriter::StartScope(const std::string& scopeName) + { + Indent(); + m_stream << scopeName << "\n"; + Indent(); + m_stream << "{\n"; + IncIndent(); + } + + void AbstractBaseWriter::StartMenuDefScope() + { + StartScope("menuDef"); + } + + void AbstractBaseWriter::StartItemDefScope() + { + StartScope("itemDef"); + } + + void AbstractBaseWriter::StartFunctionDefScope() + { + StartScope("functionDef"); + } + + void AbstractBaseWriter::EndScope() + { + DecIndent(); + Indent(); + m_stream << "}\n"; + } + + std::vector AbstractBaseWriter::CreateScriptTokenList(const char* script) + { + const std::string scriptString(script); + std::istringstream stringStream(scriptString); + ParserSingleInputStream inputStream(stringStream, "MenuScript"); + + SimpleLexer::Config lexerConfig; + lexerConfig.m_emit_new_line_tokens = false; + lexerConfig.m_read_strings = true; + lexerConfig.m_string_escape_sequences = true; + lexerConfig.m_read_integer_numbers = false; + lexerConfig.m_read_floating_point_numbers = false; + SimpleLexer lexer(&inputStream, std::move(lexerConfig)); + + std::vector result; + auto hasLexerTokens = true; + while (hasLexerTokens) + { + const auto& token = lexer.GetToken(0); + switch (token.m_type) + { + case SimpleParserValueType::IDENTIFIER: + result.emplace_back(token.IdentifierValue()); + break; + + case SimpleParserValueType::STRING: + result.emplace_back(token.StringValue()); + break; + + case SimpleParserValueType::CHARACTER: + result.emplace_back(1, token.CharacterValue()); + break; + + case SimpleParserValueType::INVALID: + case SimpleParserValueType::END_OF_FILE: + hasLexerTokens = false; + break; + + default: + assert(false); + break; + } + + lexer.PopTokens(1); + } + + return result; + } + + bool AbstractBaseWriter::DoesTokenNeedQuotationMarks(const std::string& token) + { + if (token.empty()) + return true; + + const auto hasAlNumCharacter = std::ranges::any_of(token, + [](const char& c) + { + return isalnum(c); + }); + + if (!hasAlNumCharacter) + return false; + + const auto hasNonIdentifierCharacter = std::ranges::any_of(token, + [](const char& c) + { + return !isalnum(c) && c != '_'; + }); + + return hasNonIdentifierCharacter; + } + + void AbstractBaseWriter::WriteEscapedString(const std::string_view& str) const + { + m_stream << "\""; + + for (const auto& c : str) + { + switch (c) + { + case '\r': + m_stream << "\\r"; + break; + case '\n': + m_stream << "\\n"; + break; + case '\t': + m_stream << "\\t"; + break; + case '\f': + m_stream << "\\f"; + break; + case '"': + m_stream << "\\\""; + break; + default: + m_stream << c; + break; + } + } + + m_stream << "\""; + } + + const std::string& AbstractBaseWriter::BoolValue(const bool value) + { + return value ? BOOL_VALUE_TRUE : BOOL_VALUE_FALSE; + } + + void AbstractBaseWriter::WriteKey(const std::string& keyName) const + { + m_stream << keyName; + + if (keyName.size() < MENU_KEY_SPACING) + { + const auto spacingLength = MENU_KEY_SPACING - keyName.size(); + for (auto i = 0u; i < spacingLength; i++) + m_stream << " "; + } + } + + void AbstractBaseWriter::WriteStringProperty(const std::string& propertyKey, const std::string& propertyValue) const + { + if (propertyValue.empty()) + return; + + Indent(); + WriteKey(propertyKey); + + WriteEscapedString(propertyValue); + m_stream << "\n"; + } + + void AbstractBaseWriter::WriteStringProperty(const std::string& propertyKey, const char* propertyValue) const + { + if (propertyValue == nullptr || propertyValue[0] == '\0') + return; + + Indent(); + WriteKey(propertyKey); + + WriteEscapedString(propertyValue); + m_stream << "\n"; + } + + void AbstractBaseWriter::WriteBoolProperty(const std::string& propertyKey, const bool propertyValue, const bool defaultValue) const + { + if (propertyValue == defaultValue) + return; + + Indent(); + WriteKey(propertyKey); + m_stream << BoolValue(propertyValue) << "\n"; + } + + void AbstractBaseWriter::WriteIntProperty(const std::string& propertyKey, const int propertyValue, const int defaultValue) const + { + if (propertyValue == defaultValue) + return; + + Indent(); + WriteKey(propertyKey); + m_stream << propertyValue << "\n"; + } + + void AbstractBaseWriter::WriteFloatProperty(const std::string& propertyKey, const float propertyValue, const float defaultValue) const + { + if (std::fabs(propertyValue - defaultValue) < std::numeric_limits::epsilon()) + return; + + Indent(); + WriteKey(propertyKey); + m_stream << propertyValue << "\n"; + } + + void AbstractBaseWriter::WriteColorProperty(const std::string& propertyKey, const float (&propertyValue)[4], const float (&defaultValue)[4]) const + { + if (std::fabs(propertyValue[0] - defaultValue[0]) < std::numeric_limits::epsilon() + && std::fabs(propertyValue[1] - defaultValue[1]) < std::numeric_limits::epsilon() + && std::fabs(propertyValue[2] - defaultValue[2]) < std::numeric_limits::epsilon() + && std::fabs(propertyValue[3] - defaultValue[3]) < std::numeric_limits::epsilon()) + { + return; + } + + Indent(); + WriteKey(propertyKey); + m_stream << propertyValue[0] << " " << propertyValue[1] << " " << propertyValue[2] << " " << propertyValue[3] << "\n"; + } + + void AbstractBaseWriter::WriteKeywordProperty(const std::string& propertyKey, const bool shouldWrite) const + { + if (!shouldWrite) + return; + + Indent(); + WriteKey(propertyKey); + m_stream << "\n"; + } + + void AbstractBaseWriter::WriteFlagsProperty(const std::string& propertyKey, const int flagsValue) const + { + for (auto i = 0u; i < sizeof(flagsValue) * 8; i++) + { + if (flagsValue & (1 << i)) + { + Indent(); + WriteKey(propertyKey); + m_stream << i << "\n"; + } + } + } + + void AbstractBaseWriter::Start() + { + Indent(); + m_stream << "{\n"; + IncIndent(); + } + + void AbstractBaseWriter::End() + { + for (auto i = 0u; i < m_indent; i++) + { + DecIndent(); + Indent(); + m_stream << "}\n"; + } + } + + void AbstractBaseWriter::IncludeMenu(const std::string& menuPath) const + { + Indent(); + m_stream << "loadMenu { \"" << menuPath << "\" }\n"; + } +} // namespace menu diff --git a/src/ObjWriting/Menu/AbstractMenuWriter.h b/src/ObjWriting/Menu/AbstractMenuWriter.h new file mode 100644 index 00000000..3127a3e8 --- /dev/null +++ b/src/ObjWriting/Menu/AbstractMenuWriter.h @@ -0,0 +1,59 @@ +#pragma once + +#include "IMenuWriter.h" + +#include +#include +#include +#include + +namespace menu +{ + class AbstractBaseWriter : public IWriter + { + protected: + static constexpr auto MENU_KEY_SPACING = 28u; + static const inline std::string BOOL_VALUE_TRUE = "1"; + static const inline std::string BOOL_VALUE_FALSE = "0"; + static constexpr inline float COLOR_0000[4]{0.0f, 0.0f, 0.0f, 0.0f}; + static constexpr inline float COLOR_1111[4]{1.0f, 1.0f, 1.0f, 1.0f}; + + public: + void Start() override; + void End() override; + + void IncludeMenu(const std::string& menuPath) const override; + + protected: + explicit AbstractBaseWriter(std::ostream& stream); + + void IncIndent(); + void DecIndent(); + void Indent() const; + + void StartScope(const std::string& scopeName); + void StartMenuDefScope(); + void StartItemDefScope(); + void StartFunctionDefScope(); + void EndScope(); + + static std::vector CreateScriptTokenList(const char* script); + static bool DoesTokenNeedQuotationMarks(const std::string& token); + + void WriteEscapedString(const std::string_view& str) const; + + static const std::string& BoolValue(bool value); + void WriteKey(const std::string& keyName) const; + void WriteStringProperty(const std::string& propertyKey, const std::string& propertyValue) const; + void WriteStringProperty(const std::string& propertyKey, const char* propertyValue) const; + void WriteBoolProperty(const std::string& propertyKey, bool propertyValue, bool defaultValue) const; + void WriteIntProperty(const std::string& propertyKey, int propertyValue, int defaultValue) const; + void WriteFloatProperty(const std::string& propertyKey, float propertyValue, float defaultValue) const; + void WriteColorProperty(const std::string& propertyKey, const float (&propertyValue)[4], const float (&defaultValue)[4]) const; + void WriteKeywordProperty(const std::string& propertyKey, bool shouldWrite) const; + void WriteFlagsProperty(const std::string& propertyKey, int flagsValue) const; + + std::ostream& m_stream; + size_t m_indent; + }; +} // namespace menu diff --git a/src/ObjWriting/Menu/IMenuWriter.h b/src/ObjWriting/Menu/IMenuWriter.h new file mode 100644 index 00000000..393fc33f --- /dev/null +++ b/src/ObjWriting/Menu/IMenuWriter.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace menu +{ + class IWriter + { + public: + IWriter() = default; + virtual ~IWriter() = default; + + virtual void Start() = 0; + virtual void End() = 0; + + virtual void IncludeMenu(const std::string& menuPath) const = 0; + }; +} // namespace menu