feat: IW3 menu dumping and loading (#909)

* feat: IW3 menu dumping

* fix: IW3 menu dumper preserve menu ownerdraw flag masks

* fix: IW3 menu dumper preserve numeric script arguments

Emit numeric tokens without quotes so the linker keeps colour arguments separate.

Before: `"0.1" "0.1" "0.12" "0.5"` becomes `"0.10.10.120.5"`
After: `0.1 0.1 0.12 0.5`

This fixes `mouseExit` colour commands failing to clear menu hover borders.

* fix: IW3 menu dumper preserve empty item text

Keep explicit empty text distinct from null text when dumping menu items.

This fixes CD-key values overflowing their input boxes.

* test: cover IW3 menu dumper material case

* test: cover IW3 menu list path precedence

* chore: clang format

* fix: reverse order of union to avoid edge case bug in zcg

* feat: IW3 menu loading

* fix: validate menu expression name table

* chore: explain numeric token quoting in comment

* refactor: simplify menu dump fallback path

* chore: add more dynamic window flags

* refactor: reuse IW3 menu window flag constants

* refactor: use explicit menu item feature lookup

* refactor: represent menu item text as optional

* refactor: clarify shared IW3 and IW4 menu constants

IW4 only extends the type enum so they can be shared

* refactor: clarify IW3 menu zone state naming

* fix: log IW3 menu conversion failures

* refactor: align IW3 menu list loader with IW4

* refactor: align IW3 menu dumpers with IW4

* refactor: align IW3 menu converter with IW4

* chore: use default enum numeration for operationEnum

* chore: small optimization on edit field capability initialization

* fix: iw3 menu optimizations for rect not considering relative position to parent

* chore: align iw3 menu converting closer to iw4

* chore: align iw3 menu dumping closer to iw4

* fix: not dumping menu flags properties correctly

* chore: adjust iw3 expression dumping to work similar to iw4

* chore: adjust iw3 expression converting to work similar to iw4

* chore: use unordered_map for MenuExpressionMatchers

---------

Co-authored-by: Jan Laupetin <[email protected]>
This commit is contained in:
mo
2026-08-31 00:04:01 +02:00
committed by GitHub
co-authored by Jan Laupetin
parent 4d4921e230
commit 51770e7ae5
42 changed files with 3218 additions and 60 deletions
+134 -5
View File
@@ -2513,6 +2513,37 @@ namespace IW3
int vertAlign;
};
enum WindowDefStaticFlag : unsigned int
{
WINDOW_FLAG_DECORATION = 0x100000,
WINDOW_FLAG_HORIZONTAL_SCROLL = 0x200000,
WINDOW_FLAG_AUTO_WRAPPED = 0x800000,
WINDOW_FLAG_POPUP = 0x1000000,
WINDOW_FLAG_OUT_OF_BOUNDS_CLICK = 0x2000000,
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,
};
enum WindowDefDynamicFlag : unsigned int
{
WINDOW_FLAG_1 = 0x1,
WINDOW_FLAG_FOCUSED = 0x2,
WINDOW_FLAG_VISIBLE = 0x4,
WINDOW_FLAG_FADING_OUT = 0x10,
WINDOW_FLAG_FADING_IN = 0x20,
WINDOW_FLAG_HOVERED = 0x40,
WINDOW_FLAG_LISTBOX_HOVER_100 = 0x100,
WINDOW_FLAG_LISTBOX_HOVER_200 = 0x200,
WINDOW_FLAG_LISTBOX_HOVER_400 = 0x400,
WINDOW_FLAG_LISTBOX_HOVER_800 = 0x800,
WINDOW_FLAG_LISTBOX_HOVER_1000 = 0x1000,
WINDOW_FLAG_4000 = 0x4000,
WINDOW_FLAG_NON_DEFAULT_BACKCOLOR = 0x8000,
WINDOW_FLAG_NON_DEFAULT_FORECOLOR = 0x10000,
};
struct windowDef_t
{
const char* name;
@@ -2522,10 +2553,10 @@ namespace IW3
int style;
int border;
int ownerDraw;
int ownerDrawFlags;
unsigned int ownerDrawFlags;
float borderSize;
int staticFlags;
int dynamicFlags[1];
unsigned int staticFlags;
unsigned int dynamicFlags[1];
int nextTime;
float foreColor[4];
float backColor[4];
@@ -2561,10 +2592,99 @@ namespace IW3
operandInternalDataUnion internals;
};
enum operationEnum : int
{
OP_NOOP,
OP_RIGHTPAREN,
OP_MULTIPLY,
OP_DIVIDE,
OP_MODULUS,
OP_ADD,
OP_SUBTRACT,
OP_NOT,
OP_LESSTHAN,
OP_LESSTHANEQUALTO,
OP_GREATERTHAN,
OP_GREATERTHANEQUALTO,
OP_EQUALS,
OP_NOTEQUAL,
OP_AND,
OP_OR,
OP_LEFTPAREN,
OP_COMMA,
OP_BITWISEAND,
OP_BITWISEOR,
OP_BITWISENOT,
OP_BITSHIFTLEFT,
OP_BITSHIFTRIGHT,
OP_FIRSTFUNCTIONCALL,
OP_SIN = OP_FIRSTFUNCTIONCALL,
OP_COS,
OP_MIN,
OP_MAX,
OP_MILLISECONDS,
OP_DVARINT,
OP_DVARBOOL,
OP_DVARFLOAT,
OP_DVARSTRING,
OP_STAT,
OP_UIACTIVE,
OP_FLASHBANGED,
OP_SCOPED,
OP_SCOREBOARDVISIBLE,
OP_INKILLCAM,
OP_PLAYERFIELD,
OP_SELECTINGLOCATION,
OP_TEAMFIELD,
OP_OTHERTEAMFIELD,
OP_MARINESFIELD,
OP_OPFORFIELD,
OP_MENUISOPEN,
OP_WRITINGDATA,
OP_INLOBBY,
OP_INPRIVATEPARTY,
OP_PRIVATEPARTYHOST,
OP_PRIVATEPARTYHOSTINLOBBY,
OP_ALONEINPARTY,
OP_ADSJAVELIN,
OP_WEAPLOCKBLINK,
OP_WEAPATTACKTOP,
OP_WEAPATTACKDIRECT,
OP_SECONDSASTIME,
OP_TABLELOOKUP,
OP_LOCALIZESTRING,
OP_LOCALVARINT,
OP_LOCALVARBOOL,
OP_LOCALVARFLOAT,
OP_LOCALVARSTRING,
OP_TIMELEFT,
OP_SECONDSASCOUNTDOWN,
OP_GAMEMSGWNDACTIVE,
OP_TOINT,
OP_TOSTRING,
OP_TOFLOAT,
OP_GAMETYPENAME,
OP_GAMETYPE,
OP_GAMETYPEDESCRIPTION,
OP_SCORE,
OP_FRIENDSONLINE,
OP_FOLLOWING,
OP_STATRANGEBITSSET,
OP_KEYBINDING,
OP_ACTIONSLOTUSABLE,
OP_HUDFADE,
OP_MAXPLAYERS,
OP_ACCEPTINGINVITE,
OP_ISINTERMISSION,
NUM_OPERATORS,
};
union entryInternalData
{
Operand operand;
int op;
operationEnum op;
};
enum expressionEntryType : int
@@ -2604,7 +2724,7 @@ namespace IW3
int elementStyle;
int numColumns;
columnInfo_s columnInfo[16];
const char* doubleClick;
const char* onDoubleClick;
int notselectable;
int noScrollBars;
int usePaging;
@@ -2667,6 +2787,15 @@ namespace IW3
ITEM_TYPE_GAME_MESSAGE_WINDOW = 0x13
};
enum ItemDefDvarFlag
{
ITEM_DVAR_FLAG_ENABLE = 0x1,
ITEM_DVAR_FLAG_DISABLE = 0x2,
ITEM_DVAR_FLAG_SHOW = 0x4,
ITEM_DVAR_FLAG_HIDE = 0x8,
ITEM_DVAR_FLAG_FOCUS = 0x10,
};
struct itemDef_s
{
windowDef_t window;
+1 -1
View File
@@ -2313,7 +2313,7 @@ namespace IW4
int style;
int border;
int ownerDraw;
int ownerDrawFlags;
unsigned int ownerDrawFlags;
float borderSize;
int staticFlags;
unsigned int dynamicFlags[1];
+1 -1
View File
@@ -3233,7 +3233,7 @@ namespace IW5
int style;
int border;
int ownerDraw;
int ownerDrawFlags;
unsigned int ownerDrawFlags;
float borderSize;
int staticFlags;
int dynamicFlags[1];
+94
View File
@@ -0,0 +1,94 @@
#pragma once
#include "Game/IW3/IW3.h"
#include <type_traits>
namespace IW3
{
inline constexpr const char* g_expFunctionNames[]{
"NOOP",
")",
"*",
"/",
"%",
"+",
"-",
"!",
"<",
"<=",
">",
">=",
"==",
"!=",
"&&",
"||",
"(",
",",
"&",
"|",
"~",
"<<",
">>",
"sin",
"cos",
"min",
"max",
"milliseconds",
"dvarint",
"dvarbool",
"dvarfloat",
"dvarstring",
"stat",
"ui_active",
"flashbanged",
"scoped",
"scoreboard_visible",
"inkillcam",
"player",
"selecting_location",
"team",
"otherteam",
"marinesfield",
"opforfield",
"menuisopen",
"writingdata",
"inlobby",
"inprivateparty",
"privatepartyhost",
"privatepartyhostinlobby",
"aloneinparty",
"adsjavelin",
"weaplockblink",
"weapattacktop",
"weapattackdirect",
"secondsastime",
"tablelookup",
"locstring",
"localvarint",
"localvarbool",
"localvarfloat",
"localvarstring",
"timeleft",
"secondsascountdown",
"gamemsgwndactive",
"int",
"string",
"float",
"gametypename",
"gametype",
"gametypedescription",
"scoreatrank",
"friendsonline",
"spectatingclient",
"statrangeanybitsset",
"keybinding",
"actionslotusable",
"hudfade",
"maxrecommendedplayers",
"acceptinginvite",
"isintermission",
};
static_assert(std::extent_v<decltype(g_expFunctionNames)> == static_cast<unsigned>(NUM_OPERATORS));
} // namespace IW3
@@ -0,0 +1,215 @@
#include "LoaderMenuListIW3.h"
#include "Game/IW3/Menu/MenuConversionZoneStateIW3.h"
#include "Game/IW3/Menu/MenuConverterIW3.h"
#include "ObjLoading.h"
#include "Parsing/Menu/MenuAssetZoneState.h"
#include "Parsing/Menu/MenuFileReader.h"
#include "Utils/Logging/Log.h"
#include <deque>
using namespace IW3;
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::IW3, 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>> CreateMenuListLoaderIW3(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/IW3/IW3.h"
#include "SearchPath/ISearchPath.h"
#include "Utils/MemoryManager.h"
#include <memory>
namespace menu
{
std::unique_ptr<AssetCreator<IW3::AssetMenuList>> CreateMenuListLoaderIW3(MemoryManager& memory, ISearchPath& searchPath);
}
@@ -0,0 +1,8 @@
#include "MenuConversionZoneStateIW3.h"
using namespace IW3;
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/IW3/IW3.h"
#include <string>
#include <unordered_map>
#include <vector>
namespace IW3
{
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 IW3
@@ -0,0 +1,729 @@
#include "MenuConverterIW3.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 <format>
#include <sstream>
#include <vector>
using namespace IW3;
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("IW3 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("IW3 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);
ConvertColor(listBox->selectBorder, commonListBox->m_select_border);
ConvertColor(listBox->disableColor, commonItem.m_disable_color);
listBox->selectIcon = ConvertMaterial(commonListBox->m_select_icon, &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.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/IW3/IW3.h"
#include "Parsing/Menu/Domain/CommonMenuDef.h"
#include "SearchPath/ISearchPath.h"
#include "Utils/MemoryManager.h"
#include <memory>
namespace IW3
{
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 IW3
+2 -1
View File
@@ -7,6 +7,7 @@
#include "Game/IW3/IW3.h"
#include "Game/IW3/Image/ImageLoaderEmbeddedIW3.h"
#include "Game/IW3/Image/ImageLoaderExternalIW3.h"
#include "Game/IW3/Menu/LoaderMenuListIW3.h"
#include "Game/IW3/Techset/PixelShaderLoaderIW3.h"
#include "Game/IW3/Techset/VertexShaderLoaderIW3.h"
#include "Game/IW3/Weapon/AccuracyGraphLoaderIW3.h"
@@ -121,7 +122,7 @@ namespace
// collection.AddAssetCreator(std::make_unique<AssetLoaderGfxWorld>(memory));
collection.AddAssetCreator(light_def::CreateLoaderIW3(memory, searchPath));
collection.AddAssetCreator(font::CreateLoaderIW3(memory, searchPath));
// collection.AddAssetCreator(std::make_unique<AssetLoaderMenuList>(memory));
collection.AddAssetCreator(menu::CreateMenuListLoaderIW3(memory, searchPath));
// collection.AddAssetCreator(std::make_unique<AssetLoaderMenu>(memory));
collection.AddAssetCreator(localize::CreateLoaderIW3(memory, searchPath, zone));
collection.AddAssetCreator(weapon::CreateRawLoaderIW3(memory, searchPath, zone));
@@ -1117,7 +1117,7 @@ namespace
auto* item = m_memory.Alloc<itemDef_s>();
item->window.name = ConvertString(commonItem.m_name);
item->text = ConvertString(commonItem.m_text);
item->text = commonItem.m_text ? m_memory.Dup(commonItem.m_text->c_str()) : nullptr;
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);
@@ -1123,7 +1123,7 @@ namespace
memset(item, 0, sizeof(itemDef_s));
item->window.name = ConvertString(commonItem.m_name);
item->text = ConvertString(commonItem.m_text);
item->text = commonItem.m_text ? m_memory.Dup(commonItem.m_text->c_str()) : nullptr;
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);
@@ -5,6 +5,7 @@
#include "Parsing/Simple/Expression/ISimpleExpression.h"
#include <map>
#include <optional>
#include <string>
#include <vector>
@@ -92,7 +93,7 @@ namespace menu
};
std::string m_name;
std::string m_text;
std::optional<std::string> m_text;
bool m_text_save_game = false;
bool m_text_cinematic_subtitle = false;
std::string m_group;
@@ -106,7 +107,7 @@ namespace menu
int m_border = 0;
double m_border_size = 0;
int m_owner_draw = 0;
int m_owner_draw_flags = 0;
unsigned m_owner_draw_flags = 0;
int m_align = 0;
int m_text_align = 0;
double m_text_align_x = 0;
@@ -24,10 +24,11 @@ namespace menu
CommonColor m_fore_color = CommonColor(1.0, 1.0, 1.0, 1.0);
CommonColor m_border_color;
CommonColor m_focus_color;
CommonColor m_disable_color;
CommonColor m_outline_color;
std::string m_background;
int m_owner_draw = 0;
int m_owner_draw_flags = 0;
unsigned m_owner_draw_flags = 0;
std::string m_sound_loop;
std::unique_ptr<ISimpleExpression> m_sound_loop_exp;
double m_fade_clamp = 0;
@@ -4,6 +4,7 @@ namespace menu
{
enum class FeatureLevel
{
IW3,
IW4,
IW5
};
@@ -1,5 +1,7 @@
#include "MenuExpressionMatchers.h"
#include "Game/IW3/IW3.h"
#include "Game/IW3/MenuConstantsIW3.h"
#include "Game/IW4/IW4.h"
#include "Game/IW4/MenuConstantsIW4.h"
#include "Game/IW5/IW5.h"
@@ -48,11 +50,29 @@ std::unique_ptr<SimpleExpressionMatchers::matcher_t> MenuExpressionMatchers::Par
});
}
const std::map<std::string, size_t>& MenuExpressionMatchers::GetBaseFunctionMapForFeatureLevel(const FeatureLevel featureLevel)
const std::unordered_map<std::string, size_t>& MenuExpressionMatchers::GetBaseFunctionMapForFeatureLevel(const FeatureLevel featureLevel)
{
if (featureLevel == FeatureLevel::IW3)
{
static std::unordered_map<std::string, size_t> iw3FunctionMap;
static bool iw3FunctionMapInitialized = false;
if (!iw3FunctionMapInitialized)
{
for (size_t i = IW3::OP_FIRSTFUNCTIONCALL; i < std::extent_v<decltype(IW3::g_expFunctionNames)>; i++)
{
std::string functionName(IW3::g_expFunctionNames[i]);
utils::MakeStringLowerCase(functionName);
iw3FunctionMap.emplace(std::make_pair(std::move(functionName), i));
}
}
return iw3FunctionMap;
}
if (featureLevel == FeatureLevel::IW4)
{
static std::map<std::string, size_t> iw4FunctionMap;
static std::unordered_map<std::string, size_t> iw4FunctionMap;
static bool iw4FunctionMapInitialized = false;
if (!iw4FunctionMapInitialized)
@@ -69,7 +89,7 @@ const std::map<std::string, size_t>& MenuExpressionMatchers::GetBaseFunctionMapF
}
if (featureLevel == FeatureLevel::IW5)
{
static std::map<std::string, size_t> iw5FunctionMap;
static std::unordered_map<std::string, size_t> iw5FunctionMap;
static bool iw5FunctionMapInitialized = false;
if (!iw5FunctionMapInitialized)
@@ -4,15 +4,12 @@
#include "Parsing/Simple/Expression/SimpleExpressionMatchers.h"
#include <memory>
#include <unordered_map>
namespace menu
{
class MenuExpressionMatchers final : public SimpleExpressionMatchers
{
const MenuFileParserState* m_state;
static const std::map<std::string, size_t>& GetBaseFunctionMapForFeatureLevel(FeatureLevel featureLevel);
public:
MenuExpressionMatchers();
explicit MenuExpressionMatchers(const MenuFileParserState* state);
@@ -20,5 +17,10 @@ namespace menu
protected:
std::unique_ptr<matcher_t> ParseOperandExtension(const supplier_t* labelSupplier) const override;
std::unique_ptr<ISimpleExpression> ProcessOperandExtension(SequenceResult<SimpleParserValue>& result) const override;
private:
const MenuFileParserState* m_state;
static const std::unordered_map<std::string, size_t>& GetBaseFunctionMapForFeatureLevel(FeatureLevel featureLevel);
};
} // namespace menu
@@ -39,6 +39,9 @@ void MenuFileReader::SetupDefinesProxy()
defines->AddDefine(DefinesStreamProxy::Define("PC", "1"));
switch (m_feature_level)
{
case FeatureLevel::IW3:
defines->AddDefine(DefinesStreamProxy::Define("FEATURE_LEVEL_IW3", "1"));
break;
case FeatureLevel::IW4:
defines->AddDefine(DefinesStreamProxy::Define("FEATURE_LEVEL_IW4", "1"));
break;
@@ -281,7 +281,7 @@ namespace
AddMatchers({
create
.Or({
create.Numeric(),
create.ScriptStrictNumeric(),
create.String(),
create.Identifier(),
create.Type(SimpleParserValueType::CHARACTER),
@@ -928,6 +928,15 @@ EventHandlerSetScopeSequences::EventHandlerSetScopeSequences(std::vector<std::un
void EventHandlerSetScopeSequences::AddSequences(const FeatureLevel featureLevel, const bool permissive) const
{
AddSequence(std::make_unique<SequenceSkipEmptyStatements>());
// IW3 stores event handlers as opaque script strings rather than structured handlers.
if (featureLevel == FeatureLevel::IW3)
{
AddSequence(std::make_unique<SequenceCloseBlock>());
AddSequence(std::make_unique<SequenceSkipScriptToken>());
return;
}
// If else and stuff
// Creating factory with no label supplier. Cannot use labels with it.
@@ -109,7 +109,8 @@ GlobalScopeSequences::GlobalScopeSequences(std::vector<std::unique_ptr<MenuFileP
void GlobalScopeSequences::AddSequences(const FeatureLevel featureLevel, const bool permissive) const
{
AddSequence(std::make_unique<SequenceCloseBlock>());
AddSequence(std::make_unique<SequenceFunctionDef>());
if (featureLevel != FeatureLevel::IW3)
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 IW4_FEATURE_TYPE_BY_TYPE[0x18]{
inline static const CommonItemFeatureType IW3_IW4_FEATURE_TYPE_BY_TYPE[0x18]{
CommonItemFeatureType::EDIT_FIELD, // ITEM_TYPE_TEXT
CommonItemFeatureType::NONE, // ITEM_TYPE_BUTTON
CommonItemFeatureType::NONE, // ITEM_TYPE_RADIOBUTTON
@@ -78,32 +78,34 @@ namespace
CommonItemFeatureType::EDIT_FIELD, // ITEM_TYPE_PASSWORDFIELD
};
public:
static void SetItemType(CommonItemDef& item, const FeatureLevel featureLevel, const TokenPos& pos, const int type)
static CommonItemFeatureType GetFeatureTypeForItemType(const FeatureLevel featureLevel, const TokenPos& pos, const int type)
{
if (type < 0)
throw ParsingException(pos, "Invalid item type");
if (item.m_feature_type != CommonItemFeatureType::NONE)
throw ParsingException(pos, "Item type has already been set");
item.m_type = type;
switch (featureLevel)
{
case FeatureLevel::IW3:
case FeatureLevel::IW4:
if (static_cast<unsigned>(type) >= std::extent_v<decltype(IW4_FEATURE_TYPE_BY_TYPE)>)
if (static_cast<unsigned>(type) >= std::extent_v<decltype(IW3_IW4_FEATURE_TYPE_BY_TYPE)>)
throw ParsingException(pos, "Invalid item type");
item.m_feature_type = IW4_FEATURE_TYPE_BY_TYPE[static_cast<unsigned>(type)];
break;
return IW3_IW4_FEATURE_TYPE_BY_TYPE[static_cast<unsigned>(type)];
case FeatureLevel::IW5:
default:
if (static_cast<unsigned>(type) >= std::extent_v<decltype(IW5_FEATURE_TYPE_BY_TYPE)>)
throw ParsingException(pos, "Invalid item type");
item.m_feature_type = IW5_FEATURE_TYPE_BY_TYPE[static_cast<unsigned>(type)];
break;
return IW5_FEATURE_TYPE_BY_TYPE[static_cast<unsigned>(type)];
}
}
public:
static void SetItemType(CommonItemDef& item, const FeatureLevel featureLevel, const TokenPos& pos, const int type)
{
const auto featureType = GetFeatureTypeForItemType(featureLevel, pos, type);
if (item.m_feature_type != CommonItemFeatureType::NONE)
throw ParsingException(pos, "Item type has already been set");
item.m_type = type;
item.m_feature_type = featureType;
switch (item.m_feature_type)
{
@@ -130,9 +132,16 @@ namespace
throw ParsingException(pos, "Item must have be listbox to use this declaration");
}
static void EnsureHasEditFieldFeatures(const CommonItemDef& item, const TokenPos& pos)
static void EnsureHasEditFieldFeatures(CommonItemDef& item, const FeatureLevel featureLevel, const TokenPos& pos)
{
if (item.m_feature_type != CommonItemFeatureType::EDIT_FIELD || !item.m_edit_field_features)
// Due to default item types, edit field capabilities might not have been initialized
if (item.m_feature_type == CommonItemFeatureType::NONE
&& GetFeatureTypeForItemType(featureLevel, pos, item.m_type) == CommonItemFeatureType::EDIT_FIELD)
{
item.m_feature_type = CommonItemFeatureType::EDIT_FIELD;
item.m_edit_field_features = std::make_unique<CommonItemFeaturesEditField>();
}
else if (item.m_feature_type != CommonItemFeatureType::EDIT_FIELD || !item.m_edit_field_features)
throw ParsingException(pos, "Item must have be edit field to use this declaration");
}
@@ -360,7 +369,7 @@ namespace
{
assert(state->m_current_item);
ItemScopeOperations::EnsureHasEditFieldFeatures(*state->m_current_item, result.NextCapture(CAPTURE_FIRST_TOKEN).GetPos());
ItemScopeOperations::EnsureHasEditFieldFeatures(*state->m_current_item, state->m_feature_level, result.NextCapture(CAPTURE_FIRST_TOKEN).GetPos());
state->m_current_item->m_dvar = MenuMatcherFactory::TokenTextValue(result.NextCapture(CAPTURE_DVAR_NAME));
state->m_current_item->m_edit_field_features->m_def_val = MenuMatcherFactory::TokenNumericExpressionValue(state, result);
state->m_current_item->m_edit_field_features->m_min_val = MenuMatcherFactory::TokenNumericExpressionValue(state, result);
@@ -455,8 +464,8 @@ namespace
static constexpr auto CAPTURE_FIRST_TOKEN = 1;
static constexpr auto CAPTURE_COLUMN_COUNT = 2;
static constexpr const char* SYNTAX_IW4 = "<xpos> <width> <maxChars> [alignment]";
static constexpr auto BASE_COLUMN_COUNT_IW4 = 3;
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_IW5 = "<xpos> <ypos> <width> <height> <maxChars> [alignment]";
static constexpr auto BASE_COLUMN_COUNT_IW5 = 5;
@@ -478,7 +487,7 @@ namespace
void ProcessMatch(MenuFileParserState* state, SequenceResult<SimpleParserValue>& result) const override
{
assert(state->m_current_item);
assert(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);
const auto& firstToken = result.NextCapture(CAPTURE_FIRST_TOKEN);
ItemScopeOperations::EnsureHasListboxFeatures(*state->m_current_item, firstToken.GetPos());
@@ -491,10 +500,10 @@ namespace
size_t baseColumnCount;
const char* syntax;
bool hasHeightValues;
if (state->m_feature_level == FeatureLevel::IW4)
if (state->m_feature_level == FeatureLevel::IW3 || state->m_feature_level == FeatureLevel::IW4)
{
baseColumnCount = BASE_COLUMN_COUNT_IW4;
syntax = SYNTAX_IW4;
baseColumnCount = BASE_COLUMN_COUNT_IW3_IW4;
syntax = SYNTAX_IW3_IW4;
hasHeightValues = false;
}
else
@@ -1151,25 +1160,29 @@ void ItemScopeSequences::AddSequences(const FeatureLevel featureLevel, const boo
AddSequence(std::make_unique<GenericStringPropertySequence>("localvar",
[](const MenuFileParserState* state, const TokenPos& pos, const std::string& value)
{
ItemScopeOperations::EnsureHasEditFieldFeatures(*state->m_current_item, pos);
ItemScopeOperations::EnsureHasEditFieldFeatures(
*state->m_current_item, state->m_feature_level, pos);
state->m_current_item->m_edit_field_features->m_local_var = value;
}));
AddSequence(std::make_unique<GenericIntPropertySequence>("maxChars",
[](const MenuFileParserState* state, const TokenPos& pos, const int value)
{
ItemScopeOperations::EnsureHasEditFieldFeatures(*state->m_current_item, pos);
ItemScopeOperations::EnsureHasEditFieldFeatures(
*state->m_current_item, state->m_feature_level, pos);
state->m_current_item->m_edit_field_features->m_max_chars = value;
}));
AddSequence(std::make_unique<GenericIntPropertySequence>("maxPaintChars",
[](const MenuFileParserState* state, const TokenPos& pos, const int value)
{
ItemScopeOperations::EnsureHasEditFieldFeatures(*state->m_current_item, pos);
ItemScopeOperations::EnsureHasEditFieldFeatures(
*state->m_current_item, state->m_feature_level, pos);
state->m_current_item->m_edit_field_features->m_max_paint_chars = value;
}));
AddSequence(std::make_unique<GenericKeywordPropertySequence>("maxCharsGotoNext",
[](const MenuFileParserState* state, const TokenPos& pos)
{
ItemScopeOperations::EnsureHasEditFieldFeatures(*state->m_current_item, pos);
ItemScopeOperations::EnsureHasEditFieldFeatures(
*state->m_current_item, state->m_feature_level, pos);
state->m_current_item->m_edit_field_features->m_max_chars_goto_next = true;
}));
@@ -309,6 +309,11 @@ void MenuScopeSequences::AddSequences(const FeatureLevel featureLevel, const boo
{
state->m_current_menu->m_focus_color = value;
}));
AddSequence(std::make_unique<GenericColorPropertySequence>("disablecolor",
[](const MenuFileParserState* state, const TokenPos&, const CommonColor value)
{
state->m_current_menu->m_disable_color = value;
}));
AddSequence(std::make_unique<GenericColorPropertySequence>("outlinecolor",
[](const MenuFileParserState* state, const TokenPos&, const CommonColor& value)
{
@@ -0,0 +1,51 @@
#include "MenuDumperIW3.h"
#include "MenuListDumperIW3.h"
#include "MenuWriterIW3.h"
#include "ObjWriting.h"
#include <string>
using namespace IW3;
namespace
{
std::string GetPathForMenu(menu::MenuDumpingZoneState* zoneState, const XAssetInfo<menuDef_t>& asset)
{
const auto menuDumpingState = zoneState->m_menu_dumping_state_map.find(asset.Asset());
if (menuDumpingState == zoneState->m_menu_dumping_state_map.end())
return "ui_mp/" + std::string(asset.Asset()->window.name) + ".menu";
return menuDumpingState->second.m_path;
}
} // namespace
namespace menu
{
void MenuDumperIW3::DumpAsset(AssetDumpingContext& context, const XAssetInfo<AssetMenu::Type>& asset)
{
const auto* menu = asset.Asset();
auto* zoneState = context.GetZoneAssetDumperState<MenuDumpingZoneState>();
if (!ObjWriting::ShouldHandleAssetType(ASSET_TYPE_MENULIST))
{
// Make sure menu paths based on menu lists are created
auto menuListAssets = context.m_zone.m_pools.PoolAssets<AssetMenuList>();
for (auto* menuListAsset : menuListAssets)
CreateDumpingStateForMenuListIW3(zoneState, menuListAsset->Asset());
}
const auto menuFilePath = GetPathForMenu(zoneState, asset);
const auto assetFile = context.OpenAssetFile(menuFilePath);
if (!assetFile)
return;
auto menuWriter = CreateMenuWriterIW3(*assetFile);
menuWriter->Start();
menuWriter->WriteMenu(*menu);
menuWriter->End();
}
} // namespace menu
@@ -0,0 +1,13 @@
#pragma once
#include "Dumping/AbstractAssetDumper.h"
#include "Game/IW3/IW3.h"
namespace menu
{
class MenuDumperIW3 final : public AbstractAssetDumper<IW3::AssetMenu>
{
protected:
void DumpAsset(AssetDumpingContext& context, const XAssetInfo<IW3::AssetMenu::Type>& asset) override;
};
} // namespace menu
@@ -0,0 +1,120 @@
#include "MenuListDumperIW3.h"
#include "MenuWriterIW3.h"
#include <filesystem>
#include <format>
namespace fs = std::filesystem;
using namespace IW3;
namespace
{
void DumpMenus(menu::IWriterIW3& menuDumper, menu::MenuDumpingZoneState* zoneState, const MenuList* menuList)
{
if (!menuList->menus)
return;
for (auto menuNum = 0; menuNum < menuList->menuCount; menuNum++)
{
const auto* menu = menuList->menus[menuNum];
if (!menu)
continue;
const auto menuDumpingState = zoneState->m_menu_dumping_state_map.find(menu);
if (menuDumpingState == zoneState->m_menu_dumping_state_map.end())
continue;
// If the menu was embedded directly as menu list write its data in the menu list file
if (menuDumpingState->second.m_alias_menu_list == menuList)
menuDumper.WriteMenu(*menu);
else
menuDumper.IncludeMenu(menuDumpingState->second.m_path);
}
}
std::string PathForMenu(const std::string& menuListParentPath, const menuDef_t* menu)
{
const auto* menuAssetName = menu->window.name;
if (!menuAssetName)
return {};
if (menuAssetName[0] == ',')
menuAssetName = &menuAssetName[1];
return std::format("{}{}.menu", menuListParentPath, menuAssetName);
}
} // namespace
namespace menu
{
void CreateDumpingStateForMenuListIW3(MenuDumpingZoneState* zoneState, const MenuList* menuList)
{
if (!menuList || menuList->menuCount <= 0 || !menuList->menus || !menuList->name)
return;
const std::string menuListName(menuList->name);
const fs::path p(menuListName);
std::string parentPath;
if (p.has_parent_path())
parentPath = p.parent_path().generic_string() + "/";
for (auto i = 0; i < menuList->menuCount; i++)
{
const auto* menu = menuList->menus[i];
if (!menu)
continue;
auto menuPath = PathForMenu(parentPath, menu);
if (menuPath.empty())
continue;
auto existingState = zoneState->m_menu_dumping_state_map.find(menu);
if (existingState == zoneState->m_menu_dumping_state_map.end())
{
const auto isTheSameAsMenuList = menuPath == menuListName;
zoneState->CreateMenuDumpingState(menu, std::move(menuPath), isTheSameAsMenuList ? menuList : nullptr);
}
else if (!existingState->second.m_alias_menu_list)
{
const auto isTheSameAsMenuList = menuPath == menuListName;
if (isTheSameAsMenuList)
{
existingState->second.m_alias_menu_list = menuList;
existingState->second.m_path = std::move(menuPath);
}
}
}
}
void MenuListDumperIW3::DumpAsset(AssetDumpingContext& context, const XAssetInfo<AssetMenuList::Type>& asset)
{
const auto* menuList = asset.Asset();
const auto assetFile = context.OpenAssetFile(asset.m_name);
if (!assetFile)
return;
auto* zoneState = context.GetZoneAssetDumperState<MenuDumpingZoneState>();
const auto menuWriter = CreateMenuWriterIW3(*assetFile);
menuWriter->Start();
DumpMenus(*menuWriter, zoneState, menuList);
menuWriter->End();
}
void MenuListDumperIW3::Dump(AssetDumpingContext& context)
{
auto* zoneState = context.GetZoneAssetDumperState<MenuDumpingZoneState>();
auto menuListAssets = context.m_zone.m_pools.PoolAssets<AssetMenuList>();
for (const auto* asset : menuListAssets)
CreateDumpingStateForMenuListIW3(zoneState, asset->Asset());
AbstractAssetDumper::Dump(context);
}
} // namespace menu
@@ -0,0 +1,19 @@
#pragma once
#include "Dumping/AbstractAssetDumper.h"
#include "Game/IW3/IW3.h"
#include "Menu/MenuDumpingZoneState.h"
namespace menu
{
void CreateDumpingStateForMenuListIW3(MenuDumpingZoneState* zoneState, const IW3::MenuList* menuList);
class MenuListDumperIW3 final : public AbstractAssetDumper<IW3::AssetMenuList>
{
public:
void Dump(AssetDumpingContext& context) override;
protected:
void DumpAsset(AssetDumpingContext& context, const XAssetInfo<IW3::AssetMenuList::Type>& asset) override;
};
} // namespace menu
@@ -0,0 +1,681 @@
#include "MenuWriterIW3.h"
#include "Game/IW3/MenuConstantsIW3.h"
#include "Menu/AbstractMenuWriter.h"
#include "ObjWriting.h"
#include <cassert>
#include <cmath>
#include <limits>
#include <sstream>
using namespace IW3;
namespace
{
// Set this to true to skip interpretative expression dumping
constexpr auto DUMP_NAIVE = false;
size_t FindStatementClosingParenthesis(const statement_s& statement, const size_t openingParenthesisPosition)
{
assert(statement.numEntries >= 0);
assert(openingParenthesisPosition < static_cast<size_t>(statement.numEntries));
const auto statementEnd = static_cast<size_t>(statement.numEntries);
// The openingParenthesisPosition does not necessarily point to an actual opening parenthesis operator. That's fine though.
// We will pretend it does since the game does sometimes leave out opening parenthesis from the entries.
auto currentParenthesisDepth = 1;
for (auto currentSearchPosition = openingParenthesisPosition + 1; currentSearchPosition < statementEnd; currentSearchPosition++)
{
const auto* expEntry = statement.entries[currentSearchPosition];
if (!expEntry || expEntry->type != EET_OPERATOR)
continue;
// Any function means a "left out" left paren
if (expEntry->data.op == OP_LEFTPAREN || expEntry->data.op >= OP_FIRSTFUNCTIONCALL)
{
currentParenthesisDepth++;
}
else if (expEntry->data.op == OP_RIGHTPAREN)
{
if (currentParenthesisDepth > 0)
currentParenthesisDepth--;
if (currentParenthesisDepth == 0)
return currentSearchPosition;
}
}
return statementEnd;
}
class MenuWriter final : public menu::AbstractBaseWriter, public menu::IWriterIW3
{
public:
explicit MenuWriter(std::ostream& stream)
: AbstractBaseWriter(stream)
{
}
void WriteMenu(const menuDef_t& menu) override
{
StartMenuDefScope();
WriteMenuData(menu);
EndScope();
}
void Start() override
{
AbstractBaseWriter::Start();
}
void End() override
{
AbstractBaseWriter::End();
}
void IncludeMenu(const std::string& menuPath) const override
{
AbstractBaseWriter::IncludeMenu(menuPath);
}
private:
static bool HasStatement(const statement_s& statement)
{
return statement.numEntries > 0 && statement.entries;
}
void WriteStatementNaive(const statement_s& statement) const
{
const auto entryCount = static_cast<size_t>(statement.numEntries);
const auto missingClosingParenthesis = statement.numEntries > 0 && statement.entries[0]->type == EET_OPERATOR
&& statement.entries[0]->data.op == OP_LEFTPAREN
&& FindStatementClosingParenthesis(statement, 0) >= static_cast<size_t>(statement.numEntries);
for (auto i = 0uz; i < entryCount; i++)
{
const auto& entry = statement.entries[i];
if (entry->type == EET_OPERAND)
{
size_t pos = i;
bool discard = false;
WriteStatementOperand(statement, pos, discard);
}
else
{
assert(entry->data.op >= 0 && static_cast<unsigned>(entry->data.op) < std::extent_v<decltype(g_expFunctionNames)>);
if (entry->data.op >= 0 && static_cast<unsigned>(entry->data.op) < std::extent_v<decltype(g_expFunctionNames)>)
m_stream << g_expFunctionNames[entry->data.op];
if (entry->data.op >= OP_FIRSTFUNCTIONCALL)
m_stream << "(";
}
}
if (missingClosingParenthesis)
m_stream << ")";
}
void WriteStatementOperator(const statement_s& statement, size_t& currentPos, bool& spaceNext) const
{
const auto& expEntry = statement.entries[currentPos];
if (spaceNext && expEntry->data.op != OP_COMMA)
m_stream << " ";
if (expEntry->data.op == OP_LEFTPAREN)
{
const auto closingParenPos = FindStatementClosingParenthesis(statement, currentPos);
m_stream << "(";
WriteStatementEntryRange(statement, currentPos + 1, closingParenPos);
m_stream << ")";
currentPos = closingParenPos + 1;
spaceNext = true;
}
else
{
if (expEntry->data.op >= 0 && static_cast<unsigned>(expEntry->data.op) < std::extent_v<decltype(g_expFunctionNames)>)
m_stream << g_expFunctionNames[expEntry->data.op];
if (expEntry->data.op >= OP_FIRSTFUNCTIONCALL)
{
// Functions do not have opening parenthesis in the entries. We can just pretend they do though
const auto closingParenPos = FindStatementClosingParenthesis(statement, currentPos);
m_stream << "(";
WriteStatementEntryRange(statement, currentPos + 1, closingParenPos);
m_stream << ")";
currentPos = closingParenPos + 1;
}
else
currentPos++;
spaceNext = expEntry->data.op != OP_NOT;
}
}
void WriteStatementOperand(const statement_s& statement, size_t& currentPos, bool& spaceNext) const
{
const auto& expEntry = statement.entries[currentPos];
if (spaceNext)
m_stream << " ";
const auto& operand = expEntry->data.operand;
switch (operand.dataType)
{
case VAL_FLOAT:
m_stream << operand.internals.floatVal;
break;
case VAL_INT:
m_stream << operand.internals.intVal;
break;
case VAL_STRING:
WriteEscapedString(operand.internals.stringVal);
break;
default:
break;
}
currentPos++;
spaceNext = true;
}
void WriteStatementEntryRange(const statement_s& statement, const size_t startOffset, const size_t endOffset) const
{
assert(startOffset <= endOffset);
assert(endOffset <= static_cast<size_t>(statement.numEntries));
auto currentPos = startOffset;
auto spaceNext = false;
while (currentPos < endOffset)
{
const auto& expEntry = statement.entries[currentPos];
if (expEntry->type == EET_OPERATOR)
{
WriteStatementOperator(statement, currentPos, spaceNext);
}
else
{
WriteStatementOperand(statement, currentPos, spaceNext);
}
}
}
void WriteStatement(const statement_s& statement) const
{
if (!HasStatement(statement))
return;
WriteStatementEntryRange(statement, 0, static_cast<size_t>(statement.numEntries));
}
void WriteStatementSkipInitialUnnecessaryParenthesis(const statement_s& statement) const
{
if (!HasStatement(statement))
return;
const auto statementEnd = static_cast<size_t>(statement.numEntries);
if (statement.numEntries >= 1 && statement.entries[0]->type == EET_OPERATOR && statement.entries[0]->data.op == OP_LEFTPAREN)
{
const auto parenthesisEnd = FindStatementClosingParenthesis(statement, 0);
if (parenthesisEnd >= statementEnd)
WriteStatementEntryRange(statement, 1, statementEnd);
else if (parenthesisEnd == statementEnd - 1)
WriteStatementEntryRange(statement, 1, statementEnd - 1);
else
WriteStatementEntryRange(statement, 0, statementEnd);
}
else
{
WriteStatementEntryRange(statement, 0, statementEnd);
}
}
void WriteStatementProperty(const std::string& propertyKey, const statement_s& statement, const bool isBooleanStatement) const
{
if (!HasStatement(statement))
return;
Indent();
WriteKey(propertyKey);
if (isBooleanStatement)
{
m_stream << "when(";
if constexpr (DUMP_NAIVE)
WriteStatementNaive(statement);
else
WriteStatementSkipInitialUnnecessaryParenthesis(statement);
m_stream << ");\n";
}
else
{
if constexpr (DUMP_NAIVE)
WriteStatementNaive(statement);
else
WriteStatement(statement);
m_stream << ";\n";
}
}
// #define WRITE_ORIGINAL_SCRIPT
void WriteUnconditionalScript(const char* script) const
{
#ifdef WRITE_ORIGINAL_SCRIPT
Indent();
m_stream << script << "\n";
return;
#endif
const auto tokenList = CreateScriptTokenList(script);
auto isNewStatement = true;
for (const auto& token : tokenList)
{
if (isNewStatement)
{
if (token == ";")
continue;
Indent();
}
if (token == ";")
{
m_stream << ";\n";
isNewStatement = true;
continue;
}
if (!isNewStatement)
m_stream << " ";
else
isNewStatement = false;
if (DoesTokenNeedQuotationMarks(token))
WriteEscapedString(token);
else
m_stream << token;
}
if (!isNewStatement)
m_stream << ";\n";
}
void WriteScriptProperty(const std::string& propertyKey, const char* script)
{
if (!script || !script[0])
return;
Indent();
m_stream << propertyKey << "\n";
Indent();
m_stream << "{\n";
IncIndent();
WriteUnconditionalScript(script);
DecIndent();
Indent();
m_stream << "}\n";
}
void WriteRectProperty(const std::string& propertyKey, const rectDef_s& rect) const
{
Indent();
WriteKey(propertyKey);
m_stream << rect.x << " " << rect.y << " " << rect.w << " " << rect.h << " " << rect.horzAlign << " " << rect.vertAlign << "\n";
}
void WriteMaterialProperty(const std::string& propertyKey, const Material* material) const
{
if (!material || !material->info.name)
return;
const auto* materialName = material->info.name;
if (materialName[0] == ',')
materialName++;
WriteStringProperty(propertyKey, materialName);
}
void WriteSoundAliasProperty(const std::string& propertyKey, const snd_alias_list_t* soundAlias) const
{
if (soundAlias)
WriteStringProperty(propertyKey, soundAlias->aliasName);
}
void WriteItemKeyHandlers(const ItemKeyHandler* handler)
{
for (const auto* current = handler; current; current = current->next)
{
std::string key;
if (current->key >= '!' && current->key <= '~' && current->key != '"')
key = std::format("execKey \"{}\"", static_cast<char>(current->key));
else
key = std::format("execKeyInt {}", current->key);
WriteScriptProperty(key, current->action);
}
}
void WriteMultiTokenStringProperty(const std::string& propertyKey, const char* value) const
{
if (!value)
return;
Indent();
WriteKey(propertyKey);
m_stream << "{ ";
const auto tokenList = CreateScriptTokenList(value);
auto firstToken = true;
for (const auto& token : tokenList)
{
if (firstToken)
firstToken = false;
else
m_stream << ";";
WriteEscapedString(token);
}
if (!firstToken)
m_stream << " ";
m_stream << "}\n";
}
void WriteColumnProperty(const listBoxDef_s& listBox) const
{
if (listBox.numColumns <= 0)
return;
Indent();
WriteKey("columns");
m_stream << listBox.numColumns << "\n";
const auto columnCount = std::min<size_t>(listBox.numColumns, std::size(listBox.columnInfo));
for (size_t columnIndex = 0u; columnIndex < columnCount; columnIndex++)
{
const auto& column = listBox.columnInfo[columnIndex];
Indent();
for (auto i = 0u; i < MENU_KEY_SPACING; i++)
m_stream << " ";
m_stream << column.pos << " " << column.width << " " << column.maxChars << " " << column.alignment << "\n";
}
}
void WriteListBoxProperties(const itemDef_s& item)
{
if (item.type != ITEM_TYPE_LISTBOX || !item.typeData.listBox)
return;
const auto& listBox = *item.typeData.listBox;
WriteKeywordProperty("notselectable", listBox.notselectable != 0);
WriteKeywordProperty("noscrollbars", listBox.noScrollBars != 0);
WriteKeywordProperty("usepaging", listBox.usePaging != 0);
WriteFloatProperty("elementwidth", listBox.elementWidth, 0.0f);
WriteFloatProperty("elementheight", listBox.elementHeight, 0.0f);
WriteFloatProperty("feeder", item.special, 0.0f);
WriteIntProperty("elementtype", listBox.elementStyle, 0);
WriteColumnProperty(listBox);
WriteScriptProperty("doubleclick", listBox.onDoubleClick);
WriteColorProperty("selectBorder", listBox.selectBorder, COLOR_0000);
WriteColorProperty("disableColor", listBox.disableColor, COLOR_0000);
WriteMaterialProperty("selectIcon", listBox.selectIcon);
}
void WriteDvarFloatProperty(const itemDef_s& item, const editFieldDef_s& editField) const
{
if (!item.dvar)
return;
Indent();
WriteKey("dvarFloat");
WriteEscapedString(item.dvar);
m_stream << " " << editField.defVal << " " << editField.minVal << " " << editField.maxVal << "\n";
}
void WriteEditFieldProperties(const itemDef_s& item) const
{
switch (item.type)
{
case ITEM_TYPE_TEXT:
case ITEM_TYPE_EDITFIELD:
case ITEM_TYPE_NUMERICFIELD:
case ITEM_TYPE_SLIDER:
case ITEM_TYPE_YESNO:
case ITEM_TYPE_BIND:
case ITEM_TYPE_VALIDFILEFIELD:
case ITEM_TYPE_DECIMALFIELD:
case ITEM_TYPE_UPREDITFIELD:
break;
default:
return;
}
if (!item.typeData.editField)
return;
const auto& editField = *item.typeData.editField;
if (std::fabs(-1.0f - editField.defVal) >= std::numeric_limits<float>::epsilon()
|| std::fabs(-1.0f - editField.minVal) >= std::numeric_limits<float>::epsilon()
|| std::fabs(-1.0f - editField.maxVal) >= std::numeric_limits<float>::epsilon())
{
WriteDvarFloatProperty(item, editField);
}
else
{
WriteStringProperty("dvar", item.dvar);
}
WriteIntProperty("maxChars", editField.maxChars, 0);
WriteKeywordProperty("maxCharsGotoNext", editField.maxCharsGotoNext != 0);
WriteIntProperty("maxPaintChars", editField.maxPaintChars, 0);
}
void WriteMultiValueProperty(const multiDef_s& multi) const
{
if (multi.count <= 0)
return;
Indent();
WriteKey(multi.strDef ? "dvarStrList" : "dvarFloatList");
m_stream << "{";
const auto valueCount = std::min<size_t>(multi.count, std::size(multi.dvarValue));
for (size_t valueIndex = 0u; valueIndex < valueCount; valueIndex++)
{
if (!multi.dvarList[valueIndex] || (multi.strDef && !multi.dvarStr[valueIndex]))
continue;
m_stream << " ";
WriteEscapedString(multi.dvarList[valueIndex]);
m_stream << " ";
if (multi.strDef)
WriteEscapedString(multi.dvarStr[valueIndex]);
else
m_stream << multi.dvarValue[valueIndex];
}
m_stream << " }\n";
}
void WriteMultiProperties(const itemDef_s& item) const
{
if (item.type != ITEM_TYPE_MULTI || !item.typeData.multi)
return;
WriteStringProperty("dvar", item.dvar);
WriteMultiValueProperty(*item.typeData.multi);
}
void WriteEnumDvarProperties(const itemDef_s& item) const
{
if (item.type != ITEM_TYPE_DVARENUM)
return;
WriteStringProperty("dvar", item.dvar);
WriteStringProperty("dvarEnumList", item.typeData.enumDvarName);
}
void WriteItemTextProperty(const char* text) const
{
// IW3 distinguishes explicitly empty text from null text, which falls back to the item's dvar.
if (!text)
return;
Indent();
WriteKey("text");
WriteEscapedString(text);
m_stream << "\n";
}
void WriteItemData(const itemDef_s& item)
{
WriteStringProperty("name", item.window.name);
WriteItemTextProperty(item.text);
WriteStringProperty("group", item.window.group);
WriteRectProperty("rect", item.window.rectClient);
WriteIntProperty("style", item.window.style, 0);
WriteKeywordProperty("decoration", item.window.staticFlags & WINDOW_FLAG_DECORATION);
WriteKeywordProperty("autowrapped", item.window.staticFlags & WINDOW_FLAG_AUTO_WRAPPED);
WriteKeywordProperty("horizontalscroll", item.window.staticFlags & WINDOW_FLAG_HORIZONTAL_SCROLL);
WriteIntProperty("type", item.type, ITEM_TYPE_TEXT);
WriteIntProperty("border", item.window.border, 0);
WriteFloatProperty("borderSize", item.window.borderSize, 0.0f);
if (HasStatement(item.visibleExp))
WriteStatementProperty("visible", item.visibleExp, true);
else if (item.window.dynamicFlags[0] & WINDOW_FLAG_VISIBLE)
WriteIntProperty("visible", 1, 0);
WriteIntProperty("ownerdraw", item.window.ownerDraw, 0);
WriteFlagsProperty("ownerdrawFlag", item.window.ownerDrawFlags);
WriteIntProperty("align", item.alignment, 0);
WriteIntProperty("textalign", item.textAlignMode, 0);
WriteFloatProperty("textalignx", item.textalignx, 0.0f);
WriteFloatProperty("textaligny", item.textaligny, 0.0f);
WriteFloatProperty("textscale", item.textscale, 0.0f);
WriteIntProperty("textstyle", item.textStyle, 0);
WriteIntProperty("textfont", item.fontEnum, 0);
WriteColorProperty("backcolor", item.window.backColor, COLOR_0000);
WriteColorProperty("forecolor", item.window.foreColor, COLOR_1111);
WriteColorProperty("bordercolor", item.window.borderColor, COLOR_0000);
WriteColorProperty("outlinecolor", item.window.outlineColor, COLOR_0000);
WriteMaterialProperty("background", item.window.background);
WriteScriptProperty("onFocus", item.onFocus);
WriteScriptProperty("leaveFocus", item.leaveFocus);
WriteScriptProperty("mouseEnter", item.mouseEnter);
WriteScriptProperty("mouseExit", item.mouseExit);
WriteScriptProperty("mouseEnterText", item.mouseEnterText);
WriteScriptProperty("mouseExitText", item.mouseExitText);
WriteScriptProperty("action", item.action);
WriteScriptProperty("accept", item.onAccept);
WriteSoundAliasProperty("focusSound", item.focusSound);
WriteStringProperty("dvarTest", item.dvarTest);
if (item.dvarFlags & ITEM_DVAR_FLAG_ENABLE)
WriteMultiTokenStringProperty("enableDvar", item.enableDvar);
else if (item.dvarFlags & ITEM_DVAR_FLAG_DISABLE)
WriteMultiTokenStringProperty("disableDvar", item.enableDvar);
else if (item.dvarFlags & ITEM_DVAR_FLAG_SHOW)
WriteMultiTokenStringProperty("showDvar", item.enableDvar);
else if (item.dvarFlags & ITEM_DVAR_FLAG_HIDE)
WriteMultiTokenStringProperty("hideDvar", item.enableDvar);
else if (item.dvarFlags & ITEM_DVAR_FLAG_FOCUS)
WriteMultiTokenStringProperty("focusDvar", item.enableDvar);
WriteItemKeyHandlers(item.onKey);
WriteStatementProperty("exp text", item.textExp, false);
WriteStatementProperty("exp material", item.materialExp, false);
WriteStatementProperty("exp rect X", item.rectXExp, false);
WriteStatementProperty("exp rect Y", item.rectYExp, false);
WriteStatementProperty("exp rect W", item.rectWExp, false);
WriteStatementProperty("exp rect H", item.rectHExp, false);
WriteStatementProperty("exp forecolor A", item.forecolorAExp, false);
WriteIntProperty("gamemsgwindowindex", item.gameMsgWindowIndex, 0);
WriteIntProperty("gamemsgwindowmode", item.gameMsgWindowMode, 0);
WriteListBoxProperties(item);
WriteEditFieldProperties(item);
WriteMultiProperties(item);
WriteEnumDvarProperties(item);
}
void WriteItemDefs(const itemDef_s* const* items, const size_t itemCount)
{
if (!items || itemCount <= 0)
return;
for (size_t itemIndex = 0u; itemIndex < itemCount; itemIndex++)
{
const auto* item = items[itemIndex];
if (!item)
continue;
StartItemDefScope();
WriteItemData(*item);
EndScope();
}
}
void WriteMenuData(const menuDef_t& menu)
{
WriteStringProperty("name", menu.window.name);
WriteBoolProperty("fullscreen", menu.fullScreen != 0, false);
WriteKeywordProperty("decoration", menu.window.staticFlags & WINDOW_FLAG_DECORATION);
WriteRectProperty("rect", menu.window.rect);
WriteIntProperty("style", menu.window.style, 0);
WriteIntProperty("border", menu.window.border, 0);
WriteFloatProperty("borderSize", menu.window.borderSize, 0.0f);
WriteColorProperty("backcolor", menu.window.backColor, COLOR_0000);
WriteColorProperty("forecolor", menu.window.foreColor, COLOR_1111);
WriteColorProperty("bordercolor", menu.window.borderColor, COLOR_0000);
WriteColorProperty("focuscolor", menu.focusColor, COLOR_0000);
WriteColorProperty("disablecolor", menu.disableColor, COLOR_0000);
WriteColorProperty("outlinecolor", menu.window.outlineColor, COLOR_0000);
WriteMaterialProperty("background", menu.window.background);
WriteIntProperty("ownerdraw", menu.window.ownerDraw, 0);
WriteFlagsProperty("ownerdrawFlag", menu.window.ownerDrawFlags);
WriteKeywordProperty("outOfBoundsClick", menu.window.staticFlags & WINDOW_FLAG_OUT_OF_BOUNDS_CLICK);
WriteStringProperty("soundLoop", menu.soundName);
WriteKeywordProperty("popup", menu.window.staticFlags & WINDOW_FLAG_POPUP);
WriteFloatProperty("fadeClamp", menu.fadeClamp, 0.0f);
WriteIntProperty("fadeCycle", menu.fadeCycle, 0);
WriteFloatProperty("fadeAmount", menu.fadeAmount, 0.0f);
WriteFloatProperty("fadeInAmount", menu.fadeInAmount, 0.0f);
WriteFloatProperty("blurWorld", menu.blurRadius, 0.0f);
WriteKeywordProperty("legacySplitScreenScale", menu.window.staticFlags & WINDOW_FLAG_LEGACY_SPLIT_SCREEN_SCALE);
WriteKeywordProperty("hiddenDuringScope", menu.window.staticFlags & WINDOW_FLAG_HIDDEN_DURING_SCOPE);
WriteKeywordProperty("hiddenDuringFlashbang", menu.window.staticFlags & WINDOW_FLAG_HIDDEN_DURING_FLASH_BANG);
WriteKeywordProperty("hiddenDuringUI", menu.window.staticFlags & WINDOW_FLAG_HIDDEN_DURING_UI);
WriteStringProperty("allowedBinding", menu.allowedBinding);
if (HasStatement(menu.visibleExp))
WriteStatementProperty("visible", menu.visibleExp, true);
else if (menu.window.dynamicFlags[0] & WINDOW_FLAG_VISIBLE)
WriteIntProperty("visible", 1, 0);
WriteStatementProperty("exp rect X", menu.rectXExp, false);
WriteStatementProperty("exp rect Y", menu.rectYExp, false);
WriteScriptProperty("onOpen", menu.onOpen);
WriteScriptProperty("onClose", menu.onClose);
WriteScriptProperty("onESC", menu.onESC);
WriteItemKeyHandlers(menu.onKey);
WriteItemDefs(menu.items, menu.itemCount);
}
};
} // namespace
namespace menu
{
std::unique_ptr<IWriterIW3> CreateMenuWriterIW3(std::ostream& stream)
{
return std::make_unique<MenuWriter>(stream);
}
} // namespace menu
@@ -0,0 +1,18 @@
#pragma once
#include "Game/IW3/IW3.h"
#include "Menu/IMenuWriter.h"
#include <memory>
#include <ostream>
namespace menu
{
class IWriterIW3 : public IWriter
{
public:
virtual void WriteMenu(const IW3::menuDef_t& menu) = 0;
};
std::unique_ptr<IWriterIW3> CreateMenuWriterIW3(std::ostream& stream);
} // namespace menu
+4 -2
View File
@@ -9,6 +9,8 @@
#include "Game/IW3/XModel/XModelDumperIW3.h"
#include "LightDef/LightDefDumperIW3.h"
#include "Localize/LocalizeDumperIW3.h"
#include "Menu/MenuDumperIW3.h"
#include "Menu/MenuListDumperIW3.h"
#include "PhysPreset/PhysPresetInfoStringDumperIW3.h"
#include "RawFile/RawFileDumperIW3.h"
#include "Sound/LoadedSoundDumperIW3.h"
@@ -43,8 +45,8 @@ void ObjWriter::RegisterAssetDumpers(AssetDumpingContext& context)
// REGISTER_DUMPER(AssetDumperGfxWorld)
RegisterAssetDumper(std::make_unique<light_def::DumperIW3>());
RegisterAssetDumper(std::make_unique<font::JsonDumperIW3>());
// REGISTER_DUMPER(AssetDumperMenuList)
// REGISTER_DUMPER(AssetDumpermenuDef_t)
RegisterAssetDumper(std::make_unique<menu::MenuListDumperIW3>());
RegisterAssetDumper(std::make_unique<menu::MenuDumperIW3>());
RegisterAssetDumper(std::make_unique<localize::DumperIW3>());
RegisterAssetDumper(std::make_unique<weapon::DumperIW3>());
// REGISTER_DUMPER(AssetDumperSndDriverGlobals)
+13 -3
View File
@@ -5,6 +5,7 @@
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <sstream>
namespace menu
@@ -117,6 +118,14 @@ namespace menu
if (token.empty())
return true;
// The menu linker concatenates adjacent quoted numeric arguments. For example,
// `"0.1" "0.1" "0.12" "0.5"` becomes `"0.10.10.120.5"` instead of four
// color components.
char* numericEnd;
(void)std::strtof(token.c_str(), &numericEnd);
if (numericEnd == token.c_str() + token.size())
return false;
const auto hasAlNumCharacter = std::ranges::any_of(token,
[](const char& c)
{
@@ -263,15 +272,16 @@ namespace menu
m_stream << "\n";
}
void AbstractBaseWriter::WriteFlagsProperty(const std::string& propertyKey, const int flagsValue) const
void AbstractBaseWriter::WriteFlagsProperty(const std::string& propertyKey, const unsigned flagsValue) const
{
for (auto i = 0u; i < sizeof(flagsValue) * 8; i++)
{
if (flagsValue & (1 << i))
const unsigned mask = 1u << i;
if (flagsValue & mask)
{
Indent();
WriteKey(propertyKey);
m_stream << i << "\n";
m_stream << mask << "\n";
}
}
}
+1 -1
View File
@@ -51,7 +51,7 @@ namespace menu
void WriteFloatProperty(const std::string& propertyKey, float propertyValue, float defaultValue) const;
void WriteColorProperty(const std::string& propertyKey, const float (&propertyValue)[4], const float (&defaultValue)[4]) const;
void WriteKeywordProperty(const std::string& propertyKey, bool shouldWrite) const;
void WriteFlagsProperty(const std::string& propertyKey, int flagsValue) const;
void WriteFlagsProperty(const std::string& propertyKey, unsigned flagsValue) const;
std::ostream& m_stream;
size_t m_indent;
+1 -1
View File
@@ -70,7 +70,7 @@ set condition multi itemDef_s::type == ITEM_TYPE_MULTI;
set condition enumDvarName itemDef_s::type == ITEM_TYPE_DVARENUM;
// listBoxDef_s
set string listBoxDef_s::doubleClick;
set string listBoxDef_s::onDoubleClick;
// multiDef_s
use multiDef_s;