mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2026-09-09 07:27:05 +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,52 @@
|
||||
#include "MenuDumperT4.h"
|
||||
|
||||
#include "MenuListDumperT4.h"
|
||||
#include "MenuWriterT4.h"
|
||||
#include "ObjWriting.h"
|
||||
|
||||
#include <format>
|
||||
#include <string>
|
||||
|
||||
using namespace T4;
|
||||
|
||||
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 std::format("ui_mp/{}.menu", asset.Asset()->window.name);
|
||||
|
||||
return menuDumpingState->second.m_path;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace menu
|
||||
{
|
||||
void MenuDumperT4::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
|
||||
const auto menuListAssets = context.m_zone.m_pools.PoolAssets<AssetMenuList>();
|
||||
for (auto* menuListAsset : menuListAssets)
|
||||
CreateDumpingStateForMenuListT4(zoneState, menuListAsset->Asset());
|
||||
}
|
||||
|
||||
const auto menuFilePath = GetPathForMenu(zoneState, asset);
|
||||
const auto assetFile = context.OpenAssetFile(menuFilePath);
|
||||
|
||||
if (!assetFile)
|
||||
return;
|
||||
|
||||
const auto menuWriter = CreateMenuWriterT4(*assetFile);
|
||||
|
||||
menuWriter->Start();
|
||||
menuWriter->WriteMenu(*menu);
|
||||
menuWriter->End();
|
||||
}
|
||||
} // namespace menu
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "Dumping/AbstractAssetDumper.h"
|
||||
#include "Game/T4/T4.h"
|
||||
|
||||
namespace menu
|
||||
{
|
||||
class MenuDumperT4 final : public AbstractAssetDumper<T4::AssetMenu>
|
||||
{
|
||||
protected:
|
||||
void DumpAsset(AssetDumpingContext& context, const XAssetInfo<T4::AssetMenu::Type>& asset) override;
|
||||
};
|
||||
} // namespace menu
|
||||
@@ -0,0 +1,123 @@
|
||||
#include "MenuListDumperT4.h"
|
||||
|
||||
#include "MenuWriterT4.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
using namespace T4;
|
||||
|
||||
namespace
|
||||
{
|
||||
void DumpMenus(menu::IWriterT4& 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;
|
||||
|
||||
const auto* menuAssetName = menu->window.name;
|
||||
const auto isReference = menuAssetName && menuAssetName[0] == ',';
|
||||
|
||||
// If the menu was embedded directly as menu list write its data in the menu list file
|
||||
if (!isReference && 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 CreateDumpingStateForMenuListT4(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 MenuListDumperT4::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 = CreateMenuWriterT4(*assetFile);
|
||||
|
||||
menuWriter->Start();
|
||||
DumpMenus(*menuWriter, zoneState, menuList);
|
||||
menuWriter->End();
|
||||
}
|
||||
|
||||
void MenuListDumperT4::Dump(AssetDumpingContext& context)
|
||||
{
|
||||
auto* zoneState = context.GetZoneAssetDumperState<MenuDumpingZoneState>();
|
||||
|
||||
const auto menuListAssets = context.m_zone.m_pools.PoolAssets<AssetMenuList>();
|
||||
for (const auto* asset : menuListAssets)
|
||||
CreateDumpingStateForMenuListT4(zoneState, asset->Asset());
|
||||
|
||||
AbstractAssetDumper::Dump(context);
|
||||
}
|
||||
} // namespace menu
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "Dumping/AbstractAssetDumper.h"
|
||||
#include "Game/T4/T4.h"
|
||||
#include "Menu/MenuDumpingZoneState.h"
|
||||
|
||||
namespace menu
|
||||
{
|
||||
void CreateDumpingStateForMenuListT4(MenuDumpingZoneState* zoneState, const T4::MenuList* menuList);
|
||||
|
||||
class MenuListDumperT4 final : public AbstractAssetDumper<T4::AssetMenuList>
|
||||
{
|
||||
public:
|
||||
void Dump(AssetDumpingContext& context) override;
|
||||
|
||||
protected:
|
||||
void DumpAsset(AssetDumpingContext& context, const XAssetInfo<T4::AssetMenuList::Type>& asset) override;
|
||||
};
|
||||
} // namespace menu
|
||||
@@ -0,0 +1,686 @@
|
||||
#include "MenuWriterT4.h"
|
||||
|
||||
#include "Game/T4/MenuConstantsT4.h"
|
||||
#include "Menu/AbstractMenuWriter.h"
|
||||
#include "ObjWriting.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
|
||||
using namespace T4;
|
||||
|
||||
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::IWriterT4
|
||||
{
|
||||
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);
|
||||
WriteScriptProperty("onListboxSelectionChange", item.onListboxSelectionChange);
|
||||
WriteColorProperty("selectBorder", listBox.selectBorder, COLOR_0000);
|
||||
WriteColorProperty("disableColor", listBox.disableColor, COLOR_0000);
|
||||
WriteColorProperty("focusColor", listBox.focusColor, COLOR_0000);
|
||||
WriteMaterialProperty("selectIcon", listBox.selectIcon);
|
||||
WriteMaterialProperty("backgroundItemListbox", listBox.backgroundItemListbox);
|
||||
WriteMaterialProperty("highlightTexture", listBox.highlightTexture);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
// T4 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("onFocus", menu.onFocus);
|
||||
WriteScriptProperty("onClose", menu.onClose);
|
||||
WriteScriptProperty("onESC", menu.onESC);
|
||||
WriteItemKeyHandlers(menu.onKey);
|
||||
WriteItemDefs(menu.items, menu.itemCount);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace menu
|
||||
{
|
||||
std::unique_ptr<IWriterT4> CreateMenuWriterT4(std::ostream& stream)
|
||||
{
|
||||
return std::make_unique<MenuWriter>(stream);
|
||||
}
|
||||
} // namespace menu
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "Game/T4/T4.h"
|
||||
#include "Menu/IMenuWriter.h"
|
||||
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
|
||||
namespace menu
|
||||
{
|
||||
class IWriterT4 : public IWriter
|
||||
{
|
||||
public:
|
||||
virtual void WriteMenu(const T4::menuDef_t& menu) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IWriterT4> CreateMenuWriterT4(std::ostream& stream);
|
||||
} // namespace menu
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "Game/T4/XAnim/XAnimDumperT4.h"
|
||||
#include "Game/T4/XModel/XModelDumperT4.h"
|
||||
#include "Localize/LocalizeDumperT4.h"
|
||||
#include "Menu/MenuDumperT4.h"
|
||||
#include "Menu/MenuListDumperT4.h"
|
||||
#include "PhysConstraints/PhysConstraintsInfoStringDumperT4.h"
|
||||
#include "PhysPreset/PhysPresetInfoStringDumperT4.h"
|
||||
#include "RawFile/RawFileDumperT4.h"
|
||||
@@ -27,6 +29,8 @@ void ObjWriter::RegisterAssetDumpers(AssetDumpingContext& context)
|
||||
RegisterAssetDumper(std::make_unique<sound::LoadedSoundDumperT4>());
|
||||
RegisterAssetDumper(std::make_unique<map_ents::DumperT4>());
|
||||
RegisterAssetDumper(std::make_unique<font::JsonDumperT4>());
|
||||
RegisterAssetDumper(std::make_unique<menu::MenuListDumperT4>());
|
||||
RegisterAssetDumper(std::make_unique<menu::MenuDumperT4>());
|
||||
RegisterAssetDumper(std::make_unique<localize::DumperT4>());
|
||||
RegisterAssetDumper(std::make_unique<weapon::DumperT4>());
|
||||
RegisterAssetDumper(std::make_unique<raw_file::DumperT4>());
|
||||
|
||||
Reference in New Issue
Block a user