#include "MenuConverterIW4.h" #include "Menu/AbstractMenuConverter.h" #include "MenuConversionZoneStateIW4.h" #include "Parsing/Menu/Domain/EventHandler/CommonEventHandlerCondition.h" #include "Parsing/Menu/Domain/EventHandler/CommonEventHandlerScript.h" #include "Parsing/Menu/Domain/EventHandler/CommonEventHandlerSetLocalVar.h" #include "Parsing/Menu/Domain/Expression/CommonExpressionBaseFunctionCall.h" #include "Parsing/Menu/Domain/Expression/CommonExpressionCustomFunctionCall.h" #include "Parsing/Menu/MenuAssetZoneState.h" #include "Parsing/Simple/Expression/SimpleExpressionBinaryOperation.h" #include "Parsing/Simple/Expression/SimpleExpressionConditionalOperator.h" #include "Parsing/Simple/Expression/SimpleExpressionUnaryOperation.h" #include "Utils/ClassUtils.h" #include "Utils/StringUtils.h" #include #include #include #include using namespace IW4; using namespace menu; namespace { class MenuConverter : 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), }; } [[nodiscard]] static rectDef_s ConvertRectDefRelativeTo(const CommonRect& rect, const CommonRect& rectRelativeTo) { return rectDef_s{ static_cast(rectRelativeTo.x + rect.x), static_cast(rectRelativeTo.y + rect.y), static_cast(rect.w), static_cast(rect.h), static_cast(rect.horizontalAlign), static_cast(rect.verticalAlign), }; } static void ConvertColor(float (&output)[4], const CommonColor& input) { output[0] = static_cast(input.r); output[1] = static_cast(input.g); output[2] = static_cast(input.b); output[3] = static_cast(input.a); } static void ApplyFlag(int& flags, const bool shouldApply, const int flagValue) { if (!shouldApply) return; 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()) return nullptr; auto* materialDependency = m_context.LoadDependency(materialName); if (!materialDependency) throw MenuConversionException(std::format("Failed to load material \"{}\"", materialName), menu, item); return materialDependency->Asset(); } [[nodiscard]] snd_alias_list_t* ConvertSound(const std::string& soundName, const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const { if (soundName.empty()) return nullptr; auto* soundDependency = m_context.LoadDependency(soundName); if (!soundDependency) throw MenuConversionException(std::format("Failed to load sound \"{}\"", soundName), menu, item); 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)]{ OP_NOT, OP_BITWISENOT, OP_SUBTRACT, }; 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)]{ OP_ADD, OP_SUBTRACT, OP_MULTIPLY, OP_DIVIDE, OP_MODULUS, OP_BITWISEAND, OP_BITWISEOR, OP_BITSHIFTLEFT, OP_BITSHIFTRIGHT, OP_GREATERTHAN, OP_GREATERTHANEQUALTO, OP_LESSTHAN, OP_LESSTHANEQUALTO, OP_EQUALS, OP_NOTEQUAL, OP_AND, OP_OR, }; void ConvertExpressionEntryBinaryOperation(Statement_s* gameStatement, std::vector& entries, const SimpleExpressionBinaryOperation* binaryOperation, 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); 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); // 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); } void ConvertExpressionEntryExpressionValue(std::vector& entries, const SimpleExpressionValue* expressionValue) const { expressionEntry entry{}; entry.type = EET_OPERAND; if (expressionValue->m_type == 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.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.dataType = VAL_STRING; entry.data.operand.internals.stringVal.string = m_conversion_zone_state->AddString(*expressionValue->m_string_value); } entries.emplace_back(entry); } void ConvertExpressionEntry(Statement_s* gameStatement, std::vector& entries, const ISimpleExpression* expression, const CommonMenuDef* menu, const CommonItemDef* item) const { if (!m_disable_optimizations && expression->IsStatic()) { const auto expressionStaticValue = expression->EvaluateStatic(); ConvertExpressionEntryExpressionValue(entries, &expressionStaticValue); } else if (const auto* expressionValue = dynamic_cast(expression)) { ConvertExpressionEntryExpressionValue(entries, expressionValue); } else if (const auto* binaryOperation = dynamic_cast(expression)) { ConvertExpressionEntryBinaryOperation(gameStatement, entries, binaryOperation, menu, item); } else if (const auto* unaryOperation = dynamic_cast(expression)) { ConvertExpressionEntryUnaryOperation(gameStatement, entries, unaryOperation, menu, item); } else if (const auto* baseFunctionCall = dynamic_cast(expression)) { ConvertExpressionEntryBaseFunctionCall(gameStatement, entries, baseFunctionCall, menu, item); } else if (const auto* customFunctionCall = dynamic_cast(expression)) { ConvertExpressionEntryCustomFunctionCall(gameStatement, entries, customFunctionCall, menu, item); } else if (dynamic_cast(expression)) { throw MenuConversionException("Cannot use conditional expression in menu expressions", menu, item); } else { assert(false); throw MenuConversionException("Unknown expression entry type in menu expressions", menu, item); } } _NODISCARD Statement_s* ConvertExpression(const ISimpleExpression* expression, const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const { if (!expression) 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 expressionEntries; ConvertExpressionEntry(statement, expressionEntries, expression, menu, item); auto* outputExpressionEntries = m_memory.Alloc(expressionEntries.size()); memcpy(outputExpressionEntries, expressionEntries.data(), sizeof(expressionEntry) * expressionEntries.size()); statement->entries = outputExpressionEntries; statement->numEntries = static_cast(expressionEntries.size()); return statement; } _NODISCARD Statement_s* ConvertOrApplyStatement(float& staticValue, const ISimpleExpression* expression, const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const { if (m_disable_optimizations) return ConvertExpression(expression, menu, item); if (!expression) return nullptr; if (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: staticValue = static_cast(value.m_int_value); break; case SimpleExpressionValue::Type::STRING: throw MenuConversionException("Cannot convert string expression value to floating point", menu, item); } return nullptr; } return ConvertExpression(expression, menu, item); } _NODISCARD Statement_s* ConvertOrApplyStatement(const char*& staticValue, const ISimpleExpression* expression, const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const { if (m_disable_optimizations) return ConvertExpression(expression, menu, item); if (!expression) return nullptr; if (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: throw MenuConversionException("Cannot convert numeric expression value to string", menu, item); } return nullptr; } return ConvertExpression(expression, menu, item); } _NODISCARD Statement_s* ConvertOrApplyStatement(Material*& staticValue, const ISimpleExpression* expression, const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const { if (m_disable_optimizations) return ConvertExpression(expression, menu, item); if (!expression) return nullptr; if (expression->IsStatic()) { const auto value = expression->EvaluateStatic(); switch (value.m_type) { case SimpleExpressionValue::Type::STRING: staticValue = ConvertMaterial(*value.m_string_value, menu, item); break; 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, const ISimpleExpression* expression, const CommonMenuDef* commonMenu, const CommonItemDef* commonItem = nullptr) const { if (expression == nullptr) return nullptr; bool isStatic; bool isTruthy; if (m_disable_optimizations) { const auto* staticValue = dynamic_cast(expression); isStatic = staticValue != nullptr; isTruthy = isStatic && (staticValue->m_type == SimpleExpressionValue::Type::INT || staticValue->m_type == SimpleExpressionValue::Type::DOUBLE) && staticValue->IsTruthy(); } else { isStatic = expression->IsStatic(); isTruthy = isStatic && expression->EvaluateStatic().IsTruthy(); } if (isStatic) { if (isTruthy) window->dynamicFlags[0] |= WINDOW_FLAG_VISIBLE; return nullptr; } window->dynamicFlags[0] |= WINDOW_FLAG_VISIBLE; return ConvertExpression(expression, commonMenu, commonItem); } _NODISCARD static EventType SetLocalVarTypeToEventType(const SetLocalVarType setLocalVarType) { switch (setLocalVarType) { case SetLocalVarType::BOOL: return EVENT_SET_LOCAL_VAR_BOOL; case SetLocalVarType::STRING: return EVENT_SET_LOCAL_VAR_STRING; case SetLocalVarType::FLOAT: return EVENT_SET_LOCAL_VAR_FLOAT; case SetLocalVarType::INT: return EVENT_SET_LOCAL_VAR_INT; default: case SetLocalVarType::UNKNOWN: assert(false); return EVENT_SET_LOCAL_VAR_INT; } } void ConvertEventHandlerSetLocalVar(std::vector& elements, const CommonEventHandlerSetLocalVar* setLocalVar, const CommonMenuDef* menu, const CommonItemDef* item) const { assert(setLocalVar); if (!setLocalVar) return; auto* outputHandler = m_memory.Alloc(); auto* outputSetLocalVar = m_memory.Alloc(); outputHandler->eventType = SetLocalVarTypeToEventType(setLocalVar->m_type); outputHandler->eventData.setLocalVarData = outputSetLocalVar; outputSetLocalVar->localVarName = m_memory.Dup(setLocalVar->m_var_name.c_str()); outputSetLocalVar->expression = ConvertExpression(setLocalVar->m_value.get(), menu, item); elements.push_back(outputHandler); } void ConvertEventHandlerScript(std::vector& elements, const CommonEventHandlerScript* script) const { assert(script); if (!script) return; auto* outputHandler = m_memory.Alloc(); outputHandler->eventType = EVENT_UNCONDITIONAL; outputHandler->eventData.unconditionalScript = m_memory.Dup(script->m_script.c_str()); elements.push_back(outputHandler); } void ConvertEventHandlerCondition(std::vector& elements, const CommonEventHandlerCondition* condition, const CommonMenuDef* menu, const CommonItemDef* item) const { assert(condition); if (!condition || !condition->m_condition) return; if (!m_disable_optimizations && condition->m_condition->IsStatic()) { const auto staticValueIsTruthy = condition->m_condition->EvaluateStatic().IsTruthy(); if (staticValueIsTruthy) ConvertEventHandlerElements(elements, condition->m_condition_elements.get(), menu, item); else if (condition->m_else_elements) ConvertEventHandlerElements(elements, condition->m_else_elements.get(), menu, item); } else { auto* outputHandler = m_memory.Alloc(); auto* outputCondition = m_memory.Alloc(); outputHandler->eventType = EVENT_IF; outputHandler->eventData.conditionalScript = outputCondition; outputCondition->eventExpression = ConvertExpression(condition->m_condition.get(), menu, item); outputCondition->eventHandlerSet = ConvertEventHandlerSet(condition->m_condition_elements.get(), menu, item); elements.push_back(outputHandler); if (condition->m_else_elements) { auto* outputElseHandler = m_memory.Alloc(); outputElseHandler->eventType = EVENT_ELSE; outputElseHandler->eventData.elseScript = ConvertEventHandlerSet(condition->m_else_elements.get(), menu, item); elements.push_back(outputElseHandler); } } } void ConvertEventHandler(std::vector& elements, const ICommonEventHandlerElement* eventHandler, const CommonMenuDef* menu, const CommonItemDef* item) const { assert(eventHandler); if (!eventHandler) return; switch (eventHandler->GetType()) { case CommonEventHandlerElementType::CONDITION: ConvertEventHandlerCondition(elements, dynamic_cast(eventHandler), menu, item); break; case CommonEventHandlerElementType::SCRIPT: ConvertEventHandlerScript(elements, dynamic_cast(eventHandler)); break; case CommonEventHandlerElementType::SET_LOCAL_VAR: ConvertEventHandlerSetLocalVar(elements, dynamic_cast(eventHandler), menu, item); break; } } void ConvertEventHandlerElements(std::vector& elements, const CommonEventHandlerSet* eventHandlerSet, const CommonMenuDef* menu, const CommonItemDef* item) const { for (const auto& element : eventHandlerSet->m_elements) ConvertEventHandler(elements, element.get(), menu, item); } _NODISCARD MenuEventHandlerSet* ConvertEventHandlerSet(const CommonEventHandlerSet* eventHandlerSet, const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const { if (!eventHandlerSet) return nullptr; std::vector elements; ConvertEventHandlerElements(elements, eventHandlerSet, menu, item); if (elements.empty()) return nullptr; auto* outputSet = m_memory.Alloc(); auto* outputElements = m_memory.Alloc(elements.size()); memcpy(outputElements, elements.data(), sizeof(void*) * elements.size()); outputSet->eventHandlerCount = static_cast(elements.size()); outputSet->eventHandlers = outputElements; return outputSet; } _NODISCARD ItemKeyHandler* ConvertKeyHandler(const std::multimap>& keyHandlers, const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const { if (keyHandlers.empty()) return nullptr; const auto keyHandlerCount = keyHandlers.size(); auto* output = m_memory.Alloc(keyHandlerCount); auto currentKeyHandler = keyHandlers.cbegin(); for (auto i = 0u; i < keyHandlerCount; i++) { output[i].key = currentKeyHandler->first; output[i].action = ConvertEventHandlerSet(currentKeyHandler->second.get(), menu, item); if (i + 1 < keyHandlerCount) output[i].next = &output[i + 1]; else output[i].next = nullptr; ++currentKeyHandler; } return output; } ItemFloatExpression* ConvertFloatExpressions(const CommonItemDef* commonItem, itemDef_s* item, const CommonMenuDef* parentMenu, int& floatExpressionCount) const { struct FloatExpressionLocation { ISimpleExpression* m_expression; bool m_expression_is_static; ItemFloatExpressionTarget m_target; float* m_static_value; unsigned m_static_value_array_size; unsigned m_dynamic_flags_to_set; }; FloatExpressionLocation locations[]{ {commonItem->m_rect_x_exp.get(), false, ITEM_FLOATEXP_TGT_RECT_X, &item->window.rectClient.x, 1, 0 }, {commonItem->m_rect_y_exp.get(), false, ITEM_FLOATEXP_TGT_RECT_Y, &item->window.rectClient.y, 1, 0 }, {commonItem->m_rect_w_exp.get(), false, ITEM_FLOATEXP_TGT_RECT_W, &item->window.rectClient.w, 1, 0 }, {commonItem->m_rect_h_exp.get(), false, ITEM_FLOATEXP_TGT_RECT_H, &item->window.rectClient.h, 1, 0 }, {commonItem->m_forecolor_expressions.m_r_exp.get(), false, ITEM_FLOATEXP_TGT_FORECOLOR_R, &item->window.foreColor[0], 1, WINDOW_FLAG_NON_DEFAULT_FORECOLOR}, {commonItem->m_forecolor_expressions.m_g_exp.get(), false, ITEM_FLOATEXP_TGT_FORECOLOR_G, &item->window.foreColor[1], 1, WINDOW_FLAG_NON_DEFAULT_FORECOLOR}, {commonItem->m_forecolor_expressions.m_b_exp.get(), false, ITEM_FLOATEXP_TGT_FORECOLOR_B, &item->window.foreColor[2], 1, WINDOW_FLAG_NON_DEFAULT_FORECOLOR}, {commonItem->m_forecolor_expressions.m_a_exp.get(), false, ITEM_FLOATEXP_TGT_FORECOLOR_A, &item->window.foreColor[3], 1, WINDOW_FLAG_NON_DEFAULT_FORECOLOR}, {commonItem->m_forecolor_expressions.m_rgb_exp.get(), false, ITEM_FLOATEXP_TGT_FORECOLOR_RGB, &item->window.foreColor[0], 3, WINDOW_FLAG_NON_DEFAULT_FORECOLOR}, {commonItem->m_glowcolor_expressions.m_r_exp.get(), false, ITEM_FLOATEXP_TGT_GLOWCOLOR_R, &item->glowColor[0], 1, 0 }, {commonItem->m_glowcolor_expressions.m_g_exp.get(), false, ITEM_FLOATEXP_TGT_GLOWCOLOR_G, &item->glowColor[1], 1, 0 }, {commonItem->m_glowcolor_expressions.m_b_exp.get(), false, ITEM_FLOATEXP_TGT_GLOWCOLOR_B, &item->glowColor[2], 1, 0 }, {commonItem->m_glowcolor_expressions.m_a_exp.get(), false, ITEM_FLOATEXP_TGT_GLOWCOLOR_A, &item->glowColor[3], 1, 0 }, {commonItem->m_glowcolor_expressions.m_rgb_exp.get(), false, ITEM_FLOATEXP_TGT_GLOWCOLOR_RGB, &item->glowColor[0], 3, 0 }, {commonItem->m_backcolor_expressions.m_r_exp.get(), false, ITEM_FLOATEXP_TGT_BACKCOLOR_R, &item->window.backColor[0], 1, 0 }, {commonItem->m_backcolor_expressions.m_g_exp.get(), false, ITEM_FLOATEXP_TGT_BACKCOLOR_G, &item->window.backColor[1], 1, 0 }, {commonItem->m_backcolor_expressions.m_b_exp.get(), false, ITEM_FLOATEXP_TGT_BACKCOLOR_B, &item->window.backColor[2], 1, 0 }, {commonItem->m_backcolor_expressions.m_a_exp.get(), false, ITEM_FLOATEXP_TGT_BACKCOLOR_A, &item->window.backColor[3], 1, 0 }, {commonItem->m_backcolor_expressions.m_rgb_exp.get(), false, ITEM_FLOATEXP_TGT_BACKCOLOR_RGB, &item->window.backColor[0], 3, 0 }, }; floatExpressionCount = 0; for (auto& [expression, expressionIsStatic, target, staticValue, staticValueArraySize, dynamicFlagsToSet] : locations) { expressionIsStatic = !m_disable_optimizations && staticValue != nullptr && expression && expression->IsStatic(); if (expressionIsStatic) { const auto evaluatedValue = expression->EvaluateStatic(); if (evaluatedValue.m_type == SimpleExpressionValue::Type::INT) { item->window.dynamicFlags[0] |= dynamicFlagsToSet; auto* staticValuePtr = staticValue; for (auto i = 0u; i < staticValueArraySize; i++) { *staticValuePtr = static_cast(evaluatedValue.m_int_value); staticValuePtr++; } continue; } if (evaluatedValue.m_type == SimpleExpressionValue::Type::DOUBLE) { item->window.dynamicFlags[0] |= dynamicFlagsToSet; auto* staticValuePtr = staticValue; for (auto i = 0u; i < staticValueArraySize; i++) { *staticValue = static_cast(evaluatedValue.m_double_value); staticValuePtr++; } continue; } // Do not consider this a mistake since the games menus do this by mistake and it should be able to compile them anyway // But the game should also not know what to do with this i guess expressionIsStatic = false; } if (expression) floatExpressionCount++; } if (floatExpressionCount <= 0) return nullptr; auto* floatExpressions = m_memory.Alloc(floatExpressionCount); auto floatExpressionIndex = 0; for (const auto& [expression, expressionIsStatic, target, staticValue, staticValueArraySize, dynamicFlagsToSet] : locations) { if (!expression || expressionIsStatic) continue; assert(floatExpressionIndex < floatExpressionCount && floatExpressionIndex >= 0); floatExpressions[floatExpressionIndex].target = target; floatExpressions[floatExpressionIndex].expression = ConvertExpression(expression, parentMenu, commonItem); item->window.dynamicFlags[0] |= dynamicFlagsToSet; floatExpressionIndex++; } return floatExpressions; } _NODISCARD const char* CreateEnableDvarString(const std::vector& stringElements) const { std::ostringstream ss; for (const auto& element : stringElements) { ss << "\"" << element << "\" "; } return m_memory.Dup(ss.str().c_str()); } _NODISCARD const char* ConvertEnableDvar(const CommonItemDef& commonItem, int& dvarFlags) const { dvarFlags = 0; if (!commonItem.m_enable_dvar.empty()) { dvarFlags |= ITEM_DVAR_FLAG_ENABLE; return CreateEnableDvarString(commonItem.m_enable_dvar); } if (!commonItem.m_disable_dvar.empty()) { dvarFlags |= ITEM_DVAR_FLAG_DISABLE; return CreateEnableDvarString(commonItem.m_disable_dvar); } if (!commonItem.m_show_dvar.empty()) { dvarFlags |= ITEM_DVAR_FLAG_SHOW; return CreateEnableDvarString(commonItem.m_show_dvar); } if (!commonItem.m_hide_dvar.empty()) { dvarFlags |= ITEM_DVAR_FLAG_HIDE; return CreateEnableDvarString(commonItem.m_hide_dvar); } if (!commonItem.m_focus_dvar.empty()) { dvarFlags |= ITEM_DVAR_FLAG_FOCUS; return CreateEnableDvarString(commonItem.m_focus_dvar); } return nullptr; } _NODISCARD listBoxDef_s* ConvertListBoxFeatures(itemDef_s* item, CommonItemFeaturesListBox* commonListBox, const CommonMenuDef& parentMenu, const CommonItemDef& commonItem) const { if (commonListBox == nullptr) return nullptr; auto* listBox = m_memory.Alloc(); listBox->notselectable = commonListBox->m_not_selectable ? 1 : 0; listBox->noScrollBars = commonListBox->m_no_scrollbars ? 1 : 0; listBox->usePaging = commonListBox->m_use_paging ? 1 : 0; listBox->elementWidth = static_cast(commonListBox->m_element_width); listBox->elementHeight = static_cast(commonListBox->m_element_height); item->special = static_cast(commonListBox->m_feeder); listBox->elementStyle = commonListBox->m_element_style; listBox->onDoubleClick = ConvertEventHandlerSet(commonListBox->m_on_double_click.get(), &parentMenu, &commonItem); 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())); for (auto i = 0; i < listBox->numColumns; i++) { auto& col = listBox->columnInfo[i]; const auto& commonCol = commonListBox->m_columns[i]; col.pos = commonCol.m_x_pos; col.width = commonCol.m_width; col.maxChars = commonCol.m_max_chars; col.alignment = commonCol.m_alignment; } return listBox; } _NODISCARD editFieldDef_s* ConvertEditFieldFeatures(itemDef_s* item, CommonItemFeaturesEditField* commonEditField, const CommonMenuDef& parentMenu, const CommonItemDef& commonItem) const { if (commonEditField == nullptr) return nullptr; auto* editField = m_memory.Alloc(); editField->defVal = static_cast(commonEditField->m_def_val); editField->minVal = static_cast(commonEditField->m_min_val); editField->maxVal = static_cast(commonEditField->m_max_val); item->localVar = ConvertString(commonEditField->m_local_var); editField->maxChars = commonEditField->m_max_chars; editField->maxCharsGotoNext = commonEditField->m_max_chars_goto_next ? 1 : 0; editField->maxPaintChars = commonEditField->m_max_paint_chars; return editField; } _NODISCARD multiDef_s* ConvertMultiValueFeatures(itemDef_s* item, CommonItemFeaturesMultiValue* commonMultiValue, const CommonMenuDef& parentMenu, const CommonItemDef& commonItem) const { if (commonMultiValue == nullptr) return nullptr; auto* multiValue = m_memory.Alloc(); multiValue->count = static_cast(std::min(std::extent_v, commonMultiValue->m_step_names.size())); multiValue->strDef = !commonMultiValue->m_string_values.empty() ? 1 : 0; for (auto i = 0; i < multiValue->count; i++) { multiValue->dvarList[i] = ConvertString(commonMultiValue->m_step_names[i]); if (multiValue->strDef) { if (commonMultiValue->m_string_values.size() > static_cast(i)) multiValue->dvarStr[i] = ConvertString(commonMultiValue->m_string_values[i]); } else { if (commonMultiValue->m_double_values.size() > static_cast(i)) multiValue->dvarValue[i] = static_cast(commonMultiValue->m_double_values[i]); } } return multiValue; } _NODISCARD newsTickerDef_s* ConvertNewsTickerFeatures(itemDef_s* item, CommonItemFeaturesNewsTicker* commonNewsTicker, const CommonMenuDef& parentMenu, const CommonItemDef& commonItem) const { if (commonNewsTicker == nullptr) return nullptr; auto* newsTicker = m_memory.Alloc(); newsTicker->spacing = commonNewsTicker->m_spacing; newsTicker->speed = commonNewsTicker->m_speed; newsTicker->feedId = commonNewsTicker->m_news_feed_id; return newsTicker; } _NODISCARD itemDef_s* ConvertItem(const CommonMenuDef& parentMenu, const CommonItemDef& commonItem) const { auto* item = m_memory.Alloc(); item->window.name = ConvertString(commonItem.m_name); item->text = ConvertString(commonItem.m_text); ApplyFlag(item->itemFlags, commonItem.m_text_save_game, ITEM_FLAG_SAVE_GAME_INFO); ApplyFlag(item->itemFlags, commonItem.m_text_cinematic_subtitle, ITEM_FLAG_CINEMATIC_SUBTITLE); item->window.group = ConvertString(commonItem.m_group); item->window.rectClient = ConvertRectDef(commonItem.m_rect); item->window.rect = ConvertRectDefRelativeTo(commonItem.m_rect, parentMenu.m_rect); item->window.style = commonItem.m_style; 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->window.border = commonItem.m_border; item->window.borderSize = static_cast(commonItem.m_border_size); item->visibleExp = ConvertVisibleExpression(&item->window, commonItem.m_visible_expression.get(), &parentMenu, &commonItem); item->disabledExp = ConvertExpression(commonItem.m_disabled_expression.get(), &parentMenu, &commonItem); item->window.ownerDraw = commonItem.m_owner_draw; item->window.ownerDrawFlags = commonItem.m_owner_draw_flags; item->alignment = commonItem.m_align; item->textAlignMode = commonItem.m_text_align; item->textalignx = static_cast(commonItem.m_text_align_x); 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); 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; ConvertColor(item->window.borderColor, commonItem.m_border_color); ConvertColor(item->window.outlineColor, commonItem.m_outline_color); ConvertColor(item->window.disableColor, commonItem.m_disable_color); ConvertColor(item->glowColor, commonItem.m_glow_color); item->window.background = ConvertMaterial(commonItem.m_background, &parentMenu, &commonItem); item->onFocus = ConvertEventHandlerSet(commonItem.m_on_focus.get(), &parentMenu, &commonItem); item->leaveFocus = ConvertEventHandlerSet(commonItem.m_on_leave_focus.get(), &parentMenu, &commonItem); item->mouseEnter = ConvertEventHandlerSet(commonItem.m_on_mouse_enter.get(), &parentMenu, &commonItem); item->mouseExit = ConvertEventHandlerSet(commonItem.m_on_mouse_exit.get(), &parentMenu, &commonItem); item->mouseEnterText = ConvertEventHandlerSet(commonItem.m_on_mouse_enter_text.get(), &parentMenu, &commonItem); item->mouseExitText = ConvertEventHandlerSet(commonItem.m_on_mouse_exit_text.get(), &parentMenu, &commonItem); item->action = ConvertEventHandlerSet(commonItem.m_on_action.get(), &parentMenu, &commonItem); item->accept = ConvertEventHandlerSet(commonItem.m_on_accept.get(), &parentMenu, &commonItem); item->focusSound = ConvertSound(commonItem.m_focus_sound, &parentMenu, &commonItem); item->dvarTest = ConvertString(commonItem.m_dvar_test); item->enableDvar = ConvertEnableDvar(commonItem, item->dvarFlags); item->onKey = ConvertKeyHandler(commonItem.m_key_handlers, &parentMenu, &commonItem); item->textExp = ConvertOrApplyStatement(item->text, commonItem.m_text_expression.get(), &parentMenu, &commonItem); item->materialExp = ConvertOrApplyStatement(item->window.background, commonItem.m_material_expression.get(), &parentMenu, &commonItem); item->disabledExp = ConvertExpression(commonItem.m_disabled_expression.get(), &parentMenu, &commonItem); item->floatExpressions = ConvertFloatExpressions(&commonItem, item, &parentMenu, item->floatExpressionCount); item->gameMsgWindowIndex = commonItem.m_game_message_window_index; item->gameMsgWindowMode = commonItem.m_game_message_window_mode; 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) { case CommonItemFeatureType::LISTBOX: item->typeData.listBox = ConvertListBoxFeatures(item, commonItem.m_list_box_features.get(), parentMenu, commonItem); break; case CommonItemFeatureType::EDIT_FIELD: item->typeData.editField = ConvertEditFieldFeatures(item, commonItem.m_edit_field_features.get(), parentMenu, commonItem); break; case CommonItemFeatureType::MULTI_VALUE: item->typeData.multi = ConvertMultiValueFeatures(item, commonItem.m_multi_value_features.get(), parentMenu, commonItem); break; case CommonItemFeatureType::ENUM_DVAR: item->typeData.enumDvarName = ConvertString(commonItem.m_enum_dvar_name); break; case CommonItemFeatureType::NEWS_TICKER: item->typeData.ticker = ConvertNewsTickerFeatures(item, commonItem.m_news_ticker_features.get(), parentMenu, commonItem); break; case CommonItemFeatureType::NONE: default: if (item->type == ITEM_TYPE_TEXT_SCROLL) item->typeData.scroll = m_memory.Alloc(); break; } return item; } itemDef_s** ConvertMenuItems(const CommonMenuDef& commonMenu, int& itemCount) const { if (commonMenu.m_items.empty()) { itemCount = 0; return nullptr; } auto* items = m_memory.Alloc(commonMenu.m_items.size()); for (auto i = 0u; i < commonMenu.m_items.size(); i++) items[i] = ConvertItem(commonMenu, *commonMenu.m_items[i]); itemCount = static_cast(commonMenu.m_items.size()); return items; } public: MenuConverter(const bool disableOptimizations, ISearchPath& searchPath, MemoryManager& memory, AssetCreationContext& context) : AbstractMenuConverter(disableOptimizations, searchPath, memory, context), m_conversion_zone_state(context.GetZoneAssetLoaderState()), m_parsing_zone_state(context.GetZoneAssetLoaderState()) { assert(m_conversion_zone_state); assert(m_parsing_zone_state); } void ConvertMenu(const menu::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; 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.style = commonMenu.m_style; menu.window.border = commonMenu.m_border; menu.window.borderSize = static_cast(commonMenu.m_border_size); ConvertColor(menu.window.backColor, commonMenu.m_back_color); ConvertColor(menu.window.foreColor, commonMenu.m_fore_color); ConvertColor(menu.window.borderColor, commonMenu.m_border_color); ConvertColor(menu.focusColor, commonMenu.m_focus_color); menu.window.background = ConvertMaterial(commonMenu.m_background, &commonMenu); menu.window.ownerDraw = commonMenu.m_owner_draw; menu.window.ownerDrawFlags = commonMenu.m_owner_draw_flags; ApplyFlag(menu.window.staticFlags, commonMenu.m_out_of_bounds_click, WINDOW_FLAG_OUT_OF_BOUNDS_CLICK); menu.soundName = ConvertString(commonMenu.m_sound_loop); ApplyFlag(menu.window.staticFlags, commonMenu.m_popup, WINDOW_FLAG_POPUP); menu.fadeClamp = static_cast(commonMenu.m_fade_clamp); menu.fadeCycle = commonMenu.m_fade_cycle; menu.fadeAmount = static_cast(commonMenu.m_fade_amount); menu.fadeInAmount = static_cast(commonMenu.m_fade_in_amount); menu.blurRadius = static_cast(commonMenu.m_blur_radius); ApplyFlag(menu.window.staticFlags, commonMenu.m_legacy_split_screen_scale, WINDOW_FLAG_LEGACY_SPLIT_SCREEN_SCALE); ApplyFlag(menu.window.staticFlags, commonMenu.m_hidden_during_scope, WINDOW_FLAG_HIDDEN_DURING_SCOPE); ApplyFlag(menu.window.staticFlags, commonMenu.m_hidden_during_flashbang, WINDOW_FLAG_HIDDEN_DURING_FLASH_BANG); 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.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); menu.rectHExp = ConvertOrApplyStatement(menu.window.rect.h, commonMenu.m_rect_h_exp.get(), &commonMenu); menu.openSoundExp = ConvertExpression(commonMenu.m_open_sound_exp.get(), &commonMenu); menu.closeSoundExp = ConvertExpression(commonMenu.m_close_sound_exp.get(), &commonMenu); menu.onOpen = ConvertEventHandlerSet(commonMenu.m_on_open.get(), &commonMenu); menu.onClose = ConvertEventHandlerSet(commonMenu.m_on_close.get(), &commonMenu); menu.onCloseRequest = ConvertEventHandlerSet(commonMenu.m_on_request_close.get(), &commonMenu); menu.onESC = ConvertEventHandlerSet(commonMenu.m_on_esc.get(), &commonMenu); menu.onKey = ConvertKeyHandler(commonMenu.m_key_handlers, &commonMenu); menu.items = ConvertMenuItems(commonMenu, menu.itemCount); menu.expressionData = m_conversion_zone_state->m_supporting_data; } catch (const MenuConversionException& e) { PrintConversionExceptionDetails(e); } } MenuConversionZoneState* m_conversion_zone_state; MenuAssetZoneState* m_parsing_zone_state; }; } // namespace std::unique_ptr IMenuConverter::Create(bool disableOptimizations, ISearchPath& searchPath, MemoryManager& memory, AssetCreationContext& context) { return std::make_unique(disableOptimizations, searchPath, memory, context); }