Merge pull request #456 from diamante0018/develop
[Vote/UI] Add UI Script Handlers
This commit is contained in:
commit
d3a2604e61
@ -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");
|
||||
|
@ -909,7 +909,7 @@ namespace Components
|
||||
Dvar::Register<const char*>("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();
|
||||
});
|
||||
|
@ -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<std::recursive_mutex> _(Friends::Mutex);
|
||||
if (Friends::CurrentFriend >= Friends::FriendsList.size()) return;
|
||||
|
@ -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<std::string> 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<bool>())
|
||||
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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<int>();
|
||||
|
||||
|
@ -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<std::string>() + "\\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("");
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ namespace Components
|
||||
|
||||
Dvar::Register<int>("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<bool>())
|
||||
{
|
||||
@ -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(""));
|
||||
});
|
||||
|
@ -553,7 +553,7 @@ namespace Components
|
||||
|
||||
// Fix mouse pitch adjustments
|
||||
Dvar::Register<bool>("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<bool>())
|
||||
{
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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>();
|
||||
bool ui_browserShowEmpty = Dvar::Var("ui_browserShowEmpty").get<bool>();
|
||||
int ui_browserShowHardcore = Dvar::Var("ui_browserKillcam").get<int>();
|
||||
int ui_browserShowPassword = Dvar::Var("ui_browserShowPassword").get<int>();
|
||||
int ui_browserMod = Dvar::Var("ui_browserMod").get<int>();
|
||||
int ui_joinGametype = Dvar::Var("ui_joinGametype").get<int>();
|
||||
auto ui_browserShowFull = Dvar::Var("ui_browserShowFull").get<bool>();
|
||||
auto ui_browserShowEmpty = Dvar::Var("ui_browserShowEmpty").get<bool>();
|
||||
auto ui_browserShowHardcore = Dvar::Var("ui_browserKillcam").get<int>();
|
||||
auto ui_browserShowPassword = Dvar::Var("ui_browserShowPassword").get<int>();
|
||||
auto ui_browserMod = Dvar::Var("ui_browserMod").get<int>();
|
||||
auto ui_joinGametype = Dvar::Var("ui_joinGametype").get<int>();
|
||||
|
||||
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<int>();
|
||||
|
||||
auto key = token.get<int>();
|
||||
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<std::string>());
|
||||
});
|
||||
|
||||
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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -14,7 +14,7 @@ namespace Components
|
||||
Dvar::Register<const char*>("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;
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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<std::string>();
|
||||
info.gametype = metaObject["gametype"].get<std::string>();
|
||||
info.mapname = metaObject["mapname"].get<std::string>();
|
||||
info.length = metaObject["length"].get<int>();
|
||||
Theatre::DemoInfo demoInfo;
|
||||
demoInfo.name = demo.substr(0, demo.find_last_of("."));
|
||||
demoInfo.author = metaObject["author"].get<std::string>();
|
||||
demoInfo.gametype = metaObject["gametype"].get<std::string>();
|
||||
demoInfo.mapname = metaObject["mapname"].get<std::string>();
|
||||
demoInfo.length = metaObject["length"].get<int>();
|
||||
auto timestamp = metaObject["timestamp"].get<std::string>();
|
||||
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())
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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<std::string>();
|
||||
|
||||
@ -319,7 +319,7 @@ namespace Components
|
||||
Utils::Hook::Call<void(const char*)>(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<std::string>();
|
||||
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
@ -5,17 +5,17 @@ namespace Components
|
||||
std::unordered_map<std::string, Utils::Slot<UIScript::Callback>> UIScript::UIScripts;
|
||||
std::unordered_map<int, Utils::Slot<UIScript::CallbackRaw>> 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<const char*>();
|
||||
return {this->get<const char*>()};
|
||||
}
|
||||
|
||||
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<UIScript::Callback> 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<UIScript::Callback>& callback)
|
||||
{
|
||||
UIScript::UIScripts[name] = callback;
|
||||
}
|
||||
|
||||
void UIScript::AddOwnerDraw(int ownerdraw, Utils::Slot<UIScript::CallbackRaw> callback)
|
||||
void UIScript::AddOwnerDraw(int ownerdraw, const Utils::Slot<UIScript::CallbackRaw>& 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
|
||||
|
@ -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<typename T> T get();
|
||||
bool isValid();
|
||||
template<typename T> 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> callback);
|
||||
static void AddOwnerDraw(int ownerdraw, Utils::Slot<CallbackRaw> callback);
|
||||
static Game::uiInfo_s* UI_GetClientInfo(int localClientNum);
|
||||
|
||||
static void Add(const std::string& name, const Utils::Slot<Callback>& callback);
|
||||
static void AddOwnerDraw(int ownerdraw, const Utils::Slot<CallbackRaw>& callback);
|
||||
|
||||
private:
|
||||
static void OwnerDrawHandleKeyStub(int ownerDraw, int flags, float *special, int key);
|
||||
|
@ -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 <Game::sharedUiInfo->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));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,8 @@ namespace Components
|
||||
using CommandHandler = std::function<bool(const Game::gentity_s* ent, const Command::ServerParams* params)>;
|
||||
static std::unordered_map<std::string, CommandHandler> VoteCommands;
|
||||
|
||||
static constexpr auto* CallVoteDesc = "%c \"GAME_VOTECOMMANDSARE\x15 map_restart, map_rotate, map <mapname>, g_gametype <typename>, typemap <typename> <mapname>\"";
|
||||
static constexpr auto* CallVoteDesc = "%c \"GAME_VOTECOMMANDSARE\x15 map_restart, map_rotate, map <mapname>, g_gametype <typename>, typemap <typename> <mapname>, "
|
||||
" kick <player>, tempBanUser <player>\"";
|
||||
|
||||
static void DisplayVote(const Game::gentity_s* ent);
|
||||
static bool IsInvalidVoteString(const std::string& input);
|
||||
|
@ -53,6 +53,11 @@ namespace Game
|
||||
|
||||
const dvar_t** version = reinterpret_cast<const dvar_t**>(0x1AD7930);
|
||||
|
||||
const dvar_t** ui_currentMap = reinterpret_cast<const dvar_t**>(0x62E2834);
|
||||
const dvar_t** ui_gametype = reinterpret_cast<const dvar_t**>(0x62E2828);
|
||||
const dvar_t** ui_mapname = reinterpret_cast<const dvar_t**>(0x62E279C);
|
||||
const dvar_t** ui_netGameType = reinterpret_cast<const dvar_t**>(0x62E2838);
|
||||
|
||||
__declspec(naked) void Dvar_SetVariant(dvar_t*, DvarValue, DvarSetSource)
|
||||
{
|
||||
static DWORD Dvar_SetVariant_t = 0x647400;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -408,6 +408,8 @@ namespace Game
|
||||
unsigned int* playerCardUIStringIndex = reinterpret_cast<unsigned int*>(0x62CD7A8);
|
||||
char (*playerCardUIStringBuf)[PLAYER_CARD_UI_STRING_COUNT][38] = reinterpret_cast<char(*)[PLAYER_CARD_UI_STRING_COUNT][38]>(0x62CB4F8);
|
||||
|
||||
uiInfo_s* uiInfoArray = reinterpret_cast<uiInfo_s*>(0x62E2858);
|
||||
|
||||
int* logfile = reinterpret_cast<int*>(0x1AD8F28);
|
||||
|
||||
GamerSettingState* gamerSettings = reinterpret_cast<GamerSettingState*>(0x107D3E8);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user