diff --git a/src/Common/Game/IW4/IW4_Assets.h b/src/Common/Game/IW4/IW4_Assets.h index 2766fe6f..1bf4ebdb 100644 --- a/src/Common/Game/IW4/IW4_Assets.h +++ b/src/Common/Game/IW4/IW4_Assets.h @@ -1314,6 +1314,21 @@ namespace IW4 char vertAlign; }; + enum WindowDefStaticFlag : unsigned int + { + WINDOW_FLAG_DECORATION = 0x100000, + WINDOW_FLAG_HORIZONTAL_SCROLL = 0x200000, + WINDOW_FLAG_OUT_OF_BOUNDS_CLICK = 0x2000000, + WINDOW_FLAG_SCREEN_SPACE = 0x400000, + WINDOW_FLAG_AUTO_WRAPPED = 0x800000, + WINDOW_FLAG_POPUP = 0x1000000, + WINDOW_FLAG_LEGACY_SPLIT_SCREEN_SCALE = 0x4000000, + WINDOW_FLAG_HIDDEN_DURING_FLASH_BANG = 0x10000000, + WINDOW_FLAG_HIDDEN_DURING_SCOPE = 0x20000000, + WINDOW_FLAG_HIDDEN_DURING_UI = 0x40000000, + WINDOW_FLAG_TEXT_ONLY_FOCUS = 0x80000000, + }; + struct windowDef_t { const char* name; @@ -1336,6 +1351,21 @@ namespace IW4 Material* background; }; + enum ItemDefFlag : unsigned int + { + ITEM_FLAG_SAVE_GAME_INFO = 0x1, + ITEM_FLAG_CINEMATIC_SUBTITLE = 0x2, + }; + + 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, + }; + enum ItemDefType { ITEM_TYPE_TEXT = 0x0, diff --git a/src/ObjCommon/Menu/GenericMenu.h b/src/ObjCommon/Menu/GenericMenu.h new file mode 100644 index 00000000..e69de29b diff --git a/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperMenuDef.cpp b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperMenuDef.cpp new file mode 100644 index 00000000..4bf9a2f1 --- /dev/null +++ b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperMenuDef.cpp @@ -0,0 +1,375 @@ +#include "AssetDumperMenuDef.h" + +#include +#include +#include + +#include "Game/IW4/GameAssetPoolIW4.h" +#include "Menu/MenuDumper.h" + +namespace fs = std::filesystem; + +using namespace IW4; + +class MenuDumperIw4 : public MenuDumper +{ + static constexpr auto MENU_KEY_SPACING = 28u; + static const inline std::string BOOL_VALUE_TRUE = "1"; + static const inline std::string BOOL_VALUE_FALSE = "0"; + static constexpr inline float COLOR_0000[4]{0.0f, 0.0f, 0.0f, 0.0f}; + static constexpr inline float COLOR_1111[4]{1.0f, 1.0f, 1.0f, 1.0f}; + + static const std::string& BoolValue(const bool value) + { + return value ? BOOL_VALUE_TRUE : BOOL_VALUE_FALSE; + } + + void WriteKey(const std::string& keyName) const + { + m_stream << keyName; + + if (keyName.size() < MENU_KEY_SPACING) + { + const auto spacingLength = MENU_KEY_SPACING - keyName.size(); + for (auto i = 0u; i < spacingLength; i++) + m_stream << " "; + } + } + + void WriteStringProperty(const std::string& propertyKey, const std::string& propertyValue) const + { + if (propertyValue.empty()) + return; + + Indent(); + WriteKey(propertyKey); + m_stream << "\"" << propertyValue << "\"\n"; + } + + void WriteStringProperty(const std::string& propertyKey, const char* propertyValue) const + { + if (propertyValue == nullptr || propertyValue[0] == '\0') + return; + + Indent(); + WriteKey(propertyKey); + m_stream << "\"" << propertyValue << "\"\n"; + } + + void WriteBoolProperty(const std::string& propertyKey, const bool propertyValue, const bool defaultValue) const + { + if (propertyValue == defaultValue) + return; + + Indent(); + WriteKey(propertyKey); + m_stream << BoolValue(propertyValue) << "\n"; + } + + void WriteIntProperty(const std::string& propertyKey, const int propertyValue, const int defaultValue) const + { + if (propertyValue == defaultValue) + return; + + Indent(); + WriteKey(propertyKey); + m_stream << propertyValue << "\n"; + } + + void WriteFloatProperty(const std::string& propertyKey, const float propertyValue, const float defaultValue) const + { + if (std::fabs(propertyValue - defaultValue) < std::numeric_limits::epsilon()) + return; + + Indent(); + WriteKey(propertyKey); + m_stream << propertyValue << "\n"; + } + + void WriteColorProperty(const std::string& propertyKey, const float (&propertyValue)[4], const float (&defaultValue)[4]) const + { + if (std::fabs(propertyValue[0] - defaultValue[0]) < std::numeric_limits::epsilon() + && std::fabs(propertyValue[1] - defaultValue[1]) < std::numeric_limits::epsilon() + && std::fabs(propertyValue[2] - defaultValue[2]) < std::numeric_limits::epsilon() + && std::fabs(propertyValue[3] - defaultValue[3]) < std::numeric_limits::epsilon()) + { + return; + } + + Indent(); + WriteKey(propertyKey); + m_stream << propertyValue[0] << " " << propertyValue[1] << " " << propertyValue[2] << " " << propertyValue[3] << "\n"; + } + + void WriteKeywordProperty(const std::string& propertyKey, const bool shouldWrite) const + { + if (!shouldWrite) + return; + + Indent(); + WriteKey(propertyKey); + m_stream << "\n"; + } + + void WriteFlagsProperty(const std::string& propertyKey, const int flagsValue) const + { + for (auto i = 0u; i < sizeof(flagsValue) * 8; i++) + { + if (flagsValue & (1 << i)) + { + Indent(); + WriteKey(propertyKey); + m_stream << i << "\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 << " " << static_cast(rect.horzAlign) << " " << static_cast(rect.vertAlign) << "\n"; + } + + void WriteMaterialProperty(const std::string& propertyKey, const Material* materialValue) const + { + if (materialValue == nullptr) + return; + + WriteStringProperty(propertyKey, materialValue->info.name); + } + + void WriteSoundAliasProperty(const std::string& propertyKey, const snd_alias_list_t* soundAliasValue) const + { + if (soundAliasValue == nullptr) + return; + + WriteStringProperty(propertyKey, soundAliasValue->aliasName); + } + + void WriteStatementProperty(const std::string& propertyKey, const Statement_s* statementValue, const bool isBooleanStatement) const + { + if (statementValue == nullptr) + return; + + Indent(); + WriteKey(propertyKey); + m_stream << "\n"; + } + + void WriteMenuEventHandlerSetProperty(const std::string& propertyKey, const MenuEventHandlerSet* eventHandlerValue) const + { + if (eventHandlerValue == nullptr) + return; + + Indent(); + WriteKey(propertyKey); + m_stream << "\n"; + } + + void WriteItemKeyHandlerProperty(const ItemKeyHandler* itemKeyHandlerValue) const + { + } + + void WriteItemData(const itemDef_s* item) + { + WriteStringProperty("name", item->window.name); + WriteStringProperty("text", item->text); + WriteKeywordProperty("textsavegame", item->itemFlags & ITEM_FLAG_SAVE_GAME_INFO); + WriteKeywordProperty("textcinematicsubtitle", item->itemFlags & ITEM_FLAG_CINEMATIC_SUBTITLE); + WriteStringProperty("group", item->window.group); + WriteRectProperty("rect", item->window.rect); + WriteIntProperty("style", item->window.style, 0); + WriteKeywordProperty("decoration", item->window.staticFlags & WINDOW_FLAG_DECORATION); + WriteIntProperty("type", item->type, ITEM_TYPE_TEXT); + WriteIntProperty("border", item->window.border, 0); + WriteFloatProperty("borderSize", item->window.borderSize, 0.0f); + WriteStatementProperty("visible", item->visibleExp, true); + WriteStatementProperty("disabled", item->disabledExp, true); + WriteIntProperty("ownerDraw", item->window.ownerDraw, 0); + 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); + WriteColorProperty("disablecolor", item->window.disableColor, COLOR_0000); + WriteColorProperty("disablecolor", item->glowColor, COLOR_0000); + WriteMaterialProperty("background", item->window.background); + WriteMenuEventHandlerSetProperty("onFocus", item->onFocus); + WriteMenuEventHandlerSetProperty("leaveFocus", item->leaveFocus); + WriteMenuEventHandlerSetProperty("mouseEnter", item->mouseEnter); + WriteMenuEventHandlerSetProperty("mouseExit", item->mouseExit); + WriteMenuEventHandlerSetProperty("mouseEnterText", item->mouseEnterText); + WriteMenuEventHandlerSetProperty("mouseExitText", item->mouseExitText); + WriteMenuEventHandlerSetProperty("action", item->action); + WriteMenuEventHandlerSetProperty("accept", item->accept); + WriteFloatProperty("special", item->special, 0.0f); + WriteSoundAliasProperty("focusSound", item->focusSound); + WriteFlagsProperty("ownerdrawFlag", item->window.ownerDrawFlags); + WriteStringProperty("dvarTest", item->dvarTest); + + if(item->dvarFlags & ITEM_DVAR_FLAG_ENABLE) + WriteStringProperty("enableDvar", item->enableDvar); + else if(item->dvarFlags & ITEM_DVAR_FLAG_DISABLE) + WriteStringProperty("disableDvar", item->enableDvar); + else if(item->dvarFlags & ITEM_DVAR_FLAG_SHOW) + WriteStringProperty("showDvar", item->enableDvar); + else if(item->dvarFlags & ITEM_DVAR_FLAG_HIDE) + WriteStringProperty("hideDvar", item->enableDvar); + else if(item->dvarFlags & ITEM_DVAR_FLAG_FOCUS) + WriteStringProperty("focusDvar", item->enableDvar); + + WriteItemKeyHandlerProperty(item->onKey); + WriteStatementProperty("exp text", item->textExp, false); + WriteStatementProperty("exp material", item->materialExp, false); + WriteStatementProperty("exp disabled", item->disabledExp, false); + // floatexpressions + WriteIntProperty("gamemsgwindowindex", item->gameMsgWindowIndex, 0); + WriteIntProperty("gamemsgwindowmode", item->gameMsgWindowMode, 0); + // decodeEffect + } + + void WriteItemDefs(const itemDef_s* const* itemDefs, const size_t itemCount) + { + for(auto i = 0u; i < itemCount; i++) + { + Indent(); + m_stream << "itemDef\n"; + Indent(); + m_stream << "{\n"; + IncIndent(); + + WriteItemData(itemDefs[i]); + + DecIndent(); + Indent(); + m_stream << "}\n"; + } + } + + void WriteMenuData(const menuDef_t* menu) + { + WriteStringProperty("name", menu->window.name); + WriteBoolProperty("fullscreen", menu->fullScreen, false); + WriteKeywordProperty("screenSpace", menu->window.staticFlags & WINDOW_FLAG_SCREEN_SPACE); + 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); + 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); + WriteKeywordProperty("textOnlyFocus", menu->window.staticFlags & WINDOW_FLAG_TEXT_ONLY_FOCUS); + WriteStatementProperty("visible", menu->visibleExp, true); + WriteStatementProperty("exp rect X", menu->rectXExp, false); + WriteStatementProperty("exp rect Y", menu->rectYExp, false); + WriteStatementProperty("exp rect W", menu->rectWExp, false); + WriteStatementProperty("exp rect H", menu->rectHExp, false); + WriteStatementProperty("exp openSound", menu->openSoundExp, false); + WriteStatementProperty("exp closeSound", menu->closeSoundExp, false); + WriteMenuEventHandlerSetProperty("onOpen", menu->onOpen); + WriteMenuEventHandlerSetProperty("onClose", menu->onClose); + WriteMenuEventHandlerSetProperty("onRequestClose", menu->onCloseRequest); + WriteMenuEventHandlerSetProperty("onESC", menu->onESC); + WriteItemKeyHandlerProperty(menu->onKey); + WriteItemDefs(menu->items, menu->itemCount); + } + +public: + explicit MenuDumperIw4(std::ostream& stream) + : MenuDumper(stream) + { + } + + void WriteMenu(const menuDef_t* menu) + { + Indent(); + m_stream << "menuDef\n"; + Indent(); + m_stream << "{\n"; + IncIndent(); + + WriteMenuData(menu); + + DecIndent(); + Indent(); + m_stream << "}\n"; + } +}; + +const MenuList* AssetDumperMenuDef::GetParentMenuList(XAssetInfo* asset) +{ + const auto* menu = asset->Asset(); + const auto* gameAssetPool = dynamic_cast(asset->m_zone->m_pools.get()); + for (const auto* menuList : *gameAssetPool->m_menu_list) + { + const auto* menuListAsset = menuList->Asset(); + + for (auto menuIndex = 0; menuIndex < menuListAsset->menuCount; menuIndex++) + { + if (menuListAsset->menus[menuIndex] == menu) + return menuListAsset; + } + } + + return nullptr; +} + +std::string AssetDumperMenuDef::GetPathForMenu(XAssetInfo* asset) +{ + const auto* list = GetParentMenuList(asset); + + if (!list) + return "ui_mp/" + std::string(asset->Asset()->window.name) + ".menu"; + + const fs::path p(list->name); + std::string parentPath; + if (p.has_parent_path()) + parentPath = p.parent_path().string() + "/"; + + return parentPath + std::string(asset->Asset()->window.name) + ".menu"; +} + +bool AssetDumperMenuDef::ShouldDump(XAssetInfo* asset) +{ + return true; +} + +void AssetDumperMenuDef::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) +{ + const auto* menu = asset->Asset(); + const auto assetFile = context.OpenAssetFile(GetPathForMenu(asset)); + + if (!assetFile) + return; + + MenuDumperIw4 menuDumper(*assetFile); + + menuDumper.Start(); + menuDumper.WriteMenu(menu); + menuDumper.End(); +} diff --git a/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperMenuDef.h b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperMenuDef.h new file mode 100644 index 00000000..5dc78fda --- /dev/null +++ b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperMenuDef.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Dumping/AbstractAssetDumper.h" +#include "Game/IW4/IW4.h" + +namespace IW4 +{ + class AssetDumperMenuDef final : public AbstractAssetDumper + { + static const MenuList* GetParentMenuList(XAssetInfo* asset); + static std::string GetPathForMenu(XAssetInfo* asset); + + protected: + bool ShouldDump(XAssetInfo* asset) override; + void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; + }; +} diff --git a/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperMenuList.cpp b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperMenuList.cpp new file mode 100644 index 00000000..97797b5a --- /dev/null +++ b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperMenuList.cpp @@ -0,0 +1,51 @@ +#include "AssetDumperMenuList.h" + +#include +#include + +#include "Menu/MenuDumper.h" + +namespace fs = std::filesystem; + +using namespace IW4; + +bool AssetDumperMenuList::ShouldDump(XAssetInfo* asset) +{ + const auto* menuList = asset->Asset(); + const fs::path p(asset->Asset()->name); + const auto extension = p.extension().string(); + + if (extension == ".menu" && menuList->menuCount == 1) + return false; + + return true; +} + +void AssetDumperMenuList::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) +{ + const auto* menuList = asset->Asset(); + const auto assetFile = context.OpenAssetFile(asset->m_name); + const fs::path p(asset->Asset()->name); + + if (!assetFile) + return; + + std::string parentPath; + if(p.has_parent_path()) + parentPath = p.parent_path().string() + "/"; + + MenuDumper menuDumper(*assetFile); + + menuDumper.Start(); + + for(auto menuNum = 0; menuNum < menuList->menuCount; menuNum++) + { + const auto* menu = menuList->menus[menuNum]; + + std::ostringstream ss; + ss << parentPath << menu->window.name << ".menu"; + menuDumper.IncludeMenu(ss.str()); + } + + menuDumper.End(); +} diff --git a/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperMenuList.h b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperMenuList.h new file mode 100644 index 00000000..a1632aa8 --- /dev/null +++ b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperMenuList.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Dumping/AbstractAssetDumper.h" +#include "Game/IW4/IW4.h" + +namespace IW4 +{ + class AssetDumperMenuList final : public AbstractAssetDumper + { + protected: + bool ShouldDump(XAssetInfo* asset) override; + void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; + }; +} diff --git a/src/ObjWriting/Game/IW4/ZoneDumperIW4.cpp b/src/ObjWriting/Game/IW4/ZoneDumperIW4.cpp index 6f0cb54b..42326cd8 100644 --- a/src/ObjWriting/Game/IW4/ZoneDumperIW4.cpp +++ b/src/ObjWriting/Game/IW4/ZoneDumperIW4.cpp @@ -7,6 +7,8 @@ #include "AssetDumpers/AssetDumperGfxImage.h" #include "AssetDumpers/AssetDumperLoadedSound.h" #include "AssetDumpers/AssetDumperLocalizeEntry.h" +#include "AssetDumpers/AssetDumperMenuDef.h" +#include "AssetDumpers/AssetDumperMenuList.h" #include "AssetDumpers/AssetDumperRawFile.h" #include "AssetDumpers/AssetDumperStringTable.h" #include "AssetDumpers/AssetDumperVehicle.h" @@ -53,8 +55,8 @@ bool ZoneDumper::DumpZone(AssetDumpingContext& context) const // DUMP_ASSET_POOL(AssetDumperGfxWorld, m_gfx_world) // DUMP_ASSET_POOL(AssetDumperGfxLightDef, m_gfx_light_def) // DUMP_ASSET_POOL(AssetDumperFont_s, m_font) - // DUMP_ASSET_POOL(AssetDumperMenuList, m_menu_list) - // DUMP_ASSET_POOL(AssetDumpermenuDef_t, m_menu_def) + DUMP_ASSET_POOL(AssetDumperMenuList, m_menu_list) + DUMP_ASSET_POOL(AssetDumperMenuDef, m_menu_def) DUMP_ASSET_POOL(AssetDumperLocalizeEntry, m_localize) DUMP_ASSET_POOL(AssetDumperWeapon, m_weapon) // DUMP_ASSET_POOL(AssetDumperSndDriverGlobals, m_snd_driver_globals) diff --git a/src/ObjWriting/Menu/MenuDumper.cpp b/src/ObjWriting/Menu/MenuDumper.cpp new file mode 100644 index 00000000..a1936394 --- /dev/null +++ b/src/ObjWriting/Menu/MenuDumper.cpp @@ -0,0 +1,47 @@ +#include "MenuDumper.h" + +MenuDumper::MenuDumper(std::ostream& stream) + : m_stream(stream), + m_indent(0u) +{ +} + +void MenuDumper::IncIndent() +{ + m_indent++; +} + +void MenuDumper::DecIndent() +{ + if (m_indent > 0) + m_indent--; +} + +void MenuDumper::Indent() const +{ + for (auto i = 0u; i < m_indent; i++) + m_stream << " "; +} + +void MenuDumper::Start() +{ + Indent(); + m_stream << "{\n"; + IncIndent(); +} + +void MenuDumper::End() +{ + for (auto i = 0u; i < m_indent; i++) + { + DecIndent(); + Indent(); + m_stream << "}\n"; + } +} + +void MenuDumper::IncludeMenu(const std::string& menuPath) const +{ + Indent(); + m_stream << "loadMenu { \"" << menuPath << "\" }\n"; +} diff --git a/src/ObjWriting/Menu/MenuDumper.h b/src/ObjWriting/Menu/MenuDumper.h new file mode 100644 index 00000000..c471bb03 --- /dev/null +++ b/src/ObjWriting/Menu/MenuDumper.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +class MenuDumper +{ +protected: + std::ostream& m_stream; + size_t m_indent; + + void IncIndent(); + void DecIndent(); + void Indent() const; + +public: + explicit MenuDumper(std::ostream& stream); + + void Start(); + void End(); + + void IncludeMenu(const std::string& menuPath) const; +}; \ No newline at end of file