From 662780a896116e6df4fb518bb552409532885206 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Mon, 31 Aug 2026 22:10:37 +0200 Subject: [PATCH] chore: align iw3,iw4,iw5 menu code (#987) * chore: small adjustments to iw3 menu dumper * chore: align iw4 menu loader closer to iw3 * chore: align iw4 menu dumper closer to iw3 * chore: adjust iw4 menu expression dumping * chore: adjust iw4 menu expression converting * chore: align iw5 menu loader closer to iw3 * chore: align iw4 menu dumper closer to iw3 * chore: adjust iw5 menu expression dumping * chore: adjust iw5 menu expression converting * fix: not respecting reference menus when inlining menu list menus * fix: compilation on linux --- src/Common/Game/IW4/IW4_Assets.h | 6 +- src/Common/Game/IW5/IW5_Assets.h | 8 +- .../Game/IW3/Menu/MenuConverterIW3.cpp | 1 + .../Game/IW4/Menu/LoaderMenuListIW4.cpp | 17 +- .../Game/IW4/Menu/LoaderMenuListIW4.h | 2 +- .../IW4/Menu/MenuConversionZoneStateIW4.cpp | 2 +- .../IW4/Menu/MenuConversionZoneStateIW4.h | 31 +- .../Game/IW4/Menu/MenuConverterIW4.cpp | 602 ++++++++---------- .../Game/IW4/Menu/MenuConverterIW4.h | 4 +- .../Game/IW5/Menu/LoaderMenuListIW5.cpp | 18 +- .../Game/IW5/Menu/LoaderMenuListIW5.h | 2 +- .../IW5/Menu/MenuConversionZoneStateIW5.cpp | 2 +- .../IW5/Menu/MenuConversionZoneStateIW5.h | 31 +- .../Game/IW5/Menu/MenuConverterIW5.cpp | 601 ++++++++--------- .../Game/IW5/Menu/MenuConverterIW5.h | 4 +- .../Game/IW3/Menu/MenuDumperIW3.cpp | 7 +- .../Game/IW3/Menu/MenuListDumperIW3.cpp | 7 +- .../Game/IW4/Menu/MenuDumperIW4.cpp | 8 +- .../Game/IW4/Menu/MenuListDumperIW4.cpp | 56 +- .../Game/IW4/Menu/MenuWriterIW4.cpp | 584 ++++++++--------- .../Game/IW5/Menu/MenuDumperIW5.cpp | 48 +- .../Game/IW5/Menu/MenuListDumperIW5.cpp | 122 +++- .../Game/IW5/Menu/MenuListDumperIW5.h | 6 + .../Game/IW5/Menu/MenuWriterIW5.cpp | 589 ++++++++--------- 24 files changed, 1331 insertions(+), 1427 deletions(-) diff --git a/src/Common/Game/IW4/IW4_Assets.h b/src/Common/Game/IW4/IW4_Assets.h index 67b3a1e1..e3d63d32 100644 --- a/src/Common/Game/IW4/IW4_Assets.h +++ b/src/Common/Game/IW4/IW4_Assets.h @@ -2315,7 +2315,7 @@ namespace IW4 int ownerDraw; unsigned int ownerDrawFlags; float borderSize; - int staticFlags; + unsigned int staticFlags; unsigned int dynamicFlags[1]; int nextTime; float foreColor[4]; @@ -2385,14 +2385,14 @@ namespace IW4 int gameMsgWindowIndex; int gameMsgWindowMode; const char* text; - int itemFlags; + unsigned int itemFlags; menuDef_t* parent; MenuEventHandlerSet* mouseEnterText; MenuEventHandlerSet* mouseExitText; MenuEventHandlerSet* mouseEnter; MenuEventHandlerSet* mouseExit; MenuEventHandlerSet* action; - MenuEventHandlerSet* accept; + MenuEventHandlerSet* onAccept; MenuEventHandlerSet* onFocus; MenuEventHandlerSet* leaveFocus; const char* dvar; diff --git a/src/Common/Game/IW5/IW5_Assets.h b/src/Common/Game/IW5/IW5_Assets.h index 61146ec2..47caeaa4 100644 --- a/src/Common/Game/IW5/IW5_Assets.h +++ b/src/Common/Game/IW5/IW5_Assets.h @@ -3235,8 +3235,8 @@ namespace IW5 int ownerDraw; unsigned int ownerDrawFlags; float borderSize; - int staticFlags; - int dynamicFlags[1]; + unsigned int staticFlags; + unsigned int dynamicFlags[1]; int nextTime; float foreColor[4]; float backColor[4]; @@ -3419,14 +3419,14 @@ namespace IW5 int gameMsgWindowIndex; int gameMsgWindowMode; const char* text; - int itemFlags; + unsigned int itemFlags; menuDef_t* parent; MenuEventHandlerSet* mouseEnterText; MenuEventHandlerSet* mouseExitText; MenuEventHandlerSet* mouseEnter; MenuEventHandlerSet* mouseExit; MenuEventHandlerSet* action; - MenuEventHandlerSet* accept; + MenuEventHandlerSet* onAccept; MenuEventHandlerSet* onFocus; MenuEventHandlerSet* hasFocus; MenuEventHandlerSet* leaveFocus; diff --git a/src/ObjLoading/Game/IW3/Menu/MenuConverterIW3.cpp b/src/ObjLoading/Game/IW3/Menu/MenuConverterIW3.cpp index 8e77db1b..5cfbca11 100644 --- a/src/ObjLoading/Game/IW3/Menu/MenuConverterIW3.cpp +++ b/src/ObjLoading/Game/IW3/Menu/MenuConverterIW3.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include diff --git a/src/ObjLoading/Game/IW4/Menu/LoaderMenuListIW4.cpp b/src/ObjLoading/Game/IW4/Menu/LoaderMenuListIW4.cpp index 98ede01a..c45605ae 100644 --- a/src/ObjLoading/Game/IW4/Menu/LoaderMenuListIW4.cpp +++ b/src/ObjLoading/Game/IW4/Menu/LoaderMenuListIW4.cpp @@ -1,15 +1,12 @@ #include "LoaderMenuListIW4.h" -#include "Game/IW4/IW4.h" #include "Game/IW4/Menu/MenuConversionZoneStateIW4.h" #include "Game/IW4/Menu/MenuConverterIW4.h" #include "ObjLoading.h" #include "Parsing/Menu/MenuFileReader.h" #include "Utils/Logging/Log.h" -#include -#include -#include +#include using namespace IW4; @@ -44,7 +41,8 @@ namespace const auto menuListResult = ParseMenuFile(*file.m_stream, assetName, zoneState); if (menuListResult) { - ProcessParsedResults(assetName, context, *menuListResult, zoneState, conversionState, menus, registration); + if (!ProcessParsedResults(assetName, context, *menuListResult, zoneState, conversionState, menus, registration)) + return AssetCreationResult::Failure(); for (const auto& menuToLoad : menuListResult->m_menus_to_load) menuLoadQueue.emplace_back(menuToLoad); @@ -59,7 +57,8 @@ namespace { const auto& menuFileToLoad = menuLoadQueue.front(); - LoadMenuFileFromQueue(menuFileToLoad, context, zoneState, conversionState, menus, registration); + if (!LoadMenuFileFromQueue(menuFileToLoad, context, zoneState, conversionState, menus, registration)) + return AssetCreationResult::Failure(); menuLoadQueue.pop_front(); } @@ -108,7 +107,8 @@ namespace const auto menuFileResult = ParseMenuFile(*file.m_stream, menuFilePath, zoneState); if (menuFileResult) { - ProcessParsedResults(menuFilePath, context, *menuFileResult, zoneState, conversionState, menus, registration); + if (!ProcessParsedResults(menuFilePath, context, *menuFileResult, zoneState, conversionState, menus, registration)) + return false; if (!menuFileResult->m_menus_to_load.empty()) con::warn("Menu file has menus to load even though it is not a menu list, ignoring: \"{}\"", menuFilePath); @@ -158,8 +158,7 @@ namespace auto* menuAsset = m_memory.Alloc(); AssetRegistration menuRegistration(commonMenu->m_name, menuAsset); - converter->ConvertMenu(*commonMenu, *menuAsset, menuRegistration); - if (menuAsset == nullptr) + if (!converter->ConvertMenu(*commonMenu, *menuAsset, menuRegistration)) { con::error("Failed to convert menu file \"{}\"", commonMenu->m_name); return false; diff --git a/src/ObjLoading/Game/IW4/Menu/LoaderMenuListIW4.h b/src/ObjLoading/Game/IW4/Menu/LoaderMenuListIW4.h index a22d7b2c..fa00b6e2 100644 --- a/src/ObjLoading/Game/IW4/Menu/LoaderMenuListIW4.h +++ b/src/ObjLoading/Game/IW4/Menu/LoaderMenuListIW4.h @@ -10,4 +10,4 @@ namespace menu { std::unique_ptr> CreateMenuListLoaderIW4(MemoryManager& memory, ISearchPath& searchPath); -} // namespace menu +} diff --git a/src/ObjLoading/Game/IW4/Menu/MenuConversionZoneStateIW4.cpp b/src/ObjLoading/Game/IW4/Menu/MenuConversionZoneStateIW4.cpp index ade6a824..c0a26e9a 100644 --- a/src/ObjLoading/Game/IW4/Menu/MenuConversionZoneStateIW4.cpp +++ b/src/ObjLoading/Game/IW4/Menu/MenuConversionZoneStateIW4.cpp @@ -74,7 +74,7 @@ const char* MenuConversionZoneState::AddString(const std::string& str) void MenuConversionZoneState::AddLoadedFile(std::string loadedFileName, std::vector*> menusOfFile) { - m_menus_by_filename.emplace(std::make_pair(std::move(loadedFileName), std::move(menusOfFile))); + m_menus_by_filename.emplace(std::move(loadedFileName), std::move(menusOfFile)); } void MenuConversionZoneState::FinalizeSupportingData() const diff --git a/src/ObjLoading/Game/IW4/Menu/MenuConversionZoneStateIW4.h b/src/ObjLoading/Game/IW4/Menu/MenuConversionZoneStateIW4.h index 5c383604..4f255ae2 100644 --- a/src/ObjLoading/Game/IW4/Menu/MenuConversionZoneStateIW4.h +++ b/src/ObjLoading/Game/IW4/Menu/MenuConversionZoneStateIW4.h @@ -3,26 +3,15 @@ #include "Asset/IZoneAssetCreationState.h" #include "Game/IW4/IW4.h" -#include +#include +#include +#include namespace IW4 { class MenuConversionZoneState final : public IZoneAssetCreationState { - Zone* m_zone; - std::vector m_functions; - std::map m_function_by_name; - - std::vector m_static_dvars; - std::map m_dvars_by_name; - - std::vector m_strings; - std::map m_strings_by_value; - public: - std::map*>> m_menus_by_filename; - ExpressionSupportingData* m_supporting_data; - MenuConversionZoneState(); void Inject(ZoneAssetCreationInjection& inject) override; @@ -35,5 +24,19 @@ namespace IW4 void AddLoadedFile(std::string loadedFileName, std::vector*> menusOfFile); void FinalizeSupportingData() const; + + std::unordered_map*>> m_menus_by_filename; + ExpressionSupportingData* m_supporting_data; + + private: + Zone* m_zone; + std::vector m_functions; + std::unordered_map m_function_by_name; + + std::vector m_static_dvars; + std::unordered_map m_dvars_by_name; + + std::vector m_strings; + std::unordered_map m_strings_by_value; }; } // namespace IW4 diff --git a/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp b/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp index 4ba7b3a5..061e4f8c 100644 --- a/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp +++ b/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp @@ -13,35 +13,37 @@ #include "Parsing/Simple/Expression/SimpleExpressionUnaryOperation.h" #include "Utils/StringUtils.h" +#include #include #include #include #include +#include using namespace IW4; using namespace menu; namespace { - class MenuConverter : public AbstractMenuConverter, public IMenuConverter + class MenuConverter final : public AbstractMenuConverter, public IMenuConverter { [[nodiscard]] static rectDef_s ConvertRectDef(const CommonRect& rect) { return rectDef_s{ - static_cast(rect.x), - static_cast(rect.y), - static_cast(rect.w), - static_cast(rect.h), - static_cast(rect.horizontalAlign), - static_cast(rect.verticalAlign), + .x = static_cast(rect.x), + .y = static_cast(rect.y), + .w = static_cast(rect.w), + .h = static_cast(rect.h), + .horzAlign = static_cast(rect.horizontalAlign), + .vertAlign = static_cast(rect.verticalAlign), }; } - [[nodiscard]] static rectDef_s ConvertRectDefRelativeTo(const rectDef_s& rect, const rectDef_s& rectRelativeTo) + [[nodiscard]] static rectDef_s ConvertRectDefRelativeTo(const rectDef_s& rect, const rectDef_s& relativeTo) { return rectDef_s{ - .x = rectRelativeTo.x + rect.x, - .y = rectRelativeTo.y + rect.y, + .x = relativeTo.x + rect.x, + .y = relativeTo.y + rect.y, .w = static_cast(rect.w), .h = static_cast(rect.h), .horzAlign = static_cast(rect.horzAlign), @@ -57,7 +59,7 @@ namespace output[3] = static_cast(input.a); } - static void ApplyFlag(int& flags, const bool shouldApply, const int flagValue) + static void ApplyFlag(unsigned& flags, const bool shouldApply, const unsigned flagValue) { if (!shouldApply) return; @@ -65,16 +67,6 @@ namespace flags |= flagValue; } - static int ConvertItemType(const int input) - { - return input; - } - - static int ConvertTextFont(const int input) - { - return input; - } - [[nodiscard]] Material* ConvertMaterial(const std::string& materialName, const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const { if (materialName.empty()) @@ -99,181 +91,14 @@ namespace return soundDependency->Asset(); } - bool HandleStaticDvarFunctionCall(Statement_s* gameStatement, - std::vector& entries, - const CommonExpressionBaseFunctionCall* functionCall, - const int targetFunctionIndex) const - { - if (functionCall->m_args.size() != 1) - return false; - - const auto* dvarNameExpression = functionCall->m_args[0].get(); - if (!dvarNameExpression->IsStatic()) - return false; - - const auto staticDvarNameExpressionValue = dvarNameExpression->EvaluateStatic(); - if (staticDvarNameExpressionValue.m_type != SimpleExpressionValue::Type::STRING) - return false; - - expressionEntry functionEntry{}; - functionEntry.type = EET_OPERATOR; - functionEntry.data.op = targetFunctionIndex; - entries.emplace_back(functionEntry); - - expressionEntry staticDvarIndexEntry{}; - staticDvarIndexEntry.type = EET_OPERAND; - staticDvarIndexEntry.data.operand.dataType = VAL_INT; - staticDvarIndexEntry.data.operand.internals.intVal = - static_cast(m_conversion_zone_state.AddStaticDvar(*staticDvarNameExpressionValue.m_string_value)); - entries.emplace_back(staticDvarIndexEntry); - - expressionEntry parenRight{}; - parenRight.type = EET_OPERATOR; - parenRight.data.op = OP_RIGHTPAREN; - entries.emplace_back(parenRight); - - gameStatement->supportingData = m_conversion_zone_state.m_supporting_data; - - return true; - } - - bool HandleSpecialBaseFunctionCall(Statement_s* gameStatement, - std::vector& entries, - const CommonExpressionBaseFunctionCall* functionCall, - const CommonMenuDef* menu, - const CommonItemDef* item) const - { - switch (functionCall->m_function_index) - { - case EXP_FUNC_DVAR_INT: - return HandleStaticDvarFunctionCall(gameStatement, entries, functionCall, EXP_FUNC_STATIC_DVAR_INT); - case EXP_FUNC_DVAR_BOOL: - return HandleStaticDvarFunctionCall(gameStatement, entries, functionCall, EXP_FUNC_STATIC_DVAR_BOOL); - case EXP_FUNC_DVAR_FLOAT: - return HandleStaticDvarFunctionCall(gameStatement, entries, functionCall, EXP_FUNC_STATIC_DVAR_FLOAT); - case EXP_FUNC_DVAR_STRING: - return HandleStaticDvarFunctionCall(gameStatement, entries, functionCall, EXP_FUNC_STATIC_DVAR_STRING); - default: - break; - } - - return false; - } - - void ConvertExpressionEntryBaseFunctionCall(Statement_s* gameStatement, - std::vector& entries, - const CommonExpressionBaseFunctionCall* functionCall, - const CommonMenuDef* menu, - const CommonItemDef* item) const - { - if (!HandleSpecialBaseFunctionCall(gameStatement, entries, functionCall, menu, item)) - { - expressionEntry functionEntry{}; - functionEntry.type = EET_OPERATOR; - functionEntry.data.op = static_cast(functionCall->m_function_index); - entries.emplace_back(functionEntry); - - auto firstArg = true; - for (const auto& arg : functionCall->m_args) - { - if (!firstArg) - { - expressionEntry argSeparator{}; - argSeparator.type = EET_OPERATOR; - argSeparator.data.op = OP_COMMA; - entries.emplace_back(argSeparator); - } - else - firstArg = false; - - ConvertExpressionEntry(gameStatement, entries, arg.get(), menu, item); - } - - expressionEntry parenRight{}; - parenRight.type = EET_OPERATOR; - parenRight.data.op = OP_RIGHTPAREN; - entries.emplace_back(parenRight); - } - } - - void ConvertExpressionEntryCustomFunctionCall(Statement_s* gameStatement, - std::vector& entries, - const CommonExpressionCustomFunctionCall* functionCall, - const CommonMenuDef* menu, - const CommonItemDef* item) const - { - std::string lowerCaseFunctionName(functionCall->m_function_name); - utils::MakeStringLowerCase(lowerCaseFunctionName); - - Statement_s* functionStatement = m_conversion_zone_state.FindFunction(lowerCaseFunctionName); - - if (functionStatement == nullptr) - { - // Function was not converted yet: Convert it now - const auto foundCommonFunction = m_parsing_zone_state.m_functions_by_name.find(lowerCaseFunctionName); - - if (foundCommonFunction == m_parsing_zone_state.m_functions_by_name.end()) - throw MenuConversionException("Failed to find definition for custom function \"" + functionCall->m_function_name + "\"", menu, item); - - functionStatement = ConvertExpression(foundCommonFunction->second->m_value.get(), menu, item); - functionStatement = m_conversion_zone_state.AddFunction(foundCommonFunction->second->m_name, functionStatement); - } - - expressionEntry functionEntry{}; - functionEntry.type = EET_OPERAND; - functionEntry.data.operand.dataType = VAL_FUNCTION; - functionEntry.data.operand.internals.function = functionStatement; - entries.emplace_back(functionEntry); - - // Statement uses custom function so it needs supporting data - gameStatement->supportingData = m_conversion_zone_state.m_supporting_data; - } - - constexpr static expressionOperatorType_e UNARY_OPERATION_MAPPING[static_cast(SimpleUnaryOperationId::COUNT)]{ + constexpr static expressionOperatorType_e UNARY_OPERATION_MAPPING[]{ OP_NOT, OP_BITWISENOT, OP_SUBTRACT, }; + static_assert(std::size(UNARY_OPERATION_MAPPING) == static_cast(SimpleUnaryOperationId::COUNT)); - bool IsOperation(const ISimpleExpression* expression) const - { - if (!m_disable_optimizations && expression->IsStatic()) - return false; - - return dynamic_cast(expression) || dynamic_cast(expression); - } - - void ConvertExpressionEntryUnaryOperation(Statement_s* gameStatement, - std::vector& entries, - const SimpleExpressionUnaryOperation* unaryOperation, - const CommonMenuDef* menu, - const CommonItemDef* item) const - { - assert(static_cast(unaryOperation->m_operation_type->m_id) < static_cast(SimpleUnaryOperationId::COUNT)); - expressionEntry operation{}; - operation.type = EET_OPERATOR; - operation.data.op = UNARY_OPERATION_MAPPING[static_cast(unaryOperation->m_operation_type->m_id)]; - entries.emplace_back(operation); - - if (IsOperation(unaryOperation->m_operand.get())) - { - expressionEntry parenLeft{}; - parenLeft.type = EET_OPERATOR; - parenLeft.data.op = OP_LEFTPAREN; - entries.emplace_back(parenLeft); - - ConvertExpressionEntry(gameStatement, entries, unaryOperation->m_operand.get(), menu, item); - - expressionEntry parenRight{}; - parenRight.type = EET_OPERATOR; - parenRight.data.op = OP_RIGHTPAREN; - entries.emplace_back(parenRight); - } - else - ConvertExpressionEntry(gameStatement, entries, unaryOperation->m_operand.get(), menu, item); - } - - constexpr static expressionOperatorType_e BINARY_OPERATION_MAPPING[static_cast(SimpleBinaryOperationId::COUNT)]{ + constexpr static expressionOperatorType_e BINARY_OPERATION_MAPPING[]{ OP_ADD, OP_SUBTRACT, OP_MULTIPLY, @@ -292,75 +117,195 @@ namespace OP_AND, OP_OR, }; + static_assert(std::size(BINARY_OPERATION_MAPPING) == static_cast(SimpleBinaryOperationId::COUNT)); + + [[nodiscard]] bool IsOperation(const ISimpleExpression* expression) const + { + if (!m_disable_optimizations && expression->IsStatic()) + return false; + + return dynamic_cast(expression) != nullptr + || dynamic_cast(expression) != nullptr; + } + + bool HandleStaticDvarFunctionCall(Statement_s* gameStatement, + std::vector& entries, + const CommonExpressionBaseFunctionCall* functionCall, + const int targetFunctionIndex) const + { + if (functionCall->m_args.size() != 1) + return false; + + const auto* dvarNameExpression = functionCall->m_args[0].get(); + if (!dvarNameExpression->IsStatic()) + return false; + + const auto staticDvarNameExpressionValue = dvarNameExpression->EvaluateStatic(); + if (staticDvarNameExpressionValue.m_type != SimpleExpressionValue::Type::STRING) + return false; + + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = targetFunctionIndex}}); + + const auto dvarIntVal = static_cast(m_conversion_zone_state.AddStaticDvar(*staticDvarNameExpressionValue.m_string_value)); + entries.emplace_back(expressionEntry{.type = EET_OPERAND, .data = {.operand = {.dataType = VAL_INT, .internals = {.intVal = dvarIntVal}}}}); + + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_RIGHTPAREN}}); + + gameStatement->supportingData = m_conversion_zone_state.m_supporting_data; + + return true; + } + + bool HandleSpecialBaseFunctionCall(Statement_s* gameStatement, + std::vector& entries, + const CommonExpressionBaseFunctionCall* baseFunction) const + { + switch (baseFunction->m_function_index) + { + case EXP_FUNC_DVAR_INT: + return HandleStaticDvarFunctionCall(gameStatement, entries, baseFunction, EXP_FUNC_STATIC_DVAR_INT); + case EXP_FUNC_DVAR_BOOL: + return HandleStaticDvarFunctionCall(gameStatement, entries, baseFunction, EXP_FUNC_STATIC_DVAR_BOOL); + case EXP_FUNC_DVAR_FLOAT: + return HandleStaticDvarFunctionCall(gameStatement, entries, baseFunction, EXP_FUNC_STATIC_DVAR_FLOAT); + case EXP_FUNC_DVAR_STRING: + return HandleStaticDvarFunctionCall(gameStatement, entries, baseFunction, EXP_FUNC_STATIC_DVAR_STRING); + default: + break; + } + + return false; + } + + void ConvertExpressionEntryBaseFunctionCall(Statement_s* gameStatement, + std::vector& entries, + const CommonExpressionBaseFunctionCall* baseFunction, + const CommonMenuDef* menu, + const CommonItemDef* item) const + { + if (!HandleSpecialBaseFunctionCall(gameStatement, entries, baseFunction)) + { + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = static_cast(baseFunction->m_function_index)}}); + + auto firstArg = true; + for (const auto& arg : baseFunction->m_args) + { + if (firstArg) + firstArg = false; + else + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_COMMA}}); + + ConvertExpressionEntry(gameStatement, entries, arg.get(), menu, item); + } + + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_RIGHTPAREN}}); + } + } + + void ConvertExpressionEntryCustomFunctionCall(Statement_s* gameStatement, + std::vector& entries, + const CommonExpressionCustomFunctionCall* customFunction, + const CommonMenuDef* menu, + const CommonItemDef* item) const + { + std::string lowerCaseFunctionName(customFunction->m_function_name); + utils::MakeStringLowerCase(lowerCaseFunctionName); + + auto* functionStatement = m_conversion_zone_state.FindFunction(lowerCaseFunctionName); + + if (!functionStatement) + { + // Function was not converted yet: Convert it now + const auto foundCommonFunction = m_parsing_zone_state.m_functions_by_name.find(lowerCaseFunctionName); + + if (foundCommonFunction == m_parsing_zone_state.m_functions_by_name.end()) + { + throw MenuConversionException( + std::format("Failed to find definition for custom function \"{}\"", customFunction->m_function_name), menu, item); + } + + functionStatement = ConvertExpression(foundCommonFunction->second->m_value.get(), menu, item); + functionStatement = m_conversion_zone_state.AddFunction(foundCommonFunction->second->m_name, functionStatement); + } + + entries.emplace_back(expressionEntry{ + .type = EET_OPERAND, + .data = {.operand = {.dataType = VAL_FUNCTION, .internals = {.function = functionStatement}}}, + }); + + // Statement uses custom function so it needs supporting data + gameStatement->supportingData = m_conversion_zone_state.m_supporting_data; + } + + void ConvertExpressionEntryUnaryOperation(Statement_s* gameStatement, + std::vector& entries, + const SimpleExpressionUnaryOperation* unary, + const CommonMenuDef* menu, + const CommonItemDef* item) const + { + assert(static_cast(unary->m_operation_type->m_id) < static_cast(SimpleUnaryOperationId::COUNT)); + + entries.emplace_back(expressionEntry{ + .type = EET_OPERATOR, + .data = {.op = UNARY_OPERATION_MAPPING[static_cast(unary->m_operation_type->m_id)]}, + }); + + const auto wrapOperand = IsOperation(unary->m_operand.get()); + if (wrapOperand) + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_LEFTPAREN}}); + ConvertExpressionEntry(gameStatement, entries, unary->m_operand.get(), menu, item); + if (wrapOperand) + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_RIGHTPAREN}}); + } void ConvertExpressionEntryBinaryOperation(Statement_s* gameStatement, std::vector& entries, - const SimpleExpressionBinaryOperation* binaryOperation, + const SimpleExpressionBinaryOperation* binary, const CommonMenuDef* menu, const CommonItemDef* item) const { // Game needs all nested operations to have parenthesis - if (IsOperation(binaryOperation->m_operand1.get())) - { - expressionEntry parenLeft{}; - parenLeft.type = EET_OPERATOR; - parenLeft.data.op = OP_LEFTPAREN; - entries.emplace_back(parenLeft); + const auto wrapLeft = IsOperation(binary->m_operand1.get()); + if (wrapLeft) + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_LEFTPAREN}}); + ConvertExpressionEntry(gameStatement, entries, binary->m_operand1.get(), menu, item); + if (wrapLeft) + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_RIGHTPAREN}}); - ConvertExpressionEntry(gameStatement, entries, binaryOperation->m_operand1.get(), menu, item); - - expressionEntry parenRight{}; - parenRight.type = EET_OPERATOR; - parenRight.data.op = OP_RIGHTPAREN; - entries.emplace_back(parenRight); - } - else - ConvertExpressionEntry(gameStatement, entries, binaryOperation->m_operand1.get(), menu, item); - - assert(static_cast(binaryOperation->m_operation_type->m_id) < static_cast(SimpleBinaryOperationId::COUNT)); - expressionEntry operation{}; - operation.type = EET_OPERATOR; - operation.data.op = BINARY_OPERATION_MAPPING[static_cast(binaryOperation->m_operation_type->m_id)]; - entries.emplace_back(operation); + assert(static_cast(binary->m_operation_type->m_id) < static_cast(SimpleBinaryOperationId::COUNT)); + entries.emplace_back(expressionEntry{ + .type = EET_OPERATOR, + .data = {.op = BINARY_OPERATION_MAPPING[static_cast(binary->m_operation_type->m_id)]}, + }); // Game needs all nested operations to have parenthesis - if (IsOperation(binaryOperation->m_operand2.get())) - { - expressionEntry parenLeft{}; - parenLeft.type = EET_OPERATOR; - parenLeft.data.op = OP_LEFTPAREN; - entries.emplace_back(parenLeft); - - ConvertExpressionEntry(gameStatement, entries, binaryOperation->m_operand2.get(), menu, item); - - expressionEntry parenRight{}; - parenRight.type = EET_OPERATOR; - parenRight.data.op = OP_RIGHTPAREN; - entries.emplace_back(parenRight); - } - else - ConvertExpressionEntry(gameStatement, entries, binaryOperation->m_operand2.get(), menu, item); + const auto wrapRight = IsOperation(binary->m_operand2.get()); + if (wrapRight) + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_LEFTPAREN}}); + ConvertExpressionEntry(gameStatement, entries, binary->m_operand2.get(), menu, item); + if (wrapRight) + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_RIGHTPAREN}}); } - void ConvertExpressionEntryExpressionValue(std::vector& entries, const SimpleExpressionValue* expressionValue) const + void ConvertExpressionValue(std::vector& entries, const SimpleExpressionValue& value) const { expressionEntry entry{}; entry.type = EET_OPERAND; - if (expressionValue->m_type == SimpleExpressionValue::Type::INT) + switch (value.m_type) { + case SimpleExpressionValue::Type::INT: entry.data.operand.dataType = VAL_INT; - entry.data.operand.internals.intVal = expressionValue->m_int_value; - } - else if (expressionValue->m_type == SimpleExpressionValue::Type::DOUBLE) - { + entry.data.operand.internals.intVal = value.m_int_value; + break; + case SimpleExpressionValue::Type::DOUBLE: entry.data.operand.dataType = VAL_FLOAT; - entry.data.operand.internals.floatVal = static_cast(expressionValue->m_double_value); - } - else if (expressionValue->m_type == SimpleExpressionValue::Type::STRING) - { + entry.data.operand.internals.floatVal = static_cast(value.m_double_value); + break; + case SimpleExpressionValue::Type::STRING: entry.data.operand.dataType = VAL_STRING; - entry.data.operand.internals.stringVal.string = m_conversion_zone_state.AddString(*expressionValue->m_string_value); + entry.data.operand.internals.stringVal.string = m_conversion_zone_state.AddString(*value.m_string_value); + break; } entries.emplace_back(entry); @@ -374,28 +319,28 @@ namespace { if (!m_disable_optimizations && expression->IsStatic()) { - const auto expressionStaticValue = expression->EvaluateStatic(); - ConvertExpressionEntryExpressionValue(entries, &expressionStaticValue); + const auto staticValue = expression->EvaluateStatic(); + ConvertExpressionValue(entries, staticValue); } - else if (const auto* expressionValue = dynamic_cast(expression)) + else if (const auto* value = dynamic_cast(expression)) { - ConvertExpressionEntryExpressionValue(entries, expressionValue); + ConvertExpressionValue(entries, *value); } - else if (const auto* binaryOperation = dynamic_cast(expression)) + else if (const auto* binary = dynamic_cast(expression)) { - ConvertExpressionEntryBinaryOperation(gameStatement, entries, binaryOperation, menu, item); + ConvertExpressionEntryBinaryOperation(gameStatement, entries, binary, menu, item); } - else if (const auto* unaryOperation = dynamic_cast(expression)) + else if (const auto* unary = dynamic_cast(expression)) { - ConvertExpressionEntryUnaryOperation(gameStatement, entries, unaryOperation, menu, item); + ConvertExpressionEntryUnaryOperation(gameStatement, entries, unary, menu, item); } - else if (const auto* baseFunctionCall = dynamic_cast(expression)) + else if (const auto* baseFunction = dynamic_cast(expression)) { - ConvertExpressionEntryBaseFunctionCall(gameStatement, entries, baseFunctionCall, menu, item); + ConvertExpressionEntryBaseFunctionCall(gameStatement, entries, baseFunction, menu, item); } - else if (const auto* customFunctionCall = dynamic_cast(expression)) + else if (const auto* customFunction = dynamic_cast(expression)) { - ConvertExpressionEntryCustomFunctionCall(gameStatement, entries, customFunctionCall, menu, item); + ConvertExpressionEntryCustomFunctionCall(gameStatement, entries, customFunction, menu, item); } else if (dynamic_cast(expression)) { @@ -414,18 +359,14 @@ namespace return nullptr; auto* statement = m_memory.Alloc(); - statement->lastResult = Operand{}; - statement->lastExecuteTime = 0; - statement->supportingData = nullptr; // Supporting data is set upon using it + std::vector entries; + ConvertExpressionEntry(statement, entries, expression, menu, item); - std::vector expressionEntries; - ConvertExpressionEntry(statement, expressionEntries, expression, menu, item); + auto* outEntries = m_memory.Alloc(entries.size()); + std::memcpy(outEntries, entries.data(), sizeof(expressionEntry) * entries.size()); - auto* outputExpressionEntries = m_memory.Alloc(expressionEntries.size()); - std::memcpy(outputExpressionEntries, expressionEntries.data(), sizeof(expressionEntry) * expressionEntries.size()); - - statement->entries = outputExpressionEntries; - statement->numEntries = static_cast(expressionEntries.size()); + statement->numEntries = static_cast(entries.size()); + statement->entries = outEntries; return statement; } @@ -435,26 +376,20 @@ namespace const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const { - if (m_disable_optimizations) - return ConvertExpression(expression, menu, item); - if (!expression) return nullptr; - if (expression->IsStatic()) + if (!m_disable_optimizations && expression->IsStatic()) { const auto value = expression->EvaluateStatic(); - switch (value.m_type) - { - case SimpleExpressionValue::Type::DOUBLE: - staticValue = static_cast(value.m_double_value); - break; - case SimpleExpressionValue::Type::INT: + + if (value.m_type == SimpleExpressionValue::Type::INT) staticValue = static_cast(value.m_int_value); - break; - case SimpleExpressionValue::Type::STRING: + else if (value.m_type == SimpleExpressionValue::Type::DOUBLE) + staticValue = static_cast(value.m_double_value); + else throw MenuConversionException("Cannot convert string expression value to floating point", menu, item); - } + return nullptr; } @@ -466,25 +401,17 @@ namespace const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const { - if (m_disable_optimizations) - return ConvertExpression(expression, menu, item); - if (!expression) return nullptr; - if (expression->IsStatic()) + if (!m_disable_optimizations && expression->IsStatic()) { const auto value = expression->EvaluateStatic(); - switch (value.m_type) - { - case SimpleExpressionValue::Type::STRING: - staticValue = m_memory.Dup(value.m_string_value->c_str()); - break; - - case SimpleExpressionValue::Type::DOUBLE: - case SimpleExpressionValue::Type::INT: + if (value.m_type != SimpleExpressionValue::Type::STRING) throw MenuConversionException("Cannot convert numeric expression value to string", menu, item); - } + + staticValue = m_memory.Dup(value.m_string_value->c_str()); + return nullptr; } @@ -496,37 +423,29 @@ namespace const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const { - if (m_disable_optimizations) - return ConvertExpression(expression, menu, item); - if (!expression) return nullptr; - if (expression->IsStatic()) + if (!m_disable_optimizations && expression->IsStatic()) { const auto value = expression->EvaluateStatic(); - switch (value.m_type) - { - case SimpleExpressionValue::Type::STRING: - staticValue = ConvertMaterial(*value.m_string_value, menu, item); - break; + if (value.m_type != SimpleExpressionValue::Type::STRING) + throw MenuConversionException("Cannot convert numeric expression value to material", menu, item); + + staticValue = ConvertMaterial(*value.m_string_value, menu, item); - case SimpleExpressionValue::Type::DOUBLE: - case SimpleExpressionValue::Type::INT: - throw MenuConversionException("Cannot convert numeric expression value to string", menu, item); - } return nullptr; } return ConvertExpression(expression, menu, item); } - [[nodiscard]] Statement_s* ConvertVisibleExpression(windowDef_t* window, + [[nodiscard]] Statement_s* ConvertVisibleExpression(windowDef_t& window, const ISimpleExpression* expression, const CommonMenuDef* commonMenu, const CommonItemDef* commonItem = nullptr) const { - if (expression == nullptr) + if (!expression) return nullptr; bool isStatic; @@ -547,11 +466,11 @@ namespace if (isStatic) { if (isTruthy) - window->dynamicFlags[0] |= WINDOW_FLAG_VISIBLE; + window.dynamicFlags[0] |= WINDOW_FLAG_VISIBLE; return nullptr; } - window->dynamicFlags[0] |= WINDOW_FLAG_VISIBLE; + window.dynamicFlags[0] |= WINDOW_FLAG_VISIBLE; return ConvertExpression(expression, commonMenu, commonItem); } @@ -637,7 +556,7 @@ namespace outputCondition->eventExpression = ConvertExpression(condition->m_condition.get(), menu, item); outputCondition->eventHandlerSet = ConvertEventHandlerSet(condition->m_condition_elements.get(), menu, item); - elements.push_back(outputHandler); + elements.emplace_back(outputHandler); if (condition->m_else_elements) { @@ -645,7 +564,7 @@ namespace outputElseHandler->eventType = EVENT_ELSE; outputElseHandler->eventData.elseScript = ConvertEventHandlerSet(condition->m_else_elements.get(), menu, item); - elements.push_back(outputElseHandler); + elements.emplace_back(outputElseHandler); } } } @@ -1011,11 +930,11 @@ namespace } [[nodiscard]] listBoxDef_s* ConvertListBoxFeatures(itemDef_s* item, - CommonItemFeaturesListBox* commonListBox, + const CommonItemFeaturesListBox* commonListBox, const CommonMenuDef& parentMenu, const CommonItemDef& commonItem) const { - if (commonListBox == nullptr) + if (!commonListBox) return nullptr; auto* listBox = m_memory.Alloc(); @@ -1030,7 +949,7 @@ namespace ConvertColor(listBox->selectBorder, commonListBox->m_select_border); listBox->selectIcon = ConvertMaterial(commonListBox->m_select_icon, &parentMenu, &commonItem); - listBox->numColumns = static_cast(std::min(std::extent_v, commonListBox->m_columns.size())); + listBox->numColumns = static_cast(std::min(std::size(listBox->columnInfo), commonListBox->m_columns.size())); for (auto i = 0; i < listBox->numColumns; i++) { auto& col = listBox->columnInfo[i]; @@ -1045,12 +964,9 @@ namespace return listBox; } - [[nodiscard]] editFieldDef_s* ConvertEditFieldFeatures(itemDef_s* item, - CommonItemFeaturesEditField* commonEditField, - const CommonMenuDef& parentMenu, - const CommonItemDef& commonItem) const + [[nodiscard]] editFieldDef_s* ConvertEditFieldFeatures(itemDef_s* item, const CommonItemFeaturesEditField* commonEditField) const { - if (commonEditField == nullptr) + if (!commonEditField) return nullptr; auto* editField = m_memory.Alloc(); @@ -1065,16 +981,13 @@ namespace return editField; } - [[nodiscard]] multiDef_s* ConvertMultiValueFeatures(itemDef_s* item, - CommonItemFeaturesMultiValue* commonMultiValue, - const CommonMenuDef& parentMenu, - const CommonItemDef& commonItem) const + [[nodiscard]] multiDef_s* ConvertMultiValueFeatures(const CommonItemFeaturesMultiValue* commonMultiValue) const { - if (commonMultiValue == nullptr) + if (!commonMultiValue) return nullptr; auto* multiValue = m_memory.Alloc(); - multiValue->count = static_cast(std::min(std::extent_v, commonMultiValue->m_step_names.size())); + multiValue->count = static_cast(std::min(std::size(multiValue->dvarList), commonMultiValue->m_step_names.size())); multiValue->strDef = !commonMultiValue->m_string_values.empty() ? 1 : 0; for (auto i = 0; i < multiValue->count; i++) @@ -1096,12 +1009,9 @@ namespace return multiValue; } - [[nodiscard]] newsTickerDef_s* ConvertNewsTickerFeatures(itemDef_s* item, - CommonItemFeaturesNewsTicker* commonNewsTicker, - const CommonMenuDef& parentMenu, - const CommonItemDef& commonItem) const + [[nodiscard]] newsTickerDef_s* ConvertNewsTickerFeatures(const CommonItemFeaturesNewsTicker* commonNewsTicker) const { - if (commonNewsTicker == nullptr) + if (!commonNewsTicker) return nullptr; auto* newsTicker = m_memory.Alloc(); @@ -1112,7 +1022,7 @@ namespace return newsTicker; } - [[nodiscard]] itemDef_s* ConvertItem(const CommonMenuDef& commonParentMenu, const menuDef_t& parentMenu, const CommonItemDef& commonItem) const + [[nodiscard]] itemDef_s* ConvertItem(const CommonMenuDef& commonParentMenu, menuDef_t& parentMenu, const CommonItemDef& commonItem) const { auto* item = m_memory.Alloc(); @@ -1126,11 +1036,11 @@ namespace ApplyFlag(item->window.staticFlags, commonItem.m_decoration, WINDOW_FLAG_DECORATION); ApplyFlag(item->window.staticFlags, commonItem.m_auto_wrapped, WINDOW_FLAG_AUTO_WRAPPED); ApplyFlag(item->window.staticFlags, commonItem.m_horizontal_scroll, WINDOW_FLAG_HORIZONTAL_SCROLL); - item->type = ConvertItemType(commonItem.m_type); - item->dataType = item->type; + item->type = commonItem.m_type; + item->dataType = commonItem.m_type; item->window.border = commonItem.m_border; item->window.borderSize = static_cast(commonItem.m_border_size); - item->visibleExp = ConvertVisibleExpression(&item->window, commonItem.m_visible_expression.get(), &commonParentMenu, &commonItem); + item->visibleExp = ConvertVisibleExpression(item->window, commonItem.m_visible_expression.get(), &commonParentMenu, &commonItem); item->disabledExp = ConvertExpression(commonItem.m_disabled_expression.get(), &commonParentMenu, &commonItem); item->window.ownerDraw = commonItem.m_owner_draw; item->window.ownerDrawFlags = commonItem.m_owner_draw_flags; @@ -1140,12 +1050,11 @@ namespace item->textaligny = static_cast(commonItem.m_text_align_y); item->textscale = static_cast(commonItem.m_text_scale); item->textStyle = commonItem.m_text_style; - item->fontEnum = ConvertTextFont(commonItem.m_text_font); + item->fontEnum = commonItem.m_text_font; ConvertColor(item->window.backColor, commonItem.m_back_color); ConvertColor(item->window.foreColor, commonItem.m_fore_color); - if (!commonItem.m_fore_color.Equals(CommonColor(1.0, 1.0, 1.0, 1.0))) - item->window.dynamicFlags[0] |= WINDOW_FLAG_NON_DEFAULT_FORECOLOR; + ApplyFlag(item->window.dynamicFlags[0], !commonItem.m_fore_color.Equals(CommonColor(1.0, 1.0, 1.0, 1.0)), WINDOW_FLAG_NON_DEFAULT_FORECOLOR); ConvertColor(item->window.borderColor, commonItem.m_border_color); ConvertColor(item->window.outlineColor, commonItem.m_outline_color); @@ -1159,8 +1068,9 @@ namespace item->mouseEnterText = ConvertEventHandlerSet(commonItem.m_on_mouse_enter_text.get(), &commonParentMenu, &commonItem); item->mouseExitText = ConvertEventHandlerSet(commonItem.m_on_mouse_exit_text.get(), &commonParentMenu, &commonItem); item->action = ConvertEventHandlerSet(commonItem.m_on_action.get(), &commonParentMenu, &commonItem); - item->accept = ConvertEventHandlerSet(commonItem.m_on_accept.get(), &commonParentMenu, &commonItem); + item->onAccept = ConvertEventHandlerSet(commonItem.m_on_accept.get(), &commonParentMenu, &commonItem); item->focusSound = ConvertSound(commonItem.m_focus_sound, &commonParentMenu, &commonItem); + item->dvar = ConvertString(commonItem.m_dvar); item->dvarTest = ConvertString(commonItem.m_dvar_test); item->enableDvar = ConvertEnableDvar(commonItem, item->dvarFlags); item->onKey = ConvertKeyHandler(commonItem.m_key_handlers, &commonParentMenu, &commonItem); @@ -1173,7 +1083,6 @@ namespace item->fxLetterTime = commonItem.m_fx_letter_time; item->fxDecayStartTime = commonItem.m_fx_decay_start_time; item->fxDecayDuration = commonItem.m_fx_decay_duration; - item->dvar = ConvertString(commonItem.m_dvar); switch (commonItem.m_feature_type) { @@ -1182,11 +1091,11 @@ namespace break; case CommonItemFeatureType::EDIT_FIELD: - item->typeData.editField = ConvertEditFieldFeatures(item, commonItem.m_edit_field_features.get(), commonParentMenu, commonItem); + item->typeData.editField = ConvertEditFieldFeatures(item, commonItem.m_edit_field_features.get()); break; case CommonItemFeatureType::MULTI_VALUE: - item->typeData.multi = ConvertMultiValueFeatures(item, commonItem.m_multi_value_features.get(), commonParentMenu, commonItem); + item->typeData.multi = ConvertMultiValueFeatures(commonItem.m_multi_value_features.get()); break; case CommonItemFeatureType::ENUM_DVAR: @@ -1194,7 +1103,7 @@ namespace break; case CommonItemFeatureType::NEWS_TICKER: - item->typeData.ticker = ConvertNewsTickerFeatures(item, commonItem.m_news_ticker_features.get(), commonParentMenu, commonItem); + item->typeData.ticker = ConvertNewsTickerFeatures(commonItem.m_news_ticker_features.get()); break; case CommonItemFeatureType::NONE: @@ -1208,10 +1117,12 @@ namespace // Do this last so any optimizations are considered item->window.rect = ConvertRectDefRelativeTo(item->window.rectClient, parentMenu.window.rect); + item->parent = &parentMenu; + return item; } - itemDef_s** ConvertMenuItems(const CommonMenuDef& commonMenu, const menuDef_t& menu, int& itemCount) const + itemDef_s** ConvertMenuItems(const CommonMenuDef& commonMenu, menuDef_t& menu, int& itemCount) const { if (commonMenu.m_items.empty()) { @@ -1236,15 +1147,16 @@ namespace { } - void ConvertMenu(const menu::CommonMenuDef& commonMenu, menuDef_t& menu, AssetRegistration& registration) override + bool ConvertMenu(const CommonMenuDef& commonMenu, menuDef_t& menu, AssetRegistration& registration) override { try { menu.window.name = m_memory.Dup(commonMenu.m_name.c_str()); - menu.fullScreen = commonMenu.m_full_screen; + menu.fullScreen = commonMenu.m_full_screen ? 1 : 0; ApplyFlag(menu.window.staticFlags, commonMenu.m_screen_space, WINDOW_FLAG_SCREEN_SPACE); ApplyFlag(menu.window.staticFlags, commonMenu.m_decoration, WINDOW_FLAG_DECORATION); menu.window.rect = ConvertRectDef(commonMenu.m_rect); + menu.window.rectClient = menu.window.rect; menu.window.style = commonMenu.m_style; menu.window.border = commonMenu.m_border; menu.window.borderSize = static_cast(commonMenu.m_border_size); @@ -1269,7 +1181,7 @@ namespace ApplyFlag(menu.window.staticFlags, commonMenu.m_hidden_during_ui, WINDOW_FLAG_HIDDEN_DURING_UI); menu.allowedBinding = ConvertString(commonMenu.m_allowed_binding); ApplyFlag(menu.window.staticFlags, commonMenu.m_text_only_focus, WINDOW_FLAG_TEXT_ONLY_FOCUS); - menu.visibleExp = ConvertVisibleExpression(&menu.window, commonMenu.m_visible_expression.get(), &commonMenu); + menu.visibleExp = ConvertVisibleExpression(menu.window, commonMenu.m_visible_expression.get(), &commonMenu); menu.rectXExp = ConvertOrApplyStatement(menu.window.rect.x, commonMenu.m_rect_x_exp.get(), &commonMenu); menu.rectYExp = ConvertOrApplyStatement(menu.window.rect.y, commonMenu.m_rect_y_exp.get(), &commonMenu); menu.rectWExp = ConvertOrApplyStatement(menu.window.rect.w, commonMenu.m_rect_w_exp.get(), &commonMenu); @@ -1287,7 +1199,10 @@ namespace catch (const MenuConversionException& e) { PrintConversionExceptionDetails(e); + return false; } + + return true; } MenuConversionZoneState& m_conversion_zone_state; @@ -1295,7 +1210,8 @@ namespace }; } // namespace -std::unique_ptr IMenuConverter::Create(bool disableOptimizations, ISearchPath& searchPath, MemoryManager& memory, AssetCreationContext& context) +std::unique_ptr + IMenuConverter::Create(const bool disableOptimizations, ISearchPath& searchPath, MemoryManager& memory, AssetCreationContext& context) { return std::make_unique(disableOptimizations, searchPath, memory, context); } diff --git a/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.h b/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.h index 234cedb5..d193414b 100644 --- a/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.h +++ b/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.h @@ -6,6 +6,8 @@ #include "SearchPath/ISearchPath.h" #include "Utils/MemoryManager.h" +#include + namespace IW4 { class IMenuConverter @@ -14,7 +16,7 @@ namespace IW4 IMenuConverter() = default; virtual ~IMenuConverter() = default; - virtual void ConvertMenu(const menu::CommonMenuDef& commonMenu, menuDef_t& menu, AssetRegistration& registration) = 0; + virtual bool ConvertMenu(const menu::CommonMenuDef& commonMenu, menuDef_t& menu, AssetRegistration& registration) = 0; static std::unique_ptr Create(bool disableOptimizations, ISearchPath& searchPath, MemoryManager& memory, AssetCreationContext& context); }; diff --git a/src/ObjLoading/Game/IW5/Menu/LoaderMenuListIW5.cpp b/src/ObjLoading/Game/IW5/Menu/LoaderMenuListIW5.cpp index b16d7fcf..357f0e33 100644 --- a/src/ObjLoading/Game/IW5/Menu/LoaderMenuListIW5.cpp +++ b/src/ObjLoading/Game/IW5/Menu/LoaderMenuListIW5.cpp @@ -1,15 +1,12 @@ #include "LoaderMenuListIW5.h" -#include "Game/IW5/IW5.h" #include "Game/IW5/Menu/MenuConversionZoneStateIW5.h" #include "Game/IW5/Menu/MenuConverterIW5.h" #include "ObjLoading.h" #include "Parsing/Menu/MenuFileReader.h" #include "Utils/Logging/Log.h" -#include -#include -#include +#include using namespace IW5; @@ -44,7 +41,8 @@ namespace const auto menuListResult = ParseMenuFile(*file.m_stream, assetName, zoneState); if (menuListResult) { - ProcessParsedResults(assetName, context, *menuListResult, zoneState, conversionState, menus, registration); + if (!ProcessParsedResults(assetName, context, *menuListResult, zoneState, conversionState, menus, registration)) + return AssetCreationResult::Failure(); for (const auto& menuToLoad : menuListResult->m_menus_to_load) menuLoadQueue.emplace_back(menuToLoad); @@ -59,7 +57,8 @@ namespace { const auto& menuFileToLoad = menuLoadQueue.front(); - LoadMenuFileFromQueue(menuFileToLoad, context, zoneState, conversionState, menus, registration); + if (!LoadMenuFileFromQueue(menuFileToLoad, context, zoneState, conversionState, menus, registration)) + return AssetCreationResult::Failure(); menuLoadQueue.pop_front(); } @@ -108,7 +107,8 @@ namespace const auto menuFileResult = ParseMenuFile(*file.m_stream, menuFilePath, zoneState); if (menuFileResult) { - ProcessParsedResults(menuFilePath, context, *menuFileResult, zoneState, conversionState, menus, registration); + if (!ProcessParsedResults(menuFilePath, context, *menuFileResult, zoneState, conversionState, menus, registration)) + return false; if (!menuFileResult->m_menus_to_load.empty()) con::warn("Menu file has menus to load even though it is not a menu list, ignoring: \"{}\"", menuFilePath); @@ -158,8 +158,7 @@ namespace auto* menuAsset = m_memory.Alloc(); AssetRegistration menuRegistration(commonMenu->m_name, menuAsset); - converter->ConvertMenu(*commonMenu, *menuAsset, menuRegistration); - if (menuAsset == nullptr) + if (!converter->ConvertMenu(*commonMenu, *menuAsset, menuRegistration)) { con::error("Failed to convert menu file \"{}\"", commonMenu->m_name); return false; @@ -214,6 +213,7 @@ namespace } // namespace namespace menu + { std::unique_ptr> CreateMenuListLoaderIW5(MemoryManager& memory, ISearchPath& searchPath) { diff --git a/src/ObjLoading/Game/IW5/Menu/LoaderMenuListIW5.h b/src/ObjLoading/Game/IW5/Menu/LoaderMenuListIW5.h index 4a8f1c4b..c5a3b08c 100644 --- a/src/ObjLoading/Game/IW5/Menu/LoaderMenuListIW5.h +++ b/src/ObjLoading/Game/IW5/Menu/LoaderMenuListIW5.h @@ -10,4 +10,4 @@ namespace menu { std::unique_ptr> CreateMenuListLoaderIW5(MemoryManager& memory, ISearchPath& searchPath); -} // namespace menu +} diff --git a/src/ObjLoading/Game/IW5/Menu/MenuConversionZoneStateIW5.cpp b/src/ObjLoading/Game/IW5/Menu/MenuConversionZoneStateIW5.cpp index ef55af7c..93662797 100644 --- a/src/ObjLoading/Game/IW5/Menu/MenuConversionZoneStateIW5.cpp +++ b/src/ObjLoading/Game/IW5/Menu/MenuConversionZoneStateIW5.cpp @@ -74,7 +74,7 @@ const char* MenuConversionZoneState::AddString(const std::string& str) void MenuConversionZoneState::AddLoadedFile(std::string loadedFileName, std::vector*> menusOfFile) { - m_menus_by_filename.emplace(std::make_pair(std::move(loadedFileName), std::move(menusOfFile))); + m_menus_by_filename.emplace(std::move(loadedFileName), std::move(menusOfFile)); } void MenuConversionZoneState::FinalizeSupportingData() const diff --git a/src/ObjLoading/Game/IW5/Menu/MenuConversionZoneStateIW5.h b/src/ObjLoading/Game/IW5/Menu/MenuConversionZoneStateIW5.h index 849300dd..54111928 100644 --- a/src/ObjLoading/Game/IW5/Menu/MenuConversionZoneStateIW5.h +++ b/src/ObjLoading/Game/IW5/Menu/MenuConversionZoneStateIW5.h @@ -3,26 +3,15 @@ #include "Asset/IZoneAssetCreationState.h" #include "Game/IW5/IW5.h" -#include +#include +#include +#include namespace IW5 { class MenuConversionZoneState final : public IZoneAssetCreationState { - Zone* m_zone; - std::vector m_functions; - std::map m_function_by_name; - - std::vector m_static_dvars; - std::map m_dvars_by_name; - - std::vector m_strings; - std::map m_strings_by_value; - public: - std::map*>> m_menus_by_filename; - ExpressionSupportingData* m_supporting_data; - MenuConversionZoneState(); void Inject(ZoneAssetCreationInjection& inject) override; @@ -35,5 +24,19 @@ namespace IW5 void AddLoadedFile(std::string loadedFileName, std::vector*> menusOfFile); void FinalizeSupportingData() const; + + std::unordered_map*>> m_menus_by_filename; + ExpressionSupportingData* m_supporting_data; + + private: + Zone* m_zone; + std::vector m_functions; + std::unordered_map m_function_by_name; + + std::vector m_static_dvars; + std::unordered_map m_dvars_by_name; + + std::vector m_strings; + std::unordered_map m_strings_by_value; }; } // namespace IW5 diff --git a/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp b/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp index e86b0a3c..feb2bc74 100644 --- a/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp +++ b/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp @@ -13,10 +13,12 @@ #include "Parsing/Simple/Expression/SimpleExpressionUnaryOperation.h" #include "Utils/StringUtils.h" +#include #include #include #include #include +#include using namespace IW5; using namespace menu; @@ -28,20 +30,20 @@ namespace [[nodiscard]] static rectDef_s ConvertRectDef(const CommonRect& rect) { return rectDef_s{ - static_cast(rect.x), - static_cast(rect.y), - static_cast(rect.w), - static_cast(rect.h), - static_cast(rect.horizontalAlign), - static_cast(rect.verticalAlign), + .x = static_cast(rect.x), + .y = static_cast(rect.y), + .w = static_cast(rect.w), + .h = static_cast(rect.h), + .horzAlign = static_cast(rect.horizontalAlign), + .vertAlign = static_cast(rect.verticalAlign), }; } - [[nodiscard]] static rectDef_s ConvertRectDefRelativeTo(const rectDef_s& rect, const rectDef_s& rectRelativeTo) + [[nodiscard]] static rectDef_s ConvertRectDefRelativeTo(const rectDef_s& rect, const rectDef_s& relativeTo) { return rectDef_s{ - .x = rectRelativeTo.x + rect.x, - .y = rectRelativeTo.y + rect.y, + .x = relativeTo.x + rect.x, + .y = relativeTo.y + rect.y, .w = static_cast(rect.w), .h = static_cast(rect.h), .horzAlign = static_cast(rect.horzAlign), @@ -57,7 +59,7 @@ namespace output[3] = static_cast(input.a); } - static void ApplyFlag(int& flags, const bool shouldApply, const int flagValue) + static void ApplyFlag(unsigned& flags, const bool shouldApply, const unsigned flagValue) { if (!shouldApply) return; @@ -65,16 +67,6 @@ namespace flags |= flagValue; } - static int ConvertItemType(const int input) - { - return input; - } - - static int ConvertTextFont(const int input) - { - return input; - } - [[nodiscard]] Material* ConvertMaterial(const std::string& materialName, const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const { if (materialName.empty()) @@ -99,181 +91,14 @@ namespace return soundDependency->Asset(); } - bool HandleStaticDvarFunctionCall(Statement_s* gameStatement, - std::vector& entries, - const CommonExpressionBaseFunctionCall* functionCall, - const int targetFunctionIndex) const - { - if (functionCall->m_args.size() != 1) - return false; - - const auto* dvarNameExpression = functionCall->m_args[0].get(); - if (!dvarNameExpression->IsStatic()) - return false; - - const auto staticDvarNameExpressionValue = dvarNameExpression->EvaluateStatic(); - if (staticDvarNameExpressionValue.m_type != SimpleExpressionValue::Type::STRING) - return false; - - expressionEntry functionEntry{}; - functionEntry.type = EET_OPERATOR; - functionEntry.data.op = targetFunctionIndex; - entries.emplace_back(functionEntry); - - expressionEntry staticDvarIndexEntry{}; - staticDvarIndexEntry.type = EET_OPERAND; - staticDvarIndexEntry.data.operand.dataType = VAL_INT; - staticDvarIndexEntry.data.operand.internals.intVal = - static_cast(m_conversion_zone_state.AddStaticDvar(*staticDvarNameExpressionValue.m_string_value)); - entries.emplace_back(staticDvarIndexEntry); - - expressionEntry parenRight{}; - parenRight.type = EET_OPERATOR; - parenRight.data.op = OP_RIGHTPAREN; - entries.emplace_back(parenRight); - - gameStatement->supportingData = m_conversion_zone_state.m_supporting_data; - - return true; - } - - bool HandleSpecialBaseFunctionCall(Statement_s* gameStatement, - std::vector& entries, - const CommonExpressionBaseFunctionCall* functionCall, - const CommonMenuDef* menu, - const CommonItemDef* item) const - { - switch (functionCall->m_function_index) - { - case EXP_FUNC_DVAR_INT: - return HandleStaticDvarFunctionCall(gameStatement, entries, functionCall, EXP_FUNC_STATIC_DVAR_INT); - case EXP_FUNC_DVAR_BOOL: - return HandleStaticDvarFunctionCall(gameStatement, entries, functionCall, EXP_FUNC_STATIC_DVAR_BOOL); - case EXP_FUNC_DVAR_FLOAT: - return HandleStaticDvarFunctionCall(gameStatement, entries, functionCall, EXP_FUNC_STATIC_DVAR_FLOAT); - case EXP_FUNC_DVAR_STRING: - return HandleStaticDvarFunctionCall(gameStatement, entries, functionCall, EXP_FUNC_STATIC_DVAR_STRING); - default: - break; - } - - return false; - } - - void ConvertExpressionEntryBaseFunctionCall(Statement_s* gameStatement, - std::vector& entries, - const CommonExpressionBaseFunctionCall* functionCall, - const CommonMenuDef* menu, - const CommonItemDef* item) const - { - if (!HandleSpecialBaseFunctionCall(gameStatement, entries, functionCall, menu, item)) - { - expressionEntry functionEntry{}; - functionEntry.type = EET_OPERATOR; - functionEntry.data.op = static_cast(functionCall->m_function_index); - entries.emplace_back(functionEntry); - - auto firstArg = true; - for (const auto& arg : functionCall->m_args) - { - if (!firstArg) - { - expressionEntry argSeparator{}; - argSeparator.type = EET_OPERATOR; - argSeparator.data.op = OP_COMMA; - entries.emplace_back(argSeparator); - } - else - firstArg = false; - - ConvertExpressionEntry(gameStatement, entries, arg.get(), menu, item); - } - - expressionEntry parenRight{}; - parenRight.type = EET_OPERATOR; - parenRight.data.op = OP_RIGHTPAREN; - entries.emplace_back(parenRight); - } - } - - void ConvertExpressionEntryCustomFunctionCall(Statement_s* gameStatement, - std::vector& entries, - const CommonExpressionCustomFunctionCall* functionCall, - const CommonMenuDef* menu, - const CommonItemDef* item) const - { - std::string lowerCaseFunctionName(functionCall->m_function_name); - utils::MakeStringLowerCase(lowerCaseFunctionName); - - Statement_s* functionStatement = m_conversion_zone_state.FindFunction(lowerCaseFunctionName); - - if (functionStatement == nullptr) - { - // Function was not converted yet: Convert it now - const auto foundCommonFunction = m_parsing_zone_state.m_functions_by_name.find(lowerCaseFunctionName); - - if (foundCommonFunction == m_parsing_zone_state.m_functions_by_name.end()) - throw MenuConversionException("Failed to find definition for custom function \"" + functionCall->m_function_name + "\"", menu, item); - - functionStatement = ConvertExpression(foundCommonFunction->second->m_value.get(), menu, item); - functionStatement = m_conversion_zone_state.AddFunction(lowerCaseFunctionName, functionStatement); - } - - expressionEntry functionEntry{}; - functionEntry.type = EET_OPERAND; - functionEntry.data.operand.dataType = VAL_FUNCTION; - functionEntry.data.operand.internals.function = functionStatement; - entries.emplace_back(functionEntry); - - // Statement uses custom function so it needs supporting data - gameStatement->supportingData = m_conversion_zone_state.m_supporting_data; - } - - constexpr static expressionOperatorType_e UNARY_OPERATION_MAPPING[static_cast(SimpleUnaryOperationId::COUNT)]{ + constexpr static expressionOperatorType_e UNARY_OPERATION_MAPPING[]{ OP_NOT, OP_BITWISENOT, OP_SUBTRACT, }; + static_assert(std::size(UNARY_OPERATION_MAPPING) == static_cast(SimpleUnaryOperationId::COUNT)); - bool IsOperation(const ISimpleExpression* expression) const - { - if (!m_disable_optimizations && expression->IsStatic()) - return false; - - return dynamic_cast(expression) || dynamic_cast(expression); - } - - void ConvertExpressionEntryUnaryOperation(Statement_s* gameStatement, - std::vector& entries, - const SimpleExpressionUnaryOperation* unaryOperation, - const CommonMenuDef* menu, - const CommonItemDef* item) const - { - assert(static_cast(unaryOperation->m_operation_type->m_id) < static_cast(SimpleUnaryOperationId::COUNT)); - expressionEntry operation{}; - operation.type = EET_OPERATOR; - operation.data.op = UNARY_OPERATION_MAPPING[static_cast(unaryOperation->m_operation_type->m_id)]; - entries.emplace_back(operation); - - if (IsOperation(unaryOperation->m_operand.get())) - { - expressionEntry parenLeft{}; - parenLeft.type = EET_OPERATOR; - parenLeft.data.op = OP_LEFTPAREN; - entries.emplace_back(parenLeft); - - ConvertExpressionEntry(gameStatement, entries, unaryOperation->m_operand.get(), menu, item); - - expressionEntry parenRight{}; - parenRight.type = EET_OPERATOR; - parenRight.data.op = OP_RIGHTPAREN; - entries.emplace_back(parenRight); - } - else - ConvertExpressionEntry(gameStatement, entries, unaryOperation->m_operand.get(), menu, item); - } - - constexpr static expressionOperatorType_e BINARY_OPERATION_MAPPING[static_cast(SimpleBinaryOperationId::COUNT)]{ + constexpr static expressionOperatorType_e BINARY_OPERATION_MAPPING[]{ OP_ADD, OP_SUBTRACT, OP_MULTIPLY, @@ -292,75 +117,195 @@ namespace OP_AND, OP_OR, }; + static_assert(std::size(BINARY_OPERATION_MAPPING) == static_cast(SimpleBinaryOperationId::COUNT)); + + [[nodiscard]] bool IsOperation(const ISimpleExpression* expression) const + { + if (!m_disable_optimizations && expression->IsStatic()) + return false; + + return dynamic_cast(expression) != nullptr + || dynamic_cast(expression) != nullptr; + } + + bool HandleStaticDvarFunctionCall(Statement_s* gameStatement, + std::vector& entries, + const CommonExpressionBaseFunctionCall* functionCall, + const int targetFunctionIndex) const + { + if (functionCall->m_args.size() != 1) + return false; + + const auto* dvarNameExpression = functionCall->m_args[0].get(); + if (!dvarNameExpression->IsStatic()) + return false; + + const auto staticDvarNameExpressionValue = dvarNameExpression->EvaluateStatic(); + if (staticDvarNameExpressionValue.m_type != SimpleExpressionValue::Type::STRING) + return false; + + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = targetFunctionIndex}}); + + const auto dvarIntVal = static_cast(m_conversion_zone_state.AddStaticDvar(*staticDvarNameExpressionValue.m_string_value)); + entries.emplace_back(expressionEntry{.type = EET_OPERAND, .data = {.operand = {.dataType = VAL_INT, .internals = {.intVal = dvarIntVal}}}}); + + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_RIGHTPAREN}}); + + gameStatement->supportingData = m_conversion_zone_state.m_supporting_data; + + return true; + } + + bool HandleSpecialBaseFunctionCall(Statement_s* gameStatement, + std::vector& entries, + const CommonExpressionBaseFunctionCall* baseFunction) const + { + switch (baseFunction->m_function_index) + { + case EXP_FUNC_DVAR_INT: + return HandleStaticDvarFunctionCall(gameStatement, entries, baseFunction, EXP_FUNC_STATIC_DVAR_INT); + case EXP_FUNC_DVAR_BOOL: + return HandleStaticDvarFunctionCall(gameStatement, entries, baseFunction, EXP_FUNC_STATIC_DVAR_BOOL); + case EXP_FUNC_DVAR_FLOAT: + return HandleStaticDvarFunctionCall(gameStatement, entries, baseFunction, EXP_FUNC_STATIC_DVAR_FLOAT); + case EXP_FUNC_DVAR_STRING: + return HandleStaticDvarFunctionCall(gameStatement, entries, baseFunction, EXP_FUNC_STATIC_DVAR_STRING); + default: + break; + } + + return false; + } + + void ConvertExpressionEntryBaseFunctionCall(Statement_s* gameStatement, + std::vector& entries, + const CommonExpressionBaseFunctionCall* baseFunction, + const CommonMenuDef* menu, + const CommonItemDef* item) const + { + if (!HandleSpecialBaseFunctionCall(gameStatement, entries, baseFunction)) + { + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = static_cast(baseFunction->m_function_index)}}); + + auto firstArg = true; + for (const auto& arg : baseFunction->m_args) + { + if (firstArg) + firstArg = false; + else + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_COMMA}}); + + ConvertExpressionEntry(gameStatement, entries, arg.get(), menu, item); + } + + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_RIGHTPAREN}}); + } + } + + void ConvertExpressionEntryCustomFunctionCall(Statement_s* gameStatement, + std::vector& entries, + const CommonExpressionCustomFunctionCall* customFunction, + const CommonMenuDef* menu, + const CommonItemDef* item) const + { + std::string lowerCaseFunctionName(customFunction->m_function_name); + utils::MakeStringLowerCase(lowerCaseFunctionName); + + auto* functionStatement = m_conversion_zone_state.FindFunction(lowerCaseFunctionName); + + if (!functionStatement) + { + // Function was not converted yet: Convert it now + const auto foundCommonFunction = m_parsing_zone_state.m_functions_by_name.find(lowerCaseFunctionName); + + if (foundCommonFunction == m_parsing_zone_state.m_functions_by_name.end()) + { + throw MenuConversionException( + std::format("Failed to find definition for custom function \"{}\"", customFunction->m_function_name), menu, item); + } + + functionStatement = ConvertExpression(foundCommonFunction->second->m_value.get(), menu, item); + functionStatement = m_conversion_zone_state.AddFunction(lowerCaseFunctionName, functionStatement); + } + + entries.emplace_back(expressionEntry{ + .type = EET_OPERAND, + .data = {.operand = {.dataType = VAL_FUNCTION, .internals = {.function = functionStatement}}}, + }); + + // Statement uses custom function so it needs supporting data + gameStatement->supportingData = m_conversion_zone_state.m_supporting_data; + } + + void ConvertExpressionEntryUnaryOperation(Statement_s* gameStatement, + std::vector& entries, + const SimpleExpressionUnaryOperation* unary, + const CommonMenuDef* menu, + const CommonItemDef* item) const + { + assert(static_cast(unary->m_operation_type->m_id) < static_cast(SimpleUnaryOperationId::COUNT)); + + entries.emplace_back(expressionEntry{ + .type = EET_OPERATOR, + .data = {.op = UNARY_OPERATION_MAPPING[static_cast(unary->m_operation_type->m_id)]}, + }); + + const auto wrapOperand = IsOperation(unary->m_operand.get()); + if (wrapOperand) + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_LEFTPAREN}}); + ConvertExpressionEntry(gameStatement, entries, unary->m_operand.get(), menu, item); + if (wrapOperand) + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_RIGHTPAREN}}); + } void ConvertExpressionEntryBinaryOperation(Statement_s* gameStatement, std::vector& entries, - const SimpleExpressionBinaryOperation* binaryOperation, + const SimpleExpressionBinaryOperation* binary, const CommonMenuDef* menu, const CommonItemDef* item) const { // Game needs all nested operations to have parenthesis - if (IsOperation(binaryOperation->m_operand1.get())) - { - expressionEntry parenLeft{}; - parenLeft.type = EET_OPERATOR; - parenLeft.data.op = OP_LEFTPAREN; - entries.emplace_back(parenLeft); + const auto wrapLeft = IsOperation(binary->m_operand1.get()); + if (wrapLeft) + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_LEFTPAREN}}); + ConvertExpressionEntry(gameStatement, entries, binary->m_operand1.get(), menu, item); + if (wrapLeft) + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_RIGHTPAREN}}); - ConvertExpressionEntry(gameStatement, entries, binaryOperation->m_operand1.get(), menu, item); - - expressionEntry parenRight{}; - parenRight.type = EET_OPERATOR; - parenRight.data.op = OP_RIGHTPAREN; - entries.emplace_back(parenRight); - } - else - ConvertExpressionEntry(gameStatement, entries, binaryOperation->m_operand1.get(), menu, item); - - assert(static_cast(binaryOperation->m_operation_type->m_id) < static_cast(SimpleBinaryOperationId::COUNT)); - expressionEntry operation{}; - operation.type = EET_OPERATOR; - operation.data.op = BINARY_OPERATION_MAPPING[static_cast(binaryOperation->m_operation_type->m_id)]; - entries.emplace_back(operation); + assert(static_cast(binary->m_operation_type->m_id) < static_cast(SimpleBinaryOperationId::COUNT)); + entries.emplace_back(expressionEntry{ + .type = EET_OPERATOR, + .data = {.op = BINARY_OPERATION_MAPPING[static_cast(binary->m_operation_type->m_id)]}, + }); // Game needs all nested operations to have parenthesis - if (IsOperation(binaryOperation->m_operand2.get())) - { - expressionEntry parenLeft{}; - parenLeft.type = EET_OPERATOR; - parenLeft.data.op = OP_LEFTPAREN; - entries.emplace_back(parenLeft); - - ConvertExpressionEntry(gameStatement, entries, binaryOperation->m_operand2.get(), menu, item); - - expressionEntry parenRight{}; - parenRight.type = EET_OPERATOR; - parenRight.data.op = OP_RIGHTPAREN; - entries.emplace_back(parenRight); - } - else - ConvertExpressionEntry(gameStatement, entries, binaryOperation->m_operand2.get(), menu, item); + const auto wrapRight = IsOperation(binary->m_operand2.get()); + if (wrapRight) + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_LEFTPAREN}}); + ConvertExpressionEntry(gameStatement, entries, binary->m_operand2.get(), menu, item); + if (wrapRight) + entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_RIGHTPAREN}}); } - void ConvertExpressionEntryExpressionValue(std::vector& entries, const SimpleExpressionValue* expressionValue) const + void ConvertExpressionValue(std::vector& entries, const SimpleExpressionValue& value) const { expressionEntry entry{}; entry.type = EET_OPERAND; - if (expressionValue->m_type == SimpleExpressionValue::Type::INT) + switch (value.m_type) { + case SimpleExpressionValue::Type::INT: entry.data.operand.dataType = VAL_INT; - entry.data.operand.internals.intVal = expressionValue->m_int_value; - } - else if (expressionValue->m_type == SimpleExpressionValue::Type::DOUBLE) - { + entry.data.operand.internals.intVal = value.m_int_value; + break; + case SimpleExpressionValue::Type::DOUBLE: entry.data.operand.dataType = VAL_FLOAT; - entry.data.operand.internals.floatVal = static_cast(expressionValue->m_double_value); - } - else if (expressionValue->m_type == SimpleExpressionValue::Type::STRING) - { + entry.data.operand.internals.floatVal = static_cast(value.m_double_value); + break; + case SimpleExpressionValue::Type::STRING: entry.data.operand.dataType = VAL_STRING; - entry.data.operand.internals.stringVal.string = m_conversion_zone_state.AddString(*expressionValue->m_string_value); + entry.data.operand.internals.stringVal.string = m_conversion_zone_state.AddString(*value.m_string_value); + break; } entries.emplace_back(entry); @@ -374,28 +319,28 @@ namespace { if (!m_disable_optimizations && expression->IsStatic()) { - const auto expressionStaticValue = expression->EvaluateStatic(); - ConvertExpressionEntryExpressionValue(entries, &expressionStaticValue); + const auto staticValue = expression->EvaluateStatic(); + ConvertExpressionValue(entries, staticValue); } - else if (const auto* expressionValue = dynamic_cast(expression)) + else if (const auto* value = dynamic_cast(expression)) { - ConvertExpressionEntryExpressionValue(entries, expressionValue); + ConvertExpressionValue(entries, *value); } - else if (const auto* binaryOperation = dynamic_cast(expression)) + else if (const auto* binary = dynamic_cast(expression)) { - ConvertExpressionEntryBinaryOperation(gameStatement, entries, binaryOperation, menu, item); + ConvertExpressionEntryBinaryOperation(gameStatement, entries, binary, menu, item); } - else if (const auto* unaryOperation = dynamic_cast(expression)) + else if (const auto* unary = dynamic_cast(expression)) { - ConvertExpressionEntryUnaryOperation(gameStatement, entries, unaryOperation, menu, item); + ConvertExpressionEntryUnaryOperation(gameStatement, entries, unary, menu, item); } - else if (const auto* baseFunctionCall = dynamic_cast(expression)) + else if (const auto* baseFunction = dynamic_cast(expression)) { - ConvertExpressionEntryBaseFunctionCall(gameStatement, entries, baseFunctionCall, menu, item); + ConvertExpressionEntryBaseFunctionCall(gameStatement, entries, baseFunction, menu, item); } - else if (const auto* customFunctionCall = dynamic_cast(expression)) + else if (const auto* customFunction = dynamic_cast(expression)) { - ConvertExpressionEntryCustomFunctionCall(gameStatement, entries, customFunctionCall, menu, item); + ConvertExpressionEntryCustomFunctionCall(gameStatement, entries, customFunction, menu, item); } else if (dynamic_cast(expression)) { @@ -414,20 +359,15 @@ namespace return nullptr; auto* statement = m_memory.Alloc(); - for (auto& result : statement->persistentState.lastResult) - result = Operand{}; - for (auto& lastExecutionTime : statement->persistentState.lastExecuteTime) - lastExecutionTime = 0; - statement->supportingData = nullptr; // Supporting data is set upon using it - std::vector expressionEntries; - ConvertExpressionEntry(statement, expressionEntries, expression, menu, item); + std::vector entries; + ConvertExpressionEntry(statement, entries, expression, menu, item); - auto* outputExpressionEntries = m_memory.Alloc(expressionEntries.size()); - std::memcpy(outputExpressionEntries, expressionEntries.data(), sizeof(expressionEntry) * expressionEntries.size()); + auto* outEntries = m_memory.Alloc(entries.size()); + std::memcpy(outEntries, entries.data(), sizeof(expressionEntry) * entries.size()); - statement->entries = outputExpressionEntries; - statement->numEntries = static_cast(expressionEntries.size()); + statement->numEntries = static_cast(entries.size()); + statement->entries = outEntries; return statement; } @@ -437,26 +377,20 @@ namespace const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const { - if (m_disable_optimizations) - return ConvertExpression(expression, menu, item); - if (!expression) return nullptr; - if (expression->IsStatic()) + if (!m_disable_optimizations && expression->IsStatic()) { const auto value = expression->EvaluateStatic(); - switch (value.m_type) - { - case SimpleExpressionValue::Type::DOUBLE: - staticValue = static_cast(value.m_double_value); - break; - case SimpleExpressionValue::Type::INT: + + if (value.m_type == SimpleExpressionValue::Type::INT) staticValue = static_cast(value.m_int_value); - break; - case SimpleExpressionValue::Type::STRING: + else if (value.m_type == SimpleExpressionValue::Type::DOUBLE) + staticValue = static_cast(value.m_double_value); + else throw MenuConversionException("Cannot convert string expression value to floating point", menu, item); - } + return nullptr; } @@ -468,25 +402,17 @@ namespace const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const { - if (m_disable_optimizations) - return ConvertExpression(expression, menu, item); - if (!expression) return nullptr; - if (expression->IsStatic()) + if (!m_disable_optimizations && expression->IsStatic()) { const auto value = expression->EvaluateStatic(); - switch (value.m_type) - { - case SimpleExpressionValue::Type::STRING: - staticValue = m_memory.Dup(value.m_string_value->c_str()); - break; - - case SimpleExpressionValue::Type::DOUBLE: - case SimpleExpressionValue::Type::INT: + if (value.m_type != SimpleExpressionValue::Type::STRING) throw MenuConversionException("Cannot convert numeric expression value to string", menu, item); - } + + staticValue = m_memory.Dup(value.m_string_value->c_str()); + return nullptr; } @@ -498,37 +424,29 @@ namespace const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const { - if (m_disable_optimizations) - return ConvertExpression(expression, menu, item); - if (!expression) return nullptr; - if (expression->IsStatic()) + if (!m_disable_optimizations && expression->IsStatic()) { const auto value = expression->EvaluateStatic(); - switch (value.m_type) - { - case SimpleExpressionValue::Type::STRING: - staticValue = ConvertMaterial(*value.m_string_value, menu, item); - break; + if (value.m_type != SimpleExpressionValue::Type::STRING) + throw MenuConversionException("Cannot convert numeric expression value to material", menu, item); + + staticValue = ConvertMaterial(*value.m_string_value, menu, item); - case SimpleExpressionValue::Type::DOUBLE: - case SimpleExpressionValue::Type::INT: - throw MenuConversionException("Cannot convert numeric expression value to string", menu, item); - } return nullptr; } return ConvertExpression(expression, menu, item); } - [[nodiscard]] Statement_s* ConvertVisibleExpression(windowDef_t* window, + [[nodiscard]] Statement_s* ConvertVisibleExpression(windowDef_t& window, const ISimpleExpression* expression, const CommonMenuDef* commonMenu, const CommonItemDef* commonItem = nullptr) const { - if (expression == nullptr) + if (!expression) return nullptr; bool isStatic; @@ -549,11 +467,11 @@ namespace if (isStatic) { if (isTruthy) - window->dynamicFlags[0] |= WINDOW_FLAG_VISIBLE; + window.dynamicFlags[0] |= WINDOW_FLAG_VISIBLE; return nullptr; } - window->dynamicFlags[0] |= WINDOW_FLAG_VISIBLE; + window.dynamicFlags[0] |= WINDOW_FLAG_VISIBLE; return ConvertExpression(expression, commonMenu, commonItem); } @@ -639,7 +557,7 @@ namespace outputCondition->eventExpression = ConvertExpression(condition->m_condition.get(), menu, item); outputCondition->eventHandlerSet = ConvertEventHandlerSet(condition->m_condition_elements.get(), menu, item); - elements.push_back(outputHandler); + elements.emplace_back(outputHandler); if (condition->m_else_elements) { @@ -647,7 +565,7 @@ namespace outputElseHandler->eventType = EVENT_ELSE; outputElseHandler->eventData.elseScript = ConvertEventHandlerSet(condition->m_else_elements.get(), menu, item); - elements.push_back(outputElseHandler); + elements.emplace_back(outputElseHandler); } } } @@ -1013,11 +931,11 @@ namespace } [[nodiscard]] listBoxDef_s* ConvertListBoxFeatures(itemDef_s* item, - CommonItemFeaturesListBox* commonListBox, + const CommonItemFeaturesListBox* commonListBox, const CommonMenuDef& parentMenu, const CommonItemDef& commonItem) const { - if (commonListBox == nullptr) + if (!commonListBox) return nullptr; auto* listBox = m_memory.Alloc(); @@ -1034,7 +952,7 @@ namespace listBox->elementHeightExp = ConvertOrApplyStatement(listBox->elementHeight, commonListBox->m_element_height_expression.get(), &parentMenu, &commonItem); - listBox->numColumns = static_cast(std::min(std::extent_v, commonListBox->m_columns.size())); + listBox->numColumns = static_cast(std::min(std::size(listBox->columnInfo), commonListBox->m_columns.size())); for (auto i = 0; i < listBox->numColumns; i++) { auto& col = listBox->columnInfo[i]; @@ -1050,12 +968,9 @@ namespace return listBox; } - [[nodiscard]] editFieldDef_s* ConvertEditFieldFeatures(itemDef_s* item, - CommonItemFeaturesEditField* commonEditField, - const CommonMenuDef& parentMenu, - const CommonItemDef& commonItem) const + [[nodiscard]] editFieldDef_s* ConvertEditFieldFeatures(itemDef_s* item, const CommonItemFeaturesEditField* commonEditField) const { - if (commonEditField == nullptr) + if (!commonEditField) return nullptr; auto* editField = m_memory.Alloc(); @@ -1070,16 +985,13 @@ namespace return editField; } - [[nodiscard]] multiDef_s* ConvertMultiValueFeatures(itemDef_s* item, - CommonItemFeaturesMultiValue* commonMultiValue, - const CommonMenuDef& parentMenu, - const CommonItemDef& commonItem) const + [[nodiscard]] multiDef_s* ConvertMultiValueFeatures(const CommonItemFeaturesMultiValue* commonMultiValue) const { - if (commonMultiValue == nullptr) + if (!commonMultiValue) return nullptr; auto* multiValue = m_memory.Alloc(); - multiValue->count = static_cast(std::min(std::extent_v, commonMultiValue->m_step_names.size())); + multiValue->count = static_cast(std::min(std::size(multiValue->dvarList), commonMultiValue->m_step_names.size())); multiValue->strDef = !commonMultiValue->m_string_values.empty() ? 1 : 0; for (auto i = 0; i < multiValue->count; i++) @@ -1101,12 +1013,9 @@ namespace return multiValue; } - [[nodiscard]] newsTickerDef_s* ConvertNewsTickerFeatures(itemDef_s* item, - CommonItemFeaturesNewsTicker* commonNewsTicker, - const CommonMenuDef& parentMenu, - const CommonItemDef& commonItem) const + [[nodiscard]] newsTickerDef_s* ConvertNewsTickerFeatures(const CommonItemFeaturesNewsTicker* commonNewsTicker) const { - if (commonNewsTicker == nullptr) + if (!commonNewsTicker) return nullptr; auto* newsTicker = m_memory.Alloc(); @@ -1117,7 +1026,7 @@ namespace return newsTicker; } - [[nodiscard]] itemDef_s* ConvertItem(const CommonMenuDef& commonParentMenu, const menuDef_t& parentMenu, const CommonItemDef& commonItem) const + [[nodiscard]] itemDef_s* ConvertItem(const CommonMenuDef& commonParentMenu, menuDef_t& parentMenu, const CommonItemDef& commonItem) const { auto* item = m_memory.Alloc(); memset(item, 0, sizeof(itemDef_s)); @@ -1132,11 +1041,11 @@ namespace ApplyFlag(item->window.staticFlags, commonItem.m_decoration, WINDOW_FLAG_DECORATION); ApplyFlag(item->window.staticFlags, commonItem.m_auto_wrapped, WINDOW_FLAG_AUTO_WRAPPED); ApplyFlag(item->window.staticFlags, commonItem.m_horizontal_scroll, WINDOW_FLAG_HORIZONTAL_SCROLL); - item->type = ConvertItemType(commonItem.m_type); - item->dataType = item->type; + item->type = commonItem.m_type; + item->dataType = commonItem.m_type; item->window.border = commonItem.m_border; item->window.borderSize = static_cast(commonItem.m_border_size); - item->visibleExp = ConvertVisibleExpression(&item->window, commonItem.m_visible_expression.get(), &commonParentMenu, &commonItem); + item->visibleExp = ConvertVisibleExpression(item->window, commonItem.m_visible_expression.get(), &commonParentMenu, &commonItem); item->disabledExp = ConvertExpression(commonItem.m_disabled_expression.get(), &commonParentMenu, &commonItem); item->window.ownerDraw = commonItem.m_owner_draw; item->window.ownerDrawFlags = commonItem.m_owner_draw_flags; @@ -1146,12 +1055,11 @@ namespace item->textaligny = static_cast(commonItem.m_text_align_y); item->textscale = static_cast(commonItem.m_text_scale); item->textStyle = commonItem.m_text_style; - item->fontEnum = ConvertTextFont(commonItem.m_text_font); + item->fontEnum = commonItem.m_text_font; ConvertColor(item->window.backColor, commonItem.m_back_color); ConvertColor(item->window.foreColor, commonItem.m_fore_color); - if (!commonItem.m_fore_color.Equals(CommonColor(1.0, 1.0, 1.0, 1.0))) - item->window.dynamicFlags[0] |= WINDOW_FLAG_NON_DEFAULT_FORECOLOR; + ApplyFlag(item->window.dynamicFlags[0], !commonItem.m_fore_color.Equals(CommonColor(1.0, 1.0, 1.0, 1.0)), WINDOW_FLAG_NON_DEFAULT_FORECOLOR); ConvertColor(item->window.borderColor, commonItem.m_border_color); ConvertColor(item->window.outlineColor, commonItem.m_outline_color); @@ -1166,8 +1074,9 @@ namespace item->mouseEnterText = ConvertEventHandlerSet(commonItem.m_on_mouse_enter_text.get(), &commonParentMenu, &commonItem); item->mouseExitText = ConvertEventHandlerSet(commonItem.m_on_mouse_exit_text.get(), &commonParentMenu, &commonItem); item->action = ConvertEventHandlerSet(commonItem.m_on_action.get(), &commonParentMenu, &commonItem); - item->accept = ConvertEventHandlerSet(commonItem.m_on_accept.get(), &commonParentMenu, &commonItem); + item->onAccept = ConvertEventHandlerSet(commonItem.m_on_accept.get(), &commonParentMenu, &commonItem); item->focusSound = ConvertSound(commonItem.m_focus_sound, &commonParentMenu, &commonItem); + item->dvar = ConvertString(commonItem.m_dvar); item->dvarTest = ConvertString(commonItem.m_dvar_test); item->enableDvar = ConvertEnableDvar(commonItem, item->dvarFlags); item->onKey = ConvertKeyHandler(commonItem.m_key_handlers, &commonParentMenu, &commonItem); @@ -1181,7 +1090,6 @@ namespace item->fxLetterTime = commonItem.m_fx_letter_time; item->fxDecayStartTime = commonItem.m_fx_decay_start_time; item->fxDecayDuration = commonItem.m_fx_decay_duration; - item->dvar = ConvertString(commonItem.m_dvar); switch (commonItem.m_feature_type) { @@ -1190,11 +1098,11 @@ namespace break; case CommonItemFeatureType::EDIT_FIELD: - item->typeData.editField = ConvertEditFieldFeatures(item, commonItem.m_edit_field_features.get(), commonParentMenu, commonItem); + item->typeData.editField = ConvertEditFieldFeatures(item, commonItem.m_edit_field_features.get()); break; case CommonItemFeatureType::MULTI_VALUE: - item->typeData.multi = ConvertMultiValueFeatures(item, commonItem.m_multi_value_features.get(), commonParentMenu, commonItem); + item->typeData.multi = ConvertMultiValueFeatures(commonItem.m_multi_value_features.get()); break; case CommonItemFeatureType::ENUM_DVAR: @@ -1202,7 +1110,7 @@ namespace break; case CommonItemFeatureType::NEWS_TICKER: - item->typeData.ticker = ConvertNewsTickerFeatures(item, commonItem.m_news_ticker_features.get(), commonParentMenu, commonItem); + item->typeData.ticker = ConvertNewsTickerFeatures(commonItem.m_news_ticker_features.get()); break; case CommonItemFeatureType::NONE: @@ -1216,10 +1124,12 @@ namespace // Do this last so any optimizations are considered item->window.rect = ConvertRectDefRelativeTo(item->window.rectClient, parentMenu.window.rect); + item->parent = &parentMenu; + return item; } - itemDef_s** ConvertMenuItems(const CommonMenuDef& commonMenu, const menuDef_t& menu, int& itemCount) const + itemDef_s** ConvertMenuItems(const CommonMenuDef& commonMenu, menuDef_t& menu, int& itemCount) const { if (commonMenu.m_items.empty()) { @@ -1244,7 +1154,7 @@ namespace { } - void ConvertMenu(const menu::CommonMenuDef& commonMenu, menuDef_t& menu, AssetRegistration& registration) override + bool ConvertMenu(const CommonMenuDef& commonMenu, menuDef_t& menu, AssetRegistration& registration) override { try { @@ -1252,10 +1162,11 @@ namespace menu.data = menuData; menu.window.name = m_memory.Dup(commonMenu.m_name.c_str()); - menuData->fullScreen = commonMenu.m_full_screen; + menuData->fullScreen = commonMenu.m_full_screen ? 1 : 0; ApplyFlag(menu.window.staticFlags, commonMenu.m_screen_space, WINDOW_FLAG_SCREEN_SPACE); ApplyFlag(menu.window.staticFlags, commonMenu.m_decoration, WINDOW_FLAG_DECORATION); menu.window.rect = ConvertRectDef(commonMenu.m_rect); + menu.window.rectClient = menu.window.rect; menu.window.style = commonMenu.m_style; menu.window.border = commonMenu.m_border; menu.window.borderSize = static_cast(commonMenu.m_border_size); @@ -1280,7 +1191,7 @@ namespace ApplyFlag(menu.window.staticFlags, commonMenu.m_hidden_during_ui, WINDOW_FLAG_HIDDEN_DURING_UI); menuData->allowedBinding = ConvertString(commonMenu.m_allowed_binding); ApplyFlag(menu.window.staticFlags, commonMenu.m_text_only_focus, WINDOW_FLAG_TEXT_ONLY_FOCUS); - menuData->visibleExp = ConvertVisibleExpression(&menu.window, commonMenu.m_visible_expression.get(), &commonMenu); + menuData->visibleExp = ConvertVisibleExpression(menu.window, commonMenu.m_visible_expression.get(), &commonMenu); menuData->rectXExp = ConvertOrApplyStatement(menu.window.rect.x, commonMenu.m_rect_x_exp.get(), &commonMenu); menuData->rectYExp = ConvertOrApplyStatement(menu.window.rect.y, commonMenu.m_rect_y_exp.get(), &commonMenu); menuData->rectWExp = ConvertOrApplyStatement(menu.window.rect.w, commonMenu.m_rect_w_exp.get(), &commonMenu); @@ -1299,7 +1210,10 @@ namespace catch (const MenuConversionException& e) { PrintConversionExceptionDetails(e); + return false; } + + return true; } MenuConversionZoneState& m_conversion_zone_state; @@ -1307,7 +1221,8 @@ namespace }; } // namespace -std::unique_ptr IMenuConverter::Create(bool disableOptimizations, ISearchPath& searchPath, MemoryManager& memory, AssetCreationContext& context) +std::unique_ptr + IMenuConverter::Create(const bool disableOptimizations, ISearchPath& searchPath, MemoryManager& memory, AssetCreationContext& context) { return std::make_unique(disableOptimizations, searchPath, memory, context); } diff --git a/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.h b/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.h index c0b03845..4e685ab5 100644 --- a/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.h +++ b/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.h @@ -6,6 +6,8 @@ #include "SearchPath/ISearchPath.h" #include "Utils/MemoryManager.h" +#include + namespace IW5 { class IMenuConverter @@ -14,7 +16,7 @@ namespace IW5 IMenuConverter() = default; virtual ~IMenuConverter() = default; - virtual void ConvertMenu(const ::menu::CommonMenuDef& commonMenu, menuDef_t& menu, AssetRegistration& registration) = 0; + virtual bool ConvertMenu(const ::menu::CommonMenuDef& commonMenu, menuDef_t& menu, AssetRegistration& registration) = 0; static std::unique_ptr Create(bool disableOptimizations, ISearchPath& searchPath, MemoryManager& memory, AssetCreationContext& context); }; diff --git a/src/ObjWriting/Game/IW3/Menu/MenuDumperIW3.cpp b/src/ObjWriting/Game/IW3/Menu/MenuDumperIW3.cpp index 13d7ca19..765f89ef 100644 --- a/src/ObjWriting/Game/IW3/Menu/MenuDumperIW3.cpp +++ b/src/ObjWriting/Game/IW3/Menu/MenuDumperIW3.cpp @@ -4,6 +4,7 @@ #include "MenuWriterIW3.h" #include "ObjWriting.h" +#include #include using namespace IW3; @@ -15,7 +16,7 @@ namespace 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 std::format("ui_mp/{}.menu", asset.Asset()->window.name); return menuDumpingState->second.m_path; } @@ -31,7 +32,7 @@ namespace menu if (!ObjWriting::ShouldHandleAssetType(ASSET_TYPE_MENULIST)) { // Make sure menu paths based on menu lists are created - auto menuListAssets = context.m_zone.m_pools.PoolAssets(); + const auto menuListAssets = context.m_zone.m_pools.PoolAssets(); for (auto* menuListAsset : menuListAssets) CreateDumpingStateForMenuListIW3(zoneState, menuListAsset->Asset()); } @@ -42,7 +43,7 @@ namespace menu if (!assetFile) return; - auto menuWriter = CreateMenuWriterIW3(*assetFile); + const auto menuWriter = CreateMenuWriterIW3(*assetFile); menuWriter->Start(); menuWriter->WriteMenu(*menu); diff --git a/src/ObjWriting/Game/IW3/Menu/MenuListDumperIW3.cpp b/src/ObjWriting/Game/IW3/Menu/MenuListDumperIW3.cpp index 24e45565..aaaa9053 100644 --- a/src/ObjWriting/Game/IW3/Menu/MenuListDumperIW3.cpp +++ b/src/ObjWriting/Game/IW3/Menu/MenuListDumperIW3.cpp @@ -26,8 +26,11 @@ namespace if (menuDumpingState == zoneState->m_menu_dumping_state_map.end()) continue; + const auto* menuAssetName = menu->window.name; + const auto isReference = menuAssetName && menuAssetName[0] == ','; + // 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) + if (!isReference && menuDumpingState->second.m_alias_menu_list == menuList) menuDumper.WriteMenu(*menu); else menuDumper.IncludeMenu(menuDumpingState->second.m_path); @@ -111,7 +114,7 @@ namespace menu { auto* zoneState = context.GetZoneAssetDumperState(); - auto menuListAssets = context.m_zone.m_pools.PoolAssets(); + const auto menuListAssets = context.m_zone.m_pools.PoolAssets(); for (const auto* asset : menuListAssets) CreateDumpingStateForMenuListIW3(zoneState, asset->Asset()); diff --git a/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp b/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp index 14b1a316..53d73439 100644 --- a/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp +++ b/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp @@ -4,7 +4,7 @@ #include "MenuWriterIW4.h" #include "ObjWriting.h" -#include +#include #include using namespace IW4; @@ -16,7 +16,7 @@ namespace 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 std::format("ui_mp/{}.menu", asset.Asset()->window.name); return menuDumpingState->second.m_path; } @@ -32,7 +32,7 @@ namespace menu if (!ObjWriting::ShouldHandleAssetType(ASSET_TYPE_MENULIST)) { // Make sure menu paths based on menu lists are created - auto menuListAssets = context.m_zone.m_pools.PoolAssets(); + const auto menuListAssets = context.m_zone.m_pools.PoolAssets(); for (auto* menuListAsset : menuListAssets) CreateDumpingStateForMenuListIW4(zoneState, menuListAsset->Asset()); } @@ -43,7 +43,7 @@ namespace menu if (!assetFile) return; - auto menuWriter = CreateMenuWriterIW4(*assetFile); + const auto menuWriter = CreateMenuWriterIW4(*assetFile); menuWriter->Start(); menuWriter->WriteMenu(*menu); diff --git a/src/ObjWriting/Game/IW4/Menu/MenuListDumperIW4.cpp b/src/ObjWriting/Game/IW4/Menu/MenuListDumperIW4.cpp index 0bf8faff..e94f8912 100644 --- a/src/ObjWriting/Game/IW4/Menu/MenuListDumperIW4.cpp +++ b/src/ObjWriting/Game/IW4/Menu/MenuListDumperIW4.cpp @@ -7,8 +7,8 @@ #include #include -#include -#include +#include +#include namespace fs = std::filesystem; @@ -19,7 +19,7 @@ namespace std::vector GetAllUniqueExpressionSupportingData(const MenuList* menuList) { std::vector result; - std::set alreadyAddedSupportingData; + std::unordered_set alreadyAddedSupportingData; if (menuList->menus == nullptr) return result; @@ -34,9 +34,9 @@ namespace if (menu->expressionData == nullptr) continue; - if (alreadyAddedSupportingData.find(menu->expressionData) == alreadyAddedSupportingData.end()) + if (!alreadyAddedSupportingData.contains(menu->expressionData)) { - result.push_back(menu->expressionData); + result.emplace_back(menu->expressionData); alreadyAddedSupportingData.emplace(menu->expressionData); } } @@ -59,13 +59,10 @@ namespace 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; + if (!function) + continue; - menuDumper.WriteFunctionDef(ss.str(), function); - } + menuDumper.WriteFunctionDef(std::format("FUNC_{}", functionIndex), function); functionIndex++; } @@ -77,13 +74,18 @@ namespace for (auto menuNum = 0; menuNum < menuList->menuCount; menuNum++) { const auto* menu = menuList->menus[menuNum]; + if (!menu) + continue; const auto menuDumpingState = zoneState->m_menu_dumping_state_map.find(menu); if (menuDumpingState == zoneState->m_menu_dumping_state_map.end()) continue; + const auto* menuAssetName = menu->window.name; + const auto isReference = menuAssetName && menuAssetName[0] == ','; + // 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) + if (!isReference && menuDumpingState->second.m_alias_menu_list == menuList) menuDumper.WriteMenu(*menu); else menuDumper.IncludeMenu(menuDumpingState->second.m_path); @@ -95,15 +97,12 @@ namespace const auto* menuAssetName = menu->window.name; if (!menuAssetName) - return ""; + return {}; if (menuAssetName[0] == ',') menuAssetName = &menuAssetName[1]; - std::ostringstream ss; - ss << menuListParentPath << menuAssetName << ".menu"; - - return ss.str(); + return std::format("{}{}.menu", menuListParentPath, menuAssetName); } } // namespace @@ -111,32 +110,34 @@ namespace menu { void CreateDumpingStateForMenuListIW4(MenuDumpingZoneState* zoneState, const MenuList* menuList) { - if (menuList->menuCount <= 0 || menuList->menus == nullptr || menuList->name == nullptr) + if (!menuList || menuList->menuCount <= 0 || !menuList->menus || !menuList->name) 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() + "/"; + parentPath = p.parent_path().generic_string() + "/"; for (auto i = 0; i < menuList->menuCount; i++) { - auto* menu = menuList->menus[i]; + const auto* menu = menuList->menus[i]; - if (menu == nullptr) + if (!menu) + continue; + + auto menuPath = PathForMenu(parentPath, menu); + if (menuPath.empty()) 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) + else if (!existingState->second.m_alias_menu_list) { - auto menuPath = PathForMenu(parentPath, menu); const auto isTheSameAsMenuList = menuPath == menuListName; if (isTheSameAsMenuList) { @@ -157,15 +158,12 @@ namespace menu auto* zoneState = context.GetZoneAssetDumperState(); - auto menuWriter = CreateMenuWriterIW4(*assetFile); + const auto menuWriter = CreateMenuWriterIW4(*assetFile); menuWriter->Start(); - if (!ObjWriting::Configuration.MenuLegacyMode) DumpFunctions(*menuWriter, menuList); - DumpMenus(*menuWriter, zoneState, menuList); - menuWriter->End(); } @@ -173,7 +171,7 @@ namespace menu { auto* zoneState = context.GetZoneAssetDumperState(); - auto menuListAssets = context.m_zone.m_pools.PoolAssets(); + const auto menuListAssets = context.m_zone.m_pools.PoolAssets(); for (const auto* asset : menuListAssets) CreateDumpingStateForMenuListIW4(zoneState, asset->Asset()); diff --git a/src/ObjWriting/Game/IW4/Menu/MenuWriterIW4.cpp b/src/ObjWriting/Game/IW4/Menu/MenuWriterIW4.cpp index b20cec15..19b5d349 100644 --- a/src/ObjWriting/Game/IW4/Menu/MenuWriterIW4.cpp +++ b/src/ObjWriting/Game/IW4/Menu/MenuWriterIW4.cpp @@ -6,34 +6,29 @@ #include #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)); + // Set this to true to skip interpretative expression dumping + constexpr auto DUMP_NAIVE = false; - const auto statementEnd = static_cast(statement->numEntries); + 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]; + const auto& expEntry = statement.entries[currentSearchPosition]; if (expEntry.type != EET_OPERATOR) continue; @@ -54,7 +49,7 @@ namespace return statementEnd; } - class MenuWriter final : public ::menu::AbstractBaseWriter, public menu::IWriterIW4 + class MenuWriter final : public menu::AbstractBaseWriter, public menu::IWriterIW4 { public: explicit MenuWriter(std::ostream& stream) @@ -75,9 +70,7 @@ namespace void WriteMenu(const menuDef_t& menu) override { StartMenuDefScope(); - - WriteMenuData(&menu); - + WriteMenuData(menu); EndScope(); } @@ -97,17 +90,22 @@ namespace } private: - void WriteStatementNaive(const Statement_s* statement) const + static bool HasStatement(const Statement_s* statement) { - const auto entryCount = static_cast(statement->numEntries); + return statement && statement->numEntries > 0 && statement->entries; + } - 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); + 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]; + const auto& entry = statement.entries[i]; if (entry.type == EET_OPERAND) { size_t pos = i; @@ -144,14 +142,14 @@ namespace if (closingParenPos - i + 1u >= 1u) { - const auto& staticDvarEntry = statement->entries[i + 1]; + 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 + if (statement.supportingData && statement.supportingData->staticDvarList.staticDvars && staticDvarEntry.data.operand.internals.intVal >= 0 - && staticDvarEntry.data.operand.internals.intVal < statement->supportingData->staticDvarList.numStaticDvars) + && staticDvarEntry.data.operand.internals.intVal < statement.supportingData->staticDvarList.numStaticDvars) { - const auto* staticDvar = statement->supportingData->staticDvarList.staticDvars[staticDvarEntry.data.operand.internals.intVal]; + const auto* staticDvar = statement.supportingData->staticDvarList.staticDvars[staticDvarEntry.data.operand.internals.intVal]; if (staticDvar && staticDvar->dvarName) m_stream << staticDvar->dvarName; } @@ -183,9 +181,9 @@ namespace m_stream << ")"; } - void WriteStatementOperator(const Statement_s* statement, size_t& currentPos, bool& spaceNext) const + void WriteStatementOperator(const Statement_s& statement, size_t& currentPos, bool& spaceNext) const { - const auto& expEntry = statement->entries[currentPos]; + const auto& expEntry = statement.entries[currentPos]; if (spaceNext && expEntry.data.op != OP_COMMA) m_stream << " "; @@ -230,14 +228,14 @@ namespace if (closingParenPos - currentPos + 1 >= 1) { - const auto& staticDvarEntry = statement->entries[currentPos + 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 + if (statement.supportingData && statement.supportingData->staticDvarList.staticDvars && staticDvarEntry.data.operand.internals.intVal >= 0 - && staticDvarEntry.data.operand.internals.intVal < statement->supportingData->staticDvarList.numStaticDvars) + && staticDvarEntry.data.operand.internals.intVal < statement.supportingData->staticDvarList.numStaticDvars) { - const auto* staticDvar = statement->supportingData->staticDvarList.staticDvars[staticDvarEntry.data.operand.internals.intVal]; + const auto* staticDvar = statement.supportingData->staticDvarList.staticDvars[staticDvarEntry.data.operand.internals.intVal]; if (staticDvar && staticDvar->dvarName) m_stream << staticDvar->dvarName; } @@ -277,22 +275,22 @@ namespace } } - void WriteStatementOperandFunction(const Statement_s* statement, size_t currentPos) const + void WriteStatementOperandFunction(const Statement_s& statement, const size_t currentPos) const { - const auto& operand = statement->entries[currentPos].data.operand; + const auto& operand = statement.entries[currentPos].data.operand; - if (operand.internals.function == nullptr) + if (!operand.internals.function) return; if (!ObjWriting::Configuration.MenuLegacyMode) { int functionIndex = -1; - if (statement->supportingData && statement->supportingData->uifunctions.functions) + if (statement.supportingData && statement.supportingData->uifunctions.functions) { - for (auto supportingFunctionIndex = 0; supportingFunctionIndex < statement->supportingData->uifunctions.totalFunctions; + for (auto supportingFunctionIndex = 0; supportingFunctionIndex < statement.supportingData->uifunctions.totalFunctions; supportingFunctionIndex++) { - if (statement->supportingData->uifunctions.functions[supportingFunctionIndex] == operand.internals.function) + if (statement.supportingData->uifunctions.functions[supportingFunctionIndex] == operand.internals.function) { functionIndex = supportingFunctionIndex; break; @@ -309,14 +307,14 @@ namespace else { m_stream << "("; - WriteStatementSkipInitialUnnecessaryParenthesis(operand.internals.function); + WriteStatementSkipInitialUnnecessaryParenthesis(*operand.internals.function); m_stream << ")"; } } - void WriteStatementOperand(const Statement_s* statement, size_t& currentPos, bool& spaceNext) const + void WriteStatementOperand(const Statement_s& statement, size_t& currentPos, bool& spaceNext) const { - const auto& expEntry = statement->entries[currentPos]; + const auto& expEntry = statement.entries[currentPos]; if (spaceNext) m_stream << " "; @@ -349,16 +347,16 @@ namespace spaceNext = true; } - void WriteStatementEntryRange(const Statement_s* statement, size_t startOffset, size_t endOffset) const + void WriteStatementEntryRange(const Statement_s& statement, const size_t startOffset, const size_t endOffset) const { assert(startOffset <= endOffset); - assert(endOffset <= static_cast(statement->numEntries)); + assert(endOffset <= static_cast(statement.numEntries)); auto currentPos = startOffset; auto spaceNext = false; while (currentPos < endOffset) { - const auto& expEntry = statement->entries[currentPos]; + const auto& expEntry = statement.entries[currentPos]; if (expEntry.type == EET_OPERATOR) { @@ -371,41 +369,41 @@ namespace } } - void WriteStatement(const Statement_s* statement) const + void WriteStatement(const Statement_s& statement) const { - if (statement == nullptr || statement->numEntries < 0) + if (!statement.entries || statement.numEntries < 0) return; - WriteStatementEntryRange(statement, 0, static_cast(statement->numEntries)); + WriteStatementEntryRange(statement, 0, static_cast(statement.numEntries)); } - void WriteStatementSkipInitialUnnecessaryParenthesis(const Statement_s* statementValue) const + void WriteStatementSkipInitialUnnecessaryParenthesis(const Statement_s& statement) const { - if (statementValue == nullptr || statementValue->numEntries < 0) + if (!statement.entries || statement.numEntries < 0) return; - const auto statementEnd = static_cast(statementValue->numEntries); + const auto statementEnd = static_cast(statement.numEntries); - if (statementValue->numEntries >= 1 && statementValue->entries[0].type == EET_OPERATOR && statementValue->entries[0].data.op == OP_LEFTPAREN) + if (statement.numEntries >= 1 && statement.entries[0].type == EET_OPERATOR && statement.entries[0].data.op == OP_LEFTPAREN) { - const auto parenthesisEnd = FindStatementClosingParenthesis(statementValue, 0); + const auto parenthesisEnd = FindStatementClosingParenthesis(statement, 0); if (parenthesisEnd >= statementEnd) - WriteStatementEntryRange(statementValue, 1, statementEnd); + WriteStatementEntryRange(statement, 1, statementEnd); else if (parenthesisEnd == statementEnd - 1) - WriteStatementEntryRange(statementValue, 1, statementEnd - 1); + WriteStatementEntryRange(statement, 1, statementEnd - 1); else - WriteStatementEntryRange(statementValue, 0, statementEnd); + WriteStatementEntryRange(statement, 0, statementEnd); } else { - WriteStatementEntryRange(statementValue, 0, statementEnd); + WriteStatementEntryRange(statement, 0, statementEnd); } } - void WriteStatementProperty(const std::string& propertyKey, const Statement_s* statementValue, bool isBooleanStatement) const + void WriteStatementProperty(const std::string& propertyKey, const Statement_s* statement, const bool isBooleanStatement) const { - if (statementValue == nullptr || statementValue->numEntries < 0) + if (!HasStatement(statement)) return; Indent(); @@ -414,24 +412,30 @@ namespace if (isBooleanStatement) { m_stream << "when("; - DUMP_FUNC(statementValue); + if constexpr (DUMP_NAIVE) + WriteStatementNaive(*statement); + else + WriteStatementSkipInitialUnnecessaryParenthesis(*statement); m_stream << ");\n"; } else { - DUMP_FUNC(statementValue); + if constexpr (DUMP_NAIVE) + WriteStatementNaive(*statement); + else + WriteStatement(*statement); m_stream << ";\n"; } } void WriteSetLocalVarData(const std::string& setFunction, const SetLocalVarData* setLocalVarData) const { - if (setLocalVarData == nullptr) + if (!setLocalVarData || !setLocalVarData->expression) return; Indent(); m_stream << setFunction << " " << setLocalVarData->localVarName << " "; - WriteStatement(setLocalVarData->expression); + WriteStatement(*setLocalVarData->expression); m_stream << ";\n"; } @@ -453,7 +457,6 @@ namespace { if (token == ";") continue; - Indent(); } @@ -470,7 +473,7 @@ namespace isNewStatement = false; if (DoesTokenNeedQuotationMarks(token)) - m_stream << "\"" << token << "\""; + WriteEscapedString(token); else m_stream << token; } @@ -485,10 +488,10 @@ namespace m_stream << "{\n"; IncIndent(); - for (auto i = 0; i < eventHandlerSet->eventHandlerCount; i++) + for (auto eventHandlerIndex = 0; eventHandlerIndex < eventHandlerSet->eventHandlerCount; eventHandlerIndex++) { - const auto* eventHandler = eventHandlerSet->eventHandlers[i]; - if (eventHandler == nullptr) + const auto* eventHandler = eventHandlerSet->eventHandlers[eventHandlerIndex]; + if (!eventHandler) continue; switch (eventHandler->eventType) @@ -498,21 +501,21 @@ namespace break; case EVENT_IF: - if (eventHandler->eventData.conditionalScript == nullptr || eventHandler->eventData.conditionalScript->eventExpression == nullptr - || eventHandler->eventData.conditionalScript->eventHandlerSet == nullptr) + if (!eventHandler->eventData.conditionalScript || !eventHandler->eventData.conditionalScript->eventExpression + || !eventHandler->eventData.conditionalScript->eventHandlerSet) { continue; } Indent(); m_stream << "if ("; - WriteStatementSkipInitialUnnecessaryParenthesis(eventHandler->eventData.conditionalScript->eventExpression); + WriteStatementSkipInitialUnnecessaryParenthesis(*eventHandler->eventData.conditionalScript->eventExpression); m_stream << ")\n"; WriteMenuEventHandlerSet(eventHandler->eventData.conditionalScript->eventHandlerSet); break; case EVENT_ELSE: - if (eventHandler->eventData.elseScript == nullptr) + if (!eventHandler->eventData.elseScript) continue; Indent(); @@ -548,7 +551,7 @@ namespace void WriteMenuEventHandlerSetProperty(const std::string& propertyKey, const MenuEventHandlerSet* eventHandlerSetValue) { - if (eventHandlerSetValue == nullptr) + if (!eventHandlerSetValue) return; Indent(); @@ -564,51 +567,44 @@ namespace << static_cast(rect.vertAlign) << "\n"; } - void WriteMaterialProperty(const std::string& propertyKey, const Material* materialValue) const + void WriteMaterialProperty(const std::string& propertyKey, const Material* material) const { - if (materialValue == nullptr || materialValue->info.name == nullptr) + if (!material || !material->info.name) return; - if (materialValue->info.name[0] == ',') - WriteStringProperty(propertyKey, &materialValue->info.name[1]); - else - WriteStringProperty(propertyKey, materialValue->info.name); + const auto* materialName = material->info.name; + if (materialName[0] == ',') + materialName++; + WriteStringProperty(propertyKey, materialName); } - void WriteSoundAliasProperty(const std::string& propertyKey, const snd_alias_list_t* soundAliasValue) const + void WriteSoundAliasProperty(const std::string& propertyKey, const snd_alias_list_t* soundAlias) const { - if (soundAliasValue == nullptr) - return; - - WriteStringProperty(propertyKey, soundAliasValue->aliasName); + if (soundAlias) + WriteStringProperty(propertyKey, soundAlias->aliasName); } - void WriteDecodeEffectProperty(const std::string& propertyKey, const itemDef_s* item) const + void WriteDecodeEffectProperty(const std::string& propertyKey, const itemDef_s& item) const { - if (!item->decayActive) + if (!item.decayActive) return; Indent(); WriteKey(propertyKey); - m_stream << item->fxLetterTime << " " << item->fxDecayStartTime << " " << item->fxDecayDuration << "\n"; + m_stream << item.fxLetterTime << " " << item.fxDecayStartTime << " " << item.fxDecayDuration << "\n"; } - void WriteItemKeyHandlerProperty(const ItemKeyHandler* itemKeyHandlerValue) + void WriteItemKeyHandlers(const ItemKeyHandler* handler) { - for (const auto* currentHandler = itemKeyHandlerValue; currentHandler; currentHandler = currentHandler->next) + for (const auto* current = handler; current; current = current->next) { - if (currentHandler->key >= '!' && currentHandler->key <= '~' && currentHandler->key != '"') - { - std::ostringstream ss; - ss << "execKey \"" << static_cast(currentHandler->key) << "\""; - WriteMenuEventHandlerSetProperty(ss.str(), currentHandler->action); - } + std::string key; + if (current->key >= '!' && current->key <= '~' && current->key != '"') + key = std::format("execKey \"{}\"", static_cast(current->key)); else - { - std::ostringstream ss; - ss << "execKeyInt " << currentHandler->key; - WriteMenuEventHandlerSetProperty(ss.str(), currentHandler->action); - } + key = std::format("execKeyInt {}", current->key); + + WriteMenuEventHandlerSetProperty(key, current->action); } } @@ -619,18 +615,18 @@ namespace Indent(); WriteKey(propertyKey); + m_stream << "{ "; 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 << "\""; + + WriteEscapedString(token); } if (!firstToken) m_stream << " "; @@ -656,58 +652,61 @@ namespace } } - void WriteColumnProperty(const std::string& propertyKey, const listBoxDef_s* listBox) const + void WriteColumnProperty(const listBoxDef_s& listBox) const { - if (listBox->numColumns <= 0) + if (listBox.numColumns <= 0) return; Indent(); - WriteKey(propertyKey); - m_stream << listBox->numColumns << "\n"; + WriteKey("columns"); + m_stream << listBox.numColumns << "\n"; - for (auto col = 0; col < listBox->numColumns; col++) + const auto columnCount = std::min(listBox.numColumns, std::size(listBox.columnInfo)); + for (size_t columnIndex = 0u; columnIndex < columnCount; columnIndex++) { + const auto& column = listBox.columnInfo[columnIndex]; + 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"; + m_stream << column.pos << " " << column.width << " " << column.maxChars << " " << column.alignment << "\n"; } } - void WriteListBoxProperties(const itemDef_s* item) + void WriteListBoxProperties(const itemDef_s& item) { - if (item->type != ITEM_TYPE_LISTBOX || item->typeData.listBox == nullptr) + if (item.type != ITEM_TYPE_LISTBOX || !item.typeData.listBox) 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); + 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(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 + void WriteDvarFloatProperty(const itemDef_s& item, const editFieldDef_s& editField) const { - if (item->dvar == nullptr) + if (!item.dvar) return; Indent(); - WriteKey(propertyKey); - m_stream << "\"" << item->dvar << "\" " << editField->defVal << " " << editField->minVal << " " << editField->maxVal << "\n"; + WriteKey("dvarFloat"); + WriteEscapedString(item.dvar); + m_stream << " " << editField.defVal << " " << editField.minVal << " " << editField.maxVal << "\n"; } - void WriteEditFieldProperties(const itemDef_s* item) const + void WriteEditFieldProperties(const itemDef_s& item) const { - switch (item->type) + switch (item.type) { case ITEM_TYPE_TEXT: case ITEM_TYPE_EDITFIELD: @@ -726,153 +725,161 @@ namespace return; } - if (item->typeData.editField == nullptr) + if (!item.typeData.editField) 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()) + 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); + WriteDvarFloatProperty(item, editField); } else { - WriteStringProperty("dvar", item->dvar); + WriteStringProperty("dvar", item.dvar); } - WriteStringProperty("localvar", item->localVar); - WriteIntProperty("maxChars", editField->maxChars, 0); - WriteKeywordProperty("maxCharsGotoNext", editField->maxCharsGotoNext != 0); - WriteIntProperty("maxPaintChars", editField->maxPaintChars, 0); + 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 + void WriteMultiValueProperty(const multiDef_s& multi) const { - Indent(); - if (multiDef->strDef) - WriteKey("dvarStrList"); - else - WriteKey("dvarFloatList"); + if (multi.count <= 0) + return; + Indent(); + WriteKey(multi.strDef ? "dvarStrList" : "dvarFloatList"); m_stream << "{"; - for (auto i = 0; i < multiDef->count; i++) + const auto valueCount = std::min(multi.count, std::size(multi.dvarValue)); + for (size_t valueIndex = 0u; valueIndex < valueCount; valueIndex++) { - if (multiDef->dvarList[i] == nullptr || multiDef->strDef && multiDef->dvarStr[i] == nullptr) + if (!multi.dvarList[valueIndex] || (multi.strDef && !multi.dvarStr[valueIndex])) continue; - m_stream << " \"" << multiDef->dvarList[i] << "\""; - - if (multiDef->strDef) - m_stream << " \"" << multiDef->dvarStr[i] << "\""; + m_stream << " "; + WriteEscapedString(multi.dvarList[valueIndex]); + m_stream << " "; + if (multi.strDef) + WriteEscapedString(multi.dvarStr[valueIndex]); else - m_stream << " " << multiDef->dvarValue[i] << ""; + m_stream << multi.dvarValue[valueIndex]; } m_stream << " }\n"; } - void WriteMultiProperties(const itemDef_s* item) const + void WriteMultiProperties(const itemDef_s& item) const { - if (item->type != ITEM_TYPE_MULTI || item->typeData.multi == nullptr) + if (item.type != ITEM_TYPE_MULTI || !item.typeData.multi) return; - const auto* multiDef = item->typeData.multi; - - if (multiDef->count <= 0) - return; - - WriteStringProperty("dvar", item->dvar); - WriteStringProperty("localvar", item->localVar); - WriteMultiValueProperty(multiDef); + WriteStringProperty("dvar", item.dvar); + WriteStringProperty("localvar", item.localVar); + WriteMultiValueProperty(*item.typeData.multi); } - void WriteEnumDvarProperties(const itemDef_s* item) const + void WriteEnumDvarProperties(const itemDef_s& item) const { - if (item->type != ITEM_TYPE_DVARENUM) + if (item.type != ITEM_TYPE_DVARENUM) return; - WriteStringProperty("dvar", item->dvar); - WriteStringProperty("localvar", item->localVar); - WriteStringProperty("dvarEnumList", item->typeData.enumDvarName); + WriteStringProperty("dvar", item.dvar); + WriteStringProperty("localvar", item.localVar); + WriteStringProperty("dvarEnumList", item.typeData.enumDvarName); } - void WriteTickerProperties(const itemDef_s* item) const + void WriteTickerProperties(const itemDef_s& item) const { - if (item->type != ITEM_TYPE_NEWS_TICKER || item->typeData.ticker == nullptr) + if (item.type != ITEM_TYPE_NEWS_TICKER || item.typeData.ticker == nullptr) return; - const auto* newsTickerDef = item->typeData.ticker; + 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) + void WriteItemTextProperty(const char* text) const { - 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); + // This distinguishes explicitly empty text from null text, which falls back to the item's dvar. + if (!text) + return; - if (item->visibleExp) - WriteStatementProperty("visible", item->visibleExp, true); - else if (item->window.dynamicFlags[0] & WINDOW_FLAG_VISIBLE) + Indent(); + WriteKey("text"); + WriteEscapedString(text); + m_stream << "\n"; + } + + void WriteItemData(const itemDef_s& item) + { + WriteStringProperty("name", item.window.name); + WriteItemTextProperty(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 (HasStatement(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); + 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.onAccept); + // 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); + 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); + WriteItemKeyHandlers(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); @@ -882,68 +889,73 @@ namespace WriteTickerProperties(item); } - void WriteItemDefs(const itemDef_s* const* itemDefs, size_t itemCount) + void WriteItemDefs(const itemDef_s* const* items, const size_t itemCount) { - for (auto i = 0u; i < itemCount; i++) + if (!items || itemCount <= 0) + return; + + for (size_t itemIndex = 0u; itemIndex < itemCount; itemIndex++) { + const auto* item = items[itemIndex]; + if (!item) + continue; + StartItemDefScope(); - - WriteItemData(itemDefs[i]); - + WriteItemData(*item); EndScope(); } } - void WriteMenuData(const menuDef_t* menu) + 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); + 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) + if (HasStatement(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); + 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); + WriteItemKeyHandlers(menu.onKey); + WriteItemDefs(menu.items, menu.itemCount); } }; } // namespace diff --git a/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp b/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp index bbf6c44d..43694bd7 100644 --- a/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp +++ b/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp @@ -1,49 +1,24 @@ #include "MenuDumperIW5.h" +#include "MenuListDumperIW5.h" #include "MenuWriterIW5.h" #include "ObjWriting.h" -#include #include #include -namespace fs = std::filesystem; - using namespace IW5; namespace { - const MenuList* GetParentMenuList(const XAssetInfo& asset) + std::string GetPathForMenu(menu::MenuDumpingZoneState* zoneState, const XAssetInfo& asset) { - const auto* menu = asset.Asset(); - auto zoneMenuListPool = asset.m_zone->m_pools.PoolAssets(); - for (const auto* menuList : zoneMenuListPool) - { - const auto* menuListAsset = menuList->Asset(); + const auto menuDumpingState = zoneState->m_menu_dumping_state_map.find(asset.Asset()); - for (auto menuIndex = 0; menuIndex < menuListAsset->menuCount; menuIndex++) - { - if (menuListAsset->menus[menuIndex] == menu) - return menuListAsset; - } - } - - return nullptr; - } - - std::string GetPathForMenu(const XAssetInfo& asset) - { - const auto* list = GetParentMenuList(asset); - - if (!list) + if (menuDumpingState == zoneState->m_menu_dumping_state_map.end()) return std::format("ui_mp/{}.menu", asset.Asset()->window.name); - 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); + return menuDumpingState->second.m_path; } } // namespace @@ -52,22 +27,23 @@ namespace menu void MenuDumperIW5::DumpAsset(AssetDumpingContext& context, const XAssetInfo& asset) { const auto* menu = asset.Asset(); - const auto menuFilePath = GetPathForMenu(asset); + auto* zoneState = context.GetZoneAssetDumperState(); 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; + // Make sure menu paths based on menu lists are created + const auto menuListAssets = context.m_zone.m_pools.PoolAssets(); + for (auto* menuListAsset : menuListAssets) + CreateDumpingStateForMenuListIW5(zoneState, menuListAsset->Asset()); } + const auto menuFilePath = GetPathForMenu(zoneState, asset); const auto assetFile = context.OpenAssetFile(menuFilePath); if (!assetFile) return; - auto menuWriter = CreateMenuWriterIW5(*assetFile); + const auto menuWriter = CreateMenuWriterIW5(*assetFile); menuWriter->Start(); menuWriter->WriteMenu(*menu); diff --git a/src/ObjWriting/Game/IW5/Menu/MenuListDumperIW5.cpp b/src/ObjWriting/Game/IW5/Menu/MenuListDumperIW5.cpp index 20d333aa..1c2f8b39 100644 --- a/src/ObjWriting/Game/IW5/Menu/MenuListDumperIW5.cpp +++ b/src/ObjWriting/Game/IW5/Menu/MenuListDumperIW5.cpp @@ -8,8 +8,7 @@ #include #include #include -#include -#include +#include namespace fs = std::filesystem; @@ -20,24 +19,24 @@ namespace std::vector GetAllUniqueExpressionSupportingData(const MenuList* menuList) { std::vector result; - std::set alreadyAddedSupportingData; + std::unordered_set alreadyAddedSupportingData; - if (menuList->menus == nullptr) + if (!menuList->menus) return result; for (auto i = 0; i < menuList->menuCount; i++) { - if (menuList->menus[i] == nullptr) + if (!menuList->menus[i]) continue; const auto* menu = menuList->menus[i]; - if (menu->data == nullptr || menu->data->expressionData == nullptr) + if (!menu->data || !menu->data->expressionData) continue; - if (alreadyAddedSupportingData.find(menu->data->expressionData) == alreadyAddedSupportingData.end()) + if (!alreadyAddedSupportingData.contains(menu->data->expressionData)) { - result.push_back(menu->data->expressionData); + result.emplace_back(menu->data->expressionData); alreadyAddedSupportingData.emplace(menu->data->expressionData); } } @@ -60,7 +59,7 @@ namespace for (auto i = 0; i < supportingData->uifunctions.totalFunctions; i++) { const auto* function = supportingData->uifunctions.functions[i]; - if (function == nullptr) + if (!function) continue; menuDumper.WriteFunctionDef(std::format("FUNC_{}", functionIndex), function); @@ -70,42 +69,85 @@ namespace } } - void DumpMenus(menu::IWriterIW5& menuDumper, const MenuList* menuList) + void DumpMenus(menu::IWriterIW5& menuDumper, menu::MenuDumpingZoneState* zoneState, 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]; + if (!menu) + continue; + + const auto menuDumpingState = zoneState->m_menu_dumping_state_map.find(menu); + if (menuDumpingState == zoneState->m_menu_dumping_state_map.end()) + continue; + 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(); + const auto isReference = menuAssetName && menuAssetName[0] == ','; // If the menu was embedded directly as menu list write its data in the menu list file - if (!isReference && menuName == menuList->name) + if (!isReference && menuDumpingState->second.m_alias_menu_list == menuList) menuDumper.WriteMenu(*menu); else - menuDumper.IncludeMenu(ss.str()); + 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]; + + return std::format("{}{}.menu", menuListParentPath, menuAssetName); + } } // namespace namespace menu { + void CreateDumpingStateForMenuListIW5(MenuDumpingZoneState* zoneState, const MenuList* menuList) + { + if (!menuList || menuList->menuCount <= 0 || !menuList->menus || !menuList->name) + return; + + const std::string menuListName(menuList->name); + const fs::path p(menuListName); + std::string parentPath; + if (p.has_parent_path()) + parentPath = p.parent_path().generic_string() + "/"; + + for (auto i = 0; i < menuList->menuCount; i++) + { + const auto* menu = menuList->menus[i]; + + if (!menu) + continue; + + auto menuPath = PathForMenu(parentPath, menu); + if (menuPath.empty()) + continue; + + auto existingState = zoneState->m_menu_dumping_state_map.find(menu); + if (existingState == zoneState->m_menu_dumping_state_map.end()) + { + const auto isTheSameAsMenuList = menuPath == menuListName; + zoneState->CreateMenuDumpingState(menu, std::move(menuPath), isTheSameAsMenuList ? menuList : nullptr); + } + else if (!existingState->second.m_alias_menu_list) + { + const auto isTheSameAsMenuList = menuPath == menuListName; + if (isTheSameAsMenuList) + { + existingState->second.m_alias_menu_list = menuList; + existingState->second.m_path = std::move(menuPath); + } + } + } + } + void MenuListDumperIW5::DumpAsset(AssetDumpingContext& context, const XAssetInfo& asset) { const auto* menuList = asset.Asset(); @@ -114,15 +156,25 @@ namespace menu if (!assetFile) return; - auto menuWriter = CreateMenuWriterIW5(*assetFile); + auto* zoneState = context.GetZoneAssetDumperState(); + + const auto menuWriter = CreateMenuWriterIW5(*assetFile); menuWriter->Start(); - if (!ObjWriting::Configuration.MenuLegacyMode) DumpFunctions(*menuWriter, menuList); - - DumpMenus(*menuWriter, menuList); - + DumpMenus(*menuWriter, zoneState, menuList); menuWriter->End(); } + + void MenuListDumperIW5::Dump(AssetDumpingContext& context) + { + auto* zoneState = context.GetZoneAssetDumperState(); + + const auto menuListAssets = context.m_zone.m_pools.PoolAssets(); + for (const auto* asset : menuListAssets) + CreateDumpingStateForMenuListIW5(zoneState, asset->Asset()); + + AbstractAssetDumper::Dump(context); + } } // namespace menu diff --git a/src/ObjWriting/Game/IW5/Menu/MenuListDumperIW5.h b/src/ObjWriting/Game/IW5/Menu/MenuListDumperIW5.h index 1ec80a50..a65737a7 100644 --- a/src/ObjWriting/Game/IW5/Menu/MenuListDumperIW5.h +++ b/src/ObjWriting/Game/IW5/Menu/MenuListDumperIW5.h @@ -2,11 +2,17 @@ #include "Dumping/AbstractAssetDumper.h" #include "Game/IW5/IW5.h" +#include "Menu/MenuDumpingZoneState.h" namespace menu { + void CreateDumpingStateForMenuListIW5(MenuDumpingZoneState* zoneState, const IW5::MenuList* menuList); + class MenuListDumperIW5 final : public AbstractAssetDumper { + public: + void Dump(AssetDumpingContext& context) override; + protected: void DumpAsset(AssetDumpingContext& context, const XAssetInfo& asset) override; }; diff --git a/src/ObjWriting/Game/IW5/Menu/MenuWriterIW5.cpp b/src/ObjWriting/Game/IW5/Menu/MenuWriterIW5.cpp index 72a9307c..6d077721 100644 --- a/src/ObjWriting/Game/IW5/Menu/MenuWriterIW5.cpp +++ b/src/ObjWriting/Game/IW5/Menu/MenuWriterIW5.cpp @@ -6,34 +6,29 @@ #include #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)); + // Set this to true to skip interpretative expression dumping + constexpr auto DUMP_NAIVE = false; - const auto statementEnd = static_cast(statement->numEntries); + 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]; + const auto& expEntry = statement.entries[currentSearchPosition]; if (expEntry.type != EET_OPERATOR) continue; @@ -78,9 +73,7 @@ namespace void WriteMenu(const menuDef_t& menu) override { StartMenuDefScope(); - - WriteMenuData(&menu); - + WriteMenuData(menu); EndScope(); } @@ -100,12 +93,22 @@ namespace } private: - void WriteStatementNaive(const Statement_s* statement) const + static bool HasStatement(const Statement_s* statement) { - const auto entryCount = static_cast(statement->numEntries); + return statement && statement->numEntries > 0 && statement->entries; + } + + 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]; + const auto& entry = statement.entries[i]; if (entry.type == EET_OPERAND) { size_t pos = i; @@ -142,14 +145,14 @@ namespace if (closingParenPos - i + 1u >= 1u) { - const auto& staticDvarEntry = statement->entries[i + 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 + if (statement.supportingData && statement.supportingData->staticDvarList.staticDvars && staticDvarEntry.data.operand.internals.intVal >= 0 - && staticDvarEntry.data.operand.internals.intVal < statement->supportingData->staticDvarList.numStaticDvars) + && staticDvarEntry.data.operand.internals.intVal < statement.supportingData->staticDvarList.numStaticDvars) { - const auto* staticDvar = statement->supportingData->staticDvarList.staticDvars[staticDvarEntry.data.operand.internals.intVal]; + const auto* staticDvar = statement.supportingData->staticDvarList.staticDvars[staticDvarEntry.data.operand.internals.intVal]; if (staticDvar && staticDvar->dvarName) m_stream << staticDvar->dvarName; } @@ -178,9 +181,9 @@ namespace } } - void WriteStatementOperator(const Statement_s* statement, size_t& currentPos, bool& spaceNext) const + void WriteStatementOperator(const Statement_s& statement, size_t& currentPos, bool& spaceNext) const { - const auto& expEntry = statement->entries[currentPos]; + const auto& expEntry = statement.entries[currentPos]; if (spaceNext && expEntry.data.op != OP_COMMA) m_stream << " "; @@ -225,14 +228,14 @@ namespace if (closingParenPos - currentPos + 1 >= 1) { - const auto& staticDvarEntry = statement->entries[currentPos + 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 + if (statement.supportingData && statement.supportingData->staticDvarList.staticDvars && staticDvarEntry.data.operand.internals.intVal >= 0 - && staticDvarEntry.data.operand.internals.intVal < statement->supportingData->staticDvarList.numStaticDvars) + && staticDvarEntry.data.operand.internals.intVal < statement.supportingData->staticDvarList.numStaticDvars) { - const auto* staticDvar = statement->supportingData->staticDvarList.staticDvars[staticDvarEntry.data.operand.internals.intVal]; + const auto* staticDvar = statement.supportingData->staticDvarList.staticDvars[staticDvarEntry.data.operand.internals.intVal]; if (staticDvar && staticDvar->dvarName) m_stream << staticDvar->dvarName; } @@ -272,22 +275,22 @@ namespace } } - void WriteStatementOperandFunction(const Statement_s* statement, size_t currentPos) const + void WriteStatementOperandFunction(const Statement_s& statement, const size_t currentPos) const { - const auto& operand = statement->entries[currentPos].data.operand; + const auto& operand = statement.entries[currentPos].data.operand; - if (operand.internals.function == nullptr) + if (!operand.internals.function) return; if (!ObjWriting::Configuration.MenuLegacyMode) { int functionIndex = -1; - if (statement->supportingData && statement->supportingData->uifunctions.functions) + if (statement.supportingData && statement.supportingData->uifunctions.functions) { - for (auto supportingFunctionIndex = 0; supportingFunctionIndex < statement->supportingData->uifunctions.totalFunctions; + for (auto supportingFunctionIndex = 0; supportingFunctionIndex < statement.supportingData->uifunctions.totalFunctions; supportingFunctionIndex++) { - if (statement->supportingData->uifunctions.functions[supportingFunctionIndex] == operand.internals.function) + if (statement.supportingData->uifunctions.functions[supportingFunctionIndex] == operand.internals.function) { functionIndex = supportingFunctionIndex; break; @@ -304,14 +307,14 @@ namespace else { m_stream << "("; - WriteStatementSkipInitialUnnecessaryParenthesis(operand.internals.function); + WriteStatementSkipInitialUnnecessaryParenthesis(*operand.internals.function); m_stream << ")"; } } - void WriteStatementOperand(const Statement_s* statement, size_t& currentPos, bool& spaceNext) const + void WriteStatementOperand(const Statement_s& statement, size_t& currentPos, bool& spaceNext) const { - const auto& expEntry = statement->entries[currentPos]; + const auto& expEntry = statement.entries[currentPos]; if (spaceNext) m_stream << " "; @@ -344,16 +347,16 @@ namespace spaceNext = true; } - void WriteStatementEntryRange(const Statement_s* statement, size_t startOffset, size_t endOffset) const + void WriteStatementEntryRange(const Statement_s& statement, const size_t startOffset, const size_t endOffset) const { assert(startOffset <= endOffset); - assert(endOffset <= static_cast(statement->numEntries)); + assert(endOffset <= static_cast(statement.numEntries)); auto currentPos = startOffset; auto spaceNext = false; while (currentPos < endOffset) { - const auto& expEntry = statement->entries[currentPos]; + const auto& expEntry = statement.entries[currentPos]; if (expEntry.type == EET_OPERATOR) { @@ -366,41 +369,41 @@ namespace } } - void WriteStatement(const Statement_s* statement) const + void WriteStatement(const Statement_s& statement) const { - if (statement == nullptr || statement->numEntries < 0) + if (!statement.entries || statement.numEntries < 0) return; - WriteStatementEntryRange(statement, 0, static_cast(statement->numEntries)); + WriteStatementEntryRange(statement, 0, static_cast(statement.numEntries)); } - void WriteStatementSkipInitialUnnecessaryParenthesis(const Statement_s* statementValue) const + void WriteStatementSkipInitialUnnecessaryParenthesis(const Statement_s& statement) const { - if (statementValue == nullptr || statementValue->numEntries < 0) + if (!statement.entries || statement.numEntries < 0) return; - const auto statementEnd = static_cast(statementValue->numEntries); + const auto statementEnd = static_cast(statement.numEntries); - if (statementValue->numEntries >= 1 && statementValue->entries[0].type == EET_OPERATOR && statementValue->entries[0].data.op == OP_LEFTPAREN) + if (statement.numEntries >= 1 && statement.entries[0].type == EET_OPERATOR && statement.entries[0].data.op == OP_LEFTPAREN) { - const auto parenthesisEnd = FindStatementClosingParenthesis(statementValue, 0); + const auto parenthesisEnd = FindStatementClosingParenthesis(statement, 0); if (parenthesisEnd >= statementEnd) - WriteStatementEntryRange(statementValue, 1, statementEnd); + WriteStatementEntryRange(statement, 1, statementEnd); else if (parenthesisEnd == statementEnd - 1) - WriteStatementEntryRange(statementValue, 1, statementEnd - 1); + WriteStatementEntryRange(statement, 1, statementEnd - 1); else - WriteStatementEntryRange(statementValue, 0, statementEnd); + WriteStatementEntryRange(statement, 0, statementEnd); } else { - WriteStatementEntryRange(statementValue, 0, statementEnd); + WriteStatementEntryRange(statement, 0, statementEnd); } } - void WriteStatementProperty(const std::string& propertyKey, const Statement_s* statementValue, bool isBooleanStatement) const + void WriteStatementProperty(const std::string& propertyKey, const Statement_s* statement, const bool isBooleanStatement) const { - if (statementValue == nullptr || statementValue->numEntries < 0) + if (!HasStatement(statement)) return; Indent(); @@ -409,24 +412,30 @@ namespace if (isBooleanStatement) { m_stream << "when("; - DUMP_FUNC(statementValue); + if constexpr (DUMP_NAIVE) + WriteStatementNaive(*statement); + else + WriteStatementSkipInitialUnnecessaryParenthesis(*statement); m_stream << ");\n"; } else { - DUMP_FUNC(statementValue); + if constexpr (DUMP_NAIVE) + WriteStatementNaive(*statement); + else + WriteStatement(*statement); m_stream << ";\n"; } } void WriteSetLocalVarData(const std::string& setFunction, const SetLocalVarData* setLocalVarData) const { - if (setLocalVarData == nullptr) + if (!setLocalVarData || !setLocalVarData->expression) return; Indent(); m_stream << setFunction << " " << setLocalVarData->localVarName << " "; - WriteStatement(setLocalVarData->expression); + WriteStatement(*setLocalVarData->expression); m_stream << ";\n"; } @@ -448,7 +457,6 @@ namespace { if (token == ";") continue; - Indent(); } @@ -465,7 +473,7 @@ namespace isNewStatement = false; if (DoesTokenNeedQuotationMarks(token)) - m_stream << "\"" << token << "\""; + WriteEscapedString(token); else m_stream << token; } @@ -480,10 +488,10 @@ namespace m_stream << "{\n"; IncIndent(); - for (auto i = 0; i < eventHandlerSet->eventHandlerCount; i++) + for (auto eventHandlerIndex = 0; eventHandlerIndex < eventHandlerSet->eventHandlerCount; eventHandlerIndex++) { - const auto* eventHandler = eventHandlerSet->eventHandlers[i]; - if (eventHandler == nullptr) + const auto* eventHandler = eventHandlerSet->eventHandlers[eventHandlerIndex]; + if (!eventHandler) continue; switch (eventHandler->eventType) @@ -493,21 +501,21 @@ namespace break; case EVENT_IF: - if (eventHandler->eventData.conditionalScript == nullptr || eventHandler->eventData.conditionalScript->eventExpression == nullptr - || eventHandler->eventData.conditionalScript->eventHandlerSet == nullptr) + if (!eventHandler->eventData.conditionalScript || !eventHandler->eventData.conditionalScript->eventExpression + || !eventHandler->eventData.conditionalScript->eventHandlerSet) { continue; } Indent(); m_stream << "if ("; - WriteStatementSkipInitialUnnecessaryParenthesis(eventHandler->eventData.conditionalScript->eventExpression); + WriteStatementSkipInitialUnnecessaryParenthesis(*eventHandler->eventData.conditionalScript->eventExpression); m_stream << ")\n"; WriteMenuEventHandlerSet(eventHandler->eventData.conditionalScript->eventHandlerSet); break; case EVENT_ELSE: - if (eventHandler->eventData.elseScript == nullptr) + if (!eventHandler->eventData.elseScript) continue; Indent(); @@ -543,7 +551,7 @@ namespace void WriteMenuEventHandlerSetProperty(const std::string& propertyKey, const MenuEventHandlerSet* eventHandlerSetValue) { - if (eventHandlerSetValue == nullptr) + if (!eventHandlerSetValue) return; Indent(); @@ -559,51 +567,44 @@ namespace << static_cast(rect.vertAlign) << "\n"; } - void WriteMaterialProperty(const std::string& propertyKey, const Material* materialValue) const + void WriteMaterialProperty(const std::string& propertyKey, const Material* material) const { - if (materialValue == nullptr || materialValue->info.name == nullptr) + if (!material || !material->info.name) return; - if (materialValue->info.name[0] == ',') - WriteStringProperty(propertyKey, &materialValue->info.name[1]); - else - WriteStringProperty(propertyKey, materialValue->info.name); + const auto* materialName = material->info.name; + if (materialName[0] == ',') + materialName++; + WriteStringProperty(propertyKey, materialName); } - void WriteSoundAliasProperty(const std::string& propertyKey, const snd_alias_list_t* soundAliasValue) const + void WriteSoundAliasProperty(const std::string& propertyKey, const snd_alias_list_t* soundAlias) const { - if (soundAliasValue == nullptr) - return; - - WriteStringProperty(propertyKey, soundAliasValue->aliasName); + if (soundAlias) + WriteStringProperty(propertyKey, soundAlias->aliasName); } - void WriteDecodeEffectProperty(const std::string& propertyKey, const itemDef_s* item) const + void WriteDecodeEffectProperty(const std::string& propertyKey, const itemDef_s& item) const { - if (!item->decayActive) + if (!item.decayActive) return; Indent(); WriteKey(propertyKey); - m_stream << item->fxLetterTime << " " << item->fxDecayStartTime << " " << item->fxDecayDuration << "\n"; + m_stream << item.fxLetterTime << " " << item.fxDecayStartTime << " " << item.fxDecayDuration << "\n"; } - void WriteItemKeyHandlerProperty(const ItemKeyHandler* itemKeyHandlerValue) + void WriteItemKeyHandlers(const ItemKeyHandler* handler) { - for (const auto* currentHandler = itemKeyHandlerValue; currentHandler; currentHandler = currentHandler->next) + for (const auto* current = handler; current; current = current->next) { - if (currentHandler->key >= '!' && currentHandler->key <= '~' && currentHandler->key != '"') - { - std::ostringstream ss; - ss << "execKey \"" << static_cast(currentHandler->key) << "\""; - WriteMenuEventHandlerSetProperty(ss.str(), currentHandler->action); - } + std::string key; + if (current->key >= '!' && current->key <= '~' && current->key != '"') + key = std::format("execKey \"{}\"", static_cast(current->key)); else - { - std::ostringstream ss; - ss << "execKeyInt " << currentHandler->key; - WriteMenuEventHandlerSetProperty(ss.str(), currentHandler->action); - } + key = std::format("execKeyInt {}", current->key); + + WriteMenuEventHandlerSetProperty(key, current->action); } } @@ -614,18 +615,18 @@ namespace Indent(); WriteKey(propertyKey); + m_stream << "{ "; 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 << "\""; + + WriteEscapedString(token); } if (!firstToken) m_stream << " "; @@ -651,59 +652,63 @@ namespace } } - void WriteColumnProperty(const std::string& propertyKey, const listBoxDef_s* listBox) const + void WriteColumnProperty(const listBoxDef_s& listBox) const { - if (listBox->numColumns <= 0) + if (listBox.numColumns <= 0) return; Indent(); - WriteKey(propertyKey); - m_stream << listBox->numColumns << "\n"; + WriteKey("columns"); + m_stream << listBox.numColumns << "\n"; - for (auto col = 0; col < listBox->numColumns; col++) + const auto columnCount = std::min(listBox.numColumns, std::size(listBox.columnInfo)); + for (size_t columnIndex = 0u; columnIndex < columnCount; columnIndex++) { + const auto& column = listBox.columnInfo[columnIndex]; + 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"; + m_stream << column.xpos << " " << column.ypos << " " << column.width << " " << column.height << " " << column.maxChars << " " + << column.alignment << "\n"; } } - void WriteListBoxProperties(const itemDef_s* item) + void WriteListBoxProperties(const itemDef_s& item) { - if (item->type != ITEM_TYPE_LISTBOX || item->typeData.listBox == nullptr) + if (item.type != ITEM_TYPE_LISTBOX || !item.typeData.listBox) 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); + 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(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 + void WriteDvarFloatProperty(const itemDef_s& item, const editFieldDef_s& editField) const { - if (item->dvar == nullptr) + if (!item.dvar) return; Indent(); - WriteKey(propertyKey); - m_stream << "\"" << item->dvar << "\" " << editField->stepVal << " " << editField->minVal << " " << editField->maxVal << "\n"; + WriteKey("dvarFloat"); + WriteEscapedString(item.dvar); + m_stream << " " << editField.stepVal << " " << editField.minVal << " " << editField.maxVal << "\n"; } - void WriteEditFieldProperties(const itemDef_s* item) const + void WriteEditFieldProperties(const itemDef_s& item) const { - switch (item->type) + switch (item.type) { case ITEM_TYPE_TEXT: case ITEM_TYPE_EDITFIELD: @@ -722,155 +727,160 @@ namespace return; } - if (item->typeData.editField == nullptr) + if (!item.typeData.editField) 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()) + 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); + WriteDvarFloatProperty(item, editField); } else { - WriteStringProperty("dvar", item->dvar); + WriteStringProperty("dvar", item.dvar); } - WriteStringProperty("localvar", item->localVar); - WriteIntProperty("maxChars", editField->maxChars, 0); - WriteKeywordProperty("maxCharsGotoNext", editField->maxCharsGotoNext != 0); - WriteIntProperty("maxPaintChars", editField->maxPaintChars, 0); + 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 + void WriteMultiValueProperty(const multiDef_s& multi) const { Indent(); - if (multiDef->strDef) - WriteKey("dvarStrList"); - else - WriteKey("dvarFloatList"); - + WriteKey(multi.strDef ? "dvarStrList" : "dvarFloatList"); m_stream << "{"; - for (auto i = 0; i < multiDef->count; i++) + const auto valueCount = std::min(multi.count, std::size(multi.dvarValue)); + for (size_t valueIndex = 0u; valueIndex < valueCount; valueIndex++) { - if (multiDef->dvarList[i] == nullptr || multiDef->strDef && multiDef->dvarStr[i] == nullptr) + if (!multi.dvarList[valueIndex] || (multi.strDef && !multi.dvarStr[valueIndex])) continue; - m_stream << " \"" << multiDef->dvarList[i] << "\""; - - if (multiDef->strDef) - m_stream << " \"" << multiDef->dvarStr[i] << "\""; + m_stream << " "; + WriteEscapedString(multi.dvarList[valueIndex]); + m_stream << " "; + if (multi.strDef) + WriteEscapedString(multi.dvarStr[valueIndex]); else - m_stream << " " << multiDef->dvarValue[i] << ""; + m_stream << multi.dvarValue[valueIndex]; } m_stream << " }\n"; } - void WriteMultiProperties(const itemDef_s* item) const + void WriteMultiProperties(const itemDef_s& item) const { - if (item->type != ITEM_TYPE_MULTI || item->typeData.multi == nullptr) + if (item.type != ITEM_TYPE_MULTI || !item.typeData.multi) return; - const auto* multiDef = item->typeData.multi; - - if (multiDef->count <= 0) - return; - - WriteStringProperty("dvar", item->dvar); - WriteStringProperty("localvar", item->localVar); - WriteMultiValueProperty(multiDef); + WriteStringProperty("dvar", item.dvar); + WriteStringProperty("localvar", item.localVar); + WriteMultiValueProperty(*item.typeData.multi); } - void WriteEnumDvarProperties(const itemDef_s* item) const + void WriteEnumDvarProperties(const itemDef_s& item) const { - if (item->type != ITEM_TYPE_DVARENUM) + if (item.type != ITEM_TYPE_DVARENUM) return; - WriteStringProperty("dvar", item->dvar); - WriteStringProperty("localvar", item->localVar); - WriteStringProperty("dvarEnumList", item->typeData.enumDvarName); + WriteStringProperty("dvar", item.dvar); + WriteStringProperty("localvar", item.localVar); + WriteStringProperty("dvarEnumList", item.typeData.enumDvarName); } - void WriteTickerProperties(const itemDef_s* item) const + void WriteTickerProperties(const itemDef_s& item) const { - if (item->type != ITEM_TYPE_NEWS_TICKER || item->typeData.ticker == nullptr) + if (item.type != ITEM_TYPE_NEWS_TICKER || !item.typeData.ticker) return; - const auto* newsTickerDef = item->typeData.ticker; + 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) + void WriteItemTextProperty(const char* text) const { - 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); + // This distinguishes explicitly empty text from null text, which falls back to the item's dvar. + if (!text) + return; - if (item->visibleExp) - WriteStatementProperty("visible", item->visibleExp, true); - else if (item->window.dynamicFlags[0] & WINDOW_FLAG_VISIBLE) + Indent(); + WriteKey("text"); + WriteEscapedString(text); + m_stream << "\n"; + } + + void WriteItemData(const itemDef_s& item) + { + WriteStringProperty("name", item.window.name); + WriteItemTextProperty(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 (HasStatement(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); + 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.onAccept); + // 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); + 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); + WriteItemKeyHandlers(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); @@ -880,70 +890,75 @@ namespace WriteTickerProperties(item); } - void WriteItemDefs(const itemDef_s* const* itemDefs, size_t itemCount) + void WriteItemDefs(const itemDef_s* const* items, const size_t itemCount) { - for (auto i = 0u; i < itemCount; i++) + if (!items || itemCount <= 0) + return; + + for (size_t itemIndex = 0u; itemIndex < itemCount; itemIndex++) { + const auto* item = items[itemIndex]; + if (!item) + continue; + StartItemDefScope(); - - WriteItemData(itemDefs[i]); - + WriteItemData(*item); EndScope(); } } - void WriteMenuData(const menuDef_t* menu) + 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); + 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) + if (HasStatement(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); + 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); + WriteItemKeyHandlers(menu.data->onKey); + WriteItemDefs(menu.items, menu.itemCount); } }; } // namespace