mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2026-09-11 16:37:06 +00:00
feat: add T4 menu dumping and loading (#986)
* feat: add T4 menu dumping * chore: align with latest changes * feat: add T4 menu loading Load T4 menulists and menu files through the shared menu parser. Preserve T4-specific menu and listbox fields and cover loading, dumping, linking, and round-trip behaviour. * chore: make flag fields unsigned * chore: rename `expOperationEnum` -> `operationEnum` * chore: use default enum numeration for T4 operationEnum * chore: remove unused members of operationEnum * chore: remove redundant op from operationEnum * refactor: align T4 expression operand names with IW3 * chore: small adjustment to iw3,t4 types --------- Co-authored-by: Jan Laupetin <[email protected]>
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
#include "LoaderMenuListT4.h"
|
||||
|
||||
#include "Game/T4/Menu/MenuConversionZoneStateT4.h"
|
||||
#include "Game/T4/Menu/MenuConverterT4.h"
|
||||
#include "ObjLoading.h"
|
||||
#include "Parsing/Menu/MenuAssetZoneState.h"
|
||||
#include "Parsing/Menu/MenuFileReader.h"
|
||||
#include "Utils/Logging/Log.h"
|
||||
|
||||
#include <deque>
|
||||
|
||||
using namespace T4;
|
||||
|
||||
namespace
|
||||
{
|
||||
class MenuListLoader final : public AssetCreator<AssetMenuList>
|
||||
{
|
||||
public:
|
||||
MenuListLoader(MemoryManager& memory, ISearchPath& searchPath)
|
||||
: m_memory(memory),
|
||||
m_search_path(searchPath)
|
||||
{
|
||||
}
|
||||
|
||||
AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override
|
||||
{
|
||||
std::vector<menuDef_t*> menus;
|
||||
AssetRegistration<AssetMenuList> registration(assetName);
|
||||
|
||||
auto& zoneState = context.GetZoneAssetCreationState<menu::MenuAssetZoneState>();
|
||||
auto& conversionState = context.GetZoneAssetCreationState<MenuConversionZoneState>();
|
||||
|
||||
std::deque<std::string> menuLoadQueue;
|
||||
const auto alreadyLoadedMenuListFileMenus = conversionState.m_menus_by_filename.find(assetName);
|
||||
|
||||
if (alreadyLoadedMenuListFileMenus == conversionState.m_menus_by_filename.end())
|
||||
{
|
||||
const auto file = m_search_path.Open(assetName);
|
||||
if (!file.IsOpen())
|
||||
return AssetCreationResult::NoAction();
|
||||
|
||||
const auto menuListResult = ParseMenuFile(*file.m_stream, assetName, zoneState);
|
||||
if (menuListResult)
|
||||
{
|
||||
if (!ProcessParsedResults(assetName, context, *menuListResult, zoneState, conversionState, menus, registration))
|
||||
return AssetCreationResult::Failure();
|
||||
|
||||
for (const auto& menuToLoad : menuListResult->m_menus_to_load)
|
||||
menuLoadQueue.emplace_back(menuToLoad);
|
||||
|
||||
zoneState.AddMenusToLoad(assetName, std::move(menuListResult->m_menus_to_load));
|
||||
}
|
||||
else
|
||||
return AssetCreationResult::Failure();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto* menu : alreadyLoadedMenuListFileMenus->second)
|
||||
{
|
||||
menus.emplace_back(menu->Asset());
|
||||
registration.AddDependency(menu);
|
||||
}
|
||||
}
|
||||
|
||||
while (!menuLoadQueue.empty())
|
||||
{
|
||||
const auto& menuFileToLoad = menuLoadQueue.front();
|
||||
|
||||
if (!LoadMenuFileFromQueue(menuFileToLoad, context, zoneState, conversionState, menus, registration))
|
||||
return AssetCreationResult::Failure();
|
||||
|
||||
menuLoadQueue.pop_front();
|
||||
}
|
||||
|
||||
auto* menuListAsset = m_memory.Alloc<MenuList>();
|
||||
menuListAsset->name = m_memory.Dup(assetName.c_str());
|
||||
registration.SetAsset(menuListAsset);
|
||||
|
||||
CreateMenuListAsset(*menuListAsset, menus);
|
||||
|
||||
return AssetCreationResult::Success(context.AddAsset(std::move(registration)));
|
||||
}
|
||||
|
||||
private:
|
||||
bool LoadMenuFileFromQueue(const std::string& menuFilePath,
|
||||
AssetCreationContext& context,
|
||||
menu::MenuAssetZoneState& zoneState,
|
||||
MenuConversionZoneState& conversionState,
|
||||
std::vector<menuDef_t*>& menus,
|
||||
AssetRegistration<AssetMenuList>& registration) const
|
||||
{
|
||||
const auto alreadyLoadedMenuFile = conversionState.m_menus_by_filename.find(menuFilePath);
|
||||
if (alreadyLoadedMenuFile != conversionState.m_menus_by_filename.end())
|
||||
{
|
||||
con::debug("Already loaded \"{}\", skipping", menuFilePath);
|
||||
for (auto* menu : alreadyLoadedMenuFile->second)
|
||||
{
|
||||
menus.emplace_back(menu->Asset());
|
||||
registration.AddDependency(menu);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto file = m_search_path.Open(menuFilePath);
|
||||
if (!file.IsOpen())
|
||||
{
|
||||
con::error("Could not open menu file \"{}\"", menuFilePath);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto menuFileResult = ParseMenuFile(*file.m_stream, menuFilePath, zoneState);
|
||||
if (menuFileResult)
|
||||
{
|
||||
if (!ProcessParsedResults(menuFilePath, context, *menuFileResult, zoneState, conversionState, menus, registration))
|
||||
return false;
|
||||
if (!menuFileResult->m_menus_to_load.empty())
|
||||
con::warn("Menu file has menus to load even though it is not a menu list, ignoring: \"{}\"", menuFilePath);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
con::error("Could not read menu file \"{}\"", menuFilePath);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProcessParsedResults(const std::string& fileName,
|
||||
AssetCreationContext& context,
|
||||
menu::ParsingResult& parsingResult,
|
||||
menu::MenuAssetZoneState& zoneState,
|
||||
MenuConversionZoneState& conversionState,
|
||||
std::vector<menuDef_t*>& menus,
|
||||
AssetRegistration<AssetMenuList>& registration) const
|
||||
{
|
||||
const auto menuCount = parsingResult.m_menus.size();
|
||||
const auto menuLoadCount = parsingResult.m_menus_to_load.size();
|
||||
auto totalItemCount = 0uz;
|
||||
for (const auto& menu : parsingResult.m_menus)
|
||||
totalItemCount += menu->m_items.size();
|
||||
|
||||
con::info("Successfully read menu file \"{}\" ({} loads, {} menus, {} items)", fileName, menuLoadCount, menuCount, totalItemCount);
|
||||
|
||||
// Prepare a list of all menus of this file
|
||||
std::vector<XAssetInfo<menuDef_t>*> allMenusOfFile;
|
||||
allMenusOfFile.reserve(parsingResult.m_menus.size());
|
||||
|
||||
// Convert all menus and add them as assets
|
||||
for (auto& commonMenu : parsingResult.m_menus)
|
||||
{
|
||||
auto converter = IMenuConverter::Create(ObjLoading::Configuration.MenuNoOptimization, m_search_path, m_memory, context);
|
||||
|
||||
auto* menuAsset = m_memory.Alloc<menuDef_t>();
|
||||
AssetRegistration<AssetMenu> menuRegistration(commonMenu->m_name, menuAsset);
|
||||
|
||||
if (!converter->ConvertMenu(*commonMenu, *menuAsset, menuRegistration))
|
||||
{
|
||||
con::error("Failed to convert menu file \"{}\"", commonMenu->m_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
menus.emplace_back(menuAsset);
|
||||
auto* menuAssetInfo = context.AddAsset(std::move(menuRegistration));
|
||||
|
||||
if (menuAssetInfo)
|
||||
{
|
||||
allMenusOfFile.emplace_back(menuAssetInfo);
|
||||
registration.AddDependency(menuAssetInfo);
|
||||
}
|
||||
|
||||
zoneState.AddMenu(std::move(commonMenu));
|
||||
}
|
||||
|
||||
// Register this file with all loaded menus
|
||||
conversionState.AddLoadedFile(fileName, std::move(allMenusOfFile));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CreateMenuListAsset(MenuList& menuList, const std::vector<menuDef_t*>& menus) const
|
||||
{
|
||||
menuList.menuCount = static_cast<int>(menus.size());
|
||||
|
||||
if (menuList.menuCount > 0)
|
||||
{
|
||||
menuList.menus = m_memory.Alloc<menuDef_t*>(menuList.menuCount);
|
||||
for (auto i = 0; i < menuList.menuCount; i++)
|
||||
menuList.menus[i] = menus[i];
|
||||
}
|
||||
else
|
||||
menuList.menus = nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<menu::ParsingResult>
|
||||
ParseMenuFile(std::istream& stream, const std::string& menuFileName, const menu::MenuAssetZoneState& zoneState) const
|
||||
{
|
||||
menu::MenuFileReader reader(stream, menuFileName, menu::FeatureLevel::T4, m_search_path);
|
||||
|
||||
reader.IncludeZoneState(zoneState);
|
||||
reader.SetPermissiveMode(ObjLoading::Configuration.MenuPermissiveParsing);
|
||||
|
||||
return reader.ReadMenuFile();
|
||||
}
|
||||
|
||||
MemoryManager& m_memory;
|
||||
ISearchPath& m_search_path;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace menu
|
||||
{
|
||||
std::unique_ptr<AssetCreator<AssetMenuList>> CreateMenuListLoaderT4(MemoryManager& memory, ISearchPath& searchPath)
|
||||
{
|
||||
return std::make_unique<MenuListLoader>(memory, searchPath);
|
||||
}
|
||||
} // namespace menu
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "Asset/IAssetCreator.h"
|
||||
#include "Game/T4/T4.h"
|
||||
#include "SearchPath/ISearchPath.h"
|
||||
#include "Utils/MemoryManager.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace menu
|
||||
{
|
||||
std::unique_ptr<AssetCreator<T4::AssetMenuList>> CreateMenuListLoaderT4(MemoryManager& memory, ISearchPath& searchPath);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#include "MenuConversionZoneStateT4.h"
|
||||
|
||||
using namespace T4;
|
||||
|
||||
void MenuConversionZoneState::AddLoadedFile(std::string loadedFileName, std::vector<XAssetInfo<menuDef_t>*> menusOfFile)
|
||||
{
|
||||
m_menus_by_filename.emplace(std::move(loadedFileName), std::move(menusOfFile));
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "Asset/IZoneAssetCreationState.h"
|
||||
#include "Game/T4/T4.h"
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace T4
|
||||
{
|
||||
class MenuConversionZoneState final : public IZoneAssetCreationState
|
||||
{
|
||||
public:
|
||||
void AddLoadedFile(std::string loadedFileName, std::vector<XAssetInfo<menuDef_t>*> menusOfFile);
|
||||
|
||||
std::unordered_map<std::string, std::vector<XAssetInfo<menuDef_t>*>> m_menus_by_filename;
|
||||
};
|
||||
} // namespace T4
|
||||
@@ -0,0 +1,735 @@
|
||||
#include "MenuConverterT4.h"
|
||||
|
||||
#include "Menu/AbstractMenuConverter.h"
|
||||
#include "Parsing/Menu/Domain/EventHandler/CommonEventHandlerScript.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"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <format>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
using namespace T4;
|
||||
using namespace menu;
|
||||
|
||||
namespace
|
||||
{
|
||||
class MenuConverter final : public AbstractMenuConverter, public IMenuConverter
|
||||
{
|
||||
[[nodiscard]] static rectDef_s ConvertRectDef(const CommonRect& rect)
|
||||
{
|
||||
return rectDef_s{
|
||||
.x = static_cast<float>(rect.x),
|
||||
.y = static_cast<float>(rect.y),
|
||||
.w = static_cast<float>(rect.w),
|
||||
.h = static_cast<float>(rect.h),
|
||||
.horzAlign = rect.horizontalAlign,
|
||||
.vertAlign = rect.verticalAlign,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] static rectDef_s ConvertRectDefRelativeTo(const rectDef_s& rect, const rectDef_s& relativeTo)
|
||||
{
|
||||
return rectDef_s{
|
||||
.x = relativeTo.x + rect.x,
|
||||
.y = relativeTo.y + rect.y,
|
||||
.w = static_cast<float>(rect.w),
|
||||
.h = static_cast<float>(rect.h),
|
||||
.horzAlign = rect.horzAlign,
|
||||
.vertAlign = rect.vertAlign,
|
||||
};
|
||||
}
|
||||
|
||||
static void ConvertColor(float (&output)[4], const CommonColor& input)
|
||||
{
|
||||
output[0] = static_cast<float>(input.r);
|
||||
output[1] = static_cast<float>(input.g);
|
||||
output[2] = static_cast<float>(input.b);
|
||||
output[3] = static_cast<float>(input.a);
|
||||
}
|
||||
|
||||
static void ApplyFlag(unsigned& flags, const bool shouldApply, const unsigned flagValue)
|
||||
{
|
||||
if (!shouldApply)
|
||||
return;
|
||||
|
||||
flags |= flagValue;
|
||||
}
|
||||
|
||||
[[nodiscard]] Material* ConvertMaterial(const std::string& materialName, const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const
|
||||
{
|
||||
if (materialName.empty())
|
||||
return nullptr;
|
||||
|
||||
auto* materialDependency = m_context.LoadDependency<AssetMaterial>(materialName);
|
||||
if (!materialDependency)
|
||||
throw MenuConversionException(std::format("Failed to load material \"{}\"", materialName), menu, item);
|
||||
|
||||
return materialDependency->Asset();
|
||||
}
|
||||
|
||||
[[nodiscard]] snd_alias_list_t* ConvertSound(const std::string& soundName, const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const
|
||||
{
|
||||
if (soundName.empty())
|
||||
return nullptr;
|
||||
|
||||
auto* soundDependency = m_context.LoadDependency<AssetSound>(soundName);
|
||||
if (!soundDependency)
|
||||
throw MenuConversionException(std::format("Failed to load sound \"{}\"", soundName), menu, item);
|
||||
|
||||
return soundDependency->Asset();
|
||||
}
|
||||
|
||||
constexpr static operationEnum UNARY_OPERATION_MAPPING[]{
|
||||
OP_NOT,
|
||||
OP_BITWISENOT,
|
||||
OP_SUBTRACT,
|
||||
};
|
||||
static_assert(std::size(UNARY_OPERATION_MAPPING) == static_cast<unsigned>(SimpleUnaryOperationId::COUNT));
|
||||
|
||||
constexpr static operationEnum BINARY_OPERATION_MAPPING[]{
|
||||
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,
|
||||
};
|
||||
static_assert(std::size(BINARY_OPERATION_MAPPING) == static_cast<unsigned>(SimpleBinaryOperationId::COUNT));
|
||||
|
||||
[[nodiscard]] bool IsOperation(const ISimpleExpression* expression) const
|
||||
{
|
||||
if (!m_disable_optimizations && expression->IsStatic())
|
||||
return false;
|
||||
|
||||
return dynamic_cast<const SimpleExpressionBinaryOperation*>(expression) != nullptr
|
||||
|| dynamic_cast<const SimpleExpressionUnaryOperation*>(expression) != nullptr;
|
||||
}
|
||||
|
||||
void ConvertExpressionEntryBaseFunctionCall(std::vector<expressionEntry>& entries,
|
||||
const CommonExpressionBaseFunctionCall* baseFunction,
|
||||
const CommonMenuDef* menu,
|
||||
const CommonItemDef* item) const
|
||||
{
|
||||
entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = static_cast<operationEnum>(baseFunction->m_function_index)}});
|
||||
|
||||
auto firstArg = true;
|
||||
for (const auto& arg : baseFunction->m_args)
|
||||
{
|
||||
if (firstArg)
|
||||
firstArg = false;
|
||||
else
|
||||
entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_COMMA}});
|
||||
|
||||
ConvertExpressionEntry(entries, arg.get(), menu, item);
|
||||
}
|
||||
|
||||
entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_RIGHTPAREN}});
|
||||
}
|
||||
|
||||
void ConvertExpressionEntryUnaryOperation(std::vector<expressionEntry>& entries,
|
||||
const SimpleExpressionUnaryOperation* unary,
|
||||
const CommonMenuDef* menu,
|
||||
const CommonItemDef* item) const
|
||||
{
|
||||
assert(static_cast<unsigned>(unary->m_operation_type->m_id) < static_cast<unsigned>(SimpleUnaryOperationId::COUNT));
|
||||
|
||||
entries.emplace_back(expressionEntry{
|
||||
.type = EET_OPERATOR,
|
||||
.data = {.op = UNARY_OPERATION_MAPPING[static_cast<unsigned>(unary->m_operation_type->m_id)]},
|
||||
});
|
||||
|
||||
const auto wrapOperand = IsOperation(unary->m_operand.get());
|
||||
if (wrapOperand)
|
||||
entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_LEFTPAREN}});
|
||||
ConvertExpressionEntry(entries, unary->m_operand.get(), menu, item);
|
||||
if (wrapOperand)
|
||||
entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_RIGHTPAREN}});
|
||||
}
|
||||
|
||||
void ConvertExpressionEntryBinaryOperation(std::vector<expressionEntry>& entries,
|
||||
const SimpleExpressionBinaryOperation* binary,
|
||||
const CommonMenuDef* menu,
|
||||
const CommonItemDef* item) const
|
||||
{
|
||||
// Game needs all nested operations to have parenthesis
|
||||
const auto wrapLeft = IsOperation(binary->m_operand1.get());
|
||||
if (wrapLeft)
|
||||
entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_LEFTPAREN}});
|
||||
ConvertExpressionEntry(entries, binary->m_operand1.get(), menu, item);
|
||||
if (wrapLeft)
|
||||
entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_RIGHTPAREN}});
|
||||
|
||||
assert(static_cast<unsigned>(binary->m_operation_type->m_id) < static_cast<unsigned>(SimpleBinaryOperationId::COUNT));
|
||||
entries.emplace_back(expressionEntry{
|
||||
.type = EET_OPERATOR,
|
||||
.data = {.op = BINARY_OPERATION_MAPPING[static_cast<unsigned>(binary->m_operation_type->m_id)]},
|
||||
});
|
||||
|
||||
// Game needs all nested operations to have parenthesis
|
||||
const auto wrapRight = IsOperation(binary->m_operand2.get());
|
||||
if (wrapRight)
|
||||
entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_LEFTPAREN}});
|
||||
ConvertExpressionEntry(entries, binary->m_operand2.get(), menu, item);
|
||||
if (wrapRight)
|
||||
entries.emplace_back(expressionEntry{.type = EET_OPERATOR, .data = {.op = OP_RIGHTPAREN}});
|
||||
}
|
||||
|
||||
void ConvertExpressionValue(std::vector<expressionEntry>& entries, const SimpleExpressionValue& value) const
|
||||
{
|
||||
expressionEntry entry{};
|
||||
entry.type = EET_OPERAND;
|
||||
|
||||
switch (value.m_type)
|
||||
{
|
||||
case SimpleExpressionValue::Type::INT:
|
||||
entry.data.operand.dataType = VAL_INT;
|
||||
entry.data.operand.internals.intVal = value.m_int_value;
|
||||
break;
|
||||
case SimpleExpressionValue::Type::DOUBLE:
|
||||
entry.data.operand.dataType = VAL_FLOAT;
|
||||
entry.data.operand.internals.floatVal = static_cast<float>(value.m_double_value);
|
||||
break;
|
||||
case SimpleExpressionValue::Type::STRING:
|
||||
entry.data.operand.dataType = VAL_STRING;
|
||||
entry.data.operand.internals.stringVal = m_memory.Dup(value.m_string_value->c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
entries.emplace_back(entry);
|
||||
}
|
||||
|
||||
void ConvertExpressionEntry(std::vector<expressionEntry>& entries,
|
||||
const ISimpleExpression* expression,
|
||||
const CommonMenuDef* menu,
|
||||
const CommonItemDef* item) const
|
||||
{
|
||||
if (!m_disable_optimizations && expression->IsStatic())
|
||||
{
|
||||
const auto staticValue = expression->EvaluateStatic();
|
||||
ConvertExpressionValue(entries, staticValue);
|
||||
}
|
||||
else if (const auto* value = dynamic_cast<const SimpleExpressionValue*>(expression))
|
||||
{
|
||||
ConvertExpressionValue(entries, *value);
|
||||
}
|
||||
else if (const auto* binary = dynamic_cast<const SimpleExpressionBinaryOperation*>(expression))
|
||||
{
|
||||
ConvertExpressionEntryBinaryOperation(entries, binary, menu, item);
|
||||
}
|
||||
else if (const auto* unary = dynamic_cast<const SimpleExpressionUnaryOperation*>(expression))
|
||||
{
|
||||
ConvertExpressionEntryUnaryOperation(entries, unary, menu, item);
|
||||
}
|
||||
else if (const auto* baseFunction = dynamic_cast<const CommonExpressionBaseFunctionCall*>(expression))
|
||||
{
|
||||
ConvertExpressionEntryBaseFunctionCall(entries, baseFunction, menu, item);
|
||||
}
|
||||
else if (dynamic_cast<const CommonExpressionCustomFunctionCall*>(expression))
|
||||
{
|
||||
throw MenuConversionException("T4 does not support custom menu functions", menu, item);
|
||||
}
|
||||
else if (dynamic_cast<const SimpleExpressionConditionalOperator*>(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);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ConvertExpression(statement_s& statement, const ISimpleExpression* expression, const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const
|
||||
{
|
||||
if (!expression)
|
||||
return;
|
||||
|
||||
std::vector<expressionEntry> entries;
|
||||
ConvertExpressionEntry(entries, expression, menu, item);
|
||||
|
||||
statement.numEntries = static_cast<int>(entries.size());
|
||||
statement.entries = m_memory.Alloc<expressionEntry*>(entries.size());
|
||||
for (auto i = 0u; i < entries.size(); i++)
|
||||
{
|
||||
statement.entries[i] = m_memory.Alloc<expressionEntry>();
|
||||
*statement.entries[i] = entries[i];
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertOrApplyStatement(float& staticValue,
|
||||
statement_s& statement,
|
||||
const ISimpleExpression* expression,
|
||||
const CommonMenuDef* menu,
|
||||
const CommonItemDef* item = nullptr) const
|
||||
{
|
||||
if (!expression)
|
||||
return;
|
||||
|
||||
if (!m_disable_optimizations && expression->IsStatic())
|
||||
{
|
||||
const auto value = expression->EvaluateStatic();
|
||||
|
||||
if (value.m_type == SimpleExpressionValue::Type::INT)
|
||||
staticValue = static_cast<float>(value.m_int_value);
|
||||
else if (value.m_type == SimpleExpressionValue::Type::DOUBLE)
|
||||
staticValue = static_cast<float>(value.m_double_value);
|
||||
else
|
||||
throw MenuConversionException("Cannot convert string expression value to floating point", menu, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
ConvertExpression(statement, expression, menu, item);
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertOrApplyStatement(const char*& staticValue,
|
||||
statement_s& statement,
|
||||
const ISimpleExpression* expression,
|
||||
const CommonMenuDef* menu,
|
||||
const CommonItemDef* item = nullptr) const
|
||||
{
|
||||
if (!expression)
|
||||
return;
|
||||
|
||||
if (!m_disable_optimizations && expression->IsStatic())
|
||||
{
|
||||
const auto value = expression->EvaluateStatic();
|
||||
if (value.m_type != SimpleExpressionValue::Type::STRING)
|
||||
throw MenuConversionException("Cannot convert numeric expression value to string", menu, item);
|
||||
|
||||
staticValue = m_memory.Dup(value.m_string_value->c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
ConvertExpression(statement, expression, menu, item);
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertOrApplyStatement(Material*& staticValue,
|
||||
statement_s& statement,
|
||||
const ISimpleExpression* expression,
|
||||
const CommonMenuDef* menu,
|
||||
const CommonItemDef* item = nullptr) const
|
||||
{
|
||||
if (!expression)
|
||||
return;
|
||||
|
||||
if (!m_disable_optimizations && expression->IsStatic())
|
||||
{
|
||||
const auto value = expression->EvaluateStatic();
|
||||
if (value.m_type != SimpleExpressionValue::Type::STRING)
|
||||
throw MenuConversionException("Cannot convert numeric expression value to material", menu, item);
|
||||
|
||||
staticValue = ConvertMaterial(*value.m_string_value, menu, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
ConvertExpression(statement, expression, menu, item);
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertVisibleExpression(windowDef_t& window,
|
||||
statement_s& statement,
|
||||
const ISimpleExpression* expression,
|
||||
const CommonMenuDef* commonMenu,
|
||||
const CommonItemDef* commonItem = nullptr) const
|
||||
{
|
||||
if (!expression)
|
||||
return;
|
||||
|
||||
bool isStatic;
|
||||
bool isTruthy;
|
||||
if (m_disable_optimizations)
|
||||
{
|
||||
const auto* staticValue = dynamic_cast<const SimpleExpressionValue*>(expression);
|
||||
isStatic = staticValue != nullptr;
|
||||
isTruthy = isStatic && (staticValue->m_type == SimpleExpressionValue::Type::INT || staticValue->m_type == SimpleExpressionValue::Type::DOUBLE)
|
||||
&& staticValue->IsTruthy();
|
||||
}
|
||||
else
|
||||
{
|
||||
isStatic = expression->IsStatic();
|
||||
isTruthy = isStatic && expression->EvaluateStatic().IsTruthy();
|
||||
}
|
||||
|
||||
if (isStatic)
|
||||
{
|
||||
if (isTruthy)
|
||||
window.dynamicFlags[0] |= WINDOW_FLAG_VISIBLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
window.dynamicFlags[0] |= WINDOW_FLAG_VISIBLE;
|
||||
ConvertExpression(statement, expression, commonMenu, commonItem);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] const char*
|
||||
ConvertEventHandlerSet(const CommonEventHandlerSet* handlers, const CommonMenuDef* menu, const CommonItemDef* item = nullptr) const
|
||||
{
|
||||
if (!handlers)
|
||||
return nullptr;
|
||||
|
||||
std::ostringstream ss;
|
||||
for (const auto& element : handlers->m_elements)
|
||||
{
|
||||
if (element->GetType() != CommonEventHandlerElementType::SCRIPT)
|
||||
throw MenuConversionException("T4 event handlers must compile to a script string", menu, item);
|
||||
|
||||
ss << dynamic_cast<const CommonEventHandlerScript*>(element.get())->m_script;
|
||||
}
|
||||
|
||||
const auto script = ss.str();
|
||||
return script.empty() ? nullptr : m_memory.Dup(script.c_str());
|
||||
}
|
||||
|
||||
[[nodiscard]] ItemKeyHandler* ConvertKeyHandler(const std::multimap<int, std::unique_ptr<CommonEventHandlerSet>>& keyHandlers,
|
||||
const CommonMenuDef* menu,
|
||||
const CommonItemDef* item = nullptr) const
|
||||
{
|
||||
if (keyHandlers.empty())
|
||||
return nullptr;
|
||||
|
||||
const auto keyHandlerCount = keyHandlers.size();
|
||||
auto* output = m_memory.Alloc<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;
|
||||
}
|
||||
|
||||
[[nodiscard]] const char* CreateEnableDvarString(const std::vector<std::string>& 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)
|
||||
return nullptr;
|
||||
|
||||
auto* listBox = m_memory.Alloc<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<float>(commonListBox->m_element_width);
|
||||
listBox->elementHeight = static_cast<float>(commonListBox->m_element_height);
|
||||
item->special = static_cast<float>(commonListBox->m_feeder);
|
||||
listBox->elementStyle = commonListBox->m_element_style;
|
||||
listBox->onDoubleClick = ConvertEventHandlerSet(commonListBox->m_on_double_click.get(), &parentMenu, &commonItem);
|
||||
item->onListboxSelectionChange = ConvertEventHandlerSet(commonListBox->m_on_selection_change.get(), &parentMenu, &commonItem);
|
||||
ConvertColor(listBox->selectBorder, commonListBox->m_select_border);
|
||||
ConvertColor(listBox->disableColor, commonItem.m_disable_color);
|
||||
ConvertColor(listBox->focusColor, commonListBox->m_focus_color);
|
||||
listBox->selectIcon = ConvertMaterial(commonListBox->m_select_icon, &parentMenu, &commonItem);
|
||||
listBox->backgroundItemListbox = ConvertMaterial(commonListBox->m_background_item, &parentMenu, &commonItem);
|
||||
listBox->highlightTexture = ConvertMaterial(commonListBox->m_highlight_texture, &parentMenu, &commonItem);
|
||||
|
||||
listBox->numColumns = static_cast<int>(std::min(std::size(listBox->columnInfo), commonListBox->m_columns.size()));
|
||||
for (auto i = 0; i < listBox->numColumns; i++)
|
||||
{
|
||||
auto& col = listBox->columnInfo[i];
|
||||
const auto& commonCol = commonListBox->m_columns[i];
|
||||
|
||||
col.pos = commonCol.m_x_pos;
|
||||
col.width = commonCol.m_width;
|
||||
col.maxChars = commonCol.m_max_chars;
|
||||
col.alignment = commonCol.m_alignment;
|
||||
}
|
||||
|
||||
return listBox;
|
||||
}
|
||||
|
||||
[[nodiscard]] editFieldDef_s* ConvertEditFieldFeatures(CommonItemFeaturesEditField* commonEditField) const
|
||||
{
|
||||
if (!commonEditField)
|
||||
return nullptr;
|
||||
|
||||
auto* editField = m_memory.Alloc<editFieldDef_s>();
|
||||
editField->defVal = static_cast<float>(commonEditField->m_def_val);
|
||||
editField->minVal = static_cast<float>(commonEditField->m_min_val);
|
||||
editField->maxVal = static_cast<float>(commonEditField->m_max_val);
|
||||
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(CommonItemFeaturesMultiValue* commonMultiValue) const
|
||||
{
|
||||
if (!commonMultiValue)
|
||||
return nullptr;
|
||||
|
||||
auto* multiValue = m_memory.Alloc<multiDef_s>();
|
||||
multiValue->count = static_cast<int>(std::min(std::size(multiValue->dvarList), commonMultiValue->m_step_names.size()));
|
||||
multiValue->strDef = !commonMultiValue->m_string_values.empty() ? 1 : 0;
|
||||
|
||||
for (auto i = 0; i < multiValue->count; i++)
|
||||
{
|
||||
multiValue->dvarList[i] = ConvertString(commonMultiValue->m_step_names[i]);
|
||||
|
||||
if (multiValue->strDef)
|
||||
{
|
||||
if (commonMultiValue->m_string_values.size() > static_cast<unsigned>(i))
|
||||
multiValue->dvarStr[i] = ConvertString(commonMultiValue->m_string_values[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (commonMultiValue->m_double_values.size() > static_cast<unsigned>(i))
|
||||
multiValue->dvarValue[i] = static_cast<float>(commonMultiValue->m_double_values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return multiValue;
|
||||
}
|
||||
|
||||
[[nodiscard]] itemDef_s* ConvertItem(const CommonMenuDef& commonParentMenu, menuDef_t& parentMenu, const CommonItemDef& commonItem) const
|
||||
{
|
||||
auto* item = m_memory.Alloc<itemDef_s>();
|
||||
|
||||
item->window.name = ConvertString(commonItem.m_name);
|
||||
item->text = commonItem.m_text ? m_memory.Dup(commonItem.m_text->c_str()) : nullptr;
|
||||
item->window.group = ConvertString(commonItem.m_group);
|
||||
item->window.rectClient = ConvertRectDef(commonItem.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 = commonItem.m_type;
|
||||
item->dataType = commonItem.m_type;
|
||||
item->window.border = commonItem.m_border;
|
||||
item->window.borderSize = static_cast<float>(commonItem.m_border_size);
|
||||
ConvertVisibleExpression(item->window, item->visibleExp, commonItem.m_visible_expression.get(), &commonParentMenu, &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<float>(commonItem.m_text_align_x);
|
||||
item->textaligny = static_cast<float>(commonItem.m_text_align_y);
|
||||
item->textscale = static_cast<float>(commonItem.m_text_scale);
|
||||
item->textStyle = commonItem.m_text_style;
|
||||
item->fontEnum = commonItem.m_text_font;
|
||||
ConvertColor(item->window.backColor, commonItem.m_back_color);
|
||||
|
||||
ConvertColor(item->window.foreColor, commonItem.m_fore_color);
|
||||
ApplyFlag(item->window.dynamicFlags[0], !commonItem.m_fore_color.Equals(CommonColor(1.0, 1.0, 1.0, 1.0)), WINDOW_FLAG_NON_DEFAULT_FORECOLOR);
|
||||
|
||||
ConvertColor(item->window.borderColor, commonItem.m_border_color);
|
||||
ConvertColor(item->window.outlineColor, commonItem.m_outline_color);
|
||||
item->window.background = ConvertMaterial(commonItem.m_background, &commonParentMenu, &commonItem);
|
||||
item->onFocus = ConvertEventHandlerSet(commonItem.m_on_focus.get(), &commonParentMenu, &commonItem);
|
||||
item->leaveFocus = ConvertEventHandlerSet(commonItem.m_on_leave_focus.get(), &commonParentMenu, &commonItem);
|
||||
item->mouseEnter = ConvertEventHandlerSet(commonItem.m_on_mouse_enter.get(), &commonParentMenu, &commonItem);
|
||||
item->mouseExit = ConvertEventHandlerSet(commonItem.m_on_mouse_exit.get(), &commonParentMenu, &commonItem);
|
||||
item->mouseEnterText = ConvertEventHandlerSet(commonItem.m_on_mouse_enter_text.get(), &commonParentMenu, &commonItem);
|
||||
item->mouseExitText = ConvertEventHandlerSet(commonItem.m_on_mouse_exit_text.get(), &commonParentMenu, &commonItem);
|
||||
item->action = ConvertEventHandlerSet(commonItem.m_on_action.get(), &commonParentMenu, &commonItem);
|
||||
item->onAccept = ConvertEventHandlerSet(commonItem.m_on_accept.get(), &commonParentMenu, &commonItem);
|
||||
item->focusSound = ConvertSound(commonItem.m_focus_sound, &commonParentMenu, &commonItem);
|
||||
item->dvar = ConvertString(commonItem.m_dvar);
|
||||
item->dvarTest = ConvertString(commonItem.m_dvar_test);
|
||||
item->enableDvar = ConvertEnableDvar(commonItem, item->dvarFlags);
|
||||
item->onKey = ConvertKeyHandler(commonItem.m_key_handlers, &commonParentMenu, &commonItem);
|
||||
ConvertOrApplyStatement(item->text, item->textExp, commonItem.m_text_expression.get(), &commonParentMenu, &commonItem);
|
||||
ConvertOrApplyStatement(item->window.background, item->materialExp, commonItem.m_material_expression.get(), &commonParentMenu, &commonItem);
|
||||
ConvertOrApplyStatement(item->window.rectClient.x, item->rectXExp, commonItem.m_rect_x_exp.get(), &commonParentMenu, &commonItem);
|
||||
ConvertOrApplyStatement(item->window.rectClient.y, item->rectYExp, commonItem.m_rect_y_exp.get(), &commonParentMenu, &commonItem);
|
||||
ConvertOrApplyStatement(item->window.rectClient.w, item->rectWExp, commonItem.m_rect_w_exp.get(), &commonParentMenu, &commonItem);
|
||||
ConvertOrApplyStatement(item->window.rectClient.h, item->rectHExp, commonItem.m_rect_h_exp.get(), &commonParentMenu, &commonItem);
|
||||
ConvertOrApplyStatement(
|
||||
item->window.foreColor[3], item->forecolorAExp, commonItem.m_forecolor_expressions.m_a_exp.get(), &commonParentMenu, &commonItem);
|
||||
item->gameMsgWindowIndex = commonItem.m_game_message_window_index;
|
||||
item->gameMsgWindowMode = commonItem.m_game_message_window_mode;
|
||||
|
||||
switch (commonItem.m_feature_type)
|
||||
{
|
||||
case CommonItemFeatureType::LISTBOX:
|
||||
item->typeData.listBox = ConvertListBoxFeatures(item, commonItem.m_list_box_features.get(), commonParentMenu, commonItem);
|
||||
break;
|
||||
|
||||
case CommonItemFeatureType::EDIT_FIELD:
|
||||
item->typeData.editField = ConvertEditFieldFeatures(commonItem.m_edit_field_features.get());
|
||||
break;
|
||||
|
||||
case CommonItemFeatureType::MULTI_VALUE:
|
||||
item->typeData.multi = ConvertMultiValueFeatures(commonItem.m_multi_value_features.get());
|
||||
break;
|
||||
|
||||
case CommonItemFeatureType::ENUM_DVAR:
|
||||
item->typeData.enumDvarName = ConvertString(commonItem.m_enum_dvar_name);
|
||||
break;
|
||||
|
||||
case CommonItemFeatureType::NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Do this last so any optimizations are considered
|
||||
item->window.rect = ConvertRectDefRelativeTo(item->window.rectClient, parentMenu.window.rect);
|
||||
|
||||
item->parent = &parentMenu;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
itemDef_s** ConvertMenuItems(const CommonMenuDef& commonMenu, menuDef_t& menu, int& itemCount) const
|
||||
{
|
||||
if (commonMenu.m_items.empty())
|
||||
{
|
||||
itemCount = 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* items = m_memory.Alloc<itemDef_s*>(commonMenu.m_items.size());
|
||||
for (auto i = 0u; i < commonMenu.m_items.size(); i++)
|
||||
items[i] = ConvertItem(commonMenu, menu, *commonMenu.m_items[i]);
|
||||
|
||||
itemCount = static_cast<int>(commonMenu.m_items.size());
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public:
|
||||
MenuConverter(const bool disableOptimizations, ISearchPath& searchPath, MemoryManager& memory, AssetCreationContext& context)
|
||||
: AbstractMenuConverter(disableOptimizations, searchPath, memory, context)
|
||||
{
|
||||
}
|
||||
|
||||
bool ConvertMenu(const CommonMenuDef& commonMenu, menuDef_t& menu, AssetRegistration<AssetMenu>& registration) override
|
||||
{
|
||||
try
|
||||
{
|
||||
menu.window.name = m_memory.Dup(commonMenu.m_name.c_str());
|
||||
menu.fullScreen = commonMenu.m_full_screen ? 1 : 0;
|
||||
ApplyFlag(menu.window.staticFlags, commonMenu.m_decoration, WINDOW_FLAG_DECORATION);
|
||||
menu.window.rect = ConvertRectDef(commonMenu.m_rect);
|
||||
menu.window.rectClient = menu.window.rect;
|
||||
menu.window.style = commonMenu.m_style;
|
||||
menu.window.border = commonMenu.m_border;
|
||||
menu.window.borderSize = static_cast<float>(commonMenu.m_border_size);
|
||||
ConvertColor(menu.window.backColor, commonMenu.m_back_color);
|
||||
ConvertColor(menu.window.foreColor, commonMenu.m_fore_color);
|
||||
ApplyFlag(menu.window.dynamicFlags[0], !commonMenu.m_fore_color.Equals(CommonColor(1.0, 1.0, 1.0, 1.0)), WINDOW_FLAG_NON_DEFAULT_FORECOLOR);
|
||||
ConvertColor(menu.window.borderColor, commonMenu.m_border_color);
|
||||
ConvertColor(menu.window.outlineColor, commonMenu.m_outline_color);
|
||||
ConvertColor(menu.focusColor, commonMenu.m_focus_color);
|
||||
ConvertColor(menu.disableColor, commonMenu.m_disable_color);
|
||||
menu.window.background = ConvertMaterial(commonMenu.m_background, &commonMenu);
|
||||
menu.window.ownerDraw = commonMenu.m_owner_draw;
|
||||
menu.window.ownerDrawFlags = commonMenu.m_owner_draw_flags;
|
||||
ApplyFlag(menu.window.staticFlags, commonMenu.m_out_of_bounds_click, WINDOW_FLAG_OUT_OF_BOUNDS_CLICK);
|
||||
menu.soundName = ConvertString(commonMenu.m_sound_loop);
|
||||
ApplyFlag(menu.window.staticFlags, commonMenu.m_popup, WINDOW_FLAG_POPUP);
|
||||
menu.fadeClamp = static_cast<float>(commonMenu.m_fade_clamp);
|
||||
menu.fadeCycle = commonMenu.m_fade_cycle;
|
||||
menu.fadeAmount = static_cast<float>(commonMenu.m_fade_amount);
|
||||
menu.fadeInAmount = static_cast<float>(commonMenu.m_fade_in_amount);
|
||||
menu.blurRadius = static_cast<float>(commonMenu.m_blur_radius);
|
||||
ApplyFlag(menu.window.staticFlags, commonMenu.m_legacy_split_screen_scale, WINDOW_FLAG_LEGACY_SPLIT_SCREEN_SCALE);
|
||||
ApplyFlag(menu.window.staticFlags, commonMenu.m_hidden_during_scope, WINDOW_FLAG_HIDDEN_DURING_SCOPE);
|
||||
ApplyFlag(menu.window.staticFlags, commonMenu.m_hidden_during_flashbang, WINDOW_FLAG_HIDDEN_DURING_FLASH_BANG);
|
||||
ApplyFlag(menu.window.staticFlags, commonMenu.m_hidden_during_ui, WINDOW_FLAG_HIDDEN_DURING_UI);
|
||||
menu.allowedBinding = ConvertString(commonMenu.m_allowed_binding);
|
||||
ConvertVisibleExpression(menu.window, menu.visibleExp, commonMenu.m_visible_expression.get(), &commonMenu);
|
||||
ConvertOrApplyStatement(menu.window.rect.x, menu.rectXExp, commonMenu.m_rect_x_exp.get(), &commonMenu);
|
||||
ConvertOrApplyStatement(menu.window.rect.y, menu.rectYExp, commonMenu.m_rect_y_exp.get(), &commonMenu);
|
||||
menu.onOpen = ConvertEventHandlerSet(commonMenu.m_on_open.get(), &commonMenu);
|
||||
menu.onFocus = ConvertEventHandlerSet(commonMenu.m_on_focus.get(), &commonMenu);
|
||||
menu.onClose = ConvertEventHandlerSet(commonMenu.m_on_close.get(), &commonMenu);
|
||||
menu.onESC = ConvertEventHandlerSet(commonMenu.m_on_esc.get(), &commonMenu);
|
||||
menu.onKey = ConvertKeyHandler(commonMenu.m_key_handlers, &commonMenu);
|
||||
menu.items = ConvertMenuItems(commonMenu, menu, menu.itemCount);
|
||||
}
|
||||
catch (const MenuConversionException& e)
|
||||
{
|
||||
PrintConversionExceptionDetails(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<IMenuConverter>
|
||||
IMenuConverter::Create(const bool disableOptimizations, ISearchPath& searchPath, MemoryManager& memory, AssetCreationContext& context)
|
||||
{
|
||||
return std::make_unique<MenuConverter>(disableOptimizations, searchPath, memory, context);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "Asset/AssetCreationContext.h"
|
||||
#include "Game/T4/T4.h"
|
||||
#include "Parsing/Menu/Domain/CommonMenuDef.h"
|
||||
#include "SearchPath/ISearchPath.h"
|
||||
#include "Utils/MemoryManager.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace T4
|
||||
{
|
||||
class IMenuConverter
|
||||
{
|
||||
public:
|
||||
IMenuConverter() = default;
|
||||
virtual ~IMenuConverter() = default;
|
||||
|
||||
virtual bool ConvertMenu(const menu::CommonMenuDef& commonMenu, menuDef_t& menu, AssetRegistration<AssetMenu>& registration) = 0;
|
||||
|
||||
static std::unique_ptr<IMenuConverter> Create(bool disableOptimizations, ISearchPath& searchPath, MemoryManager& memory, AssetCreationContext& context);
|
||||
};
|
||||
} // namespace T4
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "Game/T4/Font/FontLoaderT4.h"
|
||||
#include "Game/T4/Image/ImageLoaderEmbeddedT4.h"
|
||||
#include "Game/T4/Image/ImageLoaderExternalT4.h"
|
||||
#include "Game/T4/Menu/LoaderMenuListT4.h"
|
||||
#include "Game/T4/T4.h"
|
||||
#include "Game/T4/Weapon/AccuracyGraphLoaderT4.h"
|
||||
#include "Game/T4/XAnim/XAnimLoaderT4.h"
|
||||
@@ -104,6 +105,7 @@ namespace
|
||||
collection.AddAssetCreator(image::CreateLoaderExternalT4(memory, searchPath));
|
||||
collection.AddAssetCreator(material::CreateLoaderT4(memory, searchPath));
|
||||
collection.AddAssetCreator(font::CreateLoaderT4(memory, searchPath));
|
||||
collection.AddAssetCreator(menu::CreateMenuListLoaderT4(memory, searchPath));
|
||||
collection.AddAssetCreator(localize::CreateLoaderT4(memory, searchPath, zone));
|
||||
collection.AddAssetCreator(map_ents::CreateLoaderT4(memory, searchPath));
|
||||
collection.AddAssetCreator(phys_preset::CreateRawLoaderT4(memory, searchPath, zone));
|
||||
|
||||
@@ -43,9 +43,13 @@ namespace menu
|
||||
double m_feeder = 0;
|
||||
int m_element_style = 0;
|
||||
CommonColor m_select_border;
|
||||
CommonColor m_focus_color;
|
||||
std::string m_select_icon;
|
||||
std::string m_background_item;
|
||||
std::string m_highlight_texture;
|
||||
|
||||
std::unique_ptr<CommonEventHandlerSet> m_on_double_click;
|
||||
std::unique_ptr<CommonEventHandlerSet> m_on_selection_change;
|
||||
std::unique_ptr<ISimpleExpression> m_element_height_expression;
|
||||
std::vector<Column> m_columns;
|
||||
};
|
||||
|
||||
@@ -45,6 +45,7 @@ namespace menu
|
||||
std::unique_ptr<ISimpleExpression> m_open_sound_exp;
|
||||
std::unique_ptr<ISimpleExpression> m_close_sound_exp;
|
||||
std::unique_ptr<CommonEventHandlerSet> m_on_open;
|
||||
std::unique_ptr<CommonEventHandlerSet> m_on_focus;
|
||||
std::unique_ptr<CommonEventHandlerSet> m_on_close;
|
||||
std::unique_ptr<CommonEventHandlerSet> m_on_request_close;
|
||||
std::unique_ptr<CommonEventHandlerSet> m_on_esc;
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace menu
|
||||
{
|
||||
IW3,
|
||||
IW4,
|
||||
IW5
|
||||
IW5,
|
||||
T4
|
||||
};
|
||||
} // namespace menu
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "Game/IW4/MenuConstantsIW4.h"
|
||||
#include "Game/IW5/IW5.h"
|
||||
#include "Game/IW5/MenuConstantsIW5.h"
|
||||
#include "Game/T4/MenuConstantsT4.h"
|
||||
#include "Game/T4/T4.h"
|
||||
#include "MenuMatcherFactory.h"
|
||||
#include "Parsing/Menu/Domain/Expression/CommonExpressionBaseFunctionCall.h"
|
||||
#include "Parsing/Menu/Domain/Expression/CommonExpressionCustomFunctionCall.h"
|
||||
@@ -70,6 +72,24 @@ const std::unordered_map<std::string, size_t>& MenuExpressionMatchers::GetBaseFu
|
||||
return iw3FunctionMap;
|
||||
}
|
||||
|
||||
if (featureLevel == FeatureLevel::T4)
|
||||
{
|
||||
static std::unordered_map<std::string, size_t> t4FunctionMap;
|
||||
static bool t4FunctionMapInitialized = false;
|
||||
|
||||
if (!t4FunctionMapInitialized)
|
||||
{
|
||||
for (size_t i = T4::OP_FIRSTFUNCTIONCALL; i < std::extent_v<decltype(T4::g_expFunctionNames)>; i++)
|
||||
{
|
||||
std::string functionName(T4::g_expFunctionNames[i]);
|
||||
utils::MakeStringLowerCase(functionName);
|
||||
t4FunctionMap.emplace(std::make_pair(std::move(functionName), i));
|
||||
}
|
||||
}
|
||||
|
||||
return t4FunctionMap;
|
||||
}
|
||||
|
||||
if (featureLevel == FeatureLevel::IW4)
|
||||
{
|
||||
static std::unordered_map<std::string, size_t> iw4FunctionMap;
|
||||
|
||||
@@ -48,6 +48,9 @@ void MenuFileReader::SetupDefinesProxy()
|
||||
case FeatureLevel::IW5:
|
||||
defines->AddDefine(DefinesStreamProxy::Define("FEATURE_LEVEL_IW5", "1"));
|
||||
break;
|
||||
case FeatureLevel::T4:
|
||||
defines->AddDefine(DefinesStreamProxy::Define("FEATURE_LEVEL_T4", "1"));
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
|
||||
@@ -929,8 +929,8 @@ void EventHandlerSetScopeSequences::AddSequences(const FeatureLevel featureLevel
|
||||
{
|
||||
AddSequence(std::make_unique<SequenceSkipEmptyStatements>());
|
||||
|
||||
// IW3 stores event handlers as opaque script strings rather than structured handlers.
|
||||
if (featureLevel == FeatureLevel::IW3)
|
||||
// IW3 and T4 store event handlers as opaque script strings rather than structured handlers.
|
||||
if (featureLevel == FeatureLevel::IW3 || featureLevel == FeatureLevel::T4)
|
||||
{
|
||||
AddSequence(std::make_unique<SequenceCloseBlock>());
|
||||
AddSequence(std::make_unique<SequenceSkipScriptToken>());
|
||||
|
||||
@@ -109,7 +109,7 @@ GlobalScopeSequences::GlobalScopeSequences(std::vector<std::unique_ptr<MenuFileP
|
||||
void GlobalScopeSequences::AddSequences(const FeatureLevel featureLevel, const bool permissive) const
|
||||
{
|
||||
AddSequence(std::make_unique<SequenceCloseBlock>());
|
||||
if (featureLevel != FeatureLevel::IW3)
|
||||
if (featureLevel != FeatureLevel::IW3 && featureLevel != FeatureLevel::T4)
|
||||
AddSequence(std::make_unique<SequenceFunctionDef>());
|
||||
AddSequence(std::make_unique<SequenceMenuDef>());
|
||||
AddSequence(std::make_unique<SequenceLoadMenu>());
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace
|
||||
|
||||
class ItemScopeOperations
|
||||
{
|
||||
inline static const CommonItemFeatureType IW3_IW4_FEATURE_TYPE_BY_TYPE[0x18]{
|
||||
inline static const CommonItemFeatureType IW3_IW4_T4_FEATURE_TYPE_BY_TYPE[0x18]{
|
||||
CommonItemFeatureType::EDIT_FIELD, // ITEM_TYPE_TEXT
|
||||
CommonItemFeatureType::NONE, // ITEM_TYPE_BUTTON
|
||||
CommonItemFeatureType::NONE, // ITEM_TYPE_RADIOBUTTON
|
||||
@@ -84,9 +84,10 @@ namespace
|
||||
{
|
||||
case FeatureLevel::IW3:
|
||||
case FeatureLevel::IW4:
|
||||
if (static_cast<unsigned>(type) >= std::extent_v<decltype(IW3_IW4_FEATURE_TYPE_BY_TYPE)>)
|
||||
case FeatureLevel::T4:
|
||||
if (static_cast<unsigned>(type) >= std::extent_v<decltype(IW3_IW4_T4_FEATURE_TYPE_BY_TYPE)>)
|
||||
throw ParsingException(pos, "Invalid item type");
|
||||
return IW3_IW4_FEATURE_TYPE_BY_TYPE[static_cast<unsigned>(type)];
|
||||
return IW3_IW4_T4_FEATURE_TYPE_BY_TYPE[static_cast<unsigned>(type)];
|
||||
|
||||
case FeatureLevel::IW5:
|
||||
default:
|
||||
@@ -464,8 +465,8 @@ namespace
|
||||
static constexpr auto CAPTURE_FIRST_TOKEN = 1;
|
||||
static constexpr auto CAPTURE_COLUMN_COUNT = 2;
|
||||
|
||||
static constexpr const char* SYNTAX_IW3_IW4 = "<xpos> <width> <maxChars> [alignment]";
|
||||
static constexpr auto BASE_COLUMN_COUNT_IW3_IW4 = 3;
|
||||
static constexpr const char* SYNTAX_IW3_IW4_T4 = "<xpos> <width> <maxChars> [alignment]";
|
||||
static constexpr auto BASE_COLUMN_COUNT_IW3_IW4_T4 = 3;
|
||||
|
||||
static constexpr const char* SYNTAX_IW5 = "<xpos> <ypos> <width> <height> <maxChars> [alignment]";
|
||||
static constexpr auto BASE_COLUMN_COUNT_IW5 = 5;
|
||||
@@ -487,7 +488,8 @@ namespace
|
||||
void ProcessMatch(MenuFileParserState* state, SequenceResult<SimpleParserValue>& result) const override
|
||||
{
|
||||
assert(state->m_current_item);
|
||||
assert(state->m_feature_level == FeatureLevel::IW3 || state->m_feature_level == FeatureLevel::IW4 || state->m_feature_level == FeatureLevel::IW5);
|
||||
assert(state->m_feature_level == FeatureLevel::IW3 || state->m_feature_level == FeatureLevel::IW4 || state->m_feature_level == FeatureLevel::IW5
|
||||
|| state->m_feature_level == FeatureLevel::T4);
|
||||
|
||||
const auto& firstToken = result.NextCapture(CAPTURE_FIRST_TOKEN);
|
||||
ItemScopeOperations::EnsureHasListboxFeatures(*state->m_current_item, firstToken.GetPos());
|
||||
@@ -500,10 +502,10 @@ namespace
|
||||
size_t baseColumnCount;
|
||||
const char* syntax;
|
||||
bool hasHeightValues;
|
||||
if (state->m_feature_level == FeatureLevel::IW3 || state->m_feature_level == FeatureLevel::IW4)
|
||||
if (state->m_feature_level == FeatureLevel::IW3 || state->m_feature_level == FeatureLevel::IW4 || state->m_feature_level == FeatureLevel::T4)
|
||||
{
|
||||
baseColumnCount = BASE_COLUMN_COUNT_IW3_IW4;
|
||||
syntax = SYNTAX_IW3_IW4;
|
||||
baseColumnCount = BASE_COLUMN_COUNT_IW3_IW4_T4;
|
||||
syntax = SYNTAX_IW3_IW4_T4;
|
||||
hasHeightValues = false;
|
||||
}
|
||||
else
|
||||
@@ -1143,6 +1145,35 @@ void ItemScopeSequences::AddSequences(const FeatureLevel featureLevel, const boo
|
||||
state->m_current_item->m_list_box_features->m_select_icon = value;
|
||||
}));
|
||||
|
||||
if (featureLevel == FeatureLevel::T4)
|
||||
{
|
||||
AddSequence(std::make_unique<GenericMenuEventHandlerSetPropertySequence>(
|
||||
"onListboxSelectionChange",
|
||||
[](const MenuFileParserState* state, const TokenPos& pos) -> std::unique_ptr<CommonEventHandlerSet>&
|
||||
{
|
||||
ItemScopeOperations::EnsureHasListboxFeatures(*state->m_current_item, pos);
|
||||
return state->m_current_item->m_list_box_features->m_on_selection_change;
|
||||
}));
|
||||
AddSequence(std::make_unique<GenericColorPropertySequence>("focusColor",
|
||||
[](const MenuFileParserState* state, const TokenPos& pos, const CommonColor value)
|
||||
{
|
||||
ItemScopeOperations::EnsureHasListboxFeatures(*state->m_current_item, pos);
|
||||
state->m_current_item->m_list_box_features->m_focus_color = value;
|
||||
}));
|
||||
AddSequence(std::make_unique<GenericStringPropertySequence>("backgroundItemListbox",
|
||||
[](const MenuFileParserState* state, const TokenPos& pos, const std::string& value)
|
||||
{
|
||||
ItemScopeOperations::EnsureHasListboxFeatures(*state->m_current_item, pos);
|
||||
state->m_current_item->m_list_box_features->m_background_item = value;
|
||||
}));
|
||||
AddSequence(std::make_unique<GenericStringPropertySequence>("highlightTexture",
|
||||
[](const MenuFileParserState* state, const TokenPos& pos, const std::string& value)
|
||||
{
|
||||
ItemScopeOperations::EnsureHasListboxFeatures(*state->m_current_item, pos);
|
||||
state->m_current_item->m_list_box_features->m_highlight_texture = value;
|
||||
}));
|
||||
}
|
||||
|
||||
if (featureLevel == FeatureLevel::IW5)
|
||||
{
|
||||
AddSequence(
|
||||
|
||||
@@ -261,6 +261,15 @@ void MenuScopeSequences::AddSequences(const FeatureLevel featureLevel, const boo
|
||||
{
|
||||
return state->m_current_menu->m_on_open;
|
||||
}));
|
||||
if (featureLevel == FeatureLevel::T4)
|
||||
{
|
||||
AddSequence(std::make_unique<GenericMenuEventHandlerSetPropertySequence>(
|
||||
"onFocus",
|
||||
[](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr<CommonEventHandlerSet>&
|
||||
{
|
||||
return state->m_current_menu->m_on_focus;
|
||||
}));
|
||||
}
|
||||
AddSequence(std::make_unique<GenericMenuEventHandlerSetPropertySequence>(
|
||||
"onClose",
|
||||
[](const MenuFileParserState* state, const TokenPos&) -> std::unique_ptr<CommonEventHandlerSet>&
|
||||
|
||||
Reference in New Issue
Block a user