diff --git a/src/Components/Modules/Auth.cpp b/src/Components/Modules/Auth.cpp index ffcf1ffd..8bdc7536 100644 --- a/src/Components/Modules/Auth.cpp +++ b/src/Components/Modules/Auth.cpp @@ -472,7 +472,7 @@ namespace Components }); } - UIScript::Add("security_increase_cancel", [](UIScript::Token) + UIScript::Add("security_increase_cancel", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { Auth::TokenContainer.cancel = true; Logger::Print("Token incrementation process canceled!\n"); diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index cea04936..58e0f64c 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -909,7 +909,7 @@ namespace Components Dvar::Register("ui_dl_transRate", "", Game::DVAR_NONE, ""); }, Scheduler::Pipeline::MAIN); - UIScript::Add("mod_download_cancel", [](UIScript::Token) + UIScript::Add("mod_download_cancel", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { Download::CLDownload.clear(); }); diff --git a/src/Components/Modules/Friends.cpp b/src/Components/Modules/Friends.cpp index e545c649..0ace8be5 100644 --- a/src/Components/Modules/Friends.cpp +++ b/src/Components/Modules/Friends.cpp @@ -604,12 +604,12 @@ namespace Components // Show blue icons on the minimap Utils::Hook(0x493130, Friends::IsClientInParty, HOOK_JUMP).install()->quick(); - UIScript::Add("LoadFriends", [](UIScript::Token) + UIScript::Add("LoadFriends", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { Friends::UpdateFriends(); }); - UIScript::Add("JoinFriend", [](UIScript::Token) + UIScript::Add("JoinFriend", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { std::lock_guard _(Friends::Mutex); if (Friends::CurrentFriend >= Friends::FriendsList.size()) return; diff --git a/src/Components/Modules/MapRotation.cpp b/src/Components/Modules/MapRotation.cpp index d8a29c29..5e7b7139 100644 --- a/src/Components/Modules/MapRotation.cpp +++ b/src/Components/Modules/MapRotation.cpp @@ -58,6 +58,15 @@ namespace Components } } + bool MapRotation::RotationData::contains(const std::string& key, const std::string& value) const + { + return std::ranges::any_of(this->rotationEntries_, + [&](const auto& entry) + { + return entry.first == key && entry.second == value; + }); + } + nlohmann::json MapRotation::RotationData::to_json() const { std::vector mapVector; @@ -109,6 +118,17 @@ namespace Components Logger::Debug("DedicatedRotation size after parsing is '{}'", DedicatedRotation.getEntriesSize()); } + void MapRotation::LoadMapRotation() + { + const std::string mapRotation = (*Game::sv_mapRotation)->current.string; + // People may have sv_mapRotation empty because they only use 'addMap' or 'addGametype' + if (!mapRotation.empty()) + { + Logger::Debug("sv_mapRotation is not empty. Parsing..."); + LoadRotation(mapRotation); + } + } + void MapRotation::AddMapRotationCommands() { Command::Add("addMap", [](Command::Params* params) @@ -134,6 +154,11 @@ namespace Components }); } + bool MapRotation::Contains(const std::string& key, const std::string& value) + { + return DedicatedRotation.contains(key, value); + } + bool MapRotation::ShouldRotate() { if (!Dedicated::IsEnabled() && SVDontRotate.get()) @@ -276,14 +301,7 @@ namespace Components return; } - const std::string mapRotation = (*Game::sv_mapRotation)->current.string; - // People may have sv_mapRotation empty because they only use 'addMap' or 'addGametype' - if (!mapRotation.empty()) - { - Logger::Debug("sv_mapRotation is not empty. Parsing..."); - LoadRotation(mapRotation); - } - + LoadMapRotation(); if (DedicatedRotation.getEntriesSize() == 0) { Logger::Print(Game::CON_CHANNEL_SERVER, "{} is empty or contains invalid data. Restarting map\n", (*Game::sv_mapRotation)->name); diff --git a/src/Components/Modules/MapRotation.hpp b/src/Components/Modules/MapRotation.hpp index 30e655cd..4a57d1a0 100644 --- a/src/Components/Modules/MapRotation.hpp +++ b/src/Components/Modules/MapRotation.hpp @@ -7,6 +7,8 @@ namespace Components public: MapRotation(); + static bool Contains(const std::string& key, const std::string& value); + bool unitTest() override; private: @@ -33,7 +35,8 @@ namespace Components void parse(const std::string& data); - // Json11 Implicit constructor + [[nodiscard]] bool contains(const std::string& key, const std::string& value) const; + [[nodiscard]] nlohmann::json to_json() const; private: @@ -50,6 +53,7 @@ namespace Components static RotationData DedicatedRotation; static void LoadRotation(const std::string& data); + static void LoadMapRotation(); // Use these commands before SV_MapRotate_f is called static void AddMapRotationCommands(); diff --git a/src/Components/Modules/Maps.cpp b/src/Components/Modules/Maps.cpp index 39ef37c3..9f0b04df 100644 --- a/src/Components/Modules/Maps.cpp +++ b/src/Components/Modules/Maps.cpp @@ -768,7 +768,7 @@ namespace Components Maps::UpdateDlcStatus(); - UIScript::Add("downloadDLC", [](UIScript::Token token) + UIScript::Add("downloadDLC", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { int dlc = token.get(); diff --git a/src/Components/Modules/Menus.cpp b/src/Components/Modules/Menus.cpp index 32221eeb..ca347a18 100644 --- a/src/Components/Modules/Menus.cpp +++ b/src/Components/Modules/Menus.cpp @@ -785,80 +785,80 @@ namespace Components } }, HOOK_CALL).install()->quick(); - // Intercept menu painting - Utils::Hook(0x4FFBDF, Menus::IsMenuVisible, HOOK_CALL).install()->quick(); + // Intercept menu painting + Utils::Hook(0x4FFBDF, Menus::IsMenuVisible, HOOK_CALL).install()->quick(); - // disable the 2 new tokens in ItemParse_rect - Utils::Hook::Set(0x640693, 0xEB); + // disable the 2 new tokens in ItemParse_rect + Utils::Hook::Set(0x640693, 0xEB); - // don't load ASSET_TYPE_MENU assets for every menu (might cause patch menus to fail) - Utils::Hook::Nop(0x453406, 5); + // don't load ASSET_TYPE_MENU assets for every menu (might cause patch menus to fail) + Utils::Hook::Nop(0x453406, 5); - //make Com_Error and similar go back to main_text instead of menu_xboxlive. - Utils::Hook::SetString(0x6FC790, "main_text"); + // make Com_Error and similar go back to main_text instead of menu_xboxlive. + Utils::Hook::SetString(0x6FC790, "main_text"); - Command::Add("openmenu", [](Command::Params* params) + Command::Add("openmenu", [](Command::Params* params) + { + if (params->size() != 2) { - if (params->size() != 2) - { - Logger::Print("USAGE: openmenu \n"); - return; - } + Logger::Print("USAGE: openmenu \n"); + return; + } - // Not quite sure if we want to do this if we're not ingame, but it's only needed for ingame menus. - if (Dvar::Var("cl_ingame").get()) - { - Game::Key_SetCatcher(0, 16); - } - - Game::Menus_OpenByName(Game::uiContext, params->get(1)); - }); - - Command::Add("reloadmenus", [](Command::Params*) + // Not quite sure if we want to do this if we're not ingame, but it's only needed for ingame menus. + if (Dvar::Var("cl_ingame").get()) { - // Close all menus - Game::Menus_CloseAll(Game::uiContext); + Game::Key_SetCatcher(0, 16); + } - // Free custom menus - Menus::FreeEverything(); + Game::Menus_OpenByName(Game::uiContext, params->get(1)); + }); - // Only disconnect if in-game, context is updated automatically! - if (Game::CL_IsCgameInitialized()) - { - Game::Cbuf_AddText(0, "disconnect\n"); - } - else - { - // Reinitialize ui context - Utils::Hook::Call(0x401700)(); + Command::Add("reloadmenus", [](Command::Params*) + { + // Close all menus + Game::Menus_CloseAll(Game::uiContext); - // Reopen main menu - Game::Menus_OpenByName(Game::uiContext, "main_text"); - } - }); + // Free custom menus + Menus::FreeEverything(); - Command::Add("mp_QuickMessage", [](Command::Params*) + // Only disconnect if in-game, context is updated automatically! + if (Game::CL_IsCgameInitialized()) { - Command::Execute("openmenu quickmessage"); - }); + Game::Cbuf_AddText(0, "disconnect\n"); + } + else + { + // Reinitialize ui context + Utils::Hook::Call(0x401700)(); - // Define custom menus here - Menus::Add("ui_mp/changelog.menu"); - Menus::Add("ui_mp/theater_menu.menu"); - Menus::Add("ui_mp/pc_options_multi.menu"); - Menus::Add("ui_mp/pc_options_game.menu"); - Menus::Add("ui_mp/pc_options_gamepad.menu"); - Menus::Add("ui_mp/stats_reset.menu"); - Menus::Add("ui_mp/stats_unlock.menu"); - Menus::Add("ui_mp/security_increase_popmenu.menu"); - Menus::Add("ui_mp/mod_download_popmenu.menu"); - Menus::Add("ui_mp/popup_friends.menu"); - Menus::Add("ui_mp/menu_first_launch.menu"); - Menus::Add("ui_mp/startup_messages.menu"); - Menus::Add("ui_mp/iw4x_credits.menu"); - Menus::Add("ui_mp/resetclass.menu"); - Menus::Add("ui_mp/popup_customtitle.menu"); - Menus::Add("ui_mp/popup_customclan.menu"); + // Reopen main menu + Game::Menus_OpenByName(Game::uiContext, "main_text"); + } + }); + + Command::Add("mp_QuickMessage", [](Command::Params*) + { + Command::Execute("openmenu quickmessage"); + }); + + // Define custom menus here + Menus::Add("ui_mp/changelog.menu"); + Menus::Add("ui_mp/theater_menu.menu"); + Menus::Add("ui_mp/pc_options_multi.menu"); + Menus::Add("ui_mp/pc_options_game.menu"); + Menus::Add("ui_mp/pc_options_gamepad.menu"); + Menus::Add("ui_mp/stats_reset.menu"); + Menus::Add("ui_mp/stats_unlock.menu"); + Menus::Add("ui_mp/security_increase_popmenu.menu"); + Menus::Add("ui_mp/mod_download_popmenu.menu"); + Menus::Add("ui_mp/popup_friends.menu"); + Menus::Add("ui_mp/menu_first_launch.menu"); + Menus::Add("ui_mp/startup_messages.menu"); + Menus::Add("ui_mp/iw4x_credits.menu"); + Menus::Add("ui_mp/resetclass.menu"); + Menus::Add("ui_mp/popup_customtitle.menu"); + Menus::Add("ui_mp/popup_customclan.menu"); } Menus::~Menus() diff --git a/src/Components/Modules/ModList.cpp b/src/Components/Modules/ModList.cpp index 6d4db41b..5dc9d38c 100644 --- a/src/Components/Modules/ModList.cpp +++ b/src/Components/Modules/ModList.cpp @@ -40,7 +40,7 @@ namespace Components ModList::CurrentMod = index; } - void ModList::UIScript_LoadMods(UIScript::Token) + void ModList::UIScript_LoadMods([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { auto folder = Dvar::Var("fs_basepath").get() + "\\mods"; Logger::Debug("Searching for mods in {}...", folder); @@ -48,7 +48,7 @@ namespace Components Logger::Debug("Found {} mods!", ModList::Mods.size()); } - void ModList::UIScript_RunMod(UIScript::Token) + void ModList::UIScript_RunMod([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { if (ModList::CurrentMod < ModList::Mods.size()) { @@ -56,7 +56,7 @@ namespace Components } } - void ModList::UIScript_ClearMods(UIScript::Token) + void ModList::UIScript_ClearMods([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { auto fsGame = Dvar::Var("fs_game"); fsGame.set(""); diff --git a/src/Components/Modules/ModList.hpp b/src/Components/Modules/ModList.hpp index 185aaed1..6dc2bb4d 100644 --- a/src/Components/Modules/ModList.hpp +++ b/src/Components/Modules/ModList.hpp @@ -18,8 +18,8 @@ namespace Components static unsigned int GetItemCount(); static const char* GetItemText(unsigned int index, int column); static void Select(unsigned int index); - static void UIScript_LoadMods(UIScript::Token); - static void UIScript_RunMod(UIScript::Token); - static void UIScript_ClearMods(UIScript::Token); + static void UIScript_LoadMods([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info); + static void UIScript_RunMod([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info); + static void UIScript_ClearMods([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info); }; } diff --git a/src/Components/Modules/News.cpp b/src/Components/Modules/News.cpp index 8a75adf8..6816d8e2 100644 --- a/src/Components/Modules/News.cpp +++ b/src/Components/Modules/News.cpp @@ -43,7 +43,7 @@ namespace Components Dvar::Register("cl_updateoldversion", REVISION, REVISION, REVISION, Game::DVAR_INIT, "Current version number."); - UIScript::Add("checkFirstLaunch", [](UIScript::Token) + UIScript::Add("checkFirstLaunch", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { if (Dvar::Var("g_firstLaunch").get()) { @@ -52,7 +52,7 @@ namespace Components } }); - UIScript::Add("visitWebsite", [](UIScript::Token) + UIScript::Add("visitWebsite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { Utils::OpenUrl(Utils::Cache::GetStaticUrl("")); }); diff --git a/src/Components/Modules/QuickPatch.cpp b/src/Components/Modules/QuickPatch.cpp index 3038ec76..2a472e84 100644 --- a/src/Components/Modules/QuickPatch.cpp +++ b/src/Components/Modules/QuickPatch.cpp @@ -553,7 +553,7 @@ namespace Components // Fix mouse pitch adjustments Dvar::Register("ui_mousePitch", false, Game::DVAR_ARCHIVE, ""); - UIScript::Add("updateui_mousePitch", [](UIScript::Token) + UIScript::Add("updateui_mousePitch", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { if (Dvar::Var("ui_mousePitch").get()) { diff --git a/src/Components/Modules/ServerInfo.cpp b/src/Components/Modules/ServerInfo.cpp index f497e7d7..62f9f3a4 100644 --- a/src/Components/Modules/ServerInfo.cpp +++ b/src/Components/Modules/ServerInfo.cpp @@ -39,36 +39,36 @@ namespace Components ServerInfo::PlayerContainer.currentPlayer = index; } - void ServerInfo::ServerStatus(UIScript::Token) + void ServerInfo::ServerStatus([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { ServerInfo::PlayerContainer.currentPlayer = 0; ServerInfo::PlayerContainer.playerList.clear(); - ServerList::ServerInfo* info = ServerList::GetCurrentServer(); + auto* serverInfo = ServerList::GetCurrentServer(); if (info) { - Dvar::Var("uiSi_ServerName").set(info->hostname); - Dvar::Var("uiSi_MaxClients").set(info->clients); - Dvar::Var("uiSi_Version").set(info->shortversion); - Dvar::Var("uiSi_SecurityLevel").set(info->securityLevel); - Dvar::Var("uiSi_isPrivate").set(info->password ? "@MENU_YES" : "@MENU_NO"); - Dvar::Var("uiSi_Hardcore").set(info->hardcore ? "@MENU_ENABLED" : "@MENU_DISABLED"); + Dvar::Var("uiSi_ServerName").set(serverInfo->hostname); + Dvar::Var("uiSi_MaxClients").set(serverInfo->clients); + Dvar::Var("uiSi_Version").set(serverInfo->shortversion); + Dvar::Var("uiSi_SecurityLevel").set(serverInfo->securityLevel); + Dvar::Var("uiSi_isPrivate").set(serverInfo->password ? "@MENU_YES" : "@MENU_NO"); + Dvar::Var("uiSi_Hardcore").set(serverInfo->hardcore ? "@MENU_ENABLED" : "@MENU_DISABLED"); Dvar::Var("uiSi_KillCam").set("@MENU_NO"); Dvar::Var("uiSi_ffType").set("@MENU_DISABLED"); - Dvar::Var("uiSi_MapName").set(info->mapname); - Dvar::Var("uiSi_MapNameLoc").set(Game::UI_LocalizeMapName(info->mapname.data())); - Dvar::Var("uiSi_GameType").set(Game::UI_LocalizeGameType(info->gametype.data())); + Dvar::Var("uiSi_MapName").set(serverInfo->mapname); + Dvar::Var("uiSi_MapNameLoc").set(Game::UI_LocalizeMapName(serverInfo->mapname.data())); + Dvar::Var("uiSi_GameType").set(Game::UI_LocalizeGameType(serverInfo->gametype.data())); Dvar::Var("uiSi_ModName").set(""); - Dvar::Var("uiSi_aimAssist").set(info->aimassist ? "@MENU_YES" : "@MENU_NO"); - Dvar::Var("uiSi_voiceChat").set(info->voice ? "@MENU_YES" : "@MENU_NO"); + Dvar::Var("uiSi_aimAssist").set(serverInfo->aimassist ? "@MENU_YES" : "@MENU_NO"); + Dvar::Var("uiSi_voiceChat").set(serverInfo->voice ? "@MENU_YES" : "@MENU_NO"); - if (info->mod.size() > 5) + if (serverInfo->mod.size() > 5) { - Dvar::Var("uiSi_ModName").set(info->mod.data() + 5); + Dvar::Var("uiSi_ModName").set(serverInfo->mod.data() + 5); } - ServerInfo::PlayerContainer.target = info->addr; + ServerInfo::PlayerContainer.target = serverInfo->addr; Network::SendCommand(ServerInfo::PlayerContainer.target, "getstatus"); } } diff --git a/src/Components/Modules/ServerInfo.hpp b/src/Components/Modules/ServerInfo.hpp index 0eee5b6b..f09d87b6 100644 --- a/src/Components/Modules/ServerInfo.hpp +++ b/src/Components/Modules/ServerInfo.hpp @@ -33,7 +33,7 @@ namespace Components static Container PlayerContainer; - static void ServerStatus(UIScript::Token); + static void ServerStatus([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info); static unsigned int GetPlayerCount(); static const char* GetPlayerText(unsigned int index, int column); diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index 1c3dafd2..ec268831 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -181,7 +181,7 @@ namespace Components } } - void ServerList::UpdateVisibleList(UIScript::Token) + void ServerList::UpdateVisibleList([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { auto list = ServerList::GetList(); if (!list) return; @@ -190,7 +190,7 @@ namespace Components if (tempList.empty()) { - ServerList::Refresh(UIScript::Token()); + ServerList::Refresh(UIScript::Token(), info); } else { @@ -208,12 +208,12 @@ namespace Components } } - void ServerList::RefreshVisibleList(UIScript::Token) + void ServerList::RefreshVisibleList([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { - ServerList::RefreshVisibleListInternal(UIScript::Token()); + ServerList::RefreshVisibleListInternal(UIScript::Token(), info); } - void ServerList::RefreshVisibleListInternal(UIScript::Token, bool refresh) + void ServerList::RefreshVisibleListInternal([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info, bool refresh) { Dvar::Var("ui_serverSelected").set(false); @@ -224,38 +224,38 @@ namespace Components if (refresh) { - ServerList::Refresh(UIScript::Token()); + ServerList::Refresh(UIScript::Token(), info); return; } - bool ui_browserShowFull = Dvar::Var("ui_browserShowFull").get(); - bool ui_browserShowEmpty = Dvar::Var("ui_browserShowEmpty").get(); - int ui_browserShowHardcore = Dvar::Var("ui_browserKillcam").get(); - int ui_browserShowPassword = Dvar::Var("ui_browserShowPassword").get(); - int ui_browserMod = Dvar::Var("ui_browserMod").get(); - int ui_joinGametype = Dvar::Var("ui_joinGametype").get(); + auto ui_browserShowFull = Dvar::Var("ui_browserShowFull").get(); + auto ui_browserShowEmpty = Dvar::Var("ui_browserShowEmpty").get(); + auto ui_browserShowHardcore = Dvar::Var("ui_browserKillcam").get(); + auto ui_browserShowPassword = Dvar::Var("ui_browserShowPassword").get(); + auto ui_browserMod = Dvar::Var("ui_browserMod").get(); + auto ui_joinGametype = Dvar::Var("ui_joinGametype").get(); for (unsigned int i = 0; i < list->size(); ++i) { - ServerList::ServerInfo* info = &(*list)[i]; + auto* serverInfo = &(*list)[i]; // Filter full servers - if (!ui_browserShowFull && info->clients >= info->maxClients) continue; + if (!ui_browserShowFull && serverInfo->clients >= serverInfo->maxClients) continue; // Filter empty servers - if (!ui_browserShowEmpty && info->clients <= 0) continue; + if (!ui_browserShowEmpty && serverInfo->clients <= 0) continue; // Filter hardcore servers - if ((ui_browserShowHardcore == 0 && info->hardcore) || (ui_browserShowHardcore == 1 && !info->hardcore)) continue; + if ((ui_browserShowHardcore == 0 && serverInfo->hardcore) || (ui_browserShowHardcore == 1 && !serverInfo->hardcore)) continue; // Filter servers with password - if ((ui_browserShowPassword == 0 && info->password) || (ui_browserShowPassword == 1 && !info->password)) continue; + if ((ui_browserShowPassword == 0 && serverInfo->password) || (ui_browserShowPassword == 1 && !serverInfo->password)) continue; // Don't show modded servers - if ((ui_browserMod == 0 && info->mod.size()) || (ui_browserMod == 1 && !info->mod.size())) continue; + if ((ui_browserMod == 0 && serverInfo->mod.size()) || (ui_browserMod == 1 && !serverInfo->mod.size())) continue; // Filter by gametype - if (ui_joinGametype > 0 && (ui_joinGametype - 1) < *Game::gameTypeCount && Game::gameTypes[(ui_joinGametype - 1)].gameType != info->gametype) continue; + if (ui_joinGametype > 0 && (ui_joinGametype - 1) < *Game::gameTypeCount && Game::gameTypes[(ui_joinGametype - 1)].gameType != serverInfo->gametype) continue; ServerList::VisibleList.push_back(i); } @@ -263,7 +263,7 @@ namespace Components ServerList::SortList(); } - void ServerList::Refresh(UIScript::Token) + void ServerList::Refresh([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { Dvar::Var("ui_serverSelected").set(false); //Localization::Set("MPUI_SERVERQUERIED", "Sent requests: 0/0"); @@ -388,7 +388,7 @@ namespace Components auto list = ServerList::GetList(); if (list) list->clear(); - ServerList::RefreshVisibleListInternal(UIScript::Token()); + ServerList::RefreshVisibleListInternal(UIScript::Token(), nullptr); Game::ShowMessageBox("Server removed from favourites.", "Success"); } @@ -556,7 +556,7 @@ namespace Components if (lList) { lList->push_back(server); - ServerList::RefreshVisibleListInternal(UIScript::Token()); + ServerList::RefreshVisibleListInternal(UIScript::Token(), nullptr); } } } @@ -717,7 +717,7 @@ namespace Components netSource.set(source); - ServerList::RefreshVisibleListInternal(UIScript::Token(), true); + ServerList::RefreshVisibleListInternal(UIScript::Token(), nullptr, true); } void ServerList::UpdateGameType() @@ -733,7 +733,7 @@ namespace Components joinGametype.set(gametype); - ServerList::RefreshVisibleListInternal(UIScript::Token()); + ServerList::RefreshVisibleListInternal(UIScript::Token(), nullptr); } void ServerList::UpdateVisibleInfo() @@ -853,20 +853,18 @@ namespace Components UIScript::Add("RefreshServers", ServerList::Refresh); - UIScript::Add("JoinServer", [](UIScript::Token) + UIScript::Add("JoinServer", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { - ServerList::ServerInfo* info = ServerList::GetServer(ServerList::CurrentServer); - - if (info) + auto* serverInfo = ServerList::GetServer(ServerList::CurrentServer); + if (serverInfo) { - Party::Connect(info->addr); + Party::Connect(serverInfo->addr); } }); - UIScript::Add("ServerSort", [](UIScript::Token token) + UIScript::Add("ServerSort", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { - int key = token.get(); - + auto key = token.get(); if (ServerList::SortKey == key) { ServerList::SortAsc = !ServerList::SortAsc; @@ -881,22 +879,21 @@ namespace Components ServerList::SortList(); }); - UIScript::Add("CreateListFavorite", [](UIScript::Token) + UIScript::Add("CreateListFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { - ServerList::ServerInfo* info = ServerList::GetCurrentServer(); - + auto* serverInfo = ServerList::GetCurrentServer(); if (info) { - ServerList::StoreFavourite(info->addr.getString()); + ServerList::StoreFavourite(serverInfo->addr.getString()); } }); - UIScript::Add("CreateFavorite", [](UIScript::Token) + UIScript::Add("CreateFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { ServerList::StoreFavourite(Dvar::Var("ui_favoriteAddress").get()); }); - UIScript::Add("CreateCurrentServerFavorite", [](UIScript::Token) + UIScript::Add("CreateCurrentServerFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { if (Game::CL_IsCgameInitialized()) { @@ -908,14 +905,13 @@ namespace Components } }); - UIScript::Add("DeleteFavorite", [](UIScript::Token) + UIScript::Add("DeleteFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { - ServerList::ServerInfo* info = ServerList::GetCurrentServer(); - - if (info) + auto* serverInfo = ServerList::GetCurrentServer(); + if (serverInfo) { - ServerList::RemoveFavourite(info->addr.getString()); - }; + ServerList::RemoveFavourite(serverInfo->addr.getString()); + } }); #ifdef _DEBUG diff --git a/src/Components/Modules/ServerList.hpp b/src/Components/Modules/ServerList.hpp index 0efc1a2e..e82097e3 100644 --- a/src/Components/Modules/ServerList.hpp +++ b/src/Components/Modules/ServerList.hpp @@ -35,10 +35,10 @@ namespace Components ServerList(); ~ServerList(); - static void Refresh(UIScript::Token); - static void RefreshVisibleList(UIScript::Token); - static void RefreshVisibleListInternal(UIScript::Token, bool refresh = false); - static void UpdateVisibleList(UIScript::Token); + static void Refresh([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info); + static void RefreshVisibleList([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info); + static void RefreshVisibleListInternal([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info, bool refresh = false); + static void UpdateVisibleList([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info); static void InsertRequest(Network::Address address); static void Insert(const Network::Address& address, const Utils::InfoString& info); diff --git a/src/Components/Modules/StartupMessages.cpp b/src/Components/Modules/StartupMessages.cpp index 0d52cf51..333171a6 100644 --- a/src/Components/Modules/StartupMessages.cpp +++ b/src/Components/Modules/StartupMessages.cpp @@ -14,7 +14,7 @@ namespace Components Dvar::Register("ui_startupNextButtonText", "", Game::DVAR_EXTERNAL | Game::DVAR_INIT, ""); }, Scheduler::Pipeline::MAIN); - UIScript::Add("nextStartupMessage", [](UIScript::Token) + UIScript::Add("nextStartupMessage", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { if (!StartupMessages::MessageList.size()) return; diff --git a/src/Components/Modules/Stats.cpp b/src/Components/Modules/Stats.cpp index 9dce4f33..c96b3de7 100644 --- a/src/Components/Modules/Stats.cpp +++ b/src/Components/Modules/Stats.cpp @@ -56,7 +56,7 @@ namespace Components } } - void Stats::UpdateClasses(UIScript::Token) + void Stats::UpdateClasses([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { Stats::SendStats(); } diff --git a/src/Components/Modules/Stats.hpp b/src/Components/Modules/Stats.hpp index c4de9fd9..c77495d5 100644 --- a/src/Components/Modules/Stats.hpp +++ b/src/Components/Modules/Stats.hpp @@ -10,7 +10,7 @@ namespace Components static bool IsMaxLevel(); private: - static void UpdateClasses(UIScript::Token token); + static void UpdateClasses([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info); static void SendStats(); static int SaveStats(char* dest, const char* folder, const char* buffer, size_t length); diff --git a/src/Components/Modules/Theatre.cpp b/src/Components/Modules/Theatre.cpp index 912d94d5..29963619 100644 --- a/src/Components/Modules/Theatre.cpp +++ b/src/Components/Modules/Theatre.cpp @@ -181,7 +181,7 @@ namespace Components meta.write(nlohmann::json(Theatre::CurrentInfo.to_json()).dump()); } - void Theatre::LoadDemos(UIScript::Token) + void Theatre::LoadDemos([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { Theatre::CurrentSelection = 0; Theatre::Demos.clear(); @@ -199,17 +199,16 @@ namespace Components if (metaObject.is_object()) { - Theatre::DemoInfo info; - - info.name = demo.substr(0, demo.find_last_of(".")); - info.author = metaObject["author"].get(); - info.gametype = metaObject["gametype"].get(); - info.mapname = metaObject["mapname"].get(); - info.length = metaObject["length"].get(); + Theatre::DemoInfo demoInfo; + demoInfo.name = demo.substr(0, demo.find_last_of(".")); + demoInfo.author = metaObject["author"].get(); + demoInfo.gametype = metaObject["gametype"].get(); + demoInfo.mapname = metaObject["mapname"].get(); + demoInfo.length = metaObject["length"].get(); auto timestamp = metaObject["timestamp"].get(); - info.timeStamp = _atoi64(timestamp.data()); + demoInfo.timeStamp = _atoi64(timestamp.data()); - Theatre::Demos.push_back(info); + Theatre::Demos.push_back(demoInfo); } } } @@ -218,16 +217,16 @@ namespace Components std::reverse(Theatre::Demos.begin(), Theatre::Demos.end()); } - void Theatre::DeleteDemo(UIScript::Token) + void Theatre::DeleteDemo([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { if (Theatre::CurrentSelection < Theatre::Demos.size()) { - Theatre::DemoInfo info = Theatre::Demos[Theatre::CurrentSelection]; + Theatre::DemoInfo demoInfo = Theatre::Demos[Theatre::CurrentSelection]; - Logger::Print("Deleting demo {}...\n", info.name); + Logger::Print("Deleting demo {}...\n", demoInfo.name); - FileSystem::_DeleteFile("demos", info.name + ".dm_13"); - FileSystem::_DeleteFile("demos", info.name + ".dm_13.json"); + FileSystem::_DeleteFile("demos", demoInfo.name + ".dm_13"); + FileSystem::_DeleteFile("demos", demoInfo.name + ".dm_13.json"); // Reset our ui_demo_* dvars here, because the theater menu needs it. Dvar::Var("ui_demo_mapname").set(""); @@ -238,11 +237,11 @@ namespace Components Dvar::Var("ui_demo_date").set(""); // Reload demos - Theatre::LoadDemos(UIScript::Token()); + Theatre::LoadDemos(UIScript::Token(), info); } } - void Theatre::PlayDemo(UIScript::Token) + void Theatre::PlayDemo([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { if (Theatre::CurrentSelection < Theatre::Demos.size()) { diff --git a/src/Components/Modules/Theatre.hpp b/src/Components/Modules/Theatre.hpp index 985d8207..34c4e76b 100644 --- a/src/Components/Modules/Theatre.hpp +++ b/src/Components/Modules/Theatre.hpp @@ -44,9 +44,9 @@ namespace Components static void WriteBaseline(); static void StoreBaseline(PBYTE snapshotMsg); - static void LoadDemos(UIScript::Token); - static void DeleteDemo(UIScript::Token); - static void PlayDemo(UIScript::Token); + static void LoadDemos([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info); + static void DeleteDemo([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info); + static void PlayDemo([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info); static unsigned int GetDemoCount(); static const char* GetDemoText(unsigned int item, int column); diff --git a/src/Components/Modules/UIFeeder.cpp b/src/Components/Modules/UIFeeder.cpp index cefc0400..03c445fe 100644 --- a/src/Components/Modules/UIFeeder.cpp +++ b/src/Components/Modules/UIFeeder.cpp @@ -311,7 +311,7 @@ namespace Components } } - void UIFeeder::ApplyMap(UIScript::Token) + void UIFeeder::ApplyMap([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { const auto mapname = Dvar::Var("ui_map_name").get(); @@ -319,7 +319,7 @@ namespace Components Utils::Hook::Call(0x503B50)(mapname.data()); // Party_SetDisplayMapName } - void UIFeeder::ApplyInitialMap(UIScript::Token) + void UIFeeder::ApplyInitialMap([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { const auto mapname = Dvar::Var("ui_mapname").get(); diff --git a/src/Components/Modules/UIFeeder.hpp b/src/Components/Modules/UIFeeder.hpp index e8840514..8f597c9d 100644 --- a/src/Components/Modules/UIFeeder.hpp +++ b/src/Components/Modules/UIFeeder.hpp @@ -56,7 +56,7 @@ namespace Components static unsigned int GetMapCount(); static const char* GetMapText(unsigned int index, int column); static void SelectMap(unsigned int index); - static void ApplyMap(UIScript::Token token); - static void ApplyInitialMap(UIScript::Token token); + static void ApplyMap([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info); + static void ApplyInitialMap([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info); }; } diff --git a/src/Components/Modules/UIScript.cpp b/src/Components/Modules/UIScript.cpp index 2a591a8f..a8f5b677 100644 --- a/src/Components/Modules/UIScript.cpp +++ b/src/Components/Modules/UIScript.cpp @@ -5,17 +5,17 @@ namespace Components std::unordered_map> UIScript::UIScripts; std::unordered_map> UIScript::UIOwnerDraws; - template<> int UIScript::Token::get() + template<> int UIScript::Token::get() const { if (this->isValid()) { - return atoi(this->token); + return std::atoi(this->token); } return 0; } - template<> const char* UIScript::Token::get() + template<> const char* UIScript::Token::get() const { if (this->isValid()) { @@ -25,12 +25,12 @@ namespace Components return ""; } - template<> std::string UIScript::Token::get() + template<> std::string UIScript::Token::get() const { - return this->get(); + return {this->get()}; } - bool UIScript::Token::isValid() + bool UIScript::Token::isValid() const { return (this->token && this->token[0]); } @@ -43,12 +43,18 @@ namespace Components } } - void UIScript::Add(const std::string& name, Utils::Slot callback) + Game::uiInfo_s* UIScript::UI_GetClientInfo(int localClientNum) + { + AssertIn(localClientNum, Game::STATIC_MAX_LOCAL_CLIENTS); + return &Game::uiInfoArray[localClientNum]; + } + + void UIScript::Add(const std::string& name, const Utils::Slot& callback) { UIScript::UIScripts[name] = callback; } - void UIScript::AddOwnerDraw(int ownerdraw, Utils::Slot callback) + void UIScript::AddOwnerDraw(int ownerdraw, const Utils::Slot& callback) { UIScript::UIOwnerDraws[ownerdraw] = callback; } @@ -57,7 +63,8 @@ namespace Components { if (UIScript::UIScripts.contains(name)) { - UIScript::UIScripts[name](UIScript::Token(args)); + const auto* info = UIScript::UI_GetClientInfo(0); + UIScript::UIScripts[name](UIScript::Token(args), info); return true; } @@ -111,6 +118,8 @@ namespace Components UIScript::UIScript() { + AssertSize(Game::uiInfo_s, 0x22FC); + if (Dedicated::IsEnabled()) return; // Install handler diff --git a/src/Components/Modules/UIScript.hpp b/src/Components/Modules/UIScript.hpp index 90df6e64..a6f55c98 100644 --- a/src/Components/Modules/UIScript.hpp +++ b/src/Components/Modules/UIScript.hpp @@ -11,12 +11,12 @@ namespace Components class Token { public: - Token() : token(nullptr) {}; - Token(const char** args) : token(nullptr) { this->parse(args); }; - Token(const Token &obj) { this->token = obj.token; }; + Token() : token(nullptr) {} + Token(const char** args) : token(nullptr) { this->parse(args); } + Token(const Token &obj) { this->token = obj.token; } - template T get(); - bool isValid(); + template T get() const; + bool isValid() const; private: char* token; @@ -24,11 +24,13 @@ namespace Components void parse(const char** args); }; - typedef void(Callback)(Token token); + typedef void(Callback)(const Token& token, const Game::uiInfo_s* info); typedef void(CallbackRaw)(); - static void Add(const std::string& name, Utils::Slot callback); - static void AddOwnerDraw(int ownerdraw, Utils::Slot callback); + static Game::uiInfo_s* UI_GetClientInfo(int localClientNum); + + static void Add(const std::string& name, const Utils::Slot& callback); + static void AddOwnerDraw(int ownerdraw, const Utils::Slot& callback); private: static void OwnerDrawHandleKeyStub(int ownerDraw, int flags, float *special, int key); diff --git a/src/Components/Modules/Vote.cpp b/src/Components/Modules/Vote.cpp index 82928d16..25150744 100644 --- a/src/Components/Modules/Vote.cpp +++ b/src/Components/Modules/Vote.cpp @@ -62,6 +62,12 @@ namespace Components strncpy_s(arg2, params->get(2), _TRUNCATE); strncpy_s(arg3, params->get(3), _TRUNCATE); + if (!MapRotation::Contains("gametype", arg2) || + !MapRotation::Contains("map", arg3)) + { + return false; + } + if (!Game::Scr_IsValidGameType(arg2)) { Game::SV_GameSendServerCommand(ent - Game::g_entities, Game::SV_CMD_CAN_IGNORE, VA("%c \"GAME_INVALIDGAMETYPE\"", 0x65)); @@ -116,6 +122,11 @@ namespace Components bool Vote::HandleMap([[maybe_unused]] const Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params) { + if (!MapRotation::Contains("map", params->get(2))) + { + return false; + } + sprintf_s(Game::level->voteString, "%s %s", params->get(1), params->get(2)); sprintf_s(Game::level->voteDisplayString, "GAME_VOTE_MAP\x15%s", params->get(2)); return true; @@ -123,6 +134,11 @@ namespace Components bool Vote::HandleGametype([[maybe_unused]] const Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params) { + if (!MapRotation::Contains("gametype", params->get(2))) + { + return false; + } + if (!Game::Scr_IsValidGameType(params->get(2))) { Game::SV_GameSendServerCommand(ent - Game::g_entities, Game::SV_CMD_CAN_IGNORE, VA("%c \"GAME_INVALIDGAMETYPE\"", 0x65)); @@ -272,5 +288,41 @@ namespace Components { ClientCommand::Add("callvote", Cmd_CallVote_f); ClientCommand::Add("vote", Cmd_Vote_f); + + UIScript::Add("voteKick", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) + { + if (info->playerIndex >= 0 && info->playerIndex < Game::sharedUiInfo->playerCount) + { + Game::Cbuf_AddText(0, VA("callvote kick \"%s\"\n", Game::sharedUiInfo->playerNames[info->playerIndex])); + } + }); + + UIScript::Add("voteTempBan", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) + { + if (info->playerIndex >= 0 && info->playerIndex < Game::sharedUiInfo->playerCount) + { + Game::Cbuf_AddText(0, VA("callvote tempBanUser \"%s\"\n", Game::sharedUiInfo->playerNames[info->playerIndex])); + } + }); + + UIScript::Add("voteTypeMap", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) + { + Game::Cbuf_AddText(0, VA("callvote typemap %s %s\n", Game::sharedUiInfo->gameTypes[(*Game::ui_netGameType)->current.integer].gameType, + Game::sharedUiInfo->mapList[(*Game::ui_netGameType)->current.integer].mapName)); + }); + + UIScript::Add("voteMap", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) + { + if ((*Game::ui_currentMap)->current.integer >= 0 && + (*Game::ui_currentMap)->current.integer mapCount) + { + Game::Cbuf_AddText(0, VA("callvote map %s\n", Game::sharedUiInfo->mapList[(*Game::ui_currentMap)->current.integer].mapName)); + } + }); + + UIScript::Add("voteGame", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) + { + Game::Cbuf_AddText(0, VA("callvote g_gametype %s\n", Game::sharedUiInfo->gameTypes[(*Game::ui_netGameType)->current.integer].gameType)); + }); } } diff --git a/src/Components/Modules/Vote.hpp b/src/Components/Modules/Vote.hpp index c9e241e8..ffc68d23 100644 --- a/src/Components/Modules/Vote.hpp +++ b/src/Components/Modules/Vote.hpp @@ -11,7 +11,8 @@ namespace Components using CommandHandler = std::function; static std::unordered_map VoteCommands; - static constexpr auto* CallVoteDesc = "%c \"GAME_VOTECOMMANDSARE\x15 map_restart, map_rotate, map , g_gametype , typemap \""; + static constexpr auto* CallVoteDesc = "%c \"GAME_VOTECOMMANDSARE\x15 map_restart, map_rotate, map , g_gametype , typemap , " + " kick , tempBanUser \""; static void DisplayVote(const Game::gentity_s* ent); static bool IsInvalidVoteString(const std::string& input); diff --git a/src/Game/Dvars.cpp b/src/Game/Dvars.cpp index d2b59554..1b59359f 100644 --- a/src/Game/Dvars.cpp +++ b/src/Game/Dvars.cpp @@ -53,6 +53,11 @@ namespace Game const dvar_t** version = reinterpret_cast(0x1AD7930); + const dvar_t** ui_currentMap = reinterpret_cast(0x62E2834); + const dvar_t** ui_gametype = reinterpret_cast(0x62E2828); + const dvar_t** ui_mapname = reinterpret_cast(0x62E279C); + const dvar_t** ui_netGameType = reinterpret_cast(0x62E2838); + __declspec(naked) void Dvar_SetVariant(dvar_t*, DvarValue, DvarSetSource) { static DWORD Dvar_SetVariant_t = 0x647400; diff --git a/src/Game/Dvars.hpp b/src/Game/Dvars.hpp index ef17eec1..a13a4280 100644 --- a/src/Game/Dvars.hpp +++ b/src/Game/Dvars.hpp @@ -109,6 +109,11 @@ namespace Game extern const dvar_t** version; + extern const dvar_t** ui_currentMap; + extern const dvar_t** ui_gametype; + extern const dvar_t** ui_mapname; + extern const dvar_t** ui_netGameType; + extern void Dvar_SetVariant(dvar_t* var, DvarValue value, DvarSetSource source); extern void Dvar_SetFromStringFromSource(const dvar_t* dvar, const char* string, DvarSetSource source); } diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 2bb21bac..5f1e276c 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -408,6 +408,8 @@ namespace Game unsigned int* playerCardUIStringIndex = reinterpret_cast(0x62CD7A8); char (*playerCardUIStringBuf)[PLAYER_CARD_UI_STRING_COUNT][38] = reinterpret_cast(0x62CB4F8); + uiInfo_s* uiInfoArray = reinterpret_cast(0x62E2858); + int* logfile = reinterpret_cast(0x1AD8F28); GamerSettingState* gamerSettings = reinterpret_cast(0x107D3E8); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index a39098d7..3f20a99e 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -758,6 +758,8 @@ namespace Game extern unsigned int* playerCardUIStringIndex; extern char (*playerCardUIStringBuf)[PLAYER_CARD_UI_STRING_COUNT][38]; + extern uiInfo_s* uiInfoArray; + extern int* logfile; extern GamerSettingState* gamerSettings; diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index b0153a16..3f1738c3 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -8900,6 +8900,27 @@ namespace Game int dataSize; }; + struct uiInfo_s + { + UiContext uiDC; + int myTeamCount; + int playerRefresh; + int playerIndex; + int timeIndex; + int previousTimes[4]; + uiMenuCommand_t currentMenuType; + bool allowScriptMenuResponse; + char findPlayerName[1024]; + char foundPlayerServerAddresses[16][64]; + char foundPlayerServerNames[16][64]; + int numFoundPlayerServers; + int nextFindPlayerRefresh; + unsigned int mailUpdateTime; + char mailIndices[64]; + int mailCount; + int selectedMail; + }; + #pragma endregion #ifndef IDA