diff --git a/src/Common/Game/IW5/IW5_Assets.h b/src/Common/Game/IW5/IW5_Assets.h index 1cdd083f..628ce47f 100644 --- a/src/Common/Game/IW5/IW5_Assets.h +++ b/src/Common/Game/IW5/IW5_Assets.h @@ -2316,7 +2316,21 @@ namespace IW5 EXP_FUNC_STATIC_DVAR_FLOAT, EXP_FUNC_STATIC_DVAR_STRING, - EXP_FUNC_DYN_START + EXP_FUNC_DYN_START, + + EXP_FUNC_INT = EXP_FUNC_DYN_START, + EXP_FUNC_STRING, + EXP_FUNC_FLOAT, + EXP_FUNC_SIN, + EXP_FUNC_COS, + EXP_FUNC_MIN, + EXP_FUNC_MAX, + EXP_FUNC_MILLISECONDS, + EXP_FUNC_LOCAL_CLIENT_UI_MILLISECONDS, + EXP_FUNC_DVAR_INT, + EXP_FUNC_DVAR_BOOL, + EXP_FUNC_DVAR_FLOAT, + EXP_FUNC_DVAR_STRING }; enum expressionEntryType : int @@ -2560,6 +2574,19 @@ namespace IW5 WINDOW_FLAG_TEXT_ONLY_FOCUS = 0x80000000, }; + // This is data from IW4, could be different for IW5, to be investigated + enum WindowDefDynamicFlag : unsigned int + { + WINDOW_FLAG_HOVERED = 0x1, // guessed + WINDOW_FLAG_FOCUSED = 0x2, + WINDOW_FLAG_VISIBLE = 0x4, + WINDOW_FLAG_FADING_OUT = 0x10, + WINDOW_FLAG_FADING_IN = 0x20, + WINDOW_FLAG_80 = 0x80, + WINDOW_FLAG_NON_DEFAULT_BACKCOLOR = 0x8000, + WINDOW_FLAG_NON_DEFAULT_FORECOLOR = 0x10000 + }; + struct windowDef_t { const char* name; diff --git a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMenuDef.cpp b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMenuDef.cpp new file mode 100644 index 00000000..d72739a7 --- /dev/null +++ b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMenuDef.cpp @@ -0,0 +1,17 @@ +#include "AssetLoaderMenuDef.h" + +#include + +#include "ObjLoading.h" +#include "Game/IW5/IW5.h" +#include "Pool/GlobalAssetPool.h" + +using namespace IW5; + +void* AssetLoaderMenuDef::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) +{ + auto* menu = memory->Create(); + memset(menu, 0, sizeof(menuDef_t)); + menu->window.name = memory->Dup(assetName.c_str()); + return menu; +} diff --git a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMenuDef.h b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMenuDef.h new file mode 100644 index 00000000..8bf0bf90 --- /dev/null +++ b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMenuDef.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Game/IW5/IW5.h" +#include "AssetLoading/BasicAssetLoader.h" +#include "SearchPath/ISearchPath.h" + +namespace IW5 +{ + class AssetLoaderMenuDef final : public BasicAssetLoader + { + public: + _NODISCARD void* CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) override; + }; +} diff --git a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMenuList.cpp b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMenuList.cpp new file mode 100644 index 00000000..10a75ace --- /dev/null +++ b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMenuList.cpp @@ -0,0 +1,207 @@ +#include "AssetLoaderMenuList.h" + +#include +#include + +#include "ObjLoading.h" +#include "Game/IW5/IW5.h" +#include "Game/IW5/Menu/MenuConversionZoneStateIW5.h" +#include "Game/IW5/Menu/MenuConverterIW5.h" +#include "Parsing/Menu/MenuFileReader.h" +#include "Pool/GlobalAssetPool.h" + +using namespace IW5; + +namespace IW5 +{ + class MenuLoader + { + public: + static bool ProcessParsedResults(const std::string& fileName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, menu::ParsingResult* parsingResult, + menu::MenuAssetZoneState* zoneState, MenuConversionZoneState* conversionState, std::vector& menus, + std::vector& menuListDependencies) + { + const auto menuCount = parsingResult->m_menus.size(); + const auto functionCount = parsingResult->m_functions.size(); + const auto menuLoadCount = parsingResult->m_menus_to_load.size(); + auto totalItemCount = 0u; + for (const auto& menu : parsingResult->m_menus) + totalItemCount += menu->m_items.size(); + + std::cout << "Successfully read menu file \"" << fileName << "\" (" << menuLoadCount << " loads, " << menuCount << " menus, " << functionCount << " functions, " << totalItemCount << + " items)\n"; + + // Add all functions to the zone state to make them available for all menus to be converted + for (auto& function : parsingResult->m_functions) + zoneState->AddFunction(std::move(function)); + + // Prepare a list of all menus of this file + std::vector*> allMenusOfFile; + allMenusOfFile.reserve(parsingResult->m_menus.size()); + + // Convert all menus and add them as assets + for (auto& menu : parsingResult->m_menus) + { + MenuConverter converter(ObjLoading::Configuration.MenuNoOptimization, searchPath, memory, manager); + auto* menuAsset = converter.ConvertMenu(*menu); + if (menuAsset == nullptr) + { + std::cout << "Failed to convert menu file \"" << menu->m_name << "\"\n"; + return false; + } + + menus.push_back(menuAsset); + auto* menuAssetInfo = manager->AddAsset(ASSET_TYPE_MENU, menu->m_name, menuAsset, std::move(converter.GetDependencies()), std::vector()); + + if (menuAssetInfo) + { + allMenusOfFile.push_back(reinterpret_cast*>(menuAssetInfo)); + menuListDependencies.push_back(menuAssetInfo); + } + + zoneState->AddMenu(std::move(menu)); + } + + // Register this file with all loaded menus + conversionState->AddLoadedFile(fileName, std::move(allMenusOfFile)); + + return true; + } + + static MenuList* CreateMenuListAsset(const std::string& assetName, MemoryManager* memory, const std::vector& menus) + { + auto* menuListAsset = memory->Create(); + menuListAsset->name = memory->Dup(assetName.c_str()); + menuListAsset->menuCount = static_cast(menus.size()); + + if (menuListAsset->menuCount > 0) + { + menuListAsset->menus = static_cast(memory->Alloc(sizeof(uintptr_t) * menuListAsset->menuCount)); + for (auto i = 0; i < menuListAsset->menuCount; i++) + menuListAsset->menus[i] = menus[i]; + } + else + menuListAsset->menus = nullptr; + + return menuListAsset; + } + + static std::unique_ptr ParseMenuFile(const std::string& menuFileName, ISearchPath* searchPath, const menu::MenuAssetZoneState* zoneState) + { + const auto file = searchPath->Open(menuFileName); + if (!file.IsOpen()) + return nullptr; + + menu::MenuFileReader reader(*file.m_stream, menuFileName, menu::FeatureLevel::IW5, [searchPath](const std::string& filename, const std::string& sourceFile) -> std::unique_ptr + { + auto foundFileToInclude = searchPath->Open(filename); + if (!foundFileToInclude.IsOpen() || !foundFileToInclude.m_stream) + return nullptr; + + return std::move(foundFileToInclude.m_stream); + }); + + reader.IncludeZoneState(zoneState); + reader.SetPermissiveMode(ObjLoading::Configuration.MenuPermissiveParsing); + + return reader.ReadMenuFile(); + } + }; +} + +void* AssetLoaderMenuList::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) +{ + auto* menuList = memory->Create(); + memset(menuList, 0, sizeof(MenuList)); + menuList->name = memory->Dup(assetName.c_str()); + return menuList; +} + +bool AssetLoaderMenuList::CanLoadFromRaw() const +{ + return true; +} + +bool BuildMenuFileQueue(std::deque& menuLoadQueue, const std::string& menuListAssetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, menu::MenuAssetZoneState* zoneState, + MenuConversionZoneState* conversionState, std::vector& menus, std::vector& menuListDependencies) +{ + const auto alreadyLoadedMenuListFileMenus = conversionState->m_menus_by_filename.find(menuListAssetName); + + if (alreadyLoadedMenuListFileMenus == conversionState->m_menus_by_filename.end()) + { + const auto menuListResult = MenuLoader::ParseMenuFile(menuListAssetName, searchPath, zoneState); + if (menuListResult) + { + MenuLoader::ProcessParsedResults(menuListAssetName, searchPath, memory, manager, menuListResult.get(), zoneState, conversionState, menus, menuListDependencies); + + for (const auto& menuToLoad : menuListResult->m_menus_to_load) + menuLoadQueue.push_back(menuToLoad); + + zoneState->AddMenusToLoad(menuListAssetName, std::move(menuListResult->m_menus_to_load)); + } + else + return false; + } + + return true; +} + +void LoadMenuFileFromQueue(const std::string& menuFilePath, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, menu::MenuAssetZoneState* zoneState, + MenuConversionZoneState* conversionState, std::vector& menus, std::vector& menuListDependencies) +{ + const auto alreadyLoadedMenuFile = conversionState->m_menus_by_filename.find(menuFilePath); + if (alreadyLoadedMenuFile != conversionState->m_menus_by_filename.end()) + { + std::cout << "Already loaded \"" << menuFilePath << "\", skipping\n"; + for (auto* menu : alreadyLoadedMenuFile->second) + { + menus.push_back(menu->Asset()); + menuListDependencies.push_back(menu); + } + return; + } + + const auto menuFileResult = MenuLoader::ParseMenuFile(menuFilePath, searchPath, zoneState); + if (menuFileResult) + { + MenuLoader::ProcessParsedResults(menuFilePath, searchPath, memory, manager, menuFileResult.get(), zoneState, conversionState, menus, menuListDependencies); + if (!menuFileResult->m_menus_to_load.empty()) + std::cout << "WARNING: Menu file has menus to load even though it is not a menu list, ignoring: \"" << menuFilePath << "\"\n"; + } + else + std::cerr << "Could not read menu file \"" << menuFilePath << "\"\n"; +} + +bool AssetLoaderMenuList::LoadFromRaw(const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const +{ + std::vector menus; + std::vector menuListDependencies; + + auto* zoneState = manager->GetAssetLoadingContext()->GetZoneAssetLoaderState(); + auto* conversionState = manager->GetAssetLoadingContext()->GetZoneAssetLoaderState(); + + std::deque menuLoadQueue; + if (!BuildMenuFileQueue(menuLoadQueue, assetName, searchPath, memory, manager, zoneState, conversionState, menus, menuListDependencies)) + return false; + + while(!menuLoadQueue.empty()) + { + const auto& menuFileToLoad = menuLoadQueue.front(); + + LoadMenuFileFromQueue(menuFileToLoad, searchPath, memory, manager, zoneState, conversionState, menus, menuListDependencies); + + menuLoadQueue.pop_front(); + } + + auto* menuListAsset = MenuLoader::CreateMenuListAsset(assetName, memory, menus); + + if (menuListAsset) + manager->AddAsset(ASSET_TYPE_MENULIST, assetName, menuListAsset, menuListDependencies, std::vector()); + + return true; +} + +void AssetLoaderMenuList::FinalizeAssetsForZone(AssetLoadingContext* context) const +{ + context->GetZoneAssetLoaderState()->FinalizeSupportingData(); +} diff --git a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMenuList.h b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMenuList.h new file mode 100644 index 00000000..3aa87041 --- /dev/null +++ b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMenuList.h @@ -0,0 +1,18 @@ +#pragma once + +#include "Game/IW5/IW5.h" +#include "AssetLoading/BasicAssetLoader.h" +#include "AssetLoading/IAssetLoadingManager.h" +#include "SearchPath/ISearchPath.h" + +namespace IW5 +{ + class AssetLoaderMenuList final : public BasicAssetLoader + { + public: + _NODISCARD void* CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) override; + _NODISCARD bool CanLoadFromRaw() const override; + bool LoadFromRaw(const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const override; + void FinalizeAssetsForZone(AssetLoadingContext* context) const override; + }; +} diff --git a/src/ObjLoading/Game/IW5/Menu/MenuConversionZoneStateIW5.cpp b/src/ObjLoading/Game/IW5/Menu/MenuConversionZoneStateIW5.cpp new file mode 100644 index 00000000..11d2614b --- /dev/null +++ b/src/ObjLoading/Game/IW5/Menu/MenuConversionZoneStateIW5.cpp @@ -0,0 +1,121 @@ +#include "MenuConversionZoneStateIW5.h" + +#include + +using namespace IW5; + +MenuConversionZoneState::MenuConversionZoneState() + : m_zone(nullptr), + m_supporting_data(nullptr) +{ +} + +void MenuConversionZoneState::SetZone(Zone* zone) +{ + auto* memory = zone->GetMemory(); + + m_zone = zone; + m_supporting_data = memory->Create(); + memset(m_supporting_data, 0, sizeof(ExpressionSupportingData)); +} + +Statement_s* MenuConversionZoneState::FindFunction(const std::string& functionName) +{ + const auto foundFunction = m_function_by_name.find(functionName); + + if (foundFunction != m_function_by_name.end()) + return foundFunction->second; + + return nullptr; +} + +Statement_s* MenuConversionZoneState::AddFunction(const std::string& functionName, Statement_s* function) +{ + m_functions.push_back(function); + m_function_by_name.emplace(std::make_pair(functionName, function)); + + return function; +} + +size_t MenuConversionZoneState::AddStaticDvar(const std::string& dvarName) +{ + const auto foundDvar = m_dvars_by_name.find(dvarName); + + if (foundDvar != m_dvars_by_name.end()) + return foundDvar->second; + + auto* memory = m_zone->GetMemory(); + auto* staticDvar = static_cast(memory->Alloc(sizeof(StaticDvar))); + + staticDvar->dvarName = memory->Dup(dvarName.c_str()); + staticDvar->dvar = nullptr; + + const auto staticDvarIndex = m_static_dvars.size(); + m_static_dvars.push_back(staticDvar); + m_dvars_by_name.emplace(std::make_pair(dvarName, staticDvarIndex)); + + return staticDvarIndex; +} + +const char* MenuConversionZoneState::AddString(const std::string& str) +{ + const auto foundString = m_strings_by_value.find(str); + + if (foundString != m_strings_by_value.end()) + return foundString->second; + + auto* memory = m_zone->GetMemory(); + const auto* strDuped = memory->Dup(str.c_str()); + + m_strings.push_back(strDuped); + m_strings_by_value.emplace(std::make_pair(str, strDuped)); + + return strDuped; +} + +void MenuConversionZoneState::AddLoadedFile(std::string loadedFileName, std::vector*> menusOfFile) +{ + m_menus_by_filename.emplace(std::make_pair(std::move(loadedFileName), std::move(menusOfFile))); +} + +void MenuConversionZoneState::FinalizeSupportingData() const +{ + auto* memory = m_zone->GetMemory(); + + m_supporting_data->uifunctions.totalFunctions = static_cast(m_functions.size()); + m_supporting_data->staticDvarList.numStaticDvars = static_cast(m_static_dvars.size()); + m_supporting_data->uiStrings.totalStrings = static_cast(m_strings.size()); + + if (m_supporting_data->uifunctions.functions) + memory->Free(m_supporting_data->uifunctions.functions); + + if (m_supporting_data->staticDvarList.staticDvars) + memory->Free(m_supporting_data->staticDvarList.staticDvars); + + if (m_supporting_data->uiStrings.strings) + memory->Free(m_supporting_data->uiStrings.strings); + + if (!m_functions.empty()) + { + m_supporting_data->uifunctions.functions = static_cast(memory->Alloc(sizeof(void*) * m_functions.size())); + memcpy(m_supporting_data->uifunctions.functions, &m_functions[0], sizeof(void*) * m_functions.size()); + } + else + m_supporting_data->uifunctions.functions = nullptr; + + if (!m_static_dvars.empty()) + { + m_supporting_data->staticDvarList.staticDvars = static_cast(memory->Alloc(sizeof(void*) * m_static_dvars.size())); + memcpy(m_supporting_data->staticDvarList.staticDvars, &m_static_dvars[0], sizeof(void*) * m_static_dvars.size()); + } + else + m_supporting_data->staticDvarList.staticDvars = nullptr; + + if (!m_strings.empty()) + { + m_supporting_data->uiStrings.strings = static_cast(memory->Alloc(sizeof(void*) * m_strings.size())); + memcpy(m_supporting_data->uiStrings.strings, &m_strings[0], sizeof(void*) * m_strings.size()); + } + else + m_supporting_data->uiStrings.strings = nullptr; +} diff --git a/src/ObjLoading/Game/IW5/Menu/MenuConversionZoneStateIW5.h b/src/ObjLoading/Game/IW5/Menu/MenuConversionZoneStateIW5.h new file mode 100644 index 00000000..890c1dc6 --- /dev/null +++ b/src/ObjLoading/Game/IW5/Menu/MenuConversionZoneStateIW5.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include "AssetLoading/IZoneAssetLoaderState.h" +#include "Game/IW5/IW5.h" + +namespace IW5 +{ + class MenuConversionZoneState final : public IZoneAssetLoaderState + { + 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 SetZone(Zone* zone) override; + + Statement_s* FindFunction(const std::string& functionName); + + Statement_s* AddFunction(const std::string& functionName, Statement_s* function); + size_t AddStaticDvar(const std::string& dvarName); + const char* AddString(const std::string& str); + + void AddLoadedFile(std::string loadedFileName, std::vector*> menusOfFile); + + void FinalizeSupportingData() const; + }; +} diff --git a/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp b/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp new file mode 100644 index 00000000..b469e807 --- /dev/null +++ b/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp @@ -0,0 +1,1146 @@ +#include "MenuConverterIW5.h" + +#include +#include +#include + +#include "MenuConversionZoneStateIW5.h" +#include "Utils/ClassUtils.h" +#include "Menu/AbstractMenuConverter.h" +#include "Parsing/Menu/MenuAssetZoneState.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/Simple/Expression/SimpleExpressionBinaryOperation.h" +#include "Parsing/Simple/Expression/SimpleExpressionConditionalOperator.h" +#include "Parsing/Simple/Expression/SimpleExpressionUnaryOperation.h" + +using namespace IW5; +using namespace menu; + +namespace IW5 +{ + class MenuConverterImpl : public AbstractMenuConverter + { + MenuConversionZoneState* m_conversion_zone_state; + MenuAssetZoneState* m_parsing_zone_state; + + _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_manager->LoadDependency(ASSET_TYPE_MATERIAL, materialName); + if (!materialDependency) + throw MenuConversionException("Failed to load material \"" + materialName + "\"", menu, item); + + return static_cast(materialDependency->m_ptr); + } + + _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_manager->LoadDependency(ASSET_TYPE_SOUND, soundName); + if (!soundDependency) + throw MenuConversionException("Failed to load sound \"" + soundName + "\"", menu, item); + + return static_cast(soundDependency->m_ptr); + } + + 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 + { + Statement_s* functionStatement = m_conversion_zone_state->FindFunction(functionCall->m_function_name); + + if (functionStatement == nullptr) + { + // Function was not converted yet: Convert it now + const auto foundCommonFunction = m_parsing_zone_state->m_functions_by_name.find(functionCall->m_function_name); + + 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->Create(); + 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); + + auto* outputExpressionEntries = static_cast(m_memory->Alloc(sizeof(expressionEntry) * 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->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 = static_cast(m_memory->Alloc(sizeof(MenuEventHandler) + sizeof(SetLocalVarData))); + auto* outputSetLocalVar = reinterpret_cast(reinterpret_cast(outputHandler) + sizeof(MenuEventHandler)); + + 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->Create(); + 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 = static_cast(m_memory->Alloc(sizeof(MenuEventHandler) + sizeof(ConditionalScript))); + auto* outputCondition = reinterpret_cast(reinterpret_cast(outputHandler) + sizeof(MenuEventHandler)); + + 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->Create(); + 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 = static_cast(m_memory->Alloc(sizeof(MenuEventHandlerSet) + sizeof(void*) * elements.size())); + auto* outputElements = reinterpret_cast(reinterpret_cast(outputSet) + sizeof(MenuEventHandlerSet)); + memcpy(outputElements, &elements[0], sizeof(void*) * elements.size()); + + outputSet->eventHandlerCount = static_cast(elements.size()); + outputSet->eventHandlers = outputElements; + + return outputSet; + } + + _NODISCARD ItemKeyHandler* ConvertKeyHandler(const std::map>& keyHandlers, const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const + { + if (keyHandlers.empty()) + return nullptr; + + const auto keyHandlerCount = keyHandlers.size(); + auto* output = static_cast(m_memory->Alloc(sizeof(ItemKeyHandler) * 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; + } + + assert(false); + expressionIsStatic = false; + } + + if (expression) + floatExpressionCount++; + } + + if (floatExpressionCount <= 0) + return nullptr; + + auto* floatExpressions = static_cast(m_memory->Alloc(sizeof(ItemFloatExpression) * 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 = static_cast(m_memory->Alloc(sizeof(listBoxDef_s))); + memset(listBox, 0, sizeof(listBoxDef_s)); + + 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.xpos = commonCol.m_x_pos; + col.ypos = commonCol.m_y_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 = static_cast(m_memory->Alloc(sizeof(editFieldDef_s))); + memset(editField, 0, sizeof(editFieldDef_s)); + + editField->stepVal = 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 = static_cast(m_memory->Alloc(sizeof(multiDef_s))); + memset(multiValue, 0, sizeof(multiDef_s)); + + 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 = static_cast(m_memory->Alloc(sizeof(newsTickerDef_s))); + memset(newsTicker, 0, sizeof(newsTickerDef_s)); + + 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->Create(); + memset(item, 0, sizeof(itemDef_s)); + + 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->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 = static_cast(m_memory->Alloc(sizeof(textScrollDef_s))); + memset(item->typeData.scroll, 0, sizeof(textScrollDef_s)); + } + break; + } + + return item; + } + + itemDef_s** ConvertMenuItems(const CommonMenuDef& commonMenu, int& itemCount) const + { + if (commonMenu.m_items.empty()) + { + itemCount = 0; + return nullptr; + } + + auto* items = static_cast(m_memory->Alloc(sizeof(void*) * commonMenu.m_items.size())); + memset(items, 0, sizeof(void*) * 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: + MenuConverterImpl(const bool disableOptimizations, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager) + : AbstractMenuConverter(disableOptimizations, searchPath, memory, manager), + m_conversion_zone_state(manager->GetAssetLoadingContext()->GetZoneAssetLoaderState()), + m_parsing_zone_state(manager->GetAssetLoadingContext()->GetZoneAssetLoaderState()) + { + assert(m_conversion_zone_state); + assert(m_parsing_zone_state); + } + + _NODISCARD menuDef_t* ConvertMenu(const CommonMenuDef& commonMenu) const + { + auto* menu = m_memory->Create(); + auto* menuData = m_memory->Create(); + memset(menu, 0, sizeof(menuDef_t)); + + menu->window.name = m_memory->Dup(commonMenu.m_name.c_str()); + menuData->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(menuData->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); + menuData->soundName = ConvertString(commonMenu.m_sound_loop); + ApplyFlag(menu->window.staticFlags, commonMenu.m_popup, WINDOW_FLAG_POPUP); + menuData->fadeClamp = static_cast(commonMenu.m_fade_clamp); + menuData->fadeCycle = commonMenu.m_fade_cycle; + menuData->fadeAmount = static_cast(commonMenu.m_fade_amount); + menuData->fadeInAmount = static_cast(commonMenu.m_fade_in_amount); + menuData->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); + 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->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); + menuData->rectHExp = ConvertOrApplyStatement(menu->window.rect.h, commonMenu.m_rect_h_exp.get(), &commonMenu); + menuData->openSoundExp = ConvertExpression(commonMenu.m_open_sound_exp.get(), &commonMenu); + menuData->closeSoundExp = ConvertExpression(commonMenu.m_close_sound_exp.get(), &commonMenu); + menuData->onOpen = ConvertEventHandlerSet(commonMenu.m_on_open.get(), &commonMenu); + menuData->onClose = ConvertEventHandlerSet(commonMenu.m_on_close.get(), &commonMenu); + menuData->onCloseRequest = ConvertEventHandlerSet(commonMenu.m_on_request_close.get(), &commonMenu); + menuData->onESC = ConvertEventHandlerSet(commonMenu.m_on_esc.get(), &commonMenu); + menuData->onKey = ConvertKeyHandler(commonMenu.m_key_handlers, &commonMenu); + menu->items = ConvertMenuItems(commonMenu, menu->itemCount); + menuData->expressionData = m_conversion_zone_state->m_supporting_data; + + return menu; + } + + std::vector m_dependencies; + }; +} + +MenuConverter::MenuConverter(const bool disableOptimizations, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager) + : m_disable_optimizations(disableOptimizations), + m_search_path(searchPath), + m_memory(memory), + m_manager(manager) +{ +} + +std::vector& MenuConverter::GetDependencies() +{ + return m_dependencies; +} + +menuDef_t* MenuConverter::ConvertMenu(const CommonMenuDef& commonMenu) +{ + MenuConverterImpl impl(m_disable_optimizations, m_search_path, m_memory, m_manager); + + try + { + auto* result = impl.ConvertMenu(commonMenu); + m_dependencies = std::move(impl.m_dependencies); + return result; + } + catch (const MenuConversionException& e) + { + MenuConverterImpl::PrintConversionExceptionDetails(e); + } + + return nullptr; +} diff --git a/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.h b/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.h new file mode 100644 index 00000000..3570ba79 --- /dev/null +++ b/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.h @@ -0,0 +1,26 @@ +#pragma once + +#include "Utils/ClassUtils.h" +#include "AssetLoading/IAssetLoadingManager.h" +#include "Game/IW5/IW5.h" +#include "Parsing/Menu/Domain/CommonMenuDef.h" +#include "Utils/MemoryManager.h" +#include "SearchPath/ISearchPath.h" + +namespace IW5 +{ + class MenuConverter + { + bool m_disable_optimizations; + ISearchPath* m_search_path; + MemoryManager* m_memory; + IAssetLoadingManager* m_manager; + std::vector m_dependencies; + + public: + MenuConverter(bool disableOptimizations, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager); + + std::vector& GetDependencies(); + _NODISCARD menuDef_t* ConvertMenu(const menu::CommonMenuDef& commonMenu); + }; +} diff --git a/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp b/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp index b67726dd..77c4a48a 100644 --- a/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp +++ b/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp @@ -5,6 +5,8 @@ #include "ObjContainer/IPak/IPak.h" #include "ObjLoading.h" #include "AssetLoaders/AssetLoaderLocalizeEntry.h" +#include "AssetLoaders/AssetLoaderMenuDef.h" +#include "AssetLoaders/AssetLoaderMenuList.h" #include "AssetLoaders/AssetLoaderRawFile.h" #include "AssetLoaders/AssetLoaderStringTable.h" #include "AssetLoading/AssetLoadingManager.h" @@ -44,8 +46,8 @@ ObjLoader::ObjLoader() REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_GFXWORLD, GfxWorld)) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_LIGHT_DEF, GfxLightDef)) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_FONT, Font_s)) - REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_MENULIST, MenuList)) - REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_MENU, menuDef_t)) + REGISTER_ASSET_LOADER(AssetLoaderMenuList) + REGISTER_ASSET_LOADER(AssetLoaderMenuDef) REGISTER_ASSET_LOADER(AssetLoaderLocalizeEntry) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_ATTACHMENT, WeaponAttachment)) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_WEAPON, WeaponCompleteDef))