From 399d8e834deb9776cd96a4f73711c243218e33a6 Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 23 Aug 2023 20:51:11 +0200 Subject: [PATCH 01/19] Make IW4 menu rect def align be unsigned like IW5 --- src/Common/Game/IW4/IW4_Assets.h | 4 ++-- src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Common/Game/IW4/IW4_Assets.h b/src/Common/Game/IW4/IW4_Assets.h index 255664aee..aa8243c28 100644 --- a/src/Common/Game/IW4/IW4_Assets.h +++ b/src/Common/Game/IW4/IW4_Assets.h @@ -2084,8 +2084,8 @@ namespace IW4 float y; float w; float h; - char horzAlign; - char vertAlign; + unsigned char horzAlign; + unsigned char vertAlign; }; enum WindowDefStaticFlag : unsigned int diff --git a/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp b/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp index 7788d7575..24590a30c 100644 --- a/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp +++ b/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp @@ -34,8 +34,8 @@ namespace IW4 static_cast(rect.y), static_cast(rect.w), static_cast(rect.h), - static_cast(rect.horizontalAlign), - static_cast(rect.verticalAlign) + static_cast(rect.horizontalAlign), + static_cast(rect.verticalAlign) }; } @@ -46,8 +46,8 @@ namespace IW4 static_cast(rectRelativeTo.y + rect.y), static_cast(rect.w), static_cast(rect.h), - static_cast(rect.horizontalAlign), - static_cast(rect.verticalAlign) + static_cast(rect.horizontalAlign), + static_cast(rect.verticalAlign) }; } From fc570329374cd48170cad9e4f9ebcd26bf04a50e Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 23 Aug 2023 20:51:40 +0200 Subject: [PATCH 02/19] Add base for IW5 menu parsing based on IW4 menu parser --- src/Common/Game/IW5/IW5_Assets.h | 29 +- .../IW5/AssetLoaders/AssetLoaderMenuDef.cpp | 17 + .../IW5/AssetLoaders/AssetLoaderMenuDef.h | 14 + .../IW5/AssetLoaders/AssetLoaderMenuList.cpp | 207 +++ .../IW5/AssetLoaders/AssetLoaderMenuList.h | 18 + .../IW5/Menu/MenuConversionZoneStateIW5.cpp | 121 ++ .../IW5/Menu/MenuConversionZoneStateIW5.h | 39 + .../Game/IW5/Menu/MenuConverterIW5.cpp | 1146 +++++++++++++++++ .../Game/IW5/Menu/MenuConverterIW5.h | 26 + src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp | 6 +- 10 files changed, 1620 insertions(+), 3 deletions(-) create mode 100644 src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMenuDef.cpp create mode 100644 src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMenuDef.h create mode 100644 src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMenuList.cpp create mode 100644 src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMenuList.h create mode 100644 src/ObjLoading/Game/IW5/Menu/MenuConversionZoneStateIW5.cpp create mode 100644 src/ObjLoading/Game/IW5/Menu/MenuConversionZoneStateIW5.h create mode 100644 src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp create mode 100644 src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.h diff --git a/src/Common/Game/IW5/IW5_Assets.h b/src/Common/Game/IW5/IW5_Assets.h index 1cdd083f1..628ce47ff 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 000000000..d72739a78 --- /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 000000000..8bf0bf906 --- /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 000000000..10a75ace0 --- /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 000000000..3aa870410 --- /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 000000000..11d2614b1 --- /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 000000000..890c1dc63 --- /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 000000000..b469e8079 --- /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 000000000..3570ba790 --- /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 b67726dd0..77c4a48ae 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)) From ccc020ca7e297a922d4d379e067f88a2fcf1513a Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 23 Aug 2023 23:07:55 +0200 Subject: [PATCH 03/19] Make key handler map a multimap to be able to support multiple handlers for the same key --- src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp | 2 +- src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp | 2 +- src/ObjLoading/Parsing/Menu/Domain/CommonItemDef.h | 2 +- src/ObjLoading/Parsing/Menu/Domain/CommonMenuDef.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp b/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp index 24590a30c..2914194f1 100644 --- a/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp +++ b/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp @@ -661,7 +661,7 @@ namespace IW4 return outputSet; } - _NODISCARD ItemKeyHandler* ConvertKeyHandler(const std::map>& keyHandlers, const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const + _NODISCARD ItemKeyHandler* ConvertKeyHandler(const std::multimap>& keyHandlers, const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const { if (keyHandlers.empty()) return nullptr; diff --git a/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp b/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp index b469e8079..53a92840a 100644 --- a/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp +++ b/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp @@ -663,7 +663,7 @@ namespace IW5 return outputSet; } - _NODISCARD ItemKeyHandler* ConvertKeyHandler(const std::map>& keyHandlers, const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const + _NODISCARD ItemKeyHandler* ConvertKeyHandler(const std::multimap>& keyHandlers, const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const { if (keyHandlers.empty()) return nullptr; diff --git a/src/ObjLoading/Parsing/Menu/Domain/CommonItemDef.h b/src/ObjLoading/Parsing/Menu/Domain/CommonItemDef.h index 3e8bcc9a3..7adbd3a29 100644 --- a/src/ObjLoading/Parsing/Menu/Domain/CommonItemDef.h +++ b/src/ObjLoading/Parsing/Menu/Domain/CommonItemDef.h @@ -152,7 +152,7 @@ namespace menu std::unique_ptr m_on_mouse_exit_text; std::unique_ptr m_on_action; std::unique_ptr m_on_accept; - std::map> m_key_handlers; + std::multimap> m_key_handlers; std::unique_ptr m_list_box_features; std::unique_ptr m_edit_field_features; diff --git a/src/ObjLoading/Parsing/Menu/Domain/CommonMenuDef.h b/src/ObjLoading/Parsing/Menu/Domain/CommonMenuDef.h index e84a289ac..18663ff48 100644 --- a/src/ObjLoading/Parsing/Menu/Domain/CommonMenuDef.h +++ b/src/ObjLoading/Parsing/Menu/Domain/CommonMenuDef.h @@ -46,7 +46,7 @@ namespace menu std::unique_ptr m_on_close; std::unique_ptr m_on_request_close; std::unique_ptr m_on_esc; - std::map> m_key_handlers; + std::multimap> m_key_handlers; bool m_full_screen = false; bool m_screen_space = false; From 0d6e3e82985e1f14cd3da3fb502fa8e963548559 Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 23 Aug 2023 23:08:41 +0200 Subject: [PATCH 04/19] Dump outline color for IW4 menus --- src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp b/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp index 3f37fa0b5..a24789efc 100644 --- a/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp +++ b/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp @@ -770,6 +770,7 @@ void MenuDumper::WriteMenuData(const menuDef_t* menu) WriteColorProperty("forecolor", menu->window.foreColor, COLOR_1111); WriteColorProperty("bordercolor", menu->window.borderColor, COLOR_0000); WriteColorProperty("focuscolor", menu->focusColor, COLOR_0000); + WriteColorProperty("outlinecolor", menu->window.outlineColor, COLOR_0000); WriteMaterialProperty("background", menu->window.background); WriteIntProperty("ownerdraw", menu->window.ownerDraw, 0); WriteFlagsProperty("ownerdrawFlag", menu->window.ownerDrawFlags); From 8a1fe03358ddce991394a684ca5ce1c93c78255d Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 23 Aug 2023 23:08:50 +0200 Subject: [PATCH 05/19] Add base for IW5 menu parsing --- .../Game/IW5/Menu/MenuConverterIW5.cpp | 4 + .../Parsing/Menu/Domain/CommonItemDef.h | 3 + .../Parsing/Menu/Domain/CommonMenuDef.h | 2 + .../Menu/Sequence/ItemScopeSequences.cpp | 141 +++++++++++++----- .../Menu/Sequence/MenuScopeSequences.cpp | 26 +++- .../Game/IW5/Menu/MenuDumperIW5.cpp | 56 +++++-- src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.h | 1 + 7 files changed, 175 insertions(+), 58 deletions(-) diff --git a/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp b/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp index 53a92840a..1a2ff51d3 100644 --- a/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp +++ b/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp @@ -848,6 +848,7 @@ namespace IW5 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->elementHeightExp = ConvertOrApplyStatement(listBox->elementHeight, commonListBox->m_element_height_expression.get(), &parentMenu, &commonItem); listBox->numColumns = static_cast(std::min(std::extent_v, commonListBox->m_columns.size())); for (auto i = 0; i < listBox->numColumns; i++) @@ -971,6 +972,7 @@ namespace IW5 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->hasFocus = ConvertEventHandlerSet(commonItem.m_has_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); @@ -983,6 +985,7 @@ namespace IW5 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->textAlignYExp = ConvertOrApplyStatement(item->textaligny, commonItem.m_text_align_y_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); @@ -1103,6 +1106,7 @@ namespace IW5 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->onFocusDueToClose = ConvertEventHandlerSet(commonMenu.m_on_focus_due_to_close.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; diff --git a/src/ObjLoading/Parsing/Menu/Domain/CommonItemDef.h b/src/ObjLoading/Parsing/Menu/Domain/CommonItemDef.h index 7adbd3a29..8960b4a22 100644 --- a/src/ObjLoading/Parsing/Menu/Domain/CommonItemDef.h +++ b/src/ObjLoading/Parsing/Menu/Domain/CommonItemDef.h @@ -44,6 +44,7 @@ namespace menu std::string m_select_icon; std::unique_ptr m_on_double_click; + std::unique_ptr m_element_height_expression; std::vector m_columns; }; @@ -136,6 +137,7 @@ namespace menu std::unique_ptr m_visible_expression; std::unique_ptr m_disabled_expression; std::unique_ptr m_text_expression; + std::unique_ptr m_text_align_y_expression; std::unique_ptr m_material_expression; std::unique_ptr m_rect_x_exp; std::unique_ptr m_rect_y_exp; @@ -145,6 +147,7 @@ namespace menu ColorExpressions m_glowcolor_expressions; ColorExpressions m_backcolor_expressions; std::unique_ptr m_on_focus; + std::unique_ptr m_has_focus; std::unique_ptr m_on_leave_focus; std::unique_ptr m_on_mouse_enter; std::unique_ptr m_on_mouse_exit; diff --git a/src/ObjLoading/Parsing/Menu/Domain/CommonMenuDef.h b/src/ObjLoading/Parsing/Menu/Domain/CommonMenuDef.h index 18663ff48..43a8872bf 100644 --- a/src/ObjLoading/Parsing/Menu/Domain/CommonMenuDef.h +++ b/src/ObjLoading/Parsing/Menu/Domain/CommonMenuDef.h @@ -29,6 +29,7 @@ namespace menu int m_owner_draw = 0; int m_owner_draw_flags = 0; std::string m_sound_loop; + std::unique_ptr m_sound_loop_exp; double m_fade_clamp = 0; int m_fade_cycle = 0; double m_fade_amount = 0; @@ -46,6 +47,7 @@ namespace menu std::unique_ptr m_on_close; std::unique_ptr m_on_request_close; std::unique_ptr m_on_esc; + std::unique_ptr m_on_focus_due_to_close; std::multimap> m_key_handlers; bool m_full_screen = false; diff --git a/src/ObjLoading/Parsing/Menu/Sequence/ItemScopeSequences.cpp b/src/ObjLoading/Parsing/Menu/Sequence/ItemScopeSequences.cpp index 22a64e3a8..336b11823 100644 --- a/src/ObjLoading/Parsing/Menu/Sequence/ItemScopeSequences.cpp +++ b/src/ObjLoading/Parsing/Menu/Sequence/ItemScopeSequences.cpp @@ -45,6 +45,34 @@ class ItemScopeOperations CommonItemFeatureType::EDIT_FIELD // ITEM_TYPE_PASSWORDFIELD }; + inline static const CommonItemFeatureType IW5_FEATURE_TYPE_BY_TYPE[0x18] + { + CommonItemFeatureType::EDIT_FIELD, // ITEM_TYPE_TEXT + CommonItemFeatureType::NONE, // ITEM_TYPE_BUTTON + CommonItemFeatureType::NONE, // ITEM_TYPE_RADIOBUTTON + CommonItemFeatureType::NONE, // ITEM_TYPE_CHECKBOX + CommonItemFeatureType::EDIT_FIELD, // ITEM_TYPE_EDITFIELD + CommonItemFeatureType::NONE, // ITEM_TYPE_COMBO + CommonItemFeatureType::LISTBOX, // ITEM_TYPE_LISTBOX + CommonItemFeatureType::NONE, // ITEM_TYPE_MODEL + CommonItemFeatureType::NONE, // ITEM_TYPE_OWNERDRAW + CommonItemFeatureType::EDIT_FIELD, // ITEM_TYPE_NUMERICFIELD + CommonItemFeatureType::EDIT_FIELD, // ITEM_TYPE_SLIDER + CommonItemFeatureType::EDIT_FIELD, // ITEM_TYPE_YESNO + CommonItemFeatureType::MULTI_VALUE, // ITEM_TYPE_MULTI + CommonItemFeatureType::ENUM_DVAR, // ITEM_TYPE_DVARENUM + CommonItemFeatureType::EDIT_FIELD, // ITEM_TYPE_BIND + CommonItemFeatureType::NONE, // ITEM_TYPE_MENUMODEL + CommonItemFeatureType::EDIT_FIELD, // ITEM_TYPE_VALIDFILEFIELD + CommonItemFeatureType::EDIT_FIELD, // ITEM_TYPE_DECIMALFIELD + CommonItemFeatureType::EDIT_FIELD, // ITEM_TYPE_UPREDITFIELD + CommonItemFeatureType::NONE, // ITEM_TYPE_GAME_MESSAGE_WINDOW + CommonItemFeatureType::NEWS_TICKER, // ITEM_TYPE_NEWS_TICKER + CommonItemFeatureType::NONE, // ITEM_TYPE_TEXT_SCROLL + CommonItemFeatureType::EDIT_FIELD, // ITEM_TYPE_EMAILFIELD + CommonItemFeatureType::EDIT_FIELD // ITEM_TYPE_PASSWORDFIELD + }; + public: static void SetItemType(CommonItemDef& item, const FeatureLevel featureLevel, const TokenPos& pos, const int type) { @@ -66,8 +94,10 @@ class ItemScopeOperations case FeatureLevel::IW5: default: - assert(false); - throw ParsingException(pos, "Unimplemented item types for feature level"); + if (static_cast(type) >= std::extent_v) + throw ParsingException(pos, "Invalid item type"); + item.m_feature_type = IW5_FEATURE_TYPE_BY_TYPE[static_cast(type)]; + break; } switch (item.m_feature_type) @@ -431,26 +461,46 @@ namespace menu::item_scope_sequences { static constexpr auto CAPTURE_FIRST_TOKEN = 1; static constexpr auto CAPTURE_COLUMN_COUNT = 2; - static constexpr auto CAPTURE_POS = 3; - static constexpr auto CAPTURE_WIDTH = 4; - static constexpr auto CAPTURE_MAX_CHARS = 5; - static constexpr auto CAPTURE_ALIGNMENT = 6; + static constexpr auto CAPTURE_X_POS = 3; + static constexpr auto CAPTURE_Y_POS = 4; + static constexpr auto CAPTURE_WIDTH = 5; + static constexpr auto CAPTURE_HEIGHT = 6; + static constexpr auto CAPTURE_MAX_CHARS = 7; + static constexpr auto CAPTURE_ALIGNMENT = 8; public: - SequenceColumns() + explicit SequenceColumns(const FeatureLevel featureLevel) { const MenuMatcherFactory create(this); - AddMatchers({ - create.KeywordIgnoreCase("columns").Capture(CAPTURE_FIRST_TOKEN), - create.Integer().Capture(CAPTURE_COLUMN_COUNT), - create.Loop(create.And({ - create.Integer().Capture(CAPTURE_POS), - create.Integer().Capture(CAPTURE_WIDTH), - create.Integer().Capture(CAPTURE_MAX_CHARS), - create.Integer().Capture(CAPTURE_ALIGNMENT), - })), - }); + if (featureLevel == FeatureLevel::IW5) + { + AddMatchers({ + create.KeywordIgnoreCase("columns").Capture(CAPTURE_FIRST_TOKEN), + create.Integer().Capture(CAPTURE_COLUMN_COUNT), + create.Loop(create.And({ + create.Integer().Capture(CAPTURE_X_POS), + create.Integer().Capture(CAPTURE_Y_POS), + create.Integer().Capture(CAPTURE_WIDTH), + create.Integer().Capture(CAPTURE_HEIGHT), + create.Integer().Capture(CAPTURE_MAX_CHARS), + create.Integer().Capture(CAPTURE_ALIGNMENT), + })), + }); + } + else + { + AddMatchers({ + create.KeywordIgnoreCase("columns").Capture(CAPTURE_FIRST_TOKEN), + create.Integer().Capture(CAPTURE_COLUMN_COUNT), + create.Loop(create.And({ + create.Integer().Capture(CAPTURE_X_POS), + create.Integer().Capture(CAPTURE_WIDTH), + create.Integer().Capture(CAPTURE_MAX_CHARS), + create.Integer().Capture(CAPTURE_ALIGNMENT), + })), + }); + } } protected: @@ -461,14 +511,14 @@ namespace menu::item_scope_sequences ItemScopeOperations::EnsureHasListboxFeatures(*state->m_current_item, result.NextCapture(CAPTURE_FIRST_TOKEN).GetPos()); const auto& listBoxFeatures = state->m_current_item->m_list_box_features; - while (result.HasNextCapture(CAPTURE_POS)) + while (result.HasNextCapture(CAPTURE_X_POS)) { CommonItemFeaturesListBox::Column column { - result.NextCapture(CAPTURE_POS).IntegerValue(), - 0, + result.NextCapture(CAPTURE_X_POS).IntegerValue(), + state->m_feature_level == FeatureLevel::IW5 ? result.NextCapture(CAPTURE_Y_POS).IntegerValue() : 0, result.NextCapture(CAPTURE_WIDTH).IntegerValue(), - 0, + state->m_feature_level == FeatureLevel::IW5 ? result.NextCapture(CAPTURE_HEIGHT).IntegerValue() : 0, result.NextCapture(CAPTURE_MAX_CHARS).IntegerValue(), result.NextCapture(CAPTURE_ALIGNMENT).IntegerValue() }; @@ -681,36 +731,28 @@ void ItemScopeSequences::AddSequences(FeatureLevel featureLevel, bool permissive { state->m_current_item->m_background = value; })); - AddSequence(std::make_unique("onFocus", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& - { + AddSequence(std::make_unique("onFocus", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& { return state->m_current_item->m_on_focus; })); - AddSequence(std::make_unique("leaveFocus", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& - { + AddSequence(std::make_unique("leaveFocus", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& { return state->m_current_item->m_on_leave_focus; })); - AddSequence(std::make_unique("mouseEnter", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& - { + AddSequence(std::make_unique("mouseEnter", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& { return state->m_current_item->m_on_mouse_enter; })); - AddSequence(std::make_unique("mouseExit", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& - { + AddSequence(std::make_unique("mouseExit", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& { return state->m_current_item->m_on_mouse_exit; })); - AddSequence(std::make_unique("mouseEnterText", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& - { + AddSequence(std::make_unique("mouseEnterText", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& { return state->m_current_item->m_on_mouse_enter_text; })); - AddSequence(std::make_unique("mouseExitText", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& - { + AddSequence(std::make_unique("mouseExitText", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& { return state->m_current_item->m_on_mouse_exit_text; })); - AddSequence(std::make_unique("action", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& - { + AddSequence(std::make_unique("action", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& { return state->m_current_item->m_on_action; })); - AddSequence(std::make_unique("accept", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& - { + AddSequence(std::make_unique("accept", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& { return state->m_current_item->m_on_accept; })); // special @@ -854,8 +896,19 @@ void ItemScopeSequences::AddSequences(FeatureLevel featureLevel, bool permissive state->m_current_item->m_backcolor_expressions.m_rgb_exp = std::move(value); })); + if (featureLevel == FeatureLevel::IW5) + { + AddSequence(std::make_unique("hasFocus", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& { + return state->m_current_item->m_has_focus; + })); + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "textaligny"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + { + state->m_current_item->m_text_align_y_expression = std::move(value); + })); + } + // ============== ListBox ============== - AddSequence(std::make_unique()); + AddSequence(std::make_unique(featureLevel)); AddSequence(std::make_unique("notselectable", [](const MenuFileParserState* state, const TokenPos& pos) { ItemScopeOperations::EnsureHasListboxFeatures(*state->m_current_item, pos); @@ -891,8 +944,7 @@ void ItemScopeSequences::AddSequences(FeatureLevel featureLevel, bool permissive ItemScopeOperations::EnsureHasListboxFeatures(*state->m_current_item, pos); state->m_current_item->m_list_box_features->m_element_style = value; })); - AddSequence(std::make_unique("doubleclick", [](const MenuFileParserState* state, const TokenPos& pos) -> std::unique_ptr& - { + AddSequence(std::make_unique("doubleclick", [](const MenuFileParserState* state, const TokenPos& pos) -> std::unique_ptr& { ItemScopeOperations::EnsureHasListboxFeatures(*state->m_current_item, pos); return state->m_current_item->m_list_box_features->m_on_double_click; })); @@ -907,6 +959,15 @@ void ItemScopeSequences::AddSequences(FeatureLevel featureLevel, bool permissive state->m_current_item->m_list_box_features->m_select_icon = value; })); + if (featureLevel == FeatureLevel::IW5) + { + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "elementheight"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) + { + ItemScopeOperations::EnsureHasListboxFeatures(*state->m_current_item, pos); + state->m_current_item->m_list_box_features->m_element_height_expression = std::move(value); + })); + } + // ============== Edit Field ============== AddSequence(std::make_unique()); AddSequence(std::make_unique("localvar", [](const MenuFileParserState* state, const TokenPos& pos, const std::string& value) diff --git a/src/ObjLoading/Parsing/Menu/Sequence/MenuScopeSequences.cpp b/src/ObjLoading/Parsing/Menu/Sequence/MenuScopeSequences.cpp index 9f6a379ba..22a6a20ed 100644 --- a/src/ObjLoading/Parsing/Menu/Sequence/MenuScopeSequences.cpp +++ b/src/ObjLoading/Parsing/Menu/Sequence/MenuScopeSequences.cpp @@ -258,20 +258,16 @@ void MenuScopeSequences::AddSequences(FeatureLevel featureLevel, bool permissive { state->m_current_menu->m_visible_expression = std::move(value); })); - AddSequence(std::make_unique("onOpen", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& - { + AddSequence(std::make_unique("onOpen", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& { return state->m_current_menu->m_on_open; })); - AddSequence(std::make_unique("onClose", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& - { + AddSequence(std::make_unique("onClose", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& { return state->m_current_menu->m_on_close; })); - AddSequence(std::make_unique("onRequestClose", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& - { + AddSequence(std::make_unique("onRequestClose", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& { return state->m_current_menu->m_on_request_close; })); - AddSequence(std::make_unique("onESC", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& - { + AddSequence(std::make_unique("onESC", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& { return state->m_current_menu->m_on_esc; })); AddSequence(std::make_unique("border", [](const MenuFileParserState* state, const TokenPos&, const int value) @@ -396,5 +392,19 @@ void MenuScopeSequences::AddSequences(FeatureLevel featureLevel, bool permissive { state->m_current_menu->m_text_only_focus = true; })); + + + if (featureLevel == FeatureLevel::IW5) + { + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "soundLoop"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + { + state->m_current_menu->m_sound_loop_exp = std::move(value); + })); + AddSequence(std::make_unique("onFocusDueToClose", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& { + return state->m_current_menu->m_on_focus_due_to_close; + })); + } + + AddSequence(std::make_unique()); } diff --git a/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp b/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp index 11898b548..5ca1cce1d 100644 --- a/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp +++ b/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp @@ -492,6 +492,31 @@ void MenuDumper::WriteFloatExpressionsProperty(const ItemFloatExpression* floatE } } +void MenuDumper::WriteMultiTokenStringProperty(const std::string& propertyKey, const char* value) const +{ + if (!value) + return; + + Indent(); + WriteKey(propertyKey); + + const auto tokenList = CreateScriptTokenList(value); + + auto firstToken = true; + m_stream << "{ "; + for (const auto& token : tokenList) + { + if (firstToken) + firstToken = false; + else + m_stream << ";"; + m_stream << "\"" << token << "\""; + } + if (!firstToken) + m_stream << " "; + m_stream << "}\n"; +} + void MenuDumper::WriteColumnProperty(const std::string& propertyKey, const listBoxDef_s* listBox) const { if (listBox->numColumns <= 0) @@ -533,7 +558,7 @@ void MenuDumper::WriteListBoxProperties(const itemDef_s* item) WriteMenuEventHandlerSetProperty("doubleclick", listBox->onDoubleClick); WriteColorProperty("selectBorder", listBox->selectBorder, COLOR_0000); WriteMaterialProperty("selectIcon", listBox->selectIcon); - WriteStatementProperty("exp elementHeight", listBox->elementHeightExp, false); + WriteStatementProperty("exp elementheight", listBox->elementHeightExp, false); } void MenuDumper::WriteDvarFloatProperty(const std::string& propertyKey, const itemDef_s* item, const editFieldDef_s* editField) const @@ -659,9 +684,15 @@ void MenuDumper::WriteItemData(const itemDef_s* item) WriteIntProperty("type", item->type, ITEM_TYPE_TEXT); WriteIntProperty("border", item->window.border, 0); WriteFloatProperty("borderSize", item->window.borderSize, 0.0f); - WriteStatementProperty("visible", item->visibleExp, true); + + if (item->visibleExp) + WriteStatementProperty("visible", item->visibleExp, true); + else if (item->window.dynamicFlags[0] & WINDOW_FLAG_VISIBLE) + WriteIntProperty("visible", 1, 0); + WriteStatementProperty("disabled", item->disabledExp, true); - WriteIntProperty("ownerDraw", item->window.ownerDraw, 0); + WriteIntProperty("ownerdraw", item->window.ownerDraw, 0); + WriteFlagsProperty("ownerdrawFlag", item->window.ownerDrawFlags); WriteIntProperty("align", item->alignment, 0); WriteIntProperty("textalign", item->textAlignMode, 0); WriteFloatProperty("textalignx", item->textalignx, 0.0f); @@ -687,19 +718,18 @@ void MenuDumper::WriteItemData(const itemDef_s* item) WriteMenuEventHandlerSetProperty("accept", item->accept); // WriteFloatProperty("special", item->special, 0.0f); WriteSoundAliasProperty("focusSound", item->focusSound); - WriteFlagsProperty("ownerdrawFlag", item->window.ownerDrawFlags); WriteStringProperty("dvarTest", item->dvarTest); if (item->dvarFlags & ITEM_DVAR_FLAG_ENABLE) - WriteStringProperty("enableDvar", item->enableDvar); + WriteMultiTokenStringProperty("enableDvar", item->enableDvar); else if (item->dvarFlags & ITEM_DVAR_FLAG_DISABLE) - WriteStringProperty("disableDvar", item->enableDvar); + WriteMultiTokenStringProperty("disableDvar", item->enableDvar); else if (item->dvarFlags & ITEM_DVAR_FLAG_SHOW) - WriteStringProperty("showDvar", item->enableDvar); + WriteMultiTokenStringProperty("showDvar", item->enableDvar); else if (item->dvarFlags & ITEM_DVAR_FLAG_HIDE) - WriteStringProperty("hideDvar", item->enableDvar); + WriteMultiTokenStringProperty("hideDvar", item->enableDvar); else if (item->dvarFlags & ITEM_DVAR_FLAG_FOCUS) - WriteStringProperty("focusDvar", item->enableDvar); + WriteMultiTokenStringProperty("focusDvar", item->enableDvar); WriteItemKeyHandlerProperty(item->onKey); WriteStatementProperty("exp text", item->textExp, false); @@ -744,6 +774,7 @@ void MenuDumper::WriteMenuData(const menuDef_t* menu) WriteColorProperty("forecolor", menu->window.foreColor, COLOR_1111); WriteColorProperty("bordercolor", menu->window.borderColor, COLOR_0000); WriteColorProperty("focuscolor", menu->data->focusColor, COLOR_0000); + WriteColorProperty("outlinecolor", menu->window.outlineColor, COLOR_0000); WriteMaterialProperty("background", menu->window.background); WriteIntProperty("ownerdraw", menu->window.ownerDraw, 0); WriteFlagsProperty("ownerdrawFlag", menu->window.ownerDrawFlags); @@ -761,7 +792,12 @@ void MenuDumper::WriteMenuData(const menuDef_t* menu) WriteKeywordProperty("hiddenDuringUI", menu->window.staticFlags & WINDOW_FLAG_HIDDEN_DURING_UI); WriteStringProperty("allowedBinding", menu->data->allowedBinding); WriteKeywordProperty("textOnlyFocus", menu->window.staticFlags & WINDOW_FLAG_TEXT_ONLY_FOCUS); - WriteStatementProperty("visible", menu->data->visibleExp, true); + + if (menu->data->visibleExp) + WriteStatementProperty("visible", menu->data->visibleExp, true); + else if (menu->window.dynamicFlags[0] & WINDOW_FLAG_VISIBLE) + WriteIntProperty("visible", 1, 0); + WriteStatementProperty("exp rect X", menu->data->rectXExp, false); WriteStatementProperty("exp rect Y", menu->data->rectYExp, false); WriteStatementProperty("exp rect W", menu->data->rectWExp, false); diff --git a/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.h b/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.h index 1ead711fb..b9fe76a88 100644 --- a/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.h +++ b/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.h @@ -29,6 +29,7 @@ namespace IW5 void WriteSoundAliasProperty(const std::string& propertyKey, const snd_alias_list_t* soundAliasValue) const; void WriteDecodeEffectProperty(const std::string& propertyKey, const itemDef_s* item) const; void WriteItemKeyHandlerProperty(const ItemKeyHandler* itemKeyHandlerValue); + void WriteMultiTokenStringProperty(const std::string& propertyKey, const char* value) const; void WriteFloatExpressionsProperty(const ItemFloatExpression* floatExpressions, int floatExpressionCount) const; void WriteColumnProperty(const std::string& propertyKey, const listBoxDef_s* listBox) const; From aea76d0b423f88b137f0399a367170450f3ad71c Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 24 Aug 2023 19:36:37 +0200 Subject: [PATCH 06/19] Escape string values when menu dumping --- .../Game/IW4/Menu/MenuDumperIW4.cpp | 2 +- .../Game/IW5/Menu/MenuDumperIW5.cpp | 2 +- src/ObjWriting/Menu/AbstractMenuDumper.cpp | 40 ++++++++++++++++++- src/ObjWriting/Menu/AbstractMenuDumper.h | 2 + 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp b/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp index a24789efc..3007b2a0f 100644 --- a/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp +++ b/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp @@ -193,7 +193,7 @@ void MenuDumper::WriteStatementOperand(const Statement_s* statement, size_t& cur break; case VAL_STRING: - m_stream << "\"" << operand.internals.stringVal.string << "\""; + WriteEscapedString(operand.internals.stringVal.string); break; case VAL_FUNCTION: diff --git a/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp b/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp index 5ca1cce1d..fca64e5cd 100644 --- a/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp +++ b/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp @@ -192,7 +192,7 @@ void MenuDumper::WriteStatementOperand(const Statement_s* statement, size_t& cur break; case VAL_STRING: - m_stream << "\"" << operand.internals.stringVal.string << "\""; + WriteEscapedString(operand.internals.stringVal.string); break; case VAL_FUNCTION: diff --git a/src/ObjWriting/Menu/AbstractMenuDumper.cpp b/src/ObjWriting/Menu/AbstractMenuDumper.cpp index 60fef6c91..914926d2d 100644 --- a/src/ObjWriting/Menu/AbstractMenuDumper.cpp +++ b/src/ObjWriting/Menu/AbstractMenuDumper.cpp @@ -130,6 +130,38 @@ bool AbstractMenuDumper::DoesTokenNeedQuotationMarks(const std::string& token) return hasNonIdentifierCharacter; } +void AbstractMenuDumper::WriteEscapedString(const std::string_view& str) const +{ + m_stream << "\""; + + for (const auto& c : str) + { + switch (c) + { + case '\r': + m_stream << "\\r"; + break; + case '\n': + m_stream << "\\n"; + break; + case '\t': + m_stream << "\\t"; + break; + case '\f': + m_stream << "\\f"; + break; + case '"': + m_stream << "\\\""; + break; + default: + m_stream << c; + break; + } + } + + m_stream << "\""; +} + const std::string& AbstractMenuDumper::BoolValue(const bool value) { return value ? BOOL_VALUE_TRUE : BOOL_VALUE_FALSE; @@ -154,7 +186,9 @@ void AbstractMenuDumper::WriteStringProperty(const std::string& propertyKey, con Indent(); WriteKey(propertyKey); - m_stream << "\"" << propertyValue << "\"\n"; + + WriteEscapedString(propertyValue); + m_stream << "\n"; } void AbstractMenuDumper::WriteStringProperty(const std::string& propertyKey, const char* propertyValue) const @@ -164,7 +198,9 @@ void AbstractMenuDumper::WriteStringProperty(const std::string& propertyKey, con Indent(); WriteKey(propertyKey); - m_stream << "\"" << propertyValue << "\"\n"; + + WriteEscapedString(propertyValue); + m_stream << "\n"; } void AbstractMenuDumper::WriteBoolProperty(const std::string& propertyKey, const bool propertyValue, const bool defaultValue) const diff --git a/src/ObjWriting/Menu/AbstractMenuDumper.h b/src/ObjWriting/Menu/AbstractMenuDumper.h index 669586e61..5a826d057 100644 --- a/src/ObjWriting/Menu/AbstractMenuDumper.h +++ b/src/ObjWriting/Menu/AbstractMenuDumper.h @@ -30,6 +30,8 @@ class AbstractMenuDumper static std::vector CreateScriptTokenList(const char* script); static bool DoesTokenNeedQuotationMarks(const std::string& token); + void WriteEscapedString(const std::string_view& str) const; + static const std::string& BoolValue(bool value); void WriteKey(const std::string& keyName) const; void WriteStringProperty(const std::string& propertyKey, const std::string& propertyValue) const; From 4829a4206b281a1d61d0892dc6d2fb1533102aee Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 24 Aug 2023 20:12:57 +0200 Subject: [PATCH 07/19] Ensure correct expression type for static expressions in menu parsing but do not enforce when permissive --- .../Game/IW4/Menu/MenuConverterIW4.cpp | 3 +- .../Game/IW5/Menu/MenuConverterIW5.cpp | 3 +- .../Parsing/Menu/MenuFileCommonOperations.cpp | 27 +++++++ .../Parsing/Menu/MenuFileCommonOperations.h | 14 ++++ .../Menu/Sequence/ItemScopeSequences.cpp | 81 ++++++++++++------- .../Menu/Sequence/MenuScopeSequences.cpp | 25 ++++-- 6 files changed, 114 insertions(+), 39 deletions(-) create mode 100644 src/ObjLoading/Parsing/Menu/MenuFileCommonOperations.cpp create mode 100644 src/ObjLoading/Parsing/Menu/MenuFileCommonOperations.h diff --git a/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp b/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp index 2914194f1..ecd91299d 100644 --- a/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp +++ b/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp @@ -751,7 +751,8 @@ namespace IW4 continue; } - assert(false); + // 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; } diff --git a/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp b/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp index 1a2ff51d3..3da84150f 100644 --- a/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp +++ b/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp @@ -753,7 +753,8 @@ namespace IW5 continue; } - assert(false); + // 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; } diff --git a/src/ObjLoading/Parsing/Menu/MenuFileCommonOperations.cpp b/src/ObjLoading/Parsing/Menu/MenuFileCommonOperations.cpp new file mode 100644 index 000000000..48abf663a --- /dev/null +++ b/src/ObjLoading/Parsing/Menu/MenuFileCommonOperations.cpp @@ -0,0 +1,27 @@ +#include "MenuFileCommonOperations.h" + +#include "Parsing/ParsingException.h" + +using namespace menu; + +void MenuFileCommonOperations::EnsureIsNumericExpression(const MenuFileParserState* state, const TokenPos& pos, const ISimpleExpression& expression) +{ + if (!state->m_permissive_mode && expression.IsStatic()) + { + const auto staticValue = expression.EvaluateStatic(); + + if (staticValue.m_type != SimpleExpressionValue::Type::INT && staticValue.m_type != SimpleExpressionValue::Type::DOUBLE) + throw ParsingException(pos, "Expression is expected to be numeric. Use permissive mode to compile anyway."); + } +} + +void MenuFileCommonOperations::EnsureIsStringExpression(const MenuFileParserState* state, const TokenPos& pos, const ISimpleExpression& expression) +{ + if (!state->m_permissive_mode && expression.IsStatic()) + { + const auto staticValue = expression.EvaluateStatic(); + + if (staticValue.m_type != SimpleExpressionValue::Type::STRING) + throw ParsingException(pos, "Expression is expected to be string. Use permissive mode to compile anyway."); + } +} \ No newline at end of file diff --git a/src/ObjLoading/Parsing/Menu/MenuFileCommonOperations.h b/src/ObjLoading/Parsing/Menu/MenuFileCommonOperations.h new file mode 100644 index 000000000..b56c83b56 --- /dev/null +++ b/src/ObjLoading/Parsing/Menu/MenuFileCommonOperations.h @@ -0,0 +1,14 @@ +#pragma once +#include "MenuFileParserState.h" +#include "Parsing/TokenPos.h" +#include "Parsing/Simple/Expression/ISimpleExpression.h" + +namespace menu +{ + class MenuFileCommonOperations + { + public: + static void EnsureIsNumericExpression(const MenuFileParserState* state, const TokenPos& pos, const ISimpleExpression& expression); + static void EnsureIsStringExpression(const MenuFileParserState* state, const TokenPos& pos, const ISimpleExpression& expression); + }; +} diff --git a/src/ObjLoading/Parsing/Menu/Sequence/ItemScopeSequences.cpp b/src/ObjLoading/Parsing/Menu/Sequence/ItemScopeSequences.cpp index 336b11823..cb94bf8c6 100644 --- a/src/ObjLoading/Parsing/Menu/Sequence/ItemScopeSequences.cpp +++ b/src/ObjLoading/Parsing/Menu/Sequence/ItemScopeSequences.cpp @@ -10,6 +10,7 @@ #include "Generic/GenericKeywordPropertySequence.h" #include "Generic/GenericMenuEventHandlerSetPropertySequence.h" #include "Generic/GenericStringPropertySequence.h" +#include "Parsing/Menu/MenuFileCommonOperations.h" #include "Parsing/Menu/Matcher/MenuExpressionMatchers.h" #include "Parsing/Menu/Matcher/MenuMatcherFactory.h" @@ -663,12 +664,14 @@ void ItemScopeSequences::AddSequences(FeatureLevel featureLevel, bool permissive { state->m_current_item->m_border_size = value; })); - AddSequence(GenericExpressionPropertySequence::WithKeywordAndBool("visible", [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywordAndBool("visible", [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_visible_expression = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywordAndBool("disabled", [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywordAndBool("disabled", [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_disabled_expression = std::move(value); })); AddSequence(std::make_unique("ownerdraw", [](const MenuFileParserState* state, const TokenPos&, const int value) @@ -803,96 +806,114 @@ void ItemScopeSequences::AddSequences(FeatureLevel featureLevel, bool permissive state->m_current_item->m_game_message_window_mode = value; })); AddSequence(std::make_unique()); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "disabled"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "disabled"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_disabled_expression = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "text"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "text"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsStringExpression(state, pos, *value); state->m_current_item->m_text_expression = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "material"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "material"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsStringExpression(state, pos, *value); state->m_current_item->m_material_expression = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "material"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) - { - state->m_current_item->m_material_expression = std::move(value); - })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "X"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "X"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_rect_x_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "Y"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "Y"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_rect_y_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "W"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "W"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_rect_w_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "H"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "H"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_rect_h_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "forecolor", "R"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "forecolor", "R"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_forecolor_expressions.m_r_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "forecolor", "G"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "forecolor", "G"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_forecolor_expressions.m_g_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "forecolor", "B"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "forecolor", "B"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_forecolor_expressions.m_b_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "forecolor", "A"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "forecolor", "A"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_forecolor_expressions.m_a_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "forecolor", "RGB"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "forecolor", "RGB"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_forecolor_expressions.m_rgb_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "glowcolor", "R"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "glowcolor", "R"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_glowcolor_expressions.m_r_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "glowcolor", "G"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "glowcolor", "G"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_glowcolor_expressions.m_g_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "glowcolor", "B"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "glowcolor", "B"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_glowcolor_expressions.m_b_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "glowcolor", "A"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "glowcolor", "A"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_glowcolor_expressions.m_a_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "glowcolor", "RGB"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "glowcolor", "RGB"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_glowcolor_expressions.m_rgb_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "backcolor", "R"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "backcolor", "R"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_backcolor_expressions.m_r_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "backcolor", "G"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "backcolor", "G"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_backcolor_expressions.m_g_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "backcolor", "B"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "backcolor", "B"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_backcolor_expressions.m_b_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "backcolor", "A"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "backcolor", "A"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_backcolor_expressions.m_a_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "backcolor", "RGB"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "backcolor", "RGB"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_backcolor_expressions.m_rgb_exp = std::move(value); })); @@ -901,8 +922,9 @@ void ItemScopeSequences::AddSequences(FeatureLevel featureLevel, bool permissive AddSequence(std::make_unique("hasFocus", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& { return state->m_current_item->m_has_focus; })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "textaligny"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "textaligny"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_text_align_y_expression = std::move(value); })); } @@ -964,6 +986,7 @@ void ItemScopeSequences::AddSequences(FeatureLevel featureLevel, bool permissive AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "elementheight"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { ItemScopeOperations::EnsureHasListboxFeatures(*state->m_current_item, pos); + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_item->m_list_box_features->m_element_height_expression = std::move(value); })); } diff --git a/src/ObjLoading/Parsing/Menu/Sequence/MenuScopeSequences.cpp b/src/ObjLoading/Parsing/Menu/Sequence/MenuScopeSequences.cpp index 22a6a20ed..cbfbbed64 100644 --- a/src/ObjLoading/Parsing/Menu/Sequence/MenuScopeSequences.cpp +++ b/src/ObjLoading/Parsing/Menu/Sequence/MenuScopeSequences.cpp @@ -10,6 +10,7 @@ #include "Generic/GenericKeywordPropertySequence.h" #include "Generic/GenericMenuEventHandlerSetPropertySequence.h" #include "Generic/GenericStringPropertySequence.h" +#include "Parsing/Menu/MenuFileCommonOperations.h" #include "Parsing/Menu/Matcher/MenuMatcherFactory.h" #include "Parsing/Menu/Domain/CommonMenuTypes.h" #include "Parsing/Menu/Matcher/MenuExpressionMatchers.h" @@ -254,8 +255,9 @@ void MenuScopeSequences::AddSequences(FeatureLevel featureLevel, bool permissive { state->m_current_menu->m_style = value; })); - AddSequence(GenericExpressionPropertySequence::WithKeywordAndBool("visible", [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywordAndBool("visible", [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_menu->m_visible_expression = std::move(value); })); AddSequence(std::make_unique("onOpen", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& { @@ -318,28 +320,34 @@ void MenuScopeSequences::AddSequences(FeatureLevel featureLevel, bool permissive { state->m_current_menu->m_sound_loop = value; })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "X"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "X"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_menu->m_rect_x_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "Y"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "Y"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_menu->m_rect_y_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "W"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "W"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_menu->m_rect_w_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "H"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "rect", "H"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsNumericExpression(state, pos, *value); state->m_current_menu->m_rect_h_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "openSound"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "openSound"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsStringExpression(state, pos, *value); state->m_current_menu->m_open_sound_exp = std::move(value); })); - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "closeSound"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "closeSound"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsStringExpression(state, pos, *value); state->m_current_menu->m_close_sound_exp = std::move(value); })); AddSequence(std::make_unique("popup", [](const MenuFileParserState* state, const TokenPos&) @@ -396,8 +404,9 @@ void MenuScopeSequences::AddSequences(FeatureLevel featureLevel, bool permissive if (featureLevel == FeatureLevel::IW5) { - AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "soundLoop"}, [](const MenuFileParserState* state, const TokenPos&, std::unique_ptr value) + AddSequence(GenericExpressionPropertySequence::WithKeywords({"exp", "soundLoop"}, [](const MenuFileParserState* state, const TokenPos& pos, std::unique_ptr value) { + MenuFileCommonOperations::EnsureIsStringExpression(state, pos, *value); state->m_current_menu->m_sound_loop_exp = std::move(value); })); AddSequence(std::make_unique("onFocusDueToClose", [](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr& { From cd5a5c28987e20cb2e6104011faff67decfd6de1 Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 25 Aug 2023 00:38:35 +0200 Subject: [PATCH 08/19] Add missing parenthesis for custom functions when dumping menus --- src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp | 4 ++-- src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp b/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp index 3007b2a0f..f6d53566d 100644 --- a/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp +++ b/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp @@ -137,7 +137,7 @@ void MenuDumper::WriteStatementOperator(const Statement_s* statement, size_t& cu } } -void MenuDumper::WriteStatementOperandFunction(const Statement_s* statement, size_t currentPos) const +void MenuDumper::WriteStatementOperandFunction(const Statement_s* statement, const size_t currentPos) const { const auto& operand = statement->entries[currentPos].data.operand; @@ -160,7 +160,7 @@ void MenuDumper::WriteStatementOperandFunction(const Statement_s* statement, siz } if (functionIndex >= 0) - m_stream << "FUNC_" << functionIndex; + m_stream << "FUNC_" << functionIndex << "()"; else m_stream << "INVALID_FUNC"; m_stream << "()"; diff --git a/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp b/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp index fca64e5cd..4eb52115a 100644 --- a/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp +++ b/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp @@ -137,7 +137,7 @@ void MenuDumper::WriteStatementOperator(const Statement_s* statement, size_t& cu } } -void MenuDumper::WriteStatementOperandFunction(const Statement_s* statement, size_t currentPos) const +void MenuDumper::WriteStatementOperandFunction(const Statement_s* statement, const size_t currentPos) const { const auto& operand = statement->entries[currentPos].data.operand; @@ -160,7 +160,7 @@ void MenuDumper::WriteStatementOperandFunction(const Statement_s* statement, siz } if (functionIndex >= 0) - m_stream << "FUNC_" << functionIndex; + m_stream << "FUNC_" << functionIndex << "()"; else m_stream << "INVALID_FUNC"; } From 7d0abaf2563f98d3df5bb63f15ef258fcbdaee12 Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 25 Aug 2023 19:22:25 +0200 Subject: [PATCH 09/19] Fix not setting menuData when converting IW5 menus --- src/Common/Game/IW5/IW5_Assets.h | 8 ++++---- src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Common/Game/IW5/IW5_Assets.h b/src/Common/Game/IW5/IW5_Assets.h index 628ce47ff..9b073b0d2 100644 --- a/src/Common/Game/IW5/IW5_Assets.h +++ b/src/Common/Game/IW5/IW5_Assets.h @@ -2568,9 +2568,9 @@ namespace IW5 WINDOW_FLAG_AUTO_WRAPPED = 0x800000, WINDOW_FLAG_POPUP = 0x1000000, WINDOW_FLAG_LEGACY_SPLIT_SCREEN_SCALE = 0x4000000, - WINDOW_FLAG_HIDDEN_DURING_FLASH_BANG = 0x10000000, - WINDOW_FLAG_HIDDEN_DURING_SCOPE = 0x20000000, - WINDOW_FLAG_HIDDEN_DURING_UI = 0x40000000, + WINDOW_FLAG_HIDDEN_DURING_FLASH_BANG = 0x10000000, // confirmed + WINDOW_FLAG_HIDDEN_DURING_SCOPE = 0x20000000, // confirmed + WINDOW_FLAG_HIDDEN_DURING_UI = 0x40000000, // confirmed WINDOW_FLAG_TEXT_ONLY_FOCUS = 0x80000000, }; @@ -2579,7 +2579,7 @@ namespace IW5 { WINDOW_FLAG_HOVERED = 0x1, // guessed WINDOW_FLAG_FOCUSED = 0x2, - WINDOW_FLAG_VISIBLE = 0x4, + WINDOW_FLAG_VISIBLE = 0x4, // confirmed WINDOW_FLAG_FADING_OUT = 0x10, WINDOW_FLAG_FADING_IN = 0x20, WINDOW_FLAG_80 = 0x80, diff --git a/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp b/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp index 3da84150f..d056c7b37 100644 --- a/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp +++ b/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp @@ -1067,6 +1067,7 @@ namespace IW5 auto* menuData = m_memory->Create(); memset(menu, 0, sizeof(menuDef_t)); + menu->data = menuData; 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); From 88bc1c1056b29688a2460128c6ac98a7645cfbfd Mon Sep 17 00:00:00 2001 From: Jan Date: Sat, 23 Sep 2023 14:28:59 +0200 Subject: [PATCH 10/19] Fix SimpleLexer not being able to read escaped strings --- .../Parsing/Menu/MenuFileReader.cpp | 1 + src/Parser/Parsing/Impl/AbstractLexer.h | 48 ++++++++++ src/Parser/Parsing/Simple/SimpleLexer.cpp | 6 +- src/Parser/Parsing/Simple/SimpleLexer.h | 1 + src/Utils/Utils/StringUtils.cpp | 91 +++++++++++++++++++ src/Utils/Utils/StringUtils.h | 10 ++ 6 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 src/Utils/Utils/StringUtils.cpp create mode 100644 src/Utils/Utils/StringUtils.h diff --git a/src/ObjLoading/Parsing/Menu/MenuFileReader.cpp b/src/ObjLoading/Parsing/Menu/MenuFileReader.cpp index edbc58d38..d64eff638 100644 --- a/src/ObjLoading/Parsing/Menu/MenuFileReader.cpp +++ b/src/ObjLoading/Parsing/Menu/MenuFileReader.cpp @@ -129,6 +129,7 @@ std::unique_ptr MenuFileReader::ReadMenuFile() SimpleLexer::Config lexerConfig; lexerConfig.m_emit_new_line_tokens = false; lexerConfig.m_read_strings = true; + lexerConfig.m_string_escape_sequences = true; lexerConfig.m_read_integer_numbers = true; lexerConfig.m_read_floating_point_numbers = true; MenuExpressionMatchers().ApplyTokensToLexerConfig(lexerConfig); diff --git a/src/Parser/Parsing/Impl/AbstractLexer.h b/src/Parser/Parsing/Impl/AbstractLexer.h index dbf1b12b4..dfbb91c8f 100644 --- a/src/Parser/Parsing/Impl/AbstractLexer.h +++ b/src/Parser/Parsing/Impl/AbstractLexer.h @@ -2,11 +2,13 @@ #include #include +#include #include "Utils/ClassUtils.h" #include "Parsing/ILexer.h" #include "Parsing/IParserLineStream.h" #include "Parsing/ParsingException.h" +#include "Utils/StringUtils.h" template class AbstractLexer : public ILexer @@ -140,6 +142,52 @@ class AbstractLexer : public ILexer return std::string(currentLine.m_line, startPos, m_current_line_offset - startPos); } + /** + * \brief Reads an identifier from the current position + * \return The value of the read identifier + */ + std::string ReadStringWithEscapeSequences() + { + const auto& currentLine = CurrentLine(); + assert(m_current_line_offset >= 1); + assert(currentLine.m_line[m_current_line_offset - 1] == '"'); + + const auto startPos = m_current_line_offset; + const auto lineSize = currentLine.m_line.size(); + auto isEscaped = false; + auto inEscape = false; + while (true) + { + if (m_current_line_offset >= lineSize) + throw ParsingException(TokenPos(*currentLine.m_filename, currentLine.m_line_number, m_current_line_offset), "Unclosed string"); + + const auto c = currentLine.m_line[m_current_line_offset]; + + if (c == '\"' && !inEscape) + break; + + if (c == '\\' && !inEscape) + { + isEscaped = true; + inEscape = true; + } + else + { + inEscape = false; + } + + m_current_line_offset++; + } + + std::string str(currentLine.m_line, startPos, m_current_line_offset++ - startPos); + if (!isEscaped) + return str; + + std::ostringstream ss; + utils::UnescapeStringFromQuotationMarks(ss, std::move(str)); + return ss.str(); + } + /** * \brief Reads an identifier from the current position * \return The value of the read identifier diff --git a/src/Parser/Parsing/Simple/SimpleLexer.cpp b/src/Parser/Parsing/Simple/SimpleLexer.cpp index b5e0775b5..719bad83c 100644 --- a/src/Parser/Parsing/Simple/SimpleLexer.cpp +++ b/src/Parser/Parsing/Simple/SimpleLexer.cpp @@ -14,7 +14,7 @@ SimpleLexer::MultiCharacterTokenLookupEntry::MultiCharacterTokenLookupEntry(cons SimpleLexer::SimpleLexer(IParserLineStream* stream) : AbstractLexer(stream), - m_config{false, true, true, true, {}}, + m_config{false, true, false, true, true, {}}, m_check_for_multi_character_tokens(false), m_last_line(1) { @@ -31,7 +31,7 @@ SimpleLexer::SimpleLexer(IParserLineStream* stream, Config config) m_config.m_multi_character_tokens.clear(); // If reading floating point numbers then must be reading integers - assert(config.m_read_floating_point_numbers == false || config.m_read_floating_point_numbers == config.m_read_integer_numbers); + assert(m_config.m_read_floating_point_numbers == false || m_config.m_read_floating_point_numbers == m_config.m_read_integer_numbers); } void SimpleLexer::AddMultiCharacterTokenConfigToLookup(Config::MultiCharacterToken tokenConfig) @@ -121,7 +121,7 @@ SimpleParserValue SimpleLexer::GetNextToken() } if (m_config.m_read_strings && c == '\"') - return SimpleParserValue::String(pos, new std::string(ReadString())); + return SimpleParserValue::String(pos, new std::string(m_config.m_string_escape_sequences ? ReadStringWithEscapeSequences() : ReadString())); if (m_config.m_read_integer_numbers && (isdigit(c) || (c == '+' || c == '-' || (m_config.m_read_floating_point_numbers && c == '.')) && isdigit(PeekChar()))) { diff --git a/src/Parser/Parsing/Simple/SimpleLexer.h b/src/Parser/Parsing/Simple/SimpleLexer.h index 7563e2db1..8a4315d58 100644 --- a/src/Parser/Parsing/Simple/SimpleLexer.h +++ b/src/Parser/Parsing/Simple/SimpleLexer.h @@ -24,6 +24,7 @@ class SimpleLexer : public AbstractLexer bool m_emit_new_line_tokens; bool m_read_strings; + bool m_string_escape_sequences; bool m_read_integer_numbers; bool m_read_floating_point_numbers; std::vector m_multi_character_tokens; diff --git a/src/Utils/Utils/StringUtils.cpp b/src/Utils/Utils/StringUtils.cpp new file mode 100644 index 000000000..e7f84d9a5 --- /dev/null +++ b/src/Utils/Utils/StringUtils.cpp @@ -0,0 +1,91 @@ +#include "StringUtils.h" + +#include + +namespace utils +{ + std::string EscapeStringForQuotationMarks(const std::string_view& str) + { + std::ostringstream ss; + EscapeStringForQuotationMarks(ss, str); + return ss.str(); + } + + void EscapeStringForQuotationMarks(std::ostream& stream, const std::string_view& str) + { + for (const auto& c : str) + { + switch (c) + { + case '\r': + stream << "\\r"; + break; + case '\n': + stream << "\\n"; + break; + case '\t': + stream << "\\t"; + break; + case '\f': + stream << "\\f"; + break; + case '"': + stream << "\\\""; + break; + case '\\': + stream << "\\\\"; + break; + default: + stream << c; + break; + } + } + } + + std::string UnescapeStringFromQuotationMarks(const std::string_view& str) + { + std::ostringstream ss; + UnescapeStringFromQuotationMarks(ss, str); + return ss.str(); + } + + void UnescapeStringFromQuotationMarks(std::ostream& stream, const std::string_view& str) + { + auto inEscape = false; + for (const auto& c : str) + { + if (inEscape) + { + switch (c) + { + case 'r': + stream << "\r"; + break; + case 'n': + stream << "\n"; + break; + case 't': + stream << "\t"; + break; + case 'f': + stream << "\f"; + break; + case '"': + stream << "\""; + break; + case '\\': + stream << "\\"; + break; + default: + stream << c; + break; + } + inEscape = false; + } + else if (c != '\\') + stream << c; + else + inEscape = true; + } + } +} diff --git a/src/Utils/Utils/StringUtils.h b/src/Utils/Utils/StringUtils.h new file mode 100644 index 000000000..c74f141de --- /dev/null +++ b/src/Utils/Utils/StringUtils.h @@ -0,0 +1,10 @@ +#pragma once +#include + +namespace utils +{ + std::string EscapeStringForQuotationMarks(const std::string_view& str); + void EscapeStringForQuotationMarks(std::ostream& stream, const std::string_view& str); + std::string UnescapeStringFromQuotationMarks(const std::string_view& str); + void UnescapeStringFromQuotationMarks(std::ostream& stream, const std::string_view& str); +} From 2cd5d05a1928e0542c8d8d3c50faecbc3e490a8a Mon Sep 17 00:00:00 2001 From: Jan Date: Sat, 23 Sep 2023 15:21:09 +0200 Subject: [PATCH 11/19] Use escaped strings with simple lexer whenever appropriate --- .../LocalizeFile/LocalizeFileReader.cpp | 1 + .../SequenceLocalizeFileLanguageValue.cpp | 32 +------------------ .../StructuredDataDefReader.cpp | 1 + .../Techset/TechniqueFileReader.cpp | 1 + src/ObjLoading/Techset/TechsetFileReader.cpp | 1 + .../Dumping/Localize/StringFileDumper.cpp | 9 +++--- src/ObjWriting/Menu/AbstractMenuDumper.cpp | 1 + .../StructuredDataDefDumper.cpp | 5 ++- .../Simple/SimpleExpressionInterpreter.cpp | 1 + src/Parser/Parsing/Simple/SimpleLexer.h | 10 +++--- .../Parsing/Simple/SimpleExpressionTests.cpp | 1 + 11 files changed, 22 insertions(+), 41 deletions(-) diff --git a/src/ObjLoading/Parsing/LocalizeFile/LocalizeFileReader.cpp b/src/ObjLoading/Parsing/LocalizeFile/LocalizeFileReader.cpp index eb9ea67a8..073a3487b 100644 --- a/src/ObjLoading/Parsing/LocalizeFile/LocalizeFileReader.cpp +++ b/src/ObjLoading/Parsing/LocalizeFile/LocalizeFileReader.cpp @@ -32,6 +32,7 @@ std::vector LocalizeFileReader::ReadLocalizeFile() SimpleLexer::Config lexerConfig; lexerConfig.m_emit_new_line_tokens = true; lexerConfig.m_read_strings = true; + lexerConfig.m_string_escape_sequences = false; // Escape sequences are handled inside the Parser itself in the Sequence lexerConfig.m_read_integer_numbers = false; lexerConfig.m_read_floating_point_numbers = false; const auto lexer = std::make_unique(m_stream, std::move(lexerConfig)); diff --git a/src/ObjLoading/Parsing/LocalizeFile/Sequence/SequenceLocalizeFileLanguageValue.cpp b/src/ObjLoading/Parsing/LocalizeFile/Sequence/SequenceLocalizeFileLanguageValue.cpp index 8a746551e..8edcc3de5 100644 --- a/src/ObjLoading/Parsing/LocalizeFile/Sequence/SequenceLocalizeFileLanguageValue.cpp +++ b/src/ObjLoading/Parsing/LocalizeFile/Sequence/SequenceLocalizeFileLanguageValue.cpp @@ -18,37 +18,7 @@ SequenceLocalizeFileLanguageValue::SequenceLocalizeFileLanguageValue() std::string SequenceLocalizeFileLanguageValue::UnescapeValue(const std::string& value) { std::ostringstream str; - - auto isEscaped = false; - for(const auto c : value) - { - if(isEscaped) - { - switch(c) - { - case 'n': - str << '\n'; - break; - - case 'r': - str << '\r'; - break; - - default: - str << c; - break; - } - isEscaped = false; - } - else if(c == '\\') - { - isEscaped = true; - } - else - { - str << c; - } - } + utils::UnescapeStringFromQuotationMarks(str, value); return str.str(); } diff --git a/src/ObjLoading/StructuredDataDef/StructuredDataDefReader.cpp b/src/ObjLoading/StructuredDataDef/StructuredDataDefReader.cpp index 726bbd2bc..5dba0a647 100644 --- a/src/ObjLoading/StructuredDataDef/StructuredDataDefReader.cpp +++ b/src/ObjLoading/StructuredDataDef/StructuredDataDefReader.cpp @@ -47,6 +47,7 @@ std::vector> StructuredDataDefReader::R SimpleLexer::Config lexerConfig; lexerConfig.m_emit_new_line_tokens = false; lexerConfig.m_read_strings = true; + lexerConfig.m_string_escape_sequences = true; lexerConfig.m_read_integer_numbers = true; lexerConfig.m_read_floating_point_numbers = true; const auto lexer = std::make_unique(m_stream, std::move(lexerConfig)); diff --git a/src/ObjLoading/Techset/TechniqueFileReader.cpp b/src/ObjLoading/Techset/TechniqueFileReader.cpp index 959bcf219..67d37fc75 100644 --- a/src/ObjLoading/Techset/TechniqueFileReader.cpp +++ b/src/ObjLoading/Techset/TechniqueFileReader.cpp @@ -22,6 +22,7 @@ bool TechniqueFileReader::ReadTechniqueDefinition() const SimpleLexer::Config lexerConfig; lexerConfig.m_emit_new_line_tokens = false; lexerConfig.m_read_strings = true; + lexerConfig.m_string_escape_sequences = false; lexerConfig.m_read_integer_numbers = true; lexerConfig.m_read_floating_point_numbers = true; const auto lexer = std::make_unique(m_comment_proxy.get(), std::move(lexerConfig)); diff --git a/src/ObjLoading/Techset/TechsetFileReader.cpp b/src/ObjLoading/Techset/TechsetFileReader.cpp index 26f57aa97..52fdd964d 100644 --- a/src/ObjLoading/Techset/TechsetFileReader.cpp +++ b/src/ObjLoading/Techset/TechsetFileReader.cpp @@ -22,6 +22,7 @@ std::unique_ptr TechsetFileReader::ReadTechsetDefini SimpleLexer::Config lexerConfig; lexerConfig.m_emit_new_line_tokens = false; lexerConfig.m_read_strings = true; + lexerConfig.m_string_escape_sequences = false; lexerConfig.m_read_integer_numbers = false; lexerConfig.m_read_floating_point_numbers = false; const auto lexer = std::make_unique(m_comment_proxy.get(), std::move(lexerConfig)); diff --git a/src/ObjWriting/Dumping/Localize/StringFileDumper.cpp b/src/ObjWriting/Dumping/Localize/StringFileDumper.cpp index 33eb15750..b54b8a6b7 100644 --- a/src/ObjWriting/Dumping/Localize/StringFileDumper.cpp +++ b/src/ObjWriting/Dumping/Localize/StringFileDumper.cpp @@ -1,6 +1,8 @@ #include "StringFileDumper.h" #include +#include "Utils/StringUtils.h" + StringFileDumper::StringFileDumper(Zone* zone, std::ostream& stream) : AbstractTextDumper(stream), m_zone(zone), @@ -44,11 +46,10 @@ void StringFileDumper::WriteLocalizeEntry(const std::string& reference, const st m_stream << "\n"; m_stream << "REFERENCE " << reference << "\n"; - auto escapedValue = std::regex_replace(value, std::regex("\n"), "\\n"); - escapedValue = std::regex_replace(escapedValue, std::regex("\r"), "\\r"); - const auto valueSpacing = std::string(15 - m_language_caps.length(), ' '); - m_stream << "LANG_" << m_language_caps << valueSpacing << "\"" << escapedValue << "\"\n"; + m_stream << "LANG_" << m_language_caps << valueSpacing << "\""; + utils::EscapeStringForQuotationMarks(m_stream, value); + m_stream << "\"\n"; } void StringFileDumper::Finalize() diff --git a/src/ObjWriting/Menu/AbstractMenuDumper.cpp b/src/ObjWriting/Menu/AbstractMenuDumper.cpp index 914926d2d..6f02352ed 100644 --- a/src/ObjWriting/Menu/AbstractMenuDumper.cpp +++ b/src/ObjWriting/Menu/AbstractMenuDumper.cpp @@ -70,6 +70,7 @@ std::vector AbstractMenuDumper::CreateScriptTokenList(const char* s SimpleLexer::Config lexerConfig; lexerConfig.m_emit_new_line_tokens = false; lexerConfig.m_read_strings = true; + lexerConfig.m_string_escape_sequences = true; lexerConfig.m_read_integer_numbers = false; lexerConfig.m_read_floating_point_numbers = false; SimpleLexer lexer(&inputStream, std::move(lexerConfig)); diff --git a/src/ObjWriting/StructuredDataDef/StructuredDataDefDumper.cpp b/src/ObjWriting/StructuredDataDef/StructuredDataDefDumper.cpp index 823a08621..a8fed89f2 100644 --- a/src/ObjWriting/StructuredDataDef/StructuredDataDefDumper.cpp +++ b/src/ObjWriting/StructuredDataDef/StructuredDataDefDumper.cpp @@ -4,6 +4,7 @@ #include #include "Utils/Alignment.h" +#include "Utils/StringUtils.h" StructuredDataDefDumper::StructuredDataDefDumper(std::ostream& stream) : AbstractTextDumper(stream), @@ -37,7 +38,9 @@ void StructuredDataDefDumper::DumpEnum(const CommonStructuredDataEnum& _enum) for (auto i = 0u; i < entryCount; i++) { Indent(); - m_stream << "\"" << _enum.m_entries[i].m_name << "\""; + m_stream << "\""; + utils::EscapeStringForQuotationMarks(m_stream, _enum.m_entries[i].m_name); + m_stream << "\""; if (i + 1 < entryCount) m_stream << ","; diff --git a/src/Parser/Parsing/Simple/SimpleExpressionInterpreter.cpp b/src/Parser/Parsing/Simple/SimpleExpressionInterpreter.cpp index f8b0bbaf9..f4975734c 100644 --- a/src/Parser/Parsing/Simple/SimpleExpressionInterpreter.cpp +++ b/src/Parser/Parsing/Simple/SimpleExpressionInterpreter.cpp @@ -67,6 +67,7 @@ std::unique_ptr SimpleExpressionInterpreter::Evaluate() const lexerConfig.m_read_integer_numbers = true; lexerConfig.m_read_floating_point_numbers = true; lexerConfig.m_read_strings = true; + lexerConfig.m_string_escape_sequences = true; SimpleExpressionMatchers().ApplyTokensToLexerConfig(lexerConfig); SimpleLexer lexer(m_input, std::move(lexerConfig)); diff --git a/src/Parser/Parsing/Simple/SimpleLexer.h b/src/Parser/Parsing/Simple/SimpleLexer.h index 8a4315d58..fd051f2d4 100644 --- a/src/Parser/Parsing/Simple/SimpleLexer.h +++ b/src/Parser/Parsing/Simple/SimpleLexer.h @@ -22,11 +22,11 @@ class SimpleLexer : public AbstractLexer MultiCharacterToken(int id, std::string value); }; - bool m_emit_new_line_tokens; - bool m_read_strings; - bool m_string_escape_sequences; - bool m_read_integer_numbers; - bool m_read_floating_point_numbers; + bool m_emit_new_line_tokens = false; + bool m_read_strings = true; + bool m_string_escape_sequences = false; + bool m_read_integer_numbers = true; + bool m_read_floating_point_numbers = true; std::vector m_multi_character_tokens; }; diff --git a/test/ParserTests/Parsing/Simple/SimpleExpressionTests.cpp b/test/ParserTests/Parsing/Simple/SimpleExpressionTests.cpp index f94849775..14082bd35 100644 --- a/test/ParserTests/Parsing/Simple/SimpleExpressionTests.cpp +++ b/test/ParserTests/Parsing/Simple/SimpleExpressionTests.cpp @@ -112,6 +112,7 @@ namespace test::parsing::simple::expression SimpleLexer::Config lexerConfig; lexerConfig.m_read_strings = true; + lexerConfig.m_string_escape_sequences = true; lexerConfig.m_read_integer_numbers = true; lexerConfig.m_read_floating_point_numbers = true; lexerConfig.m_emit_new_line_tokens = false; From 502e134ed8a59b1dbe16e9a1bbecc9f307198d9a Mon Sep 17 00:00:00 2001 From: Jan Date: Sat, 23 Sep 2023 19:04:15 +0200 Subject: [PATCH 12/19] Fix not properly handling concurrent strings in permissive menu loading mode --- .../Parsing/Menu/Sequence/EventHandlerSetScopeSequences.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ObjLoading/Parsing/Menu/Sequence/EventHandlerSetScopeSequences.cpp b/src/ObjLoading/Parsing/Menu/Sequence/EventHandlerSetScopeSequences.cpp index d4fa456ba..07b011d4f 100644 --- a/src/ObjLoading/Parsing/Menu/Sequence/EventHandlerSetScopeSequences.cpp +++ b/src/ObjLoading/Parsing/Menu/Sequence/EventHandlerSetScopeSequences.cpp @@ -235,7 +235,8 @@ namespace menu::event_handler_set_scope_sequences AddMatchers({ create.Or({ create.Numeric(), - create.Text(), + create.String(), + create.Identifier(), create.Type(SimpleParserValueType::CHARACTER), }).Capture(CAPTURE_SCRIPT_TOKEN) }); From e56fa67e4637e9ba6bdb0e0e98d498ac1c17239a Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 24 Sep 2023 10:30:03 +0200 Subject: [PATCH 13/19] Fix not setting dataType when loading menus --- src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp | 1 + src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp b/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp index ecd91299d..1d79e5fb7 100644 --- a/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp +++ b/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp @@ -944,6 +944,7 @@ namespace IW4 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); diff --git a/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp b/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp index d056c7b37..503373b9d 100644 --- a/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp +++ b/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp @@ -948,6 +948,7 @@ namespace IW5 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); From e481d9714336d2eb6b2bfd2a211de901b19d0df6 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 24 Sep 2023 10:30:27 +0200 Subject: [PATCH 14/19] Also dump dvar and localvar for multiproperty and enum dvar items --- src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp | 3 +++ src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp b/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp index f6d53566d..9aa5b3383 100644 --- a/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp +++ b/src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.cpp @@ -645,6 +645,7 @@ void MenuDumper::WriteMultiProperties(const itemDef_s* item) const return; WriteStringProperty("dvar", item->dvar); + WriteStringProperty("localvar", item->localVar); WriteMultiValueProperty(multiDef); } @@ -653,6 +654,8 @@ void MenuDumper::WriteEnumDvarProperties(const itemDef_s* item) const if (item->type != ITEM_TYPE_DVARENUM) return; + WriteStringProperty("dvar", item->dvar); + WriteStringProperty("localvar", item->localVar); WriteStringProperty("dvarEnumList", item->typeData.enumDvarName); } diff --git a/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp b/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp index 4eb52115a..425134411 100644 --- a/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp +++ b/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp @@ -647,6 +647,7 @@ void MenuDumper::WriteMultiProperties(const itemDef_s* item) const return; WriteStringProperty("dvar", item->dvar); + WriteStringProperty("localvar", item->localVar); WriteMultiValueProperty(multiDef); } @@ -655,6 +656,8 @@ void MenuDumper::WriteEnumDvarProperties(const itemDef_s* item) const if (item->type != ITEM_TYPE_DVARENUM) return; + WriteStringProperty("dvar", item->dvar); + WriteStringProperty("localvar", item->localVar); WriteStringProperty("dvarEnumList", item->typeData.enumDvarName); } From 0b92b9b871cce498cd0f2a974561a2ed3537050e Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 24 Sep 2023 11:46:39 +0200 Subject: [PATCH 15/19] Fix setLocalVar not correctly flushing script when not setting static var --- .../Menu/Sequence/EventHandlerSetScopeSequences.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ObjLoading/Parsing/Menu/Sequence/EventHandlerSetScopeSequences.cpp b/src/ObjLoading/Parsing/Menu/Sequence/EventHandlerSetScopeSequences.cpp index 07b011d4f..486a56475 100644 --- a/src/ObjLoading/Parsing/Menu/Sequence/EventHandlerSetScopeSequences.cpp +++ b/src/ObjLoading/Parsing/Menu/Sequence/EventHandlerSetScopeSequences.cpp @@ -580,8 +580,13 @@ namespace menu::event_handler_set_scope_sequences state->m_current_script << "\" ; "; } - static void EmitDynamicSetLocalVar(const MenuFileParserState* state, const SetLocalVarType type, const std::string& varName, std::unique_ptr expression) + static void EmitDynamicSetLocalVar(MenuFileParserState* state, const SetLocalVarType type, const std::string& varName, std::unique_ptr expression) { + auto remainingScript = state->m_current_script.str(); + if (!remainingScript.empty()) + state->m_current_nested_event_handler_set->m_elements.emplace_back(std::make_unique(std::move(remainingScript))); + state->m_current_script.str(std::string()); + state->m_current_nested_event_handler_set->m_elements.emplace_back(std::make_unique(type, varName, std::move(expression))); } From b8b4b7ce2138b6cbbbbeabe301e108fa1904b4eb Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 24 Sep 2023 12:36:51 +0200 Subject: [PATCH 16/19] Fix incorrectly dumping window rect instead of window clientRect for iw5 items --- src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp b/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp index 425134411..cc153b02b 100644 --- a/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp +++ b/src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.cpp @@ -679,7 +679,7 @@ void MenuDumper::WriteItemData(const itemDef_s* item) WriteKeywordProperty("textsavegame", item->itemFlags & ITEM_FLAG_SAVE_GAME_INFO); WriteKeywordProperty("textcinematicsubtitle", item->itemFlags & ITEM_FLAG_CINEMATIC_SUBTITLE); WriteStringProperty("group", item->window.group); - WriteRectProperty("rect", item->window.rect); + WriteRectProperty("rect", item->window.rectClient); WriteIntProperty("style", item->window.style, 0); WriteKeywordProperty("decoration", item->window.staticFlags & WINDOW_FLAG_DECORATION); WriteKeywordProperty("autowrapped", item->window.staticFlags & WINDOW_FLAG_AUTO_WRAPPED); From 5ee5056c5d2fe8ff35e27c2084302b12ad8cbad0 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 24 Sep 2023 14:18:15 +0200 Subject: [PATCH 17/19] Fix considering string static value to not be true for visible expressions --- .../Game/IW4/Menu/MenuConverterIW4.cpp | 2 +- .../Game/IW5/Menu/MenuConverterIW5.cpp | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp b/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp index 1d79e5fb7..24f90347f 100644 --- a/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp +++ b/src/ObjLoading/Game/IW4/Menu/MenuConverterIW4.cpp @@ -502,7 +502,7 @@ namespace IW4 { const auto* staticValue = dynamic_cast(expression); isStatic = staticValue != nullptr; - isTruthy = isStatic && staticValue->IsTruthy(); + isTruthy = isStatic && (staticValue->m_type == SimpleExpressionValue::Type::INT || staticValue->m_type == SimpleExpressionValue::Type::DOUBLE) && staticValue->IsTruthy(); } else { diff --git a/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp b/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp index 503373b9d..1cde69a07 100644 --- a/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp +++ b/src/ObjLoading/Game/IW5/Menu/MenuConverterIW5.cpp @@ -393,9 +393,9 @@ namespace IW5 return nullptr; auto* statement = m_memory->Create(); - for(auto& result : statement->persistentState.lastResult) + for (auto& result : statement->persistentState.lastResult) result = Operand{}; - for(auto& lastExecutionTime : statement->persistentState.lastExecuteTime) + for (auto& lastExecutionTime : statement->persistentState.lastExecuteTime) lastExecutionTime = 0; statement->supportingData = nullptr; // Supporting data is set upon using it @@ -504,7 +504,7 @@ namespace IW5 { const auto* staticValue = dynamic_cast(expression); isStatic = staticValue != nullptr; - isTruthy = isStatic && staticValue->IsTruthy(); + isTruthy = isStatic && (staticValue->m_type == SimpleExpressionValue::Type::INT || staticValue->m_type == SimpleExpressionValue::Type::DOUBLE) && staticValue->IsTruthy(); } else { @@ -580,13 +580,13 @@ namespace IW5 if (!condition || !condition->m_condition) return; - if(!m_disable_optimizations && condition->m_condition->IsStatic()) + if (!m_disable_optimizations && condition->m_condition->IsStatic()) { const auto staticValueIsTruthy = condition->m_condition->EvaluateStatic().IsTruthy(); - if(staticValueIsTruthy) + if (staticValueIsTruthy) ConvertEventHandlerElements(elements, condition->m_condition_elements.get(), menu, item); - else if(condition->m_else_elements) + else if (condition->m_else_elements) ConvertEventHandlerElements(elements, condition->m_else_elements.get(), menu, item); } else @@ -663,7 +663,8 @@ namespace IW5 return outputSet; } - _NODISCARD ItemKeyHandler* ConvertKeyHandler(const std::multimap>& keyHandlers, const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const + _NODISCARD ItemKeyHandler* ConvertKeyHandler(const std::multimap>& keyHandlers, const CommonMenuDef* menu, + const CommonItemDef* item = nullptr) const { if (keyHandlers.empty()) return nullptr; @@ -734,7 +735,7 @@ namespace IW5 item->window.dynamicFlags[0] |= dynamicFlagsToSet; auto* staticValuePtr = staticValue; - for(auto i = 0u; i < staticValueArraySize; i++) + for (auto i = 0u; i < staticValueArraySize; i++) { *staticValuePtr = static_cast(evaluatedValue.m_int_value); staticValuePtr++; @@ -1022,7 +1023,7 @@ namespace IW5 case CommonItemFeatureType::NONE: default: - if(item->type == ITEM_TYPE_TEXT_SCROLL) + 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)); From cf711c3af7d87e1eb283acd1846723526d9897ab Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 24 Sep 2023 15:25:08 +0200 Subject: [PATCH 18/19] Use SimpleLexer escaped strings for localization --- .../Parsing/LocalizeFile/LocalizeFileReader.cpp | 2 +- .../Sequence/SequenceLocalizeFileLanguageValue.cpp | 10 +--------- .../Sequence/SequenceLocalizeFileLanguageValue.h | 2 -- .../Game/IW3/AssetDumpers/AssetDumperLocalizeEntry.cpp | 2 +- .../Game/IW4/AssetDumpers/AssetDumperLocalizeEntry.cpp | 2 +- .../Game/IW5/AssetDumpers/AssetDumperLocalizeEntry.cpp | 2 +- .../Game/T5/AssetDumpers/AssetDumperLocalizeEntry.cpp | 2 +- .../Game/T6/AssetDumpers/AssetDumperLocalizeEntry.cpp | 2 +- 8 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/ObjLoading/Parsing/LocalizeFile/LocalizeFileReader.cpp b/src/ObjLoading/Parsing/LocalizeFile/LocalizeFileReader.cpp index 073a3487b..d9a28df54 100644 --- a/src/ObjLoading/Parsing/LocalizeFile/LocalizeFileReader.cpp +++ b/src/ObjLoading/Parsing/LocalizeFile/LocalizeFileReader.cpp @@ -32,7 +32,7 @@ std::vector LocalizeFileReader::ReadLocalizeFile() SimpleLexer::Config lexerConfig; lexerConfig.m_emit_new_line_tokens = true; lexerConfig.m_read_strings = true; - lexerConfig.m_string_escape_sequences = false; // Escape sequences are handled inside the Parser itself in the Sequence + lexerConfig.m_string_escape_sequences = true; lexerConfig.m_read_integer_numbers = false; lexerConfig.m_read_floating_point_numbers = false; const auto lexer = std::make_unique(m_stream, std::move(lexerConfig)); diff --git a/src/ObjLoading/Parsing/LocalizeFile/Sequence/SequenceLocalizeFileLanguageValue.cpp b/src/ObjLoading/Parsing/LocalizeFile/Sequence/SequenceLocalizeFileLanguageValue.cpp index 8edcc3de5..3362aa6fc 100644 --- a/src/ObjLoading/Parsing/LocalizeFile/Sequence/SequenceLocalizeFileLanguageValue.cpp +++ b/src/ObjLoading/Parsing/LocalizeFile/Sequence/SequenceLocalizeFileLanguageValue.cpp @@ -15,14 +15,6 @@ SequenceLocalizeFileLanguageValue::SequenceLocalizeFileLanguageValue() }); } -std::string SequenceLocalizeFileLanguageValue::UnescapeValue(const std::string& value) -{ - std::ostringstream str; - utils::UnescapeStringFromQuotationMarks(str, value); - - return str.str(); -} - void SequenceLocalizeFileLanguageValue::ProcessMatch(LocalizeFileParserState* state, SequenceResult& result) const { const auto& langToken = result.NextCapture(CAPTURE_LANGUAGE_NAME); @@ -39,5 +31,5 @@ void SequenceLocalizeFileLanguageValue::ProcessMatch(LocalizeFileParserState* st state->m_current_reference_languages.emplace(langName); if(langName == state->m_language_name_caps) - state->m_entries.emplace_back(state->m_current_reference, UnescapeValue(valueToken.StringValue())); + state->m_entries.emplace_back(state->m_current_reference, valueToken.StringValue()); } diff --git a/src/ObjLoading/Parsing/LocalizeFile/Sequence/SequenceLocalizeFileLanguageValue.h b/src/ObjLoading/Parsing/LocalizeFile/Sequence/SequenceLocalizeFileLanguageValue.h index ffe2dbe1d..f6379745a 100644 --- a/src/ObjLoading/Parsing/LocalizeFile/Sequence/SequenceLocalizeFileLanguageValue.h +++ b/src/ObjLoading/Parsing/LocalizeFile/Sequence/SequenceLocalizeFileLanguageValue.h @@ -7,8 +7,6 @@ class SequenceLocalizeFileLanguageValue final : public LocalizeFileParser::seque static constexpr auto CAPTURE_LANGUAGE_NAME = 1; static constexpr auto CAPTURE_ENTRY_VALUE = 2; - static std::string UnescapeValue(const std::string& value); - protected: void ProcessMatch(LocalizeFileParserState* state, SequenceResult& result) const override; diff --git a/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperLocalizeEntry.cpp b/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperLocalizeEntry.cpp index 65c951d7e..8c1b8e08e 100644 --- a/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperLocalizeEntry.cpp +++ b/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperLocalizeEntry.cpp @@ -25,7 +25,7 @@ void AssetDumperLocalizeEntry::DumpPool(AssetDumpingContext& context, AssetPool< stringFileDumper.SetLanguageName(language); // Magic string. Original string files do have this config file. The purpose of the config file is unknown though. - stringFileDumper.SetConfigFile(R"(C:\trees\cod3\cod3\bin\StringEd.cfg)"); + stringFileDumper.SetConfigFile(R"(C:/trees/cod3/cod3/bin/StringEd.cfg)"); stringFileDumper.SetNotes(""); diff --git a/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperLocalizeEntry.cpp b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperLocalizeEntry.cpp index 91dca90cc..4e4f4c180 100644 --- a/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperLocalizeEntry.cpp +++ b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperLocalizeEntry.cpp @@ -25,7 +25,7 @@ void AssetDumperLocalizeEntry::DumpPool(AssetDumpingContext& context, AssetPool< stringFileDumper.SetLanguageName(language); // Magic string. Original string files do have this config file. The purpose of the config file is unknown though. - stringFileDumper.SetConfigFile(R"(C:\trees\cod3\cod3\bin\StringEd.cfg)"); + stringFileDumper.SetConfigFile(R"(C:/trees/cod3/cod3/bin/StringEd.cfg)"); stringFileDumper.SetNotes(""); diff --git a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperLocalizeEntry.cpp b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperLocalizeEntry.cpp index fae5d7ed1..476fc32e1 100644 --- a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperLocalizeEntry.cpp +++ b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperLocalizeEntry.cpp @@ -25,7 +25,7 @@ void AssetDumperLocalizeEntry::DumpPool(AssetDumpingContext& context, AssetPool< stringFileDumper.SetLanguageName(language); // Magic string. Original string files do have this config file. The purpose of the config file is unknown though. - stringFileDumper.SetConfigFile(R"(C:\trees\cod3\cod3\bin\StringEd.cfg)"); + stringFileDumper.SetConfigFile(R"(C:/trees/cod3/cod3/bin/StringEd.cfg)"); stringFileDumper.SetNotes(""); diff --git a/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperLocalizeEntry.cpp b/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperLocalizeEntry.cpp index fc3f052c5..0d7d6717a 100644 --- a/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperLocalizeEntry.cpp +++ b/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperLocalizeEntry.cpp @@ -25,7 +25,7 @@ void AssetDumperLocalizeEntry::DumpPool(AssetDumpingContext& context, AssetPool< stringFileDumper.SetLanguageName(language); // Magic string. Original string files do have this config file. The purpose of the config file is unknown though. - stringFileDumper.SetConfigFile(R"(C:\projects\cod\t5\bin\StringEd.cfg)"); + stringFileDumper.SetConfigFile(R"(C:/projects/cod/t5/bin/StringEd.cfg)"); stringFileDumper.SetNotes(""); diff --git a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperLocalizeEntry.cpp b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperLocalizeEntry.cpp index 127e244dd..212ff1900 100644 --- a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperLocalizeEntry.cpp +++ b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperLocalizeEntry.cpp @@ -25,7 +25,7 @@ void AssetDumperLocalizeEntry::DumpPool(AssetDumpingContext& context, AssetPool< stringFileDumper.SetLanguageName(language); // Magic string. Original string files do have this config file. The purpose of the config file is unknown though. - stringFileDumper.SetConfigFile(R"(C:\projects\cod\t6\bin\StringEd.cfg)"); + stringFileDumper.SetConfigFile(R"(C:/projects/cod/t6/bin/StringEd.cfg)"); stringFileDumper.SetNotes(""); From 36cd9e11fe5c4bb88f9362cb981bb2279a95d52e Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 24 Sep 2023 15:58:57 +0200 Subject: [PATCH 19/19] Support localize entries that have a name that cannot be represented as an identifier --- .../Sequence/SequenceLocalizeFileReference.cpp | 5 ++++- .../Dumping/Localize/StringFileDumper.cpp | 16 +++++++++++++++- .../Dumping/Localize/StringFileDumper.h | 1 + src/Utils/Utils/StringUtils.h | 4 ++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/ObjLoading/Parsing/LocalizeFile/Sequence/SequenceLocalizeFileReference.cpp b/src/ObjLoading/Parsing/LocalizeFile/Sequence/SequenceLocalizeFileReference.cpp index cb20ab81f..93f8c185a 100644 --- a/src/ObjLoading/Parsing/LocalizeFile/Sequence/SequenceLocalizeFileReference.cpp +++ b/src/ObjLoading/Parsing/LocalizeFile/Sequence/SequenceLocalizeFileReference.cpp @@ -8,7 +8,10 @@ SequenceLocalizeFileReference::SequenceLocalizeFileReference() AddMatchers({ create.Keyword("REFERENCE"), - create.Identifier().Capture(CAPTURE_REFERENCE_NAME), + create.Or({ + create.Identifier(), + create.String() + }).Capture(CAPTURE_REFERENCE_NAME), create.Type(SimpleParserValueType::NEW_LINE) }); } diff --git a/src/ObjWriting/Dumping/Localize/StringFileDumper.cpp b/src/ObjWriting/Dumping/Localize/StringFileDumper.cpp index b54b8a6b7..ba0ae1008 100644 --- a/src/ObjWriting/Dumping/Localize/StringFileDumper.cpp +++ b/src/ObjWriting/Dumping/Localize/StringFileDumper.cpp @@ -38,13 +38,27 @@ void StringFileDumper::WriteHeader() m_wrote_header = true; } +void StringFileDumper::WriteReference(const std::string& reference) const +{ + if (reference.find_first_not_of(utils::LETTERS_AL_NUM_UNDERSCORE) != std::string::npos) + { + m_stream << "REFERENCE \""; + + utils::EscapeStringForQuotationMarks(m_stream, reference); + + m_stream << "\"\n"; + } + else + m_stream << "REFERENCE " << reference << "\n"; +} + void StringFileDumper::WriteLocalizeEntry(const std::string& reference, const std::string& value) { if (!m_wrote_header) WriteHeader(); m_stream << "\n"; - m_stream << "REFERENCE " << reference << "\n"; + WriteReference(reference); const auto valueSpacing = std::string(15 - m_language_caps.length(), ' '); m_stream << "LANG_" << m_language_caps << valueSpacing << "\""; diff --git a/src/ObjWriting/Dumping/Localize/StringFileDumper.h b/src/ObjWriting/Dumping/Localize/StringFileDumper.h index 5a9c9a866..a78464c48 100644 --- a/src/ObjWriting/Dumping/Localize/StringFileDumper.h +++ b/src/ObjWriting/Dumping/Localize/StringFileDumper.h @@ -14,6 +14,7 @@ class StringFileDumper : AbstractTextDumper bool m_wrote_header; void WriteHeader(); + void WriteReference(const std::string& reference) const; public: StringFileDumper(Zone* zone, std::ostream& stream); diff --git a/src/Utils/Utils/StringUtils.h b/src/Utils/Utils/StringUtils.h index c74f141de..860f8dbb2 100644 --- a/src/Utils/Utils/StringUtils.h +++ b/src/Utils/Utils/StringUtils.h @@ -3,6 +3,10 @@ namespace utils { +#define M_LETTERS_AL_NUM "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + static constexpr const char* LETTERS_AL_NUM = M_LETTERS_AL_NUM; + static constexpr const char* LETTERS_AL_NUM_UNDERSCORE = M_LETTERS_AL_NUM "_"; + std::string EscapeStringForQuotationMarks(const std::string_view& str); void EscapeStringForQuotationMarks(std::ostream& stream, const std::string_view& str); std::string UnescapeStringFromQuotationMarks(const std::string_view& str);