diff --git a/Client/core/CClientVariables.cpp b/Client/core/CClientVariables.cpp index 10fee79a1f8..858bc1af2f9 100644 --- a/Client/core/CClientVariables.cpp +++ b/Client/core/CClientVariables.cpp @@ -1,373 +1,374 @@ -/***************************************************************************** - * - * PROJECT: Multi Theft Auto - * LICENSE: See LICENSE in the top level directory - * FILE: core/CClientVariables.cpp - * PURPOSE: Managed storage of client variables (cvars) - * - * Multi Theft Auto is available from http://www.multitheftauto.com/ - * - *****************************************************************************/ - -#include "StdInc.h" - -template <> -CClientVariables* CSingleton::m_pSingleton = NULL; - -CClientVariables::CClientVariables() -{ - m_pStorage = NULL; - m_bLoaded = false; - m_iRevision = 1; -} - -CClientVariables::~CClientVariables() -{ -} - -bool CClientVariables::Load() -{ - // Get the root node - CXMLNode* pRoot = CCore::GetSingleton().GetConfig(); - if (!pRoot) - return false; - m_iRevision++; - - // Load the cvars - m_pStorage = pRoot->FindSubNode(CONFIG_NODE_CVARS); - - if (!m_pStorage) - { - // Non-existant, create a new node - m_pStorage = pRoot->CreateSubNode(CONFIG_NODE_CVARS); - } - - // Verify that at least all the defaults are in - LoadDefaults(); - - return true; -} - -bool CClientVariables::Get(const std::string& strVariable, CVector& val) -{ - std::stringstream ss; - std::string strVal; - - if (!Node(strVariable)) - return false; - strVal = Node(strVariable)->GetTagContent(); - ss.str(strVal); - - try - { - ss >> val.fX >> val.fY >> val.fZ; - } - catch (...) - { - return false; - } - - return true; -} - -bool CClientVariables::Get(const std::string& strVariable, CVector2D& val) -{ - std::stringstream ss; - std::string strVal; - - if (!Node(strVariable)) - return false; - strVal = Node(strVariable)->GetTagContent(); - ss.str(strVal); - - try - { - ss >> val.fX >> val.fY; - } - catch (...) - { - return false; - } - - return true; -} - -bool CClientVariables::Get(const std::string& strVariable, CColor& val) -{ - std::stringstream ss; - std::string strVal; - int iR, iG, iB, iA; - - if (!Node(strVariable)) - return false; - strVal = Node(strVariable)->GetTagContent(); - ss.str(strVal); - - try - { - ss >> iR >> iG >> iB >> iA; - val.R = iR; - val.G = iG; - val.B = iB; - val.A = iA; - } - catch (...) - { - return false; - } - - return true; -} - -void CClientVariables::Set(const std::string& strVariable, CVector val) -{ - std::stringstream ss; - if (!m_pStorage) - return; - m_iRevision++; - - ss << val.fX << " " << val.fY << " " << val.fZ; - - std::string strVal = ss.str(); - const char* szVal = strVal.c_str(); - Node(strVariable)->SetTagContent(szVal); -} - -void CClientVariables::Set(const std::string& strVariable, CVector2D val) -{ - std::stringstream ss; - if (!m_pStorage) - return; - m_iRevision++; - - ss << val.fX << " " << val.fY; - - std::string strVal = ss.str(); - const char* szVal = strVal.c_str(); - Node(strVariable)->SetTagContent(szVal); -} - -void CClientVariables::Set(const std::string& strVariable, CColor val) -{ - std::stringstream ss; - if (!m_pStorage) - return; - m_iRevision++; - - ss << (unsigned int)val.R << " " << (unsigned int)val.G << " " << (unsigned int)val.B << " " << (unsigned int)val.A; - - std::string strVal = ss.str(); - const char* szVal = strVal.c_str(); - Node(strVariable)->SetTagContent(szVal); -} - -CXMLNode* CClientVariables::Node(const std::string& strVariable) -{ - CXMLNode* pNode; - if (!m_pStorage) - return NULL; - - // Try and grab the sub node - pNode = m_pStorage->FindSubNode(strVariable.c_str()); - - if (!pNode) - { - // Non-existant, create a new sub node - pNode = m_pStorage->CreateSubNode(strVariable.c_str()); - } - return pNode; -} - -bool CClientVariables::Exists(const std::string& strVariable) -{ - // Check whether this node exists, thus if it is a valid cvar - if (!m_pStorage) - return false; - return (m_pStorage->FindSubNode(strVariable.c_str()) != NULL); -} - -// Clamp int variable -void CClientVariables::ClampValue(const std::string& strVariable, int iMinValue, int iMaxValue) -{ - int iTemp; - CVARS_GET(strVariable, iTemp); - iTemp = Clamp(iMinValue, iTemp, iMaxValue); - CVARS_SET(strVariable, iTemp); -} - -// Clamp float variable -void CClientVariables::ClampValue(const std::string& strVariable, float fMinValue, float fMaxValue) -{ - float fTemp; - CVARS_GET(strVariable, fTemp); - fTemp = Clamp(fMinValue, fTemp, fMaxValue); - CVARS_SET(strVariable, fTemp); -} - -// Clamp CColor variable -void CClientVariables::ClampValue(const std::string& strVariable, CColor minValue, CColor maxValue) -{ - CColor temp; - CVARS_GET(strVariable, temp); - temp.R = Clamp(minValue.R, temp.R, maxValue.R); - temp.G = Clamp(minValue.G, temp.G, maxValue.G); - temp.B = Clamp(minValue.B, temp.B, maxValue.B); - temp.A = Clamp(minValue.A, temp.A, maxValue.A); - CVARS_SET(strVariable, temp); -} - -// Clamp CVector2D variable -void CClientVariables::ClampValue(const std::string& strVariable, CVector2D minValue, CVector2D maxValue) -{ - CVector2D temp; - CVARS_GET(strVariable, temp); - temp.fX = Clamp(minValue.fX, temp.fX, maxValue.fX); - temp.fY = Clamp(minValue.fY, temp.fY, maxValue.fY); - CVARS_SET(strVariable, temp); -} - -// Ensure CVars are within reasonable limits -void CClientVariables::ValidateValues() -{ - uint uiViewportWidth = CCore::GetSingleton().GetGraphics()->GetViewportWidth(); - uint uiViewportHeight = CCore::GetSingleton().GetGraphics()->GetViewportHeight(); - - ClampValue("console_pos", CVector2D(0, 0), CVector2D(uiViewportWidth - 32, uiViewportHeight - 32)); - ClampValue("console_size", CVector2D(50, 50), CVector2D(uiViewportWidth - 32, uiViewportHeight - 32)); - ClampValue("fps_limit", 0, 100); - ClampValue("chat_font", 0, 3); - ClampValue("chat_lines", 3, 62); - ClampValue("chat_color", CColor(0, 0, 0, 0), CColor(255, 255, 255, 255)); - ClampValue("chat_text_color", CColor(0, 0, 0, 128), CColor(255, 255, 255, 255)); - ClampValue("chat_input_color", CColor(0, 0, 0, 0), CColor(255, 255, 255, 255)); - ClampValue("chat_input_prefix_color", CColor(0, 0, 0, 128), CColor(255, 255, 255, 255)); - ClampValue("chat_input_text_color", CColor(0, 0, 0, 128), CColor(255, 255, 255, 255)); - ClampValue("chat_scale", CVector2D(0.5f, 0.5f), CVector2D(3, 3)); - ClampValue("chat_width", 0.5f, 4.f); - ClampValue("chat_line_life", 1000, 120000000); - ClampValue("chat_line_fade_out", 1000, 30000000); - ClampValue("chat_position_offset_x", -1.0f, 1.0f); - ClampValue("chat_position_offset_y", -1.0f, 1.0f); - ClampValue("chat_position_horizontal", Chat::Position::Horizontal::LEFT, Chat::Position::Horizontal::RIGHT); - ClampValue("chat_position_vertical", Chat::Position::Vertical::TOP, Chat::Position::Vertical::BOTTOM); - ClampValue("chat_text_alignment", Chat::Text::Align::LEFT, Chat::Text::Align::RIGHT); - ClampValue("text_scale", 0.8f, 3.0f); - ClampValue("mastervolume", 0.0f, 1.0f); - ClampValue("mtavolume", 0.0f, 1.0f); - ClampValue("voicevolume", 0.0f, 1.0f); - ClampValue("mapalpha", 0, 255); -} - -void CClientVariables::LoadDefaults() -{ - #define DEFAULT(__x,__y) if(!Exists(__x)) \ - Set(__x,__y) - #define _S(__x) std::string(__x) - - if (!Exists("nick")) - { - DEFAULT("nick", _S(CNickGen::GetRandomNickname())); // nickname - CCore::GetSingleton().RequestNewNickOnStart(); // Request the user to set a new nickname - } - - DEFAULT("host", _S("127.0.0.1")); // hostname - DEFAULT("port", 22003); // port - DEFAULT("password", _S("")); // password - DEFAULT("qc_host", _S("127.0.0.1")); // quick connect hostname - DEFAULT("qc_port", 22003); // quick connect port - DEFAULT("qc_password", _S("")); // quick connect password - DEFAULT("debugfile", _S("")); // debug filename - DEFAULT("console_pos", CVector2D(0, 0)); // console position - DEFAULT("console_size", CVector2D(200, 200)); // console size - DEFAULT("serverbrowser_size", CVector2D(720.0f, 495.0f)); // serverbrowser size - DEFAULT("fps_limit", 100); // frame limiter - DEFAULT("chat_font", 2); // chatbox font type - DEFAULT("chat_lines", 10); // chatbox lines - DEFAULT("chat_color", CColor(0, 0, 0, 0)); // chatbox background color - DEFAULT("chat_text_color", CColor(172, 213, 254, 255)); // chatbox text color - DEFAULT("chat_text_outline", false); - DEFAULT("chat_input_color", CColor(0, 0, 0, 0)); // chatbox input background color - DEFAULT("chat_input_prefix_color", CColor(172, 213, 254, 255)); // chatbox input prefix color - DEFAULT("chat_input_text_color", CColor(172, 213, 254, 255)); // chatbox input text color - DEFAULT("chat_scale", CVector2D(1.0f, 1.0f)); // chatbox scale - DEFAULT("chat_width", 1.5f); // chatbox width - - DEFAULT("chat_css_style_text", false); // chatbox css/hl style text - DEFAULT("chat_css_style_background", false); // chatbox css/hl style background - DEFAULT("chat_line_life", 12000); // chatbox line life time - DEFAULT("chat_line_fade_out", 3000); // chatbox line fade out time - DEFAULT("chat_use_cegui", false); // chatbox uses cegui - DEFAULT("chat_nickcompletion", true); // chatbox nick completion - DEFAULT("chat_position_offset_x", 0.0125f); // chatbox relative x position offset - DEFAULT("chat_position_offset_y", 0.015f); // chatbox relative y position offset - DEFAULT("chat_position_horizontal", Chat::Position::Horizontal::LEFT); // chatbox horizontal position - DEFAULT("chat_position_vertical", Chat::Position::Vertical::TOP); // chatbox vertical position - DEFAULT("chat_text_alignment", Chat::Text::Align::LEFT); // chatbox horizontal text alignment - DEFAULT("server_can_flash_window", true); // allow server to flash the window - DEFAULT("allow_tray_notifications", true); // allow scripts to create tray balloon notifications - DEFAULT("text_scale", 1.0f); // text scale - DEFAULT("invert_mouse", false); // mouse inverting - DEFAULT("fly_with_mouse", false); // flying with mouse controls - DEFAULT("steer_with_mouse", false); // steering with mouse controls - DEFAULT("classic_controls", false); // classic/standard controls - DEFAULT("mastervolume", 1.0f); // master volume - DEFAULT("mtavolume", 1.0f); // custom sound's volume - DEFAULT("voicevolume", 1.0f); // voice chat output volume - DEFAULT("mapalpha", 155); // map alpha - DEFAULT("browser_speed", 1); // Browser speed - DEFAULT("single_download", 0); // Single connection for downloads - DEFAULT("packet_tag", 0); // Tag network packets - DEFAULT("progress_animation", 1); // Progress spinner at the bottom of the screen - DEFAULT("update_build_type", 0); // 0-stable 1-test 2-nightly - DEFAULT("update_auto_install", 1); // 0-off 1-on - DEFAULT("volumetric_shadows", 0); // Enable volumetric shadows - DEFAULT("aspect_ratio", 0); // Display aspect ratio - DEFAULT("hud_match_aspect_ratio", 1); // GTA HUD should match the display aspect ratio - DEFAULT("anisotropic", 0); // Anisotropic filtering - DEFAULT("grass", 1); // Enable grass - DEFAULT("heat_haze", 1); // Enable heat haze - DEFAULT("tyre_smoke_enabled", 1); // Enable tyre smoke - DEFAULT("high_detail_vehicles", 0); // Disable rendering high detail vehicles all the time - DEFAULT("high_detail_peds", 0); // Disable rendering high detail peds all the time - DEFAULT("fast_clothes_loading", 1); // 0-off 1-auto 2-on - DEFAULT("allow_screen_upload", 1); // 0-off 1-on - DEFAULT("allow_external_sounds", 1); // 0-off 1-on - DEFAULT("max_clientscript_log_kb", 5000); // Max size in KB (0-No limit) - DEFAULT("display_fullscreen_style", 0); // 0-standard 1-borderless 2-borderless keep res 3-borderless stretch - DEFAULT("display_windowed", 0); // 0-off 1-on - DEFAULT("multimon_fullscreen_minimize", 1); // 0-off 1-on - DEFAULT("vertical_aim_sensitivity", 0.0015f); // 0.0015f is GTA default setting - DEFAULT("process_priority", 0); // 0-normal 1-above normal 2-high - DEFAULT("process_dpi_aware", false); // Enable DPI awareness in core initialization - DEFAULT("mute_master_when_minimized", 0); // 0-off 1-on - DEFAULT("mute_sfx_when_minimized", 0); // 0-off 1-on - DEFAULT("mute_radio_when_minimized", 0); // 0-off 1-on - DEFAULT("mute_mta_when_minimized", 0); // 0-off 1-on - DEFAULT("mute_voice_when_minimized", 0); // 0-off 1-on - DEFAULT("share_file_cache", 1); // 0-no 1-share client resource file cache with other MTA installs - DEFAULT("show_unsafe_resolutions", 0); // 0-off 1-show resolutions that are higher that the desktop - DEFAULT("fov", 70); // Camera field of view - DEFAULT("browser_remote_websites", true); // Load remote websites? - DEFAULT("browser_remote_javascript", true); // Execute javascript on remote websites? - DEFAULT("filter_duplicate_log_lines", true); // Filter duplicate log lines for debug view and clientscript.log - DEFAULT("_beta_qc_rightclick_command", _S("reconnect")); // Command to run when right clicking quick connect (beta - can be removed at any time) - - if (!Exists("locale")) - { - SString strLangCode = GetApplicationSetting("locale"); - Set("locale", !strLangCode.empty() ? strLangCode : _S("en_US")); - } - - // Set default resolution to native resolution - if (!Exists("display_resolution")) - { - RECT rect; - GetWindowRect(GetDesktopWindow(), &rect); - Set("display_resolution", SString("%dx%dx32", rect.right, rect.bottom)); - } - - // We will default this one during CProxyDirect3DDevice9 constructor, because we need a valid direct3d device to give a proper default value. -#if 0 - DEFAULT ( "streaming_memory", 50 ); // Streaming memory -#endif -} +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: core/CClientVariables.cpp + * PURPOSE: Managed storage of client variables (cvars) + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" + +template <> +CClientVariables* CSingleton::m_pSingleton = NULL; + +CClientVariables::CClientVariables() +{ + m_pStorage = NULL; + m_bLoaded = false; + m_iRevision = 1; +} + +CClientVariables::~CClientVariables() +{ +} + +bool CClientVariables::Load() +{ + // Get the root node + CXMLNode* pRoot = CCore::GetSingleton().GetConfig(); + if (!pRoot) + return false; + m_iRevision++; + + // Load the cvars + m_pStorage = pRoot->FindSubNode(CONFIG_NODE_CVARS); + + if (!m_pStorage) + { + // Non-existant, create a new node + m_pStorage = pRoot->CreateSubNode(CONFIG_NODE_CVARS); + } + + // Verify that at least all the defaults are in + LoadDefaults(); + + return true; +} + +bool CClientVariables::Get(const std::string& strVariable, CVector& val) +{ + std::stringstream ss; + std::string strVal; + + if (!Node(strVariable)) + return false; + strVal = Node(strVariable)->GetTagContent(); + ss.str(strVal); + + try + { + ss >> val.fX >> val.fY >> val.fZ; + } + catch (...) + { + return false; + } + + return true; +} + +bool CClientVariables::Get(const std::string& strVariable, CVector2D& val) +{ + std::stringstream ss; + std::string strVal; + + if (!Node(strVariable)) + return false; + strVal = Node(strVariable)->GetTagContent(); + ss.str(strVal); + + try + { + ss >> val.fX >> val.fY; + } + catch (...) + { + return false; + } + + return true; +} + +bool CClientVariables::Get(const std::string& strVariable, CColor& val) +{ + std::stringstream ss; + std::string strVal; + int iR, iG, iB, iA; + + if (!Node(strVariable)) + return false; + strVal = Node(strVariable)->GetTagContent(); + ss.str(strVal); + + try + { + ss >> iR >> iG >> iB >> iA; + val.R = iR; + val.G = iG; + val.B = iB; + val.A = iA; + } + catch (...) + { + return false; + } + + return true; +} + +void CClientVariables::Set(const std::string& strVariable, CVector val) +{ + std::stringstream ss; + if (!m_pStorage) + return; + m_iRevision++; + + ss << val.fX << " " << val.fY << " " << val.fZ; + + std::string strVal = ss.str(); + const char* szVal = strVal.c_str(); + Node(strVariable)->SetTagContent(szVal); +} + +void CClientVariables::Set(const std::string& strVariable, CVector2D val) +{ + std::stringstream ss; + if (!m_pStorage) + return; + m_iRevision++; + + ss << val.fX << " " << val.fY; + + std::string strVal = ss.str(); + const char* szVal = strVal.c_str(); + Node(strVariable)->SetTagContent(szVal); +} + +void CClientVariables::Set(const std::string& strVariable, CColor val) +{ + std::stringstream ss; + if (!m_pStorage) + return; + m_iRevision++; + + ss << (unsigned int)val.R << " " << (unsigned int)val.G << " " << (unsigned int)val.B << " " << (unsigned int)val.A; + + std::string strVal = ss.str(); + const char* szVal = strVal.c_str(); + Node(strVariable)->SetTagContent(szVal); +} + +CXMLNode* CClientVariables::Node(const std::string& strVariable) +{ + CXMLNode* pNode; + if (!m_pStorage) + return NULL; + + // Try and grab the sub node + pNode = m_pStorage->FindSubNode(strVariable.c_str()); + + if (!pNode) + { + // Non-existant, create a new sub node + pNode = m_pStorage->CreateSubNode(strVariable.c_str()); + } + return pNode; +} + +bool CClientVariables::Exists(const std::string& strVariable) +{ + // Check whether this node exists, thus if it is a valid cvar + if (!m_pStorage) + return false; + return (m_pStorage->FindSubNode(strVariable.c_str()) != NULL); +} + +// Clamp int variable +void CClientVariables::ClampValue(const std::string& strVariable, int iMinValue, int iMaxValue) +{ + int iTemp; + CVARS_GET(strVariable, iTemp); + iTemp = Clamp(iMinValue, iTemp, iMaxValue); + CVARS_SET(strVariable, iTemp); +} + +// Clamp float variable +void CClientVariables::ClampValue(const std::string& strVariable, float fMinValue, float fMaxValue) +{ + float fTemp; + CVARS_GET(strVariable, fTemp); + fTemp = Clamp(fMinValue, fTemp, fMaxValue); + CVARS_SET(strVariable, fTemp); +} + +// Clamp CColor variable +void CClientVariables::ClampValue(const std::string& strVariable, CColor minValue, CColor maxValue) +{ + CColor temp; + CVARS_GET(strVariable, temp); + temp.R = Clamp(minValue.R, temp.R, maxValue.R); + temp.G = Clamp(minValue.G, temp.G, maxValue.G); + temp.B = Clamp(minValue.B, temp.B, maxValue.B); + temp.A = Clamp(minValue.A, temp.A, maxValue.A); + CVARS_SET(strVariable, temp); +} + +// Clamp CVector2D variable +void CClientVariables::ClampValue(const std::string& strVariable, CVector2D minValue, CVector2D maxValue) +{ + CVector2D temp; + CVARS_GET(strVariable, temp); + temp.fX = Clamp(minValue.fX, temp.fX, maxValue.fX); + temp.fY = Clamp(minValue.fY, temp.fY, maxValue.fY); + CVARS_SET(strVariable, temp); +} + +// Ensure CVars are within reasonable limits +void CClientVariables::ValidateValues() +{ + uint uiViewportWidth = CCore::GetSingleton().GetGraphics()->GetViewportWidth(); + uint uiViewportHeight = CCore::GetSingleton().GetGraphics()->GetViewportHeight(); + + ClampValue("console_pos", CVector2D(0, 0), CVector2D(uiViewportWidth - 32, uiViewportHeight - 32)); + ClampValue("console_size", CVector2D(50, 50), CVector2D(uiViewportWidth - 32, uiViewportHeight - 32)); + ClampValue("fps_limit", 0, 100); + ClampValue("chat_font", 0, 3); + ClampValue("chat_lines", 3, 62); + ClampValue("chat_color", CColor(0, 0, 0, 0), CColor(255, 255, 255, 255)); + ClampValue("chat_text_color", CColor(0, 0, 0, 128), CColor(255, 255, 255, 255)); + ClampValue("chat_input_color", CColor(0, 0, 0, 0), CColor(255, 255, 255, 255)); + ClampValue("chat_input_prefix_color", CColor(0, 0, 0, 128), CColor(255, 255, 255, 255)); + ClampValue("chat_input_text_color", CColor(0, 0, 0, 128), CColor(255, 255, 255, 255)); + ClampValue("chat_scale", CVector2D(0.5f, 0.5f), CVector2D(3, 3)); + ClampValue("chat_width", 0.5f, 4.f); + ClampValue("chat_line_life", 1000, 120000000); + ClampValue("chat_line_fade_out", 1000, 30000000); + ClampValue("chat_position_offset_x", -1.0f, 1.0f); + ClampValue("chat_position_offset_y", -1.0f, 1.0f); + ClampValue("chat_position_horizontal", Chat::Position::Horizontal::LEFT, Chat::Position::Horizontal::RIGHT); + ClampValue("chat_position_vertical", Chat::Position::Vertical::TOP, Chat::Position::Vertical::BOTTOM); + ClampValue("chat_text_alignment", Chat::Text::Align::LEFT, Chat::Text::Align::RIGHT); + ClampValue("text_scale", 0.8f, 3.0f); + ClampValue("mastervolume", 0.0f, 1.0f); + ClampValue("mtavolume", 0.0f, 1.0f); + ClampValue("voicevolume", 0.0f, 1.0f); + ClampValue("mapalpha", 0, 255); +} + +void CClientVariables::LoadDefaults() +{ + #define DEFAULT(__x,__y) if(!Exists(__x)) \ + Set(__x,__y) + #define _S(__x) std::string(__x) + + if (!Exists("nick")) + { + DEFAULT("nick", _S(CNickGen::GetRandomNickname())); // nickname + CCore::GetSingleton().RequestNewNickOnStart(); // Request the user to set a new nickname + } + + DEFAULT("host", _S("127.0.0.1")); // hostname + DEFAULT("port", 22003); // port + DEFAULT("password", _S("")); // password + DEFAULT("qc_host", _S("127.0.0.1")); // quick connect hostname + DEFAULT("qc_port", 22003); // quick connect port + DEFAULT("qc_password", _S("")); // quick connect password + DEFAULT("debugfile", _S("")); // debug filename + DEFAULT("console_pos", CVector2D(0, 0)); // console position + DEFAULT("console_size", CVector2D(200, 200)); // console size + DEFAULT("serverbrowser_size", CVector2D(720.0f, 495.0f)); // serverbrowser size + DEFAULT("fps_limit", 100); // frame limiter + DEFAULT("chat_font", 2); // chatbox font type + DEFAULT("chat_lines", 10); // chatbox lines + DEFAULT("chat_color", CColor(0, 0, 0, 0)); // chatbox background color + DEFAULT("chat_text_color", CColor(172, 213, 254, 255)); // chatbox text color + DEFAULT("chat_text_outline", false); + DEFAULT("chat_input_color", CColor(0, 0, 0, 0)); // chatbox input background color + DEFAULT("chat_input_prefix_color", CColor(172, 213, 254, 255)); // chatbox input prefix color + DEFAULT("chat_input_text_color", CColor(172, 213, 254, 255)); // chatbox input text color + DEFAULT("chat_scale", CVector2D(1.0f, 1.0f)); // chatbox scale + DEFAULT("chat_width", 1.5f); // chatbox width + + DEFAULT("chat_css_style_text", false); // chatbox css/hl style text + DEFAULT("chat_css_style_background", false); // chatbox css/hl style background + DEFAULT("chat_line_life", 12000); // chatbox line life time + DEFAULT("chat_line_fade_out", 3000); // chatbox line fade out time + DEFAULT("chat_use_cegui", false); // chatbox uses cegui + DEFAULT("chat_nickcompletion", true); // chatbox nick completion + DEFAULT("chat_position_offset_x", 0.0125f); // chatbox relative x position offset + DEFAULT("chat_position_offset_y", 0.015f); // chatbox relative y position offset + DEFAULT("chat_position_horizontal", Chat::Position::Horizontal::LEFT); // chatbox horizontal position + DEFAULT("chat_position_vertical", Chat::Position::Vertical::TOP); // chatbox vertical position + DEFAULT("chat_text_alignment", Chat::Text::Align::LEFT); // chatbox horizontal text alignment + DEFAULT("server_can_flash_window", true); // allow server to flash the window + DEFAULT("allow_tray_notifications", true); // allow scripts to create tray balloon notifications + DEFAULT("text_scale", 1.0f); // text scale + DEFAULT("invert_mouse", false); // mouse inverting + DEFAULT("fly_with_mouse", false); // flying with mouse controls + DEFAULT("steer_with_mouse", false); // steering with mouse controls + DEFAULT("classic_controls", false); // classic/standard controls + DEFAULT("mastervolume", 1.0f); // master volume + DEFAULT("mtavolume", 1.0f); // custom sound's volume + DEFAULT("voicevolume", 1.0f); // voice chat output volume + DEFAULT("mapalpha", 155); // map alpha + DEFAULT("browser_speed", 1); // Browser speed + DEFAULT("single_download", 0); // Single connection for downloads + DEFAULT("packet_tag", 0); // Tag network packets + DEFAULT("progress_animation", 1); // Progress spinner at the bottom of the screen + DEFAULT("update_build_type", 0); // 0-stable 1-test 2-nightly + DEFAULT("update_auto_install", 1); // 0-off 1-on + DEFAULT("volumetric_shadows", 0); // Enable volumetric shadows + DEFAULT("aspect_ratio", 0); // Display aspect ratio + DEFAULT("hud_match_aspect_ratio", 1); // GTA HUD should match the display aspect ratio + DEFAULT("anisotropic", 0); // Anisotropic filtering + DEFAULT("grass", 1); // Enable grass + DEFAULT("heat_haze", 1); // Enable heat haze + DEFAULT("tyre_smoke_enabled", 1); // Enable tyre smoke + DEFAULT("high_detail_vehicles", 0); // Disable rendering high detail vehicles all the time + DEFAULT("high_detail_peds", 0); // Disable rendering high detail peds all the time + DEFAULT("fast_clothes_loading", 1); // 0-off 1-auto 2-on + DEFAULT("allow_screen_upload", 1); // 0-off 1-on + DEFAULT("allow_external_sounds", 1); // 0-off 1-on + DEFAULT("max_clientscript_log_kb", 5000); // Max size in KB (0-No limit) + DEFAULT("display_fullscreen_style", 0); // 0-standard 1-borderless 2-borderless keep res 3-borderless stretch + DEFAULT("display_windowed", 0); // 0-off 1-on + DEFAULT("multimon_fullscreen_minimize", 1); // 0-off 1-on + DEFAULT("vertical_aim_sensitivity", 0.0015f); // 0.0015f is GTA default setting + DEFAULT("process_priority", 0); // 0-normal 1-above normal 2-high + DEFAULT("process_dpi_aware", false); // Enable DPI awareness in core initialization + DEFAULT("mute_master_when_minimized", 0); // 0-off 1-on + DEFAULT("mute_sfx_when_minimized", 0); // 0-off 1-on + DEFAULT("mute_radio_when_minimized", 0); // 0-off 1-on + DEFAULT("mute_mta_when_minimized", 0); // 0-off 1-on + DEFAULT("mute_voice_when_minimized", 0); // 0-off 1-on + DEFAULT("share_file_cache", 1); // 0-no 1-share client resource file cache with other MTA installs + DEFAULT("show_unsafe_resolutions", 0); // 0-off 1-show resolutions that are higher that the desktop + DEFAULT("fov", 70); // Camera field of view + DEFAULT("browser_remote_websites", true); // Load remote websites? + DEFAULT("browser_remote_javascript", true); // Execute javascript on remote websites? + DEFAULT("filter_duplicate_log_lines", true); // Filter duplicate log lines for debug view and clientscript.log + DEFAULT("discord_rich_presence", true); // Enable Discord Game SDK + DEFAULT("_beta_qc_rightclick_command", _S("reconnect")); // Command to run when right clicking quick connect (beta - can be removed at any time) + + if (!Exists("locale")) + { + SString strLangCode = GetApplicationSetting("locale"); + Set("locale", !strLangCode.empty() ? strLangCode : _S("en_US")); + } + + // Set default resolution to native resolution + if (!Exists("display_resolution")) + { + RECT rect; + GetWindowRect(GetDesktopWindow(), &rect); + Set("display_resolution", SString("%dx%dx32", rect.right, rect.bottom)); + } + + // We will default this one during CProxyDirect3DDevice9 constructor, because we need a valid direct3d device to give a proper default value. +#if 0 + DEFAULT ( "streaming_memory", 50 ); // Streaming memory +#endif +} diff --git a/Client/core/CConnectManager.cpp b/Client/core/CConnectManager.cpp index 9bc7f77b67e..4a6d518a093 100644 --- a/Client/core/CConnectManager.cpp +++ b/Client/core/CConnectManager.cpp @@ -46,7 +46,7 @@ CConnectManager::~CConnectManager() g_pConnectManager = NULL; } -bool CConnectManager::Connect(const char* szHost, unsigned short usPort, const char* szNick, const char* szPassword, bool bNotifyServerBrowser) +bool CConnectManager::Connect(const char* szHost, unsigned short usPort, const char* szNick, const char* szPassword, bool bNotifyServerBrowser, const char* szSecret) { assert(szHost); assert(szNick); @@ -100,6 +100,11 @@ bool CConnectManager::Connect(const char* szHost, unsigned short usPort, const c m_usPort = usPort; m_bSave = true; + if (szSecret) + m_strDiscordSecretJoin = szSecret; + else + m_strDiscordSecretJoin.clear(); + m_strLastHost = m_strHost; m_usLastPort = m_usPort; m_strLastPassword = m_strPassword; @@ -489,3 +494,10 @@ void CConnectManager::OpenServerFirewall(in_addr Address, ushort usHttpPort, boo g_pCore->GetNetwork()->GetHTTPDownloadManager(EDownloadMode::CONNECT_TCP_SEND)->QueueFile(strDummyUrl, NULL, NULL, NULL, options); } } + +SString CConnectManager::GetJoinSecret() +{ + SString dummy = m_strDiscordSecretJoin; + m_strDiscordSecretJoin.clear(); + return dummy; +} diff --git a/Client/core/CConnectManager.h b/Client/core/CConnectManager.h index c81c899da24..ad720e1f36a 100644 --- a/Client/core/CConnectManager.h +++ b/Client/core/CConnectManager.h @@ -21,7 +21,7 @@ class CConnectManager CConnectManager(); ~CConnectManager(); - bool Connect(const char* szHost, unsigned short usPort, const char* szNick, const char* szPassword, bool bNotifyServerBrowser = false); + bool Connect(const char* szHost, unsigned short usPort, const char* szNick, const char* szPassword, bool bNotifyServerBrowser = false, const char* szSecret = nullptr); bool Reconnect(const char* szHost, unsigned short usPort, const char* szPassword, bool bSave = true); bool Abort(); @@ -34,6 +34,8 @@ class CConnectManager static bool StaticProcessPacket(unsigned char ucPacketID, class NetBitStreamInterface& bitStream); + SString GetJoinSecret(); + std::string m_strLastHost; unsigned short m_usLastPort; std::string m_strLastPassword; @@ -52,6 +54,7 @@ class CConnectManager bool m_bSave; time_t m_tConnectStarted; bool m_bHasTriedSecondConnect; + SString m_strDiscordSecretJoin; GUI_CALLBACK* m_pOnCancelClick; diff --git a/Client/core/CCore.cpp b/Client/core/CCore.cpp index 687bb4e99f3..719fef0af4f 100644 --- a/Client/core/CCore.cpp +++ b/Client/core/CCore.cpp @@ -21,6 +21,7 @@ #include "CModelCacheManager.h" #include "detours/include/detours.h" #include +#include "CDiscordManager.h" using SharedUtil::CalcMTASAPath; using namespace std; @@ -34,7 +35,7 @@ SString g_strJingleBells; template <> CCore* CSingleton::m_pSingleton = NULL; -CCore::CCore() +CCore::CCore() : m_DiscordManager(new CDiscordManager()) { // Initialize the global pointer g_pCore = this; @@ -565,6 +566,9 @@ void CCore::SetConnected(bool bConnected) { m_pLocalGUI->GetMainMenu()->SetIsIngame(bConnected); UpdateIsWindowMinimized(); // Force update of stuff + + if (bConnected) m_DiscordManager->RegisterPlay(true); + else ResetDiscordRichPresence(); } bool CCore::IsConnected() @@ -779,6 +783,7 @@ void CCore::ApplyHooks2() CCore::GetSingleton().CreateMultiplayer(); CCore::GetSingleton().CreateXML(); CCore::GetSingleton().CreateGUI(); + CCore::GetSingleton().ResetDiscordRichPresence(); } } } @@ -1980,6 +1985,28 @@ uint CCore::GetMaxStreamingMemory() return m_fMaxStreamingMemory; } +// +// ResetDiscordRichPresence +// +void CCore::ResetDiscordRichPresence() +{ + time_t currentTime; + time(¤tTime); + + // Set default parameters + SDiscordActivity activity; + activity.m_details = "In Main Menu"; + activity.m_startTimestamp = currentTime; + + m_DiscordManager->UpdateActivity(activity, [](EDiscordRes res) { + if (res == DiscordRes_Ok) + WriteDebugEvent("[DISCORD]: Rich presence default parameters reset."); + else + WriteErrorEvent("[DISCORD]: Unable to reset rich presence default parameters."); + }); + m_DiscordManager->RegisterPlay(false); +} + // // OnCrashAverted // diff --git a/Client/core/CCore.h b/Client/core/CCore.h index b06c33bed8e..ae50f2a77ed 100644 --- a/Client/core/CCore.h +++ b/Client/core/CCore.h @@ -83,25 +83,26 @@ class CCore : public CCoreInterface, public CSingleton ~CCore(); // Subsystems (query) - eCoreVersion GetVersion(); - CConsoleInterface* GetConsole(); - CCommandsInterface* GetCommands(); - CConnectManager* GetConnectManager() { return m_pConnectManager; }; - CGame* GetGame(); - CGUI* GetGUI(); - CGraphicsInterface* GetGraphics(); - CModManagerInterface* GetModManager(); - CMultiplayer* GetMultiplayer(); - CNet* GetNetwork(); - CXML* GetXML() { return m_pXML; }; - CXMLNode* GetConfig(); - CClientVariables* GetCVars() { return &m_ClientVariables; }; - CKeyBindsInterface* GetKeyBinds(); - CMouseControl* GetMouseControl() { return m_pMouseControl; }; - CLocalGUI* GetLocalGUI(); - CLocalizationInterface* GetLocalization() { return g_pLocalization; }; - CWebCoreInterface* GetWebCore(); - CTrayIconInterface* GetTrayIcon() { return m_pTrayIcon; }; + eCoreVersion GetVersion(); + CConsoleInterface* GetConsole(); + CCommandsInterface* GetCommands(); + CConnectManager* GetConnectManager() { return m_pConnectManager; }; + CGame* GetGame(); + CGUI* GetGUI(); + CGraphicsInterface* GetGraphics(); + CModManagerInterface* GetModManager(); + CMultiplayer* GetMultiplayer(); + CNet* GetNetwork(); + CXML* GetXML() { return m_pXML; }; + CXMLNode* GetConfig(); + CClientVariables* GetCVars() { return &m_ClientVariables; }; + CKeyBindsInterface* GetKeyBinds(); + CMouseControl* GetMouseControl() { return m_pMouseControl; }; + CLocalGUI* GetLocalGUI(); + CLocalizationInterface* GetLocalization() { return g_pLocalization; }; + CWebCoreInterface* GetWebCore(); + CTrayIconInterface* GetTrayIcon() { return m_pTrayIcon; }; + CDiscordManagerInterface* GetDiscordManager() { return reinterpret_cast(m_DiscordManager.get()); } void SaveConfig(bool bWaitUntilFinished = false); @@ -226,6 +227,8 @@ class CCore : public CCoreInterface, public CSingleton uint GetMinStreamingMemory(); uint GetMaxStreamingMemory(); + void ResetDiscordRichPresence(); + SString GetConnectCommandFromURI(const char* szURI); void GetConnectParametersFromURI(const char* szURI, std::string& strHost, unsigned short& usPort, std::string& strNick, std::string& strPassword); bool bScreenShot; @@ -296,6 +299,8 @@ class CCore : public CCoreInterface, public CSingleton CWebCoreInterface* m_pWebCore = nullptr; CTrayIcon* m_pTrayIcon; + std::unique_ptr m_DiscordManager; + // Hook interfaces. CMessageLoopHook* m_pMessageLoopHook; CDirectInputHookManager* m_pDirectInputHookManager; diff --git a/Client/core/CDiscordManager.cpp b/Client/core/CDiscordManager.cpp new file mode 100644 index 00000000000..1743260f9b0 --- /dev/null +++ b/Client/core/CDiscordManager.cpp @@ -0,0 +1,367 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: Client/core/CDiscordManager.cpp + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" +#include "SharedUtil.Thread.h" +#include "CDiscordManager.h" + +#ifndef DISCORD_CLIENT_ID +#define DISCORD_CLIENT_ID 468493322583801867 +#endif + +CDiscordManager::CDiscordManager() : m_DiscordCore(nullptr), m_Suicide(false), m_WaitingForServerName(false), m_StoredActivity{} +{ + Reconnect(true); // Try to interact with discord on construction + m_Thread = new CThreadHandle(CDiscordManager::DiscordThread, this); + + m_StoredActivity.GetAssets().SetLargeImage("mta_logo_round"); // Always thing + m_StoredActivity.GetAssets().SetLargeText("Playing MTA:SA"); +} + +CDiscordManager::~CDiscordManager() +{ + m_Suicide = true; + int iTries = 0; + while (iTries++ < 400 && m_Suicide) // Wait maximum of 2 sec on this + Sleep(5); + + if (m_Suicide) + m_Thread->Cancel(); // Kill it anyway + + delete m_Thread; + SAFE_DELETE(m_DiscordCore); +} + +// Establish connection with discord +void CDiscordManager::Reconnect(bool bOnInitialization) +{ + bool discordRichPresence; + CVARS_GET("discord_rich_presence", discordRichPresence); + if (!discordRichPresence) + return; // Disabled + + discord::Result res; + { + std::lock_guard guardian(m_ThreadSafety); + res = discord::Core::Create(DISCORD_CLIENT_ID, DiscordCreateFlags_NoRequireDiscord, &m_DiscordCore); + } + + if (!m_DiscordCore && bOnInitialization) + { + // Output error only when trying to connect on initialization + WriteErrorEvent(SString("[DISCORD]: Failed to instantiate core, error code: %i", static_cast(res))); + return; + } + + m_DiscordCore->SetLogHook(discord::LogLevel::Info, DiscordLogCallback); + WriteDebugEvent(SString("[DISCORD]: Instantiated at %08X", m_DiscordCore)); + + m_DiscordCore->ActivityManager().RegisterCommand(SString("%s /from_discord", *GetParentProcessPathFilename(GetCurrentProcessId()))); + m_DiscordCore->ActivityManager().OnActivityJoin.Connect(OnActivityJoin); + + if (!bOnInitialization) // Player could be in a server or in menu by now + { + if (g_pCore->IsConnected()) + Restore(); + else + g_pCore->ResetDiscordRichPresence(); + } +} + +void CDiscordManager::DiscordLogCallback(discord::LogLevel level, const char* message) +{ + SString strMessage("[DISCORD]: %s", message); + if (level >= discord::LogLevel::Warn) + WriteDebugEvent(strMessage); + else + WriteErrorEvent(strMessage); +} + +void CDiscordManager::OnActivityJoin(const char* joinSecret) +{ + if (!joinSecret) + return; + + // Parse it + SString ipport; + SString secret = SStringX(joinSecret).SplitRight("-", &ipport); + ushort port; + + if (!ipport.length() || !secret.length()) + return; + + port = std::stoi(ipport.SplitRight(":", &ipport)); + + // Verify ip & port (ip still could be invalid) + if (!ipport.length() || port < 1) + return; + + // Check if already connected to that server + if (g_pCore->IsConnected()) + { + SString ipaddr; + unsigned int port2; + CVARS_GET("host", ipaddr); + CVARS_GET("port", port2); + + if (ipaddr == ipport && port == port2) + { + CClientBase* mod = CModManager::GetSingleton().GetCurrentMod(); + if (mod) + { + mod->TriggerDiscordJoin(secret); + + // We don't want to proceed + return; + } + } + } + + SString nick; + CVARS_GET("nick", nick); + + // Unload any mod before connecting to a server + CModManager::GetSingleton().Unload(); + + // Only connect if there is no mod loaded + if (!CModManager::GetSingleton().GetCurrentMod()) + { + // Start the connect + if (CCore::GetSingleton().GetConnectManager()->Connect(ipport, port, nick, "", false, secret)) + CCore::GetSingleton().GetConsole()->Printf(_("Discord Join: Connecting to %s:%u..."), *ipport, port); + else + CCore::GetSingleton().GetConsole()->Printf(_("Discord Join: could not connect to %s:%u!"), *ipport, port); + } + else + CCore::GetSingleton().GetConsole()->Print(_("Discord Join: Failed to unload current mod")); +} + +void* CDiscordManager::DiscordThread(void* arg) +{ + CThreadHandle::AllowASyncCancel(); + CDiscordManager* that = reinterpret_cast(arg); + + while (true) + { + if (that->NeedsSuicide()) + break; + + that->DoPulse(); + + Sleep(15); + } + + that->SetDead(); + + return nullptr; +} + +// Called from separate thread so the unnecessary load won't affect the main thread, +// establishing connection with discord is sometimes time-consuming, especially when it's not running +void CDiscordManager::DoPulse() +{ + if (!m_DiscordCore) + { + // Discord is not initialized, maybe it's not installed or not yet running + // So every 15sec we will check if the player got discord running + if (m_TimeForReconnection.Get() >= 15000) + { + Reconnect(); + m_TimeForReconnection.Reset(); + } + + return; + } + + std::lock_guard guardian(m_ThreadSafety); + + if (m_WaitingForServerName) // Query request sent + { + auto info = m_QueryReceiver.GetServerResponse(); + if (info.containingInfo) + { + m_StoredActivity.SetDetails(info.serverName); + UpdateActivity([](EDiscordRes) {}); + m_WaitingForServerName = false; + } + else if (m_QueryReceiver.GetElapsedTimeSinceLastQuery() > 2000) // Resend query request every 2s if no response came + { + SString ipaddr; + unsigned int port; + CVARS_GET("host", ipaddr); + CVARS_GET("port", port); + m_QueryReceiver.RequestQuery(ipaddr, static_cast(port + SERVER_LIST_QUERY_PORT_OFFSET)); + } + } + + discord::Result res = m_DiscordCore->RunCallbacks(); + if (res == discord::Result::NotRunning) // Discord is now closed, needs to be reinstated in the next 15s + { + delete m_DiscordCore; + m_DiscordCore = nullptr; + m_TimeForReconnection.Reset(); + WriteDebugEvent("[DISCORD]: Lost the connection."); + } +} + +void CDiscordManager::UpdateActivity(SDiscordActivity& activity, std::function callback) +{ + std::lock_guard guardian(m_ThreadSafety); + + m_StoredActivity.GetTimestamps().SetStart(activity.m_startTimestamp); + m_StoredActivity.GetTimestamps().SetEnd(activity.m_endTimestamp); + m_StoredActivity.SetName(activity.m_name); + m_StoredActivity.SetState(activity.m_state); + m_StoredActivity.SetDetails(activity.m_details); + m_StoredActivity.SetType(static_cast(activity.m_activityType)); + + UpdateActivity(callback); +} + +void CDiscordManager::UpdateActivity(std::function callback) +{ + if (!m_DiscordCore) + return; + + m_DiscordCore->ActivityManager().UpdateActivity(m_StoredActivity, [=](discord::Result res) { callback(static_cast(res)); }); +} + +void CDiscordManager::SetType(EDiscordActivityT type, std::function callback) +{ + std::lock_guard guardian(m_ThreadSafety); + m_StoredActivity.SetType(static_cast(type)); + UpdateActivity(callback); +} + +void CDiscordManager::SetName(char const* name, std::function callback) +{ + std::lock_guard guardian(m_ThreadSafety); + m_StoredActivity.SetName(name); + UpdateActivity(callback); +} + +void CDiscordManager::SetState(char const* state, std::function callback) +{ + std::lock_guard guardian(m_ThreadSafety); + m_StoredActivity.SetState(state); + UpdateActivity(callback); +} + +void CDiscordManager::SetDetails(char const* details, std::function callback) +{ + std::lock_guard guardian(m_ThreadSafety); + m_StoredActivity.SetDetails(details); + UpdateActivity(callback); +} + +void CDiscordManager::SetStartEndTimestamp(int64 start, int64 end, std::function callback) +{ + std::lock_guard guardian(m_ThreadSafety); + m_StoredActivity.GetTimestamps().SetStart(start); + m_StoredActivity.GetTimestamps().SetEnd(end); + UpdateActivity(callback); +} + +void CDiscordManager::SetJoinParameters(const char* joinSecret, const char* partyId, uint partySize, uint partyMax, std::function callback) +{ + if (!joinSecret || !partyId) + return; + + SString ipaddr; + unsigned int port; + CVARS_GET("host", ipaddr); + CVARS_GET("port", port); + SString strRealJoinSecret("%s:%i-%s", *ipaddr, port, joinSecret); + + std::lock_guard guardian(m_ThreadSafety); + m_StoredActivity.GetSecrets().SetJoin(strRealJoinSecret); + m_StoredActivity.GetParty().SetId(partyId); + m_StoredActivity.GetParty().GetSize().SetCurrentSize(partySize); + m_StoredActivity.GetParty().GetSize().SetMaxSize(partyMax); + UpdateActivity(callback); +} + +void CDiscordManager::SetSpectateSecret(const char* spectateSecret, std::function callback) +{ + if (!spectateSecret) + return; + + std::lock_guard guardian(m_ThreadSafety); + m_StoredActivity.GetSecrets().SetSpectate(spectateSecret); + UpdateActivity(callback); +} + +// Default rich presence settings +void CDiscordManager::RegisterPlay(bool connected) +{ + if (!connected) + { + m_StoredActivity.GetAssets().SetSmallText(""); + m_StoredActivity.GetAssets().SetSmallImage(""); + UpdateActivity([=](EDiscordRes res) {}); + return; + } + + SString ipaddr; + unsigned int port; + CVARS_GET("host", ipaddr); + CVARS_GET("port", port); + + time_t currentTime; + time(¤tTime); + + std::lock_guard guardian(m_ThreadSafety); + m_StoredActivity.SetDetails("Retrieving server name..."); + m_StoredActivity.GetAssets().SetSmallText(SString("Connected to %s:%i", *ipaddr, port)); + + // TODO: Maybe contact with MTA:SA servers and check if this ip is a premium one + // containing a small image to set using this function + m_StoredActivity.GetAssets().SetSmallImage("a-server"); + + m_StoredActivity.GetTimestamps().SetStart(currentTime); + UpdateActivity([=](EDiscordRes res) { + if (res != DiscordRes_Ok) + WriteErrorEvent("[DISCORD]: Unable to register play rich presence."); + else + { + m_QueryReceiver.RequestQuery(ipaddr, static_cast(port + SERVER_LIST_QUERY_PORT_OFFSET)); + + m_WaitingForServerName = true; + } + }); +} + +void CDiscordManager::Restore() +{ + SString ipaddr; + unsigned int port; + CVARS_GET("host", ipaddr); + CVARS_GET("port", port); + m_QueryReceiver.RequestQuery(ipaddr, static_cast(port + SERVER_LIST_QUERY_PORT_OFFSET)); + m_WaitingForServerName = true; +} + +void CDiscordManager::Disconnect() +{ + std::lock_guard guardian(m_ThreadSafety); + SAFE_DELETE(m_DiscordCore); +} + +void CDiscordManager::DisconnectNotification() +{ + std::lock_guard guardian(m_ThreadSafety); + m_WaitingForServerName = false; // No longer wait + m_QueryReceiver.InvalidateSocket(); +} + +SString CDiscordManager::GetJoinSecret() +{ + return CCore::GetSingleton().GetConnectManager()->GetJoinSecret(); +} diff --git a/Client/core/CDiscordManager.h b/Client/core/CDiscordManager.h new file mode 100644 index 00000000000..027845a1076 --- /dev/null +++ b/Client/core/CDiscordManager.h @@ -0,0 +1,66 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: Client/core/CDiscordManager.h + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +#include +#include +#include "SharedUtil.Thread.h" + +class CDiscordManager : public CDiscordManagerInterface +{ +public: + CDiscordManager(); + ~CDiscordManager(); + + static void DiscordLogCallback(discord::LogLevel level, const char* message); + static void OnActivityJoin(const char* joinSecret); + static void* DiscordThread(void* arg); + + void Reconnect(bool bOnInitialization = false); + void DoPulse(); + + // ActivityManager + void UpdateActivity(SDiscordActivity& activity, std::function callback); // Change it all, or ... + void UpdateActivity(std::function callback); // Change it all, or ... + void SetType(EDiscordActivityT type, std::function callback); // Singular modifications + void SetName(char const* name, std::function callback); + void SetState(char const* state, std::function callback); + void SetDetails(char const* details, std::function callback); + void SetStartEndTimestamp(int64 start, int64 end, std::function callback); + void SetJoinParameters(const char* joinSecret, const char* partyId, uint partySize, uint partyMax, std::function callback); + void SetSpectateSecret(const char* spectateSecret, std::function callback); + void RegisterPlay(bool connected); + void Disconnect(); + + discord::Activity GetStoredActivity() const { return m_StoredActivity; } // For retrieving stored information in rich presence + + bool NeedsSuicide() const { return m_Suicide; } + void SetDead() { m_Suicide = false; } + void DisconnectNotification(); + + SString GetJoinSecret(); + +private: + void Restore(); + + discord::Core* m_DiscordCore; + discord::Activity m_StoredActivity; + + bool m_WaitingForServerName; + + volatile bool m_Suicide; // Thread kill command + + std::mutex m_ThreadSafety; + SharedUtil::CThreadHandle* m_Thread; + + CElapsedTime m_TimeForReconnection; + CQueryReceiver m_QueryReceiver; +}; diff --git a/Client/core/CQueryReceiver.cpp b/Client/core/CQueryReceiver.cpp new file mode 100644 index 00000000000..5267be47500 --- /dev/null +++ b/Client/core/CQueryReceiver.cpp @@ -0,0 +1,236 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: Client/core/CQueryReceiver.cpp + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" + +CQueryReceiver::CQueryReceiver() +{ + m_Socket = INVALID_SOCKET; +} + +CQueryReceiver::~CQueryReceiver() +{ + InvalidateSocket(); +} + +void CQueryReceiver::RequestQuery(in_addr address, ushort port) +{ + if (m_Socket == INVALID_SOCKET) // Create the socket + { + m_Socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + u_long flag = 1; + ioctlsocket(m_Socket, FIONBIO, &flag); // Nonblocking I/O + } + + sockaddr_in addr; + memset(&addr, NULL, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr = address; + addr.sin_port = htons(port); + + // Trailing data to work around 1 byte UDP packet filtering + int iSendByte = g_pCore->GetNetwork()->SendTo(m_Socket, "r mtasa", 1, 0, (sockaddr*)&addr, sizeof(addr)); + m_ElapsedTime.Reset(); +} + +void CQueryReceiver::RequestQuery(const SString& address, ushort port) +{ + in_addr addr; + addr.S_un.S_addr = inet_addr(*address); + if (addr.S_un.S_addr == INADDR_NONE) + { + hostent* pHostent = gethostbyname(*address); + if (!pHostent) + return; + DWORD* pIP = (DWORD*)pHostent->h_addr_list[0]; + if (!pIP) + return; + addr.S_un.S_addr = *pIP; + } + RequestQuery(addr, port); +} + +void CQueryReceiver::InvalidateSocket() +{ + if (m_Socket != INVALID_SOCKET) + { + closesocket(m_Socket); + m_Socket = INVALID_SOCKET; + } +} + +bool CQueryReceiver::ReadString(std::string& strRead, const char* szBuffer, int& i, int nLength) +{ + if (i <= nLength) + { + unsigned char len = szBuffer[i]; + if (i + len <= nLength && len > 0) + { + const char* ptr = &szBuffer[i + 1]; + i += len; + strRead = std::string(ptr, len - 1); + return true; + } + i++; + } + return false; +} + +SQueryInfo CQueryReceiver::GetServerResponse(uint restrictions) +{ + SQueryInfo info; + + if (m_Socket == INVALID_SOCKET) + return info; // Query not sent + + char szBuffer[SERVER_LIST_QUERY_BUFFER] = {0}; + + // Poll the socket + sockaddr_in clntAddr; + int addrLen = sizeof(clntAddr); + int len = recvfrom(m_Socket, szBuffer, SERVER_LIST_QUERY_BUFFER, MSG_PARTIAL, (sockaddr*)&clntAddr, &addrLen); + + if (len >= 0) + { + // Parse data + + // Check length + if (len < 15) + return info; + + // Check header + if (strncmp(szBuffer, "EYE2", 4) != 0) + return info; + + // Calculate the ping/latency + info.pingTime = m_ElapsedTime.Get(); + + // Parse relevant data + SString strTemp; + SString strMapTemp; + int i = 4; + + // Game + if (!ReadString(strTemp, szBuffer, i, len)) + return info; + if ((restrictions & RESTRICTION_GAME_NAME) == false) + info.gameName = strTemp; + + // Port (Ignore result as we must already have the correct value) + if (!ReadString(strTemp, szBuffer, i, len)) + return info; + + // Server name + if (!ReadString(strTemp, szBuffer, i, len)) + return info; + if ((restrictions & RESTRICTION_SERVER_NAME) == false) + info.serverName = strTemp; + + // Game type + if (!ReadString(strTemp, szBuffer, i, len)) + return info; + if ((restrictions & RESTRICTION_GAME_MODE) == false) + info.gameType = strTemp; + + // Map name + if (!ReadString(strMapTemp, szBuffer, i, len)) + return info; + if ((restrictions & RESTRICTION_MAP_NAME) == false) + info.mapName = strMapTemp; + + // Version + if (!ReadString(strTemp, szBuffer, i, len)) + return info; + if ((restrictions & RESTRICTION_SERVER_VERSION) == false) + info.versionText = strTemp; + + // Got space for password, serial verification, player count, players max? + if (i + 4 > len) + return info; + + if ((restrictions & RESTRICTION_PASSWORDED_FLAG) == false) + info.isPassworded = (szBuffer[i++] == 1); + + if ((restrictions & RESTRICTION_SERIALS_FLAG) == false) + info.serials = (szBuffer[i++] == 1); + + if ((restrictions & RESTRICTION_PLAYER_COUNT) == false) + info.players = (unsigned char)szBuffer[i++]; + + if ((restrictions & RESTRICTION_MAX_PLAYER_COUNT) == false) + info.playerSlot = (unsigned char)szBuffer[i++]; + + // Recover large player count if present + const SString strPlayerCount = strMapTemp.Right(strMapTemp.length() - strlen(strMapTemp) - 1); + if (!strPlayerCount.empty()) + { + SString strJoinedPlayers, strMaxPlayers; + if (strPlayerCount.Split("/", &strJoinedPlayers, &strMaxPlayers)) + { + if ((restrictions & RESTRICTION_PLAYER_COUNT) == false) + info.players = atoi(strJoinedPlayers); + if ((restrictions & RESTRICTION_MAX_PLAYER_COUNT) == false) + info.playerSlot = atoi(strMaxPlayers); + } + } + + // Recover server build type if present + const SString strBuildType = strPlayerCount.Right(strPlayerCount.length() - strlen(strPlayerCount) - 1); + if (!strBuildType.empty()) + info.buildType = atoi(strBuildType); + else + info.buildType = 1; + + // Recover server build number if present + const SString strBuildNumber = strBuildType.Right(strBuildType.length() - strlen(strBuildType) - 1); + if (!strBuildNumber.empty()) + info.buildNum = atoi(strBuildNumber); + else + info.buildNum = 0; + + // Recover server ping status if present + const SString strPingStatus = strBuildNumber.Right(strBuildNumber.length() - strlen(strBuildNumber) - 1); + CCore::GetSingleton().GetNetwork()->UpdatePingStatus(*strPingStatus, info.players); + + // Recover server http port if present + const SString strNetRoute = strPingStatus.Right(strPingStatus.length() - strlen(strPingStatus) - 1); + const SString strUpTime = strNetRoute.Right(strNetRoute.length() - strlen(strNetRoute) - 1); + const SString strHttpPort = strUpTime.Right(strUpTime.length() - strlen(strUpTime) - 1); + if (!strHttpPort.empty()) + info.httpPort = atoi(strHttpPort); + + // Get player nicks + while (i < len) + { + std::string strPlayer; + try + { + if (ReadString(strPlayer, szBuffer, i, len)) + { + // Remove color code, unless that results in an empty string + SString strResult = RemoveColorCodes(strPlayer.c_str()); + if (strResult.length() == 0) + strResult = strPlayer; + if ((restrictions & RESTRICTION_PLAYER_LIST) == false) + info.playersPool.push_back(strResult); + } + } + catch (...) + { + // yeah that's what I thought. + return info; + } + } + InvalidateSocket(); + info.containingInfo = true; + } + + return info; +} diff --git a/Client/core/CQueryReceiver.h b/Client/core/CQueryReceiver.h new file mode 100644 index 00000000000..ef42755c938 --- /dev/null +++ b/Client/core/CQueryReceiver.h @@ -0,0 +1,71 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: Client/core/CQueryReceiver.h + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +struct SQueryInfo +{ + SQueryInfo() + { + containingInfo = false; + port = 0; + isPassworded = false; + serials = false; + players = 0; + playerSlot = 0; + buildType = 1; + buildNum = 0; + httpPort = 0; + pingTime = 0; + } + + bool containingInfo; + SString gameName; + ushort port; + SString serverName; + SString gameType; + SString mapName; + SString versionText; + bool isPassworded; + bool serials; + ushort players; + ushort playerSlot; + int buildType; + int buildNum; + SString netRoute; + SString upTime; + ushort httpPort; + ushort pingTime; + + std::vector playersPool; +}; + +class CQueryReceiver +{ +public: + CQueryReceiver(); + ~CQueryReceiver(); + + void RequestQuery(in_addr address, ushort port); + void RequestQuery(const SString& address, ushort port); + void InvalidateSocket(); + + SQueryInfo GetServerResponse(uint restrictions = 0); + + uint GetElapsedTimeSinceLastQuery() { return static_cast(m_ElapsedTime.Get()); }; + + bool IsSocketValid() const { return (m_Socket != INVALID_SOCKET); } + +private: + bool ReadString(std::string& strRead, const char* szBuffer, int& i, int nLength); + + SOCKET m_Socket; + CElapsedTime m_ElapsedTime; +}; diff --git a/Client/core/CSettings.cpp b/Client/core/CSettings.cpp index 0f0805cb8ce..46c25b71745 100644 --- a/Client/core/CSettings.cpp +++ b/Client/core/CSettings.cpp @@ -381,6 +381,11 @@ void CSettings::CreateGUI() m_pCheckBoxAllowExternalSounds->GetPosition(vecTemp, false); m_pCheckBoxAllowExternalSounds->AutoSize(NULL, 20.0f); + m_pDiscordCheck = reinterpret_cast(pManager->CreateCheckBox(pTabMultiplayer, _("Enable Discord Rich Presence"), true)); + m_pDiscordCheck->SetPosition(CVector2D(vecTemp.fX, vecTemp.fY + 20.0f)); + m_pDiscordCheck->GetPosition(vecTemp, false); + m_pDiscordCheck->AutoSize(NULL, 20.0f); + m_pCheckBoxCustomizedSAFiles = reinterpret_cast(pManager->CreateCheckBox(pTabMultiplayer, _("Use customized GTA:SA files"), true)); m_pCheckBoxCustomizedSAFiles->SetPosition(CVector2D(vecTemp.fX, vecTemp.fY + 20.0f)); m_pCheckBoxCustomizedSAFiles->GetPosition(vecTemp, false); @@ -1512,6 +1517,11 @@ void CSettings::UpdateVideoTab() CVARS_GET("allow_screen_upload", bAllowScreenUploadEnabled); m_pCheckBoxAllowScreenUpload->SetSelected(bAllowScreenUploadEnabled); + // Enable Discord Rich Presence + bool discordRichPresence; + CVARS_GET("discord_rich_presence", discordRichPresence); + m_pDiscordCheck->SetSelected(discordRichPresence); + // Allow external sounds bool bAllowExternalSoundsEnabled; CVARS_GET("allow_external_sounds", bAllowExternalSoundsEnabled); @@ -3344,6 +3354,15 @@ void CSettings::SaveData() bool bAllowScreenUploadEnabled = m_pCheckBoxAllowScreenUpload->GetSelected(); CVARS_SET("allow_screen_upload", bAllowScreenUploadEnabled); + // Discord Rich Presence + bool discordRichPresence; + CVARS_GET("discord_rich_presence", discordRichPresence); + if (discordRichPresence != m_pDiscordCheck->GetSelected()) + { + CVARS_SET("discord_rich_presence", m_pDiscordCheck->GetSelected()); + g_pCore->GetDiscordManager()->Disconnect(); + } + // Allow external sounds bool bAllowExternalSoundsEnabled = m_pCheckBoxAllowExternalSounds->GetSelected(); CVARS_SET("allow_external_sounds", bAllowExternalSoundsEnabled); diff --git a/Client/core/CSettings.h b/Client/core/CSettings.h index 1b98ab3b9de..30e6089322a 100644 --- a/Client/core/CSettings.h +++ b/Client/core/CSettings.h @@ -152,6 +152,7 @@ class CSettings CGUICheckBox* m_pCheckBoxDeviceSelectionDialog; CGUICheckBox* m_pCheckBoxShowUnsafeResolutions; CGUICheckBox* m_pCheckBoxAllowScreenUpload; + CGUICheckBox* m_pDiscordCheck = nullptr; CGUICheckBox* m_pCheckBoxAllowExternalSounds; CGUICheckBox* m_pCheckBoxCustomizedSAFiles; CGUICheckBox* m_pCheckBoxGrass; diff --git a/Client/core/DXHook/CDirect3DHook9.cpp b/Client/core/DXHook/CDirect3DHook9.cpp index 4242507bca9..e2988168864 100644 --- a/Client/core/DXHook/CDirect3DHook9.cpp +++ b/Client/core/DXHook/CDirect3DHook9.cpp @@ -86,6 +86,7 @@ IDirect3D9* CDirect3DHook9::API_Direct3DCreate9(UINT SDKVersion) CCore::GetSingleton().CreateMultiplayer(); CCore::GetSingleton().CreateXML(); CCore::GetSingleton().CreateGUI(); + CCore::GetSingleton().ResetDiscordRichPresence(); } // D3DX_SDK_VERSION checks diff --git a/Client/core/ServerBrowser/CServerInfo.cpp b/Client/core/ServerBrowser/CServerInfo.cpp index 77ebfbb0732..e2eea1e307b 100644 --- a/Client/core/ServerBrowser/CServerInfo.cpp +++ b/Client/core/ServerBrowser/CServerInfo.cpp @@ -490,4 +490,4 @@ void CServerInfo::ResetServerGUI(CServerListItem* pServer) m_pServerPlayerList->AddRow(true); m_pServerPlayerList->SetItemText(i, m_hPlayerName, strPlayerName.c_str(), false, false, true); } -} \ No newline at end of file +} diff --git a/Client/core/ServerBrowser/CServerInfo.h b/Client/core/ServerBrowser/CServerInfo.h index 08168fd99c5..69234929366 100644 --- a/Client/core/ServerBrowser/CServerInfo.h +++ b/Client/core/ServerBrowser/CServerInfo.h @@ -14,11 +14,11 @@ class CServerInfo; #pragma once // Update interval for the full server (in milliseconds) -#define SERVER_UPDATE_INTERVAL 2500 +#define SERVER_UPDATE_INTERVAL 2500 // Dimensions for our window -#define INFO_WINDOW_DEFAULTWIDTH 370.0f -#define INFO_WINDOW_DEFAULTHEIGHT 400.0f +#define INFO_WINDOW_DEFAULTWIDTH 370.0f +#define INFO_WINDOW_DEFAULTHEIGHT 400.0f #define INFO_WINDOW_HSPACING 20 #define INFO_LABEL_VSPACING 0 diff --git a/Client/core/ServerBrowser/CServerList.cpp b/Client/core/ServerBrowser/CServerList.cpp index a9529b2af5f..6064a5baa2f 100644 --- a/Client/core/ServerBrowser/CServerList.cpp +++ b/Client/core/ServerBrowser/CServerList.cpp @@ -371,29 +371,20 @@ std::string CServerListItem::Pulse(bool bCanSendQuery, bool bRemoveNonResponding } else { - // Poll the socket - sockaddr_in clntAddr; - int addrLen = sizeof(clntAddr); - int len = recvfrom(m_Socket, szBuffer, SERVER_LIST_QUERY_BUFFER, MSG_PARTIAL, (sockaddr*)&clntAddr, &addrLen); - int error = WSAGetLastError(); - if (len >= 0) + // Poll the socket and parse the packet + if (ParseQuery()) { - // Parse data - if (ParseQuery(szBuffer, len)) - { - CloseSocket(); - bMaybeOffline = false; - SetDataQuality(SERVER_INFO_QUERY); - uiCacheNoReplyCount = 0; - uiRevision++; // To flag browser gui update - GetServerCache()->SetServerCachedInfo(this); // Save parsed info in the cache - return "ParsedQuery"; - } + bMaybeOffline = false; + SetDataQuality(SERVER_INFO_QUERY); + uiCacheNoReplyCount = 0; + uiRevision++; // To flag browser gui update + GetServerCache()->SetServerCachedInfo(this); // Save parsed info in the cache + return "ParsedQuery"; } ///////////////////////////////////////////////////////////////////////////////////////// // TCP send to servers that the master server can see, but this client can't - uint uiQueryAge = (uint)m_ElapsedTime.Get(); + uint uiQueryAge = queryReceiver.GetElapsedTimeSinceLastQuery(); if (uiQueryAge > 2000) { if (!m_bDoneTcpSend && GetMaxRetries() > 0) @@ -443,7 +434,7 @@ std::string CServerListItem::Pulse(bool bCanSendQuery, bool bRemoveNonResponding else { // Give up - CloseSocket(); + queryReceiver.InvalidateSocket(); uiRevision++; // To flag browser gui update if (bRemoveNonResponding) @@ -475,184 +466,33 @@ unsigned short CServerListItem::GetQueryPort() void CServerListItem::Query() { // Performs a query according to ASE protocol - sockaddr_in addr; - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_addr = Address; - addr.sin_port = htons(GetQueryPort()); - - // Initialize socket on demand - if (m_Socket == INVALID_SOCKET) - { - m_Socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); - u_long flag = 1; - ioctlsocket(m_Socket, FIONBIO, &flag); - } - - // Trailing data to work around 1 byte UDP packet filtering - g_pCore->GetNetwork()->SendTo(m_Socket, "r mtasa", 1, 0, (sockaddr*)&addr, sizeof(addr)); - m_ElapsedTime.Reset(); -} -bool ReadString(std::string& strRead, const char* szBuffer, unsigned int& i, unsigned int nLength) -{ - if (i <= nLength) - { - unsigned char len = szBuffer[i]; - if (i + len <= nLength && len > 0) - { - const char* ptr = &szBuffer[i + 1]; - i += len; - strRead = std::string(ptr, len - 1); - return true; - } - i++; - } - return false; + queryReceiver.RequestQuery(Address, GetQueryPort()); } -bool CServerListItem::ParseQuery(const char* szBuffer, unsigned int nLength) +bool CServerListItem::ParseQuery() { - // Check length - if (nLength < 15) - return false; - - // Check header - if (strncmp(szBuffer, "EYE2", 4) != 0) + SQueryInfo info = queryReceiver.GetServerResponse(uiMasterServerSaysRestrictions); + if (!info.containingInfo) return false; // Get IP as string - const char* szIP = inet_ntoa(Address); - - // Calculate the ping/latency - nPing = m_ElapsedTime.Get(); - - // Parse relevant data - SString strTemp; - SString strMapTemp; - unsigned int i = 4; - - // IP - strHost = szIP; - - // Game - if (!ReadString(strTemp, szBuffer, i, nLength)) - return false; - if ((uiMasterServerSaysRestrictions & RESTRICTION_GAME_NAME) == false) - strGameName = strTemp; - - // Port (Ignore result as we must already have the correct value) - if (!ReadString(strTemp, szBuffer, i, nLength)) - return false; - - // Server name - if (!ReadString(strTemp, szBuffer, i, nLength)) - return false; - if ((uiMasterServerSaysRestrictions & RESTRICTION_SERVER_NAME) == false) - strName = strTemp; - - // Game type - if (!ReadString(strTemp, szBuffer, i, nLength)) - return false; - if ((uiMasterServerSaysRestrictions & RESTRICTION_GAME_MODE) == false) - strGameMode = strTemp; - - // Map name - if (!ReadString(strMapTemp, szBuffer, i, nLength)) - return false; - if ((uiMasterServerSaysRestrictions & RESTRICTION_MAP_NAME) == false) - strMap = strMapTemp; - - // Version - if (!ReadString(strTemp, szBuffer, i, nLength)) - return false; - if ((uiMasterServerSaysRestrictions & RESTRICTION_SERVER_VERSION) == false) - strVersion = strTemp; - - // Got space for password, serial verification, player count, players max? - if (i + 4 > nLength) - { - return false; - } - - if ((uiMasterServerSaysRestrictions & RESTRICTION_PASSWORDED_FLAG) == false) - bPassworded = (szBuffer[i] == 1); - i++; - - if ((uiMasterServerSaysRestrictions & RESTRICTION_SERIALS_FLAG) == false) - bSerials = (szBuffer[i] == 1); - i++; - - if ((uiMasterServerSaysRestrictions & RESTRICTION_PLAYER_COUNT) == false) - nPlayers = (unsigned char)szBuffer[i]; - i++; - - if ((uiMasterServerSaysRestrictions & RESTRICTION_MAX_PLAYER_COUNT) == false) - nMaxPlayers = (unsigned char)szBuffer[i]; - i++; - - // Recover large player count if present - const SString strPlayerCount = strMapTemp.Right(strMapTemp.length() - strlen(strMapTemp) - 1); - if (!strPlayerCount.empty()) - { - SString strJoinedPlayers, strMaxPlayers; - if (strPlayerCount.Split("/", &strJoinedPlayers, &strMaxPlayers)) - { - if ((uiMasterServerSaysRestrictions & RESTRICTION_PLAYER_COUNT) == false) - nPlayers = atoi(strJoinedPlayers); - if ((uiMasterServerSaysRestrictions & RESTRICTION_MAX_PLAYER_COUNT) == false) - nMaxPlayers = atoi(strMaxPlayers); - } - } - - // Recover server build type if present - const SString strBuildType = strPlayerCount.Right(strPlayerCount.length() - strlen(strPlayerCount) - 1); - if (!strBuildType.empty()) - m_iBuildType = atoi(strBuildType); - else - m_iBuildType = 1; - - // Recover server build number if present - const SString strBuildNumber = strBuildType.Right(strBuildType.length() - strlen(strBuildType) - 1); - if (!strBuildNumber.empty()) - m_iBuildNumber = atoi(strBuildNumber); - else - m_iBuildNumber = 0; - - // Recover server ping status if present - const SString strPingStatus = strBuildNumber.Right(strBuildNumber.length() - strlen(strBuildNumber) - 1); - CCore::GetSingleton().GetNetwork()->UpdatePingStatus(*strPingStatus, nPlayers); - - // Recover server http port if present - const SString strNetRoute = strPingStatus.Right(strPingStatus.length() - strlen(strPingStatus) - 1); - const SString strUpTime = strNetRoute.Right(strNetRoute.length() - strlen(strNetRoute) - 1); - const SString strHttpPort = strUpTime.Right(strUpTime.length() - strlen(strUpTime) - 1); - if (!strHttpPort.empty()) - m_usHttpPort = atoi(strHttpPort); - - // Get player nicks - vecPlayers.clear(); - while (i < nLength) - { - std::string strPlayer; - try - { - if (ReadString(strPlayer, szBuffer, i, nLength)) - { - // Remove color code, unless that results in an empty string - SString strResult = RemoveColorCodes(strPlayer.c_str()); - if (strResult.length() == 0) - strResult = strPlayer; - if ((uiMasterServerSaysRestrictions & RESTRICTION_PLAYER_LIST) == false) - vecPlayers.push_back(strResult); - } - } - catch (...) - { - // yeah that's what I thought. - return false; - } - } + strHost = inet_ntoa(Address); + + nPing = info.pingTime; + strGameName = info.gameName; + strName = info.serverName; + strGameMode = info.gameType; + strMap = info.mapName; + strVersion = info.versionText; + bPassworded = info.isPassworded; + bSerials = info.serials; + nPlayers = info.players; + nMaxPlayers = info.playerSlot; + m_iBuildType = info.buildType; + m_iBuildNumber = info.buildNum; + m_usHttpPort = info.httpPort; + vecPlayers = info.playersPool; bScanned = true; @@ -823,15 +663,13 @@ CServerListItem::~CServerListItem() m_pItemList->RemoveItem(this); } MapRemove(ms_ValidServerListItemMap, this); - CloseSocket(); } void CServerListItem::ResetForRefresh() { - CloseSocket(); + queryReceiver.InvalidateSocket(); bScanned = false; bSkipped = false; - m_ElapsedTime.Reset(); uiQueryRetryCount = 0; if (m_iDataQuality >= SERVER_INFO_QUERY) diff --git a/Client/core/ServerBrowser/CServerList.h b/Client/core/ServerBrowser/CServerList.h index 2bbae142dfe..b76fc15285d 100644 --- a/Client/core/ServerBrowser/CServerList.h +++ b/Client/core/ServerBrowser/CServerList.h @@ -145,8 +145,6 @@ class CServerListItem nMaxPlayers = 0; nPing = 9999; uiCacheNoReplyCount = 0; - m_ElapsedTime.SetMaxIncrement(500); - m_ElapsedTime.Reset(); uiQueryRetryCount = 0; uiRevision = 1; bMaybeOffline = false; @@ -161,7 +159,6 @@ class CServerListItem strEndpointSortKey = SString("%02x%02x%02x%02x-%04x", Address.S_un.S_un_b.s_b1, Address.S_un.S_un_b.s_b2, Address.S_un.S_un_b.s_b3, Address.S_un.S_un_b.s_b4, usGamePort); - m_Socket = INVALID_SOCKET; strGameMode = ""; strMap = ""; vecPlayers.clear(); @@ -172,17 +169,6 @@ class CServerListItem m_iBuildNumber = 0; } - void CloseSocket() - { - if (m_Socket != INVALID_SOCKET) - { - closesocket(m_Socket); - m_Socket = INVALID_SOCKET; - } - } - - bool ParseQuery(const char* szBuffer, unsigned int nLength); - void Query(); std::string Pulse(bool bCanSendQuery, bool bRemoveNonResponding = false); void ResetForRefresh(); unsigned short GetQueryPort(); @@ -228,9 +214,15 @@ class CServerListItem uint uiTieBreakPosition; SString strTieBreakSortKey; + CQueryReceiver queryReceiver; + std::vector vecPlayers; - bool WaitingToSendQuery() const { return !bScanned && !bSkipped && m_Socket == INVALID_SOCKET; } + void Query(); + + bool ParseQuery(); + + bool WaitingToSendQuery() const { return !bScanned && !bSkipped && !queryReceiver.IsSocketValid(); } const SString& GetEndpoint() const { return strEndpoint; } @@ -306,8 +298,6 @@ class CServerListItem CServerListItemList* m_pItemList; protected: - int m_Socket; - CElapsedTime m_ElapsedTime; int m_iDataQuality; static std::set ms_ValidServerListItemMap; diff --git a/Client/core/StdInc.h b/Client/core/StdInc.h index 1f49a82e159..e6de6990d8e 100644 --- a/Client/core/StdInc.h +++ b/Client/core/StdInc.h @@ -49,6 +49,7 @@ #include // Core-level includes +#include "CQueryReceiver.h" #include "CrashHandler.h" #include "CCore.h" #include "CDebugView.h" diff --git a/Client/core/premake5.lua b/Client/core/premake5.lua index 7ad6d8c4380..ac63ca1b47e 100644 --- a/Client/core/premake5.lua +++ b/Client/core/premake5.lua @@ -18,11 +18,13 @@ project "Client Core" "../../vendor/jpeg-9b", "../../vendor/pthreads/include", "../../vendor/sparsehash/src/", - "../../vendor/hwbrk" + "../../vendor/hwbrk", + "../../vendor/discordgsdk/cpp" } libdirs { "../../vendor/detours/lib", + "../../vendor/discordgsdk/lib/x86", } @@ -49,7 +51,7 @@ project "Client Core" links { "ws2_32", "d3dx9", "Userenv", "DbgHelp", "xinput", "Imagehlp", "dxguid", "dinput8", "strmiids", "odbc32", "odbccp32", "shlwapi", "winmm", "gdi32", "Imm32", "Psapi", - "pthread", "libpng", "jpeg", "zlib", "tinygettext", "detours" + "pthread", "libpng", "jpeg", "zlib", "tinygettext", "detours", "discordgsdk", "discord_game_sdk.dll.lib" } defines { diff --git a/Client/mods/deathmatch/CClient.cpp b/Client/mods/deathmatch/CClient.cpp index e1811c09ec3..18de0c153b0 100644 --- a/Client/mods/deathmatch/CClient.cpp +++ b/Client/mods/deathmatch/CClient.cpp @@ -95,20 +95,20 @@ int CClient::ClientInitialize(const char* szArguments, CCoreInterface* pCore) g_pCore->GetCommands()->Add("showsync", "show sync data", COMMAND_ShowSyncData); // g_pCore->GetCommands ()->Add ( "dumpall", "dump internals (comment)", COMMAND_DumpPlayers ); #endif - #ifdef MTA_DEBUG +#ifdef MTA_DEBUG g_pCore->GetCommands()->Add("foo", "debug command for devs", COMMAND_Foo); - #endif +#endif - // Debug commands - #if defined (MTA_DEBUG) || defined(MTA_BETA) +// Debug commands +#if defined(MTA_DEBUG) || defined(MTA_BETA) g_pCore->GetCommands()->Add("showsyncing", "shows syncing information", COMMAND_ShowSyncing); - #endif +#endif #ifdef MTA_WEPSYNCDBG pCore->GetCommands()->Add("showwepdata", "shows the given player weapon data (nick)", COMMAND_ShowWepdata); #endif - #if defined (MTA_DEBUG) || defined (MTA_DEBUG_COMMANDS) +#if defined(MTA_DEBUG) || defined(MTA_DEBUG_COMMANDS) pCore->GetCommands()->Add("showwepdata", "shows the given player weapon data (nick)", COMMAND_ShowWepdata); pCore->GetCommands()->Add("showtasks", "shows the local player tasks (nick)", COMMAND_ShowTasks); pCore->GetCommands()->Add("showplayer", "shows extended player information (nick)", COMMAND_ShowPlayer); @@ -127,7 +127,7 @@ int CClient::ClientInitialize(const char* szArguments, CCoreInterface* pCore) pCore->GetCommands()->Add("debug2", "debug function 2", COMMAND_Debug2); pCore->GetCommands()->Add("debug3", "debug function 3", COMMAND_Debug3); pCore->GetCommands()->Add("debug4", "debug function 4", COMMAND_Debug4); - #endif +#endif // Got any arguments? if (szArguments && szArguments[0] != '\0') @@ -176,7 +176,11 @@ int CClient::ClientInitialize(const char* szArguments, CCoreInterface* pCore) // g_pClientGame->EnablePacketRecorder ( "log.rec" ); // g_pCore->GetConsole ()->Echo ( "Packetlogger is logging to log.rec" ); - g_pClientGame->StartGame(arguments.nickname.c_str(), arguments.password.c_str()); + SString secret = g_pCore->GetDiscordManager()->GetJoinSecret(); + + // Start the game + g_pClientGame->StartGame(arguments.nickname.c_str(), arguments.password.c_str(), CClientGame::SERVER_TYPE_NORMAL, + *secret); } else { @@ -262,8 +266,8 @@ void CClient::RestreamModel(unsigned short usModel) bool CClient::HandleException(CExceptionInformation* pExceptionInformation) { - #ifndef MTA_DEBUG - #ifndef MTA_ALLOW_DEBUG +#ifndef MTA_DEBUG +#ifndef MTA_ALLOW_DEBUG // Let the clientgame write its dump, then make the core terminate our process if (g_pClientGame && pExceptionInformation) { @@ -271,14 +275,14 @@ bool CClient::HandleException(CExceptionInformation* pExceptionInformation) } return false; - #else +#else // We want to be able to debug using the debugger in debug-mode return true; - #endif - #else +#endif +#else // We want to be able to debug using the debugger in debug-mode return true; - #endif +#endif } void CClient::GetPlayerNames(std::vector& vPlayerNames) @@ -296,6 +300,11 @@ void CClient::GetPlayerNames(std::vector& vPlayerNames) } } +void CClient::TriggerDiscordJoin(SString strSecret) +{ + g_pClientGame->TriggerDiscordJoin(strSecret); +} + CClient::InitializeArguments CClient::ExtractInitializeArguments(const char* arguments) { // Format: "nickname [password]" diff --git a/Client/mods/deathmatch/CClient.h b/Client/mods/deathmatch/CClient.h index 0bc9940b3f5..7d0ede3fcdc 100644 --- a/Client/mods/deathmatch/CClient.h +++ b/Client/mods/deathmatch/CClient.h @@ -33,6 +33,8 @@ class CClient : public CClientBase bool HandleException(CExceptionInformation* pExceptionInformation); void GetPlayerNames(std::vector& vPlayerNames); + void TriggerDiscordJoin(SString strSecret); + private: struct InitializeArguments { diff --git a/Client/mods/deathmatch/StdInc.h b/Client/mods/deathmatch/StdInc.h index 331d05ab67b..ba789f16751 100644 --- a/Client/mods/deathmatch/StdInc.h +++ b/Client/mods/deathmatch/StdInc.h @@ -26,6 +26,7 @@ // SDK includes #include #include +#include #include #include #include diff --git a/Client/mods/deathmatch/logic/CClientGame.cpp b/Client/mods/deathmatch/logic/CClientGame.cpp index e7f71dca6ac..67b53dbbb71 100644 --- a/Client/mods/deathmatch/logic/CClientGame.cpp +++ b/Client/mods/deathmatch/logic/CClientGame.cpp @@ -15,6 +15,7 @@ #include "game/CAnimBlendAssociation.h" #include "game/CAnimBlendHierarchy.h" #include +#include "CServerInfo.h" SString StringZeroPadout(const SString& strInput, uint uiPadoutSize) { @@ -51,7 +52,7 @@ CVector g_vecBulletFireEndPosition; #define DOUBLECLICK_TIMEOUT 330 #define DOUBLECLICK_MOVE_THRESHOLD 10.0f -CClientGame::CClientGame(bool bLocalPlay) +CClientGame::CClientGame(bool bLocalPlay) : m_ServerInfo(new CServerInfo()) { // Init the global var with ourself g_pClientGame = this; @@ -558,7 +559,7 @@ void CClientGame::StartPlayback() } } -bool CClientGame::StartGame(const char* szNick, const char* szPassword, eServerType Type) +bool CClientGame::StartGame(const char* szNick, const char* szPassword, eServerType Type, const char* szSecret) { m_ServerType = Type; // int dbg = _CrtSetDbgFlag ( _CRTDBG_REPORT_FLAG ); @@ -630,6 +631,12 @@ bool CClientGame::StartGame(const char* szNick, const char* szPassword, eServerT std::string strUser; pBitStream->Write(strUser.c_str(), MAX_SERIAL_LENGTH); + if (g_pNet->GetServerBitStreamVersion() >= 0x06D) + { + SString joinSecret = SStringX(szSecret); + pBitStream->WriteString(joinSecret); + } + // Send the packet as joindata g_pNet->SendPacket(PACKET_ID_PLAYER_JOINDATA, pBitStream, PACKET_PRIORITY_HIGH, PACKET_RELIABILITY_RELIABLE_ORDERED); g_pNet->DeallocateNetBitStream(pBitStream); @@ -6945,6 +6952,17 @@ void CClientGame::RestreamModel(unsigned short usModel) m_pManager->GetVehicleManager()->RestreamVehicleUpgrades(usModel); } +void CClientGame::TriggerDiscordJoin(SString strSecret) +{ + if (g_pNet->GetServerBitStreamVersion() < 0x06D) + return; + + NetBitStreamInterface* pBitStream = g_pNet->AllocateNetBitStream(); + pBitStream->WriteString(strSecret); + g_pNet->SendPacket(PACKET_ID_DISCORD_JOIN, pBitStream, PACKET_PRIORITY_LOW, PACKET_RELIABILITY_RELIABLE_ORDERED, PACKET_ORDERING_DEFAULT); + g_pNet->DeallocateNetBitStream(pBitStream); +} + void CClientGame::InsertIFPPointerToMap(const unsigned int u32BlockNameHash, const std::shared_ptr& pIFP) { m_mapOfIfpPointers[u32BlockNameHash] = pIFP; @@ -7082,3 +7100,17 @@ void CClientGame::VehicleWeaponHitHandler(SVehicleWeaponHitEvent& event) arguments.PushNumber(event.iColSurface); pVehicle->CallEvent("onClientVehicleWeaponHit", arguments, false); } + +void CClientGame::UpdateDiscordState() +{ + // Set discord state to players[/slot] count + uint playerCount = g_pClientGame->GetPlayerManager()->Count(); + uint playerSlot = g_pClientGame->GetServerInfo()->GetMaxPlayers(); + SString state(std::to_string(playerCount)); + + if (g_pCore->GetNetwork()->GetServerBitStreamVersion() >= 0x06D) + state += "/" + std::to_string(playerSlot); + + state += (playerCount == 1 && (!playerSlot || playerSlot == 1) ? " Player" : " Players"); + g_pCore->GetDiscordManager()->SetState(state, [](EDiscordRes) {}); +} diff --git a/Client/mods/deathmatch/logic/CClientGame.h b/Client/mods/deathmatch/logic/CClientGame.h index dd9b7f01ef4..d17900d4c54 100644 --- a/Client/mods/deathmatch/logic/CClientGame.h +++ b/Client/mods/deathmatch/logic/CClientGame.h @@ -52,6 +52,7 @@ class CClientModelCacheManager; class CDebugHookManager; class CResourceFileDownloadManager; +class CServerInfo; struct SVehExtrapolateSettings { @@ -226,7 +227,7 @@ class CClientGame CClientGame(bool bLocalPlay = false); ~CClientGame(); - bool StartGame(const char* szNick, const char* szPassword, eServerType Type = SERVER_TYPE_NORMAL); + bool StartGame(const char* szNick, const char* szPassword, eServerType Type = SERVER_TYPE_NORMAL, const char* szSecret = nullptr); bool StartLocalGame(eServerType Type, const char* szPassword = NULL); void SetupLocalGame(eServerType Type); // bool StartGame ( void ); @@ -276,6 +277,7 @@ class CClientGame CSyncDebug* GetSyncDebug() { return m_pSyncDebug; }; CRPCFunctions* GetRPCFunctions() { return m_pRPCFunctions; } CSingularFileDownloadManager* GetSingularFileDownloadManager() { return m_pSingularFileDownloadManager; }; + CServerInfo* GetServerInfo() { return m_ServerInfo.get(); } CClientEntity* GetRootEntity() { return m_pRootEntity; } CEvents* GetEvents() { return &m_Events; } @@ -438,6 +440,8 @@ class CClientGame bool TriggerBrowserRequestResultEvent(const std::unordered_set& newPages); void RestreamModel(unsigned short usModel); + void TriggerDiscordJoin(SString strSecret); + private: // CGUI Callbacks bool OnKeyDown(CGUIKeyEventArgs Args); @@ -700,6 +704,8 @@ class CClientGame CScriptDebugging* m_pScriptDebugging; CRegisteredCommands m_RegisteredCommands; + std::unique_ptr m_ServerInfo; + // Map statuses SString m_strCurrentMapName; SString m_strModRoot; @@ -812,6 +818,7 @@ class CClientGame // Debug class. Empty in release. public: CFoo m_Foo; + void UpdateDiscordState(); // If netc allows this function not to be here it would be better private: CEvents m_Events; diff --git a/Client/mods/deathmatch/logic/CPacketHandler.cpp b/Client/mods/deathmatch/logic/CPacketHandler.cpp index 8cb02d2116f..33bd3cbd9d5 100644 --- a/Client/mods/deathmatch/logic/CPacketHandler.cpp +++ b/Client/mods/deathmatch/logic/CPacketHandler.cpp @@ -11,6 +11,7 @@ #include "StdInc.h" #include "net/SyncStructures.h" +#include "CServerInfo.h" using std::list; @@ -205,6 +206,10 @@ bool CPacketHandler::ProcessPacket(unsigned char ucPacketID, NetBitStreamInterfa Packet_PedTask(bitStream); return true; + case PACKET_ID_SERVER_INFO_SYNC: + Packet_ServerInfoSync(bitStream); + return true; + default: break; } @@ -989,6 +994,8 @@ void CPacketHandler::Packet_PlayerList(NetBitStreamInterface& bitStream) pPlayer->CallEvent("onClientPlayerJoin", Arguments, true); } } + + g_pClientGame->UpdateDiscordState(); } void CPacketHandler::Packet_PlayerQuit(NetBitStreamInterface& bitStream) @@ -1020,6 +1027,8 @@ void CPacketHandler::Packet_PlayerQuit(NetBitStreamInterface& bitStream) { RaiseProtocolError(15); } + + g_pClientGame->UpdateDiscordState(); } void CPacketHandler::Packet_PlayerSpawn(NetBitStreamInterface& bitStream) @@ -5216,6 +5225,24 @@ void CPacketHandler::Packet_PedTask(NetBitStreamInterface& bitStream) }; } +void CPacketHandler::Packet_ServerInfoSync(NetBitStreamInterface& bitStream) +{ + uint8 flags; + + if (!bitStream.Read(flags)) + return; + + // Read in order of flags + if (flags & SERVER_INFO_FLAG_MAX_PLAYERS) + { + uint maxPlayersCount; + if (!bitStream.Read(maxPlayersCount)) + return; + + g_pClientGame->GetServerInfo()->SetMaxPlayers(maxPlayersCount); + } +} + // // // diff --git a/Client/mods/deathmatch/logic/CPacketHandler.h b/Client/mods/deathmatch/logic/CPacketHandler.h index d7d3361b05b..2be37dc18fc 100644 --- a/Client/mods/deathmatch/logic/CPacketHandler.h +++ b/Client/mods/deathmatch/logic/CPacketHandler.h @@ -98,6 +98,7 @@ class CPacketHandler void Packet_SyncSettings(NetBitStreamInterface& bitStream); void Packet_PedTask(NetBitStreamInterface& bitStream); void Packet_ChatClear(NetBitStreamInterface& bitStream); + void Packet_ServerInfoSync(NetBitStreamInterface& bitStream); // For debugging protocol errors during ENTITY_ADD packet void EntityAddDebugBegin(uint uiNumEntities, NetBitStreamInterface* pBitStream); diff --git a/Client/mods/deathmatch/logic/CServerInfo.cpp b/Client/mods/deathmatch/logic/CServerInfo.cpp new file mode 100644 index 00000000000..a64b83961f4 --- /dev/null +++ b/Client/mods/deathmatch/logic/CServerInfo.cpp @@ -0,0 +1,23 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/CServerInfo.cpp + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include +#include "CServerInfo.h" + +CServerInfo::CServerInfo() : m_MaxPlayersCount(0) +{ + +} + +void CServerInfo::SetMaxPlayers(uint set) +{ + m_MaxPlayersCount = set; + g_pClientGame->UpdateDiscordState(); +} diff --git a/Client/mods/deathmatch/logic/CServerInfo.h b/Client/mods/deathmatch/logic/CServerInfo.h new file mode 100644 index 00000000000..124b7e268f4 --- /dev/null +++ b/Client/mods/deathmatch/logic/CServerInfo.h @@ -0,0 +1,31 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/CServerInfo.h + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +enum EServerInfoSyncFlag : uint8 +{ + SERVER_INFO_FLAG_ALL = 0xFF, // 0b11111111 + SERVER_INFO_FLAG_MAX_PLAYERS = 1, // 0b00000001 + SERVER_INFO_FLAG_RESERVED = 1 << 1 // 0b00000010 and so on +}; + +class CServerInfo +{ +public: + CServerInfo(); + ~CServerInfo() {} + + uint GetMaxPlayers() const { return m_MaxPlayersCount; } + void SetMaxPlayers(uint set); + +private: + uint m_MaxPlayersCount; +}; diff --git a/Client/mods/deathmatch/logic/rpc/CWorldRPCs.cpp b/Client/mods/deathmatch/logic/rpc/CWorldRPCs.cpp index 8c3d94beaf8..d7c8d06d772 100644 --- a/Client/mods/deathmatch/logic/rpc/CWorldRPCs.cpp +++ b/Client/mods/deathmatch/logic/rpc/CWorldRPCs.cpp @@ -61,6 +61,8 @@ void CWorldRPCs::LoadFunctions() AddHandler(SET_MOON_SIZE, SetMoonSize, "SetMoonSize"); AddHandler(RESET_MOON_SIZE, ResetMoonSize, "ResetMoonSize"); + + AddHandler(SET_DISCORD_JOIN_PARAMETERS, SetDiscordJoinParams, "SetDiscordJoinParams"); } void CWorldRPCs::SetTime(NetBitStreamInterface& bitStream) @@ -622,3 +624,17 @@ void CWorldRPCs::ResetMoonSize(NetBitStreamInterface& bitStream) { g_pMultiplayer->ResetMoonSize(); } + +void CWorldRPCs::SetDiscordJoinParams(NetBitStreamInterface& bitStream) +{ + SString strKey, strPartyId; + uint uiPartySize, uiPartyMax; + + if (bitStream.ReadString(strKey) && bitStream.ReadString(strPartyId) && bitStream.Read(uiPartySize) && bitStream.Read(uiPartyMax)) + { + if (strKey.length() > 64 || strPartyId.length() > 64 || uiPartySize > uiPartyMax || strKey.find(' ') != SString::npos || strPartyId.find(' ') != SString::npos) + return; + + g_pCore->GetDiscordManager()->SetJoinParameters(strKey, strPartyId, uiPartySize, uiPartyMax, [](EDiscordRes res) {}); + } +} diff --git a/Client/mods/deathmatch/logic/rpc/CWorldRPCs.h b/Client/mods/deathmatch/logic/rpc/CWorldRPCs.h index 842f6006c52..5e86372e325 100644 --- a/Client/mods/deathmatch/logic/rpc/CWorldRPCs.h +++ b/Client/mods/deathmatch/logic/rpc/CWorldRPCs.h @@ -64,4 +64,5 @@ class CWorldRPCs : public CRPCFunctions DECLARE_RPC(SetMoonSize); DECLARE_RPC(ResetMoonSize); DECLARE_RPC(SetSyncIntervals); + DECLARE_RPC(SetDiscordJoinParams); }; diff --git a/Client/sdk/core/CClientBase.h b/Client/sdk/core/CClientBase.h index b252df55469..e5dd297422e 100644 --- a/Client/sdk/core/CClientBase.h +++ b/Client/sdk/core/CClientBase.h @@ -33,4 +33,6 @@ class CClientBase virtual bool HandleException(CExceptionInformation* pExceptionInformation) = 0; virtual void GetPlayerNames(std::vector& vPlayerNames) = 0; + + virtual void TriggerDiscordJoin(SString strSecret) = 0; }; diff --git a/Client/sdk/core/CCoreInterface.h b/Client/sdk/core/CCoreInterface.h index 04ce391bff0..ebe83a5063d 100644 --- a/Client/sdk/core/CCoreInterface.h +++ b/Client/sdk/core/CCoreInterface.h @@ -23,6 +23,7 @@ #include "CWebCoreInterface.h" #include "CTrayIconInterface.h" #include "CChatInterface.h" +#include "CDiscordManagerInterface.h" #include "xml/CXML.h" #include @@ -176,6 +177,8 @@ class CCoreInterface virtual bool ClearChat() = 0; virtual void OnGameTimerUpdate() = 0; virtual HANDLE SetThreadHardwareBreakPoint(HANDLE hThread, HWBRK_TYPE Type, HWBRK_SIZE Size, DWORD dwAddress) = 0; + + virtual CDiscordManagerInterface* GetDiscordManager() = 0; }; class CClientTime diff --git a/Client/sdk/core/CDiscordManagerInterface.h b/Client/sdk/core/CDiscordManagerInterface.h new file mode 100644 index 00000000000..fe0b4926b34 --- /dev/null +++ b/Client/sdk/core/CDiscordManagerInterface.h @@ -0,0 +1,113 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * (Shared logic for modifications) + * LICENSE: See LICENSE in the top level directory + * FILE: sdk/core/CDiscordManagerInterface.h + * + *****************************************************************************/ + +// +// Some enums are redefined so the interface won't require discord's header +// + +#pragma once + +#include + +enum EDiscordActivityT +{ + EDiscordActivityT_Playing, + EDiscordActivityT_Streaming, + EDiscordActivityT_Listening, + EDiscordActivityT_Watching +}; + +enum EDiscordRes +{ + DiscordRes_Ok = 0, + DiscordRes_ServiceUnavailable = 1, + DiscordRes_InvalidVersion = 2, + DiscordRes_LockFailed = 3, + DiscordRes_InternalError = 4, + DiscordRes_InvalidPayload = 5, + DiscordRes_InvalidCommand = 6, + DiscordRes_InvalidPermissions = 7, + DiscordRes_NotFetched = 8, + DiscordRes_NotFound = 9, + DiscordRes_Conflict = 10, + DiscordRes_InvalidSecret = 11, + DiscordRes_InvalidJoinSecret = 12, + DiscordRes_NoEligibleActivity = 13, + DiscordRes_InvalidInvite = 14, + DiscordRes_NotAuthenticated = 15, + DiscordRes_InvalidAccessToken = 16, + DiscordRes_ApplicationMismatch = 17, + DiscordRes_InvalidDataUrl = 18, + DiscordRes_InvalidBase64 = 19, + DiscordRes_NotFiltered = 20, + DiscordRes_LobbyFull = 21, + DiscordRes_InvalidLobbySecret = 22, + DiscordRes_InvalidFilename = 23, + DiscordRes_InvalidFileSize = 24, + DiscordRes_InvalidEntitlement = 25, + DiscordRes_NotInstalled = 26, + DiscordRes_NotRunning = 27, + DiscordRes_InsufficientBuffer = 28, + DiscordRes_PurchaseCanceled = 29, + DiscordRes_InvalidGuild = 30, + DiscordRes_InvalidEvent = 31, + DiscordRes_InvalidChannel = 32, + DiscordRes_InvalidOrigin = 33, + DiscordRes_RateLimited = 34, + DiscordRes_OAuth2Error = 35, + DiscordRes_SelectChannelTimeout = 36, + DiscordRes_GetGuildTimeout = 37, + DiscordRes_SelectVoiceForceRequired = 38, + DiscordRes_CaptureShortcutAlreadyListening = 39, + DiscordRes_UnauthorizedForAchievement = 40, + DiscordRes_InvalidGiftCode = 41, + DiscordRes_PurchaseError = 42, + DiscordRes_TransactionAborted = 43, +}; + +struct SDiscordActivity +{ + SDiscordActivity() + : m_activityType(EDiscordActivityT_Playing), + m_startTimestamp(0), + m_endTimestamp(0), + m_name(""), + m_state(""), + m_details(""), + m_joinSecret(""), + m_spectateSecret("") + { + } + + int64 m_startTimestamp; + int64 m_endTimestamp; + const char* m_name; + const char* m_state; + const char* m_details; + const char* m_joinSecret; + const char* m_spectateSecret; + EDiscordActivityT m_activityType; +}; + +class CDiscordManagerInterface +{ +public: + virtual void UpdateActivity(SDiscordActivity& activity, std::function callback) = 0; + virtual void SetType(EDiscordActivityT type, std::function callback) = 0; + virtual void SetName(char const* name, std::function callback) = 0; + virtual void SetState(char const* state, std::function callback) = 0; + virtual void SetDetails(char const* details, std::function callback) = 0; + virtual void SetStartEndTimestamp(int64 start, int64 end, std::function callback) = 0; + virtual void SetJoinParameters(const char* joinSecret, const char* partyId, uint partySize, uint partyMax, std::function callback) = 0; + virtual void SetSpectateSecret(const char* spectateSecret, std::function callback) = 0; + virtual void RegisterPlay(bool connected) = 0; + virtual void Disconnect() = 0; + + virtual SString GetJoinSecret() = 0; +}; diff --git a/Server/mods/deathmatch/StdInc.h b/Server/mods/deathmatch/StdInc.h index 3087baa0142..181a8413509 100644 --- a/Server/mods/deathmatch/StdInc.h +++ b/Server/mods/deathmatch/StdInc.h @@ -110,6 +110,8 @@ struct SAclRequest; #include "packets/CVehicleTrailerPacket.h" #include "packets/CVoiceDataPacket.h" #include "packets/CVoiceEndPacket.h" +#include "packets/CServerInfoSyncPacket.h" +#include "packets/CDiscordJoinPacket.h" // Lua function definitions #include "luadefs/CLuaElementDefs.h" diff --git a/Server/mods/deathmatch/logic/CGame.cpp b/Server/mods/deathmatch/logic/CGame.cpp index d18bc77f42b..de24b66bb9e 100644 --- a/Server/mods/deathmatch/logic/CGame.cpp +++ b/Server/mods/deathmatch/logic/CGame.cpp @@ -1196,6 +1196,12 @@ bool CGame::ProcessPacket(CPacket& Packet) return true; } + case PACKET_ID_DISCORD_JOIN: + { + Packet_DiscordJoin(static_cast(Packet)); + return true; + } + default: break; } @@ -1231,6 +1237,10 @@ void CGame::JoinPlayer(CPlayer& Player) marker.Set("CPlayerJoinCompletePacket"); + // Sync up server info on entry + if (Player.GetBitStreamVersion() >= 0x06D) + Player.Send(CServerInfoSyncPacket(SERVER_INFO_FLAG_ALL)); + // Add debug info if wanted if (CPerfStatDebugInfo::GetSingleton()->IsActive("PlayerInGameNotice")) CPerfStatDebugInfo::GetSingleton()->AddLine("PlayerInGameNotice", marker.GetString()); @@ -1343,6 +1353,15 @@ void CGame::InitialDataStream(CPlayer& Player) marker.Set("onPlayerJoin"); + SString joinSecret = Player.GetDiscordJoinSecret(); + if (joinSecret.length()) + { + CLuaArguments Arguments; + Arguments.PushBoolean(true); + Arguments.PushString(joinSecret); + Player.CallEvent("onPlayerDiscordJoin", Arguments); + } + // Register them on the lightweight sync manager. m_lightsyncManager.RegisterPlayer(&Player); @@ -1357,7 +1376,7 @@ void CGame::QuitPlayer(CPlayer& Player, CClient::eQuitReasons Reason, bool bSayI return; Player.SetLeavingServer(true); - + // Grab quit reaason const char* szReason = "Unknown"; switch (Reason) @@ -1496,6 +1515,7 @@ void CGame::AddBuiltInEvents() m_Events.AddEvent("onPlayerACInfo", "aclist, size, md5, sha256", NULL, false); m_Events.AddEvent("onPlayerNetworkStatus", "type, ticks", NULL, false); m_Events.AddEvent("onPlayerScreenShot", "resource, status, file_data, timestamp, tag", NULL, false); + m_Events.AddEvent("onPlayerDiscordJoin", "justConnected, secret", NULL, false); // Ped events m_Events.AddEvent("onPedWasted", "ammo, killer, weapon, bodypart", NULL, false); @@ -1715,6 +1735,7 @@ void CGame::Packet_PlayerJoinData(CPlayerJoinDataPacket& Packet) pPlayer->SetSerial(strSerial, 0); pPlayer->SetSerial(strExtra, 1); pPlayer->SetPlayerVersion(strPlayerVersion); + pPlayer->SetDiscordJoinSecret(Packet.GetDiscordJoinSecret()); // Check if client must update if (IsBelowMinimumClient(pPlayer->GetPlayerVersion()) && !pPlayer->ShouldIgnoreMinClientVersionChecks()) @@ -3795,6 +3816,18 @@ void CGame::Packet_PlayerNetworkStatus(CPlayerNetworkStatusPacket& Packet) } } +void CGame::Packet_DiscordJoin(CDiscordJoinPacket& Packet) +{ + CPlayer* pPlayer = Packet.GetSourcePlayer(); + if (pPlayer) + { + CLuaArguments Arguments; + Arguments.PushBoolean(false); + Arguments.PushString(Packet.GetSecret()); + pPlayer->CallEvent("onPlayerDiscordJoin", Arguments, NULL); + } +} + void CGame::Packet_PlayerModInfo(CPlayerModInfoPacket& Packet) { CPlayer* pPlayer = Packet.GetSourcePlayer(); diff --git a/Server/mods/deathmatch/logic/CGame.h b/Server/mods/deathmatch/logic/CGame.h index 7116fcc50dc..4bcaa27f63e 100644 --- a/Server/mods/deathmatch/logic/CGame.h +++ b/Server/mods/deathmatch/logic/CGame.h @@ -495,6 +495,7 @@ class CGame void Packet_PlayerScreenShot(class CPlayerScreenShotPacket& Packet); void Packet_PlayerNoSocket(class CPlayerNoSocketPacket& Packet); void Packet_PlayerNetworkStatus(class CPlayerNetworkStatusPacket& Packet); + void Packet_DiscordJoin(class CDiscordJoinPacket& Packet); static void PlayerCompleteConnect(CPlayer* pPlayer); diff --git a/Server/mods/deathmatch/logic/CPacketTranslator.cpp b/Server/mods/deathmatch/logic/CPacketTranslator.cpp index 29e3cd25371..96337d4f6e6 100644 --- a/Server/mods/deathmatch/logic/CPacketTranslator.cpp +++ b/Server/mods/deathmatch/logic/CPacketTranslator.cpp @@ -170,6 +170,10 @@ CPacket* CPacketTranslator::Translate(const NetServerPlayerID& Socket, ePacketID pTemp = new CPlayerNetworkStatusPacket; break; + case PACKET_ID_DISCORD_JOIN: + pTemp = new CDiscordJoinPacket; + break; + default: break; } diff --git a/Server/mods/deathmatch/logic/CPerfStat.RPCPacketUsage.cpp b/Server/mods/deathmatch/logic/CPerfStat.RPCPacketUsage.cpp index a94f110b565..33f9742b16d 100644 --- a/Server/mods/deathmatch/logic/CPerfStat.RPCPacketUsage.cpp +++ b/Server/mods/deathmatch/logic/CPerfStat.RPCPacketUsage.cpp @@ -1,565 +1,566 @@ -/***************************************************************************** - * - * PROJECT: Multi Theft Auto v1.0 - * LICENSE: See LICENSE in the top level directory - * FILE: mods/deathmatch/logic/CPerfStat.PacketUsage.cpp - * PURPOSE: Performance stats manager class - * - * Multi Theft Auto is available from http://www.multitheftauto.com/ - * - *****************************************************************************/ - -#include "StdInc.h" - -DECLARE_ENUM(eElementRPCFunctions); -IMPLEMENT_ENUM_BEGIN(eElementRPCFunctions) -ADD_ENUM1(DONT_USE_0) -ADD_ENUM1(SET_TIME) -ADD_ENUM1(SET_WEATHER) -ADD_ENUM1(SET_WEATHER_BLENDED) -ADD_ENUM1(SET_MINUTE_DURATION) -ADD_ENUM1(SET_ELEMENT_PARENT) -ADD_ENUM1(SET_ELEMENT_DATA) -ADD_ENUM1(SET_ELEMENT_POSITION) -ADD_ENUM1(SET_ELEMENT_VELOCITY) -ADD_ENUM1(SET_ELEMENT_INTERIOR) -ADD_ENUM1(SET_ELEMENT_DIMENSION) -ADD_ENUM1(ATTACH_ELEMENTS) -ADD_ENUM1(DETACH_ELEMENTS) -ADD_ENUM1(SET_ELEMENT_ALPHA) -ADD_ENUM1(SET_ELEMENT_NAME) -ADD_ENUM1(SET_ELEMENT_HEALTH) -ADD_ENUM1(SET_ELEMENT_MODEL) -ADD_ENUM1(SET_ELEMENT_ATTACHED_OFFSETS) -ADD_ENUM1(SET_PLAYER_MONEY) -ADD_ENUM1(SHOW_PLAYER_HUD_COMPONENT) -ADD_ENUM1(FORCE_PLAYER_MAP) -ADD_ENUM1(SET_PLAYER_NAMETAG_TEXT) -ADD_ENUM1(REMOVE_PLAYER_NAMETAG_COLOR) -ADD_ENUM1(SET_PLAYER_NAMETAG_COLOR) -ADD_ENUM1(SET_PLAYER_NAMETAG_SHOWING) -ADD_ENUM1(SET_PED_ARMOR) -ADD_ENUM1(SET_PED_ROTATION) -ADD_ENUM1(GIVE_PED_JETPACK) -ADD_ENUM1(REMOVE_PED_JETPACK) -ADD_ENUM1(REMOVE_PED_CLOTHES) -ADD_ENUM1(SET_PED_GRAVITY) -ADD_ENUM1(SET_PED_CHOKING) -ADD_ENUM1(SET_PED_FIGHTING_STYLE) -ADD_ENUM1(SET_PED_MOVE_ANIM) -ADD_ENUM1(WARP_PED_INTO_VEHICLE) -ADD_ENUM1(REMOVE_PED_FROM_VEHICLE) -ADD_ENUM1(SET_PED_DOING_GANG_DRIVEBY) -ADD_ENUM1(SET_PED_ANIMATION) -ADD_ENUM1(SET_PED_ANIMATION_PROGRESS) -ADD_ENUM1(SET_PED_ANIMATION_SPEED) -ADD_ENUM1(SET_PED_ON_FIRE) -ADD_ENUM1(SET_PED_HEADLESS) -ADD_ENUM1(SET_PED_FROZEN) -ADD_ENUM1(RELOAD_PED_WEAPON) -ADD_ENUM1(DESTROY_ALL_VEHICLES) -ADD_ENUM1(FIX_VEHICLE) -ADD_ENUM1(BLOW_VEHICLE) -ADD_ENUM1(SET_VEHICLE_ROTATION) -ADD_ENUM1(SET_VEHICLE_TURNSPEED) -ADD_ENUM1(SET_VEHICLE_COLOR) -ADD_ENUM1(SET_VEHICLE_LOCKED) -ADD_ENUM1(SET_VEHICLE_DOORS_UNDAMAGEABLE) -ADD_ENUM1(SET_VEHICLE_SIRENE_ON) -ADD_ENUM1(SET_VEHICLE_LANDING_GEAR_DOWN) -ADD_ENUM1(SET_HELICOPTER_ROTOR_SPEED) -ADD_ENUM1(ADD_VEHICLE_UPGRADE) -ADD_ENUM1(ADD_ALL_VEHICLE_UPGRADES) -ADD_ENUM1(REMOVE_VEHICLE_UPGRADE) -ADD_ENUM1(SET_VEHICLE_DAMAGE_STATE) -ADD_ENUM1(SET_VEHICLE_OVERRIDE_LIGHTS) -ADD_ENUM1(SET_VEHICLE_ENGINE_STATE) -ADD_ENUM1(SET_VEHICLE_DIRT_LEVEL) -ADD_ENUM1(SET_VEHICLE_DAMAGE_PROOF) -ADD_ENUM1(SET_VEHICLE_PAINTJOB) -ADD_ENUM1(SET_VEHICLE_FUEL_TANK_EXPLODABLE) -ADD_ENUM1(SET_VEHICLE_WHEEL_STATES) -ADD_ENUM1(SET_VEHICLE_FROZEN) -ADD_ENUM1(SET_TRAIN_DERAILED) -ADD_ENUM1(SET_TRAIN_DERAILABLE) -ADD_ENUM1(SET_TRAIN_DIRECTION) -ADD_ENUM1(SET_TRAIN_SPEED) -ADD_ENUM1(SET_TAXI_LIGHT_ON) -ADD_ENUM1(SET_VEHICLE_HEADLIGHT_COLOR) -ADD_ENUM1(SET_VEHICLE_DOOR_OPEN_RATIO) -ADD_ENUM1(GIVE_WEAPON) -ADD_ENUM1(TAKE_WEAPON) -ADD_ENUM1(TAKE_ALL_WEAPONS) -ADD_ENUM1(SET_WEAPON_AMMO) -ADD_ENUM1(SET_WEAPON_SLOT) -ADD_ENUM1(DESTROY_ALL_BLIPS) -ADD_ENUM1(SET_BLIP_ICON) -ADD_ENUM1(SET_BLIP_SIZE) -ADD_ENUM1(SET_BLIP_COLOR) -ADD_ENUM1(SET_BLIP_ORDERING) -ADD_ENUM1(DESTROY_ALL_OBJECTS) -ADD_ENUM1(SET_OBJECT_ROTATION) -ADD_ENUM1(MOVE_OBJECT) -ADD_ENUM1(STOP_OBJECT) -ADD_ENUM1(DESTROY_ALL_RADAR_AREAS) -ADD_ENUM1(SET_RADAR_AREA_SIZE) -ADD_ENUM1(SET_RADAR_AREA_COLOR) -ADD_ENUM1(SET_RADAR_AREA_FLASHING) -ADD_ENUM1(DESTROY_ALL_MARKERS) -ADD_ENUM1(SET_MARKER_TYPE) -ADD_ENUM1(SET_MARKER_COLOR) -ADD_ENUM1(SET_MARKER_SIZE) -ADD_ENUM1(SET_MARKER_TARGET) -ADD_ENUM1(SET_MARKER_ICON) -ADD_ENUM1(DESTROY_ALL_PICKUPS) -ADD_ENUM1(SET_PICKUP_TYPE) -ADD_ENUM1(SET_PICKUP_VISIBLE) -ADD_ENUM1(PLAY_SOUND) -ADD_ENUM1(BIND_KEY) -ADD_ENUM1(UNBIND_KEY) -ADD_ENUM1(BIND_COMMAND) -ADD_ENUM1(UNBIND_COMMAND) -ADD_ENUM1(TOGGLE_CONTROL_ABILITY) -ADD_ENUM1(TOGGLE_ALL_CONTROL_ABILITY) -ADD_ENUM1(SET_CONTROL_STATE) -ADD_ENUM1(FORCE_RECONNECT) -ADD_ENUM1(SET_TEAM_NAME) -ADD_ENUM1(SET_TEAM_COLOR) -ADD_ENUM1(SET_PLAYER_TEAM) -ADD_ENUM1(SET_TEAM_FRIENDLY_FIRE) -ADD_ENUM1(SET_WANTED_LEVEL) -ADD_ENUM1(SET_CAMERA_MATRIX) -ADD_ENUM1(SET_CAMERA_TARGET) -ADD_ENUM1(SET_CAMERA_INTERIOR) -ADD_ENUM1(FADE_CAMERA) -ADD_ENUM1(SHOW_CURSOR) -ADD_ENUM1(SHOW_CHAT) -ADD_ENUM1(SET_GRAVITY) -ADD_ENUM1(SET_GAME_SPEED) -ADD_ENUM1(SET_WAVE_HEIGHT) -ADD_ENUM1(SET_SKY_GRADIENT) -ADD_ENUM1(RESET_SKY_GRADIENT) -ADD_ENUM1(SET_HEAT_HAZE) -ADD_ENUM1(RESET_HEAT_HAZE) -ADD_ENUM1(SET_BLUR_LEVEL) -ADD_ENUM1(SET_FPS_LIMIT) -ADD_ENUM1(SET_GARAGE_OPEN) -ADD_ENUM1(RESET_MAP_INFO) -ADD_ENUM1(SET_GLITCH_ENABLED) -ADD_ENUM1(SET_CLOUDS_ENABLED) -ADD_ENUM1(REMOVE_ELEMENT_DATA) -ADD_ENUM1(SET_VEHICLE_HANDLING) -ADD_ENUM1(SET_VEHICLE_HANDLING_PROPERTY) -ADD_ENUM1(RESET_VEHICLE_HANDLING_PROPERTY) -ADD_ENUM1(RESET_VEHICLE_HANDLING) -ADD_ENUM1(TOGGLE_DEBUGGER) -ADD_ENUM1(SET_ELEMENT_WATER_LEVEL) -ADD_ENUM1(SET_ALL_ELEMENT_WATER_LEVEL) -ADD_ENUM1(SET_WORLD_WATER_LEVEL) -ADD_ENUM1(RESET_WORLD_WATER_LEVEL) -ADD_ENUM1(SET_WATER_VERTEX_POSITION) -ADD_ENUM1(SET_ELEMENT_DOUBLESIDED) -ADD_ENUM1(SET_TRAFFIC_LIGHT_STATE) -ADD_ENUM1(SET_VEHICLE_TURRET_POSITION) -ADD_ENUM1(SET_OBJECT_SCALE) -ADD_ENUM1(SET_ELEMENT_COLLISIONS_ENABLED) -ADD_ENUM1(SET_JETPACK_MAXHEIGHT) -ADD_ENUM1(SET_WATER_COLOR) -ADD_ENUM1(RESET_WATER_COLOR) -ADD_ENUM1(SET_ELEMENT_FROZEN) -ADD_ENUM1(SET_LOW_LOD_ELEMENT) -ADD_ENUM1(SET_BLIP_VISIBLE_DISTANCE) -ADD_ENUM1(SET_INTERIOR_SOUNDS_ENABLED) -ADD_ENUM1(SET_RAIN_LEVEL) -ADD_ENUM1(SET_SUN_SIZE) -ADD_ENUM1(SET_SUN_COLOR) -ADD_ENUM1(SET_WIND_VELOCITY) -ADD_ENUM1(SET_FAR_CLIP_DISTANCE) -ADD_ENUM1(SET_FOG_DISTANCE) -ADD_ENUM1(RESET_RAIN_LEVEL) -ADD_ENUM1(RESET_SUN_SIZE) -ADD_ENUM1(RESET_SUN_COLOR) -ADD_ENUM1(RESET_WIND_VELOCITY) -ADD_ENUM1(RESET_FAR_CLIP_DISTANCE) -ADD_ENUM1(RESET_FOG_DISTANCE) -ADD_ENUM1(SET_AIRCRAFT_MAXHEIGHT) -ADD_ENUM1(SET_WEAPON_PROPERTY) -ADD_ENUM1(SET_VEHICLE_VARIANT) -ADD_ENUM1(REMOVE_WORLD_MODEL) -ADD_ENUM1(RESTORE_WORLD_MODEL) -ADD_ENUM1(RESTORE_ALL_WORLD_MODELS) -ADD_ENUM1(TAKE_PLAYER_SCREEN_SHOT) -ADD_ENUM1(SET_OCCLUSIONS_ENABLED) -ADD_ENUM1(GIVE_VEHICLE_SIRENS) -ADD_ENUM1(REMOVE_VEHICLE_SIRENS) -ADD_ENUM1(SET_VEHICLE_SIRENS) -ADD_ENUM1(SET_SYNC_INTERVALS) -ADD_ENUM1(SET_JETPACK_WEAPON_ENABLED) -ADD_ENUM1(FIRE_CUSTOM_WEAPON) -ADD_ENUM1(SET_CUSTOM_WEAPON_STATE) -ADD_ENUM1(SET_CUSTOM_WEAPON_AMMO) -ADD_ENUM1(SET_CUSTOM_WEAPON_CLIP_AMMO) -ADD_ENUM1(SET_CUSTOM_WEAPON_TARGET) -ADD_ENUM1(RESET_CUSTOM_WEAPON_TARGET) -ADD_ENUM1(SET_CUSTOM_WEAPON_FLAGS) -ADD_ENUM1(SET_CUSTOM_WEAPON_FIRING_RATE) -ADD_ENUM1(RESET_CUSTOM_WEAPON_FIRING_RATE) -ADD_ENUM1(SET_WEAPON_OWNER) -ADD_ENUM1(SET_AIRCRAFT_MAXVELOCITY) -ADD_ENUM1(RESET_MOON_SIZE) -ADD_ENUM1(SET_MOON_SIZE) -ADD_ENUM1(SET_VEHICLE_PLATE_TEXT) -ADD_ENUM1(SET_PROPAGATE_CALLS_ENABLED) -ADD_ENUM1(SET_TRAIN_TRACK) -ADD_ENUM1(SET_TRAIN_POSITION) -ADD_ENUM1(SET_ELEMENT_ANGULAR_VELOCITY) -ADD_ENUM1(SET_COLSHAPE_RADIUS) -ADD_ENUM1(SET_COLSHAPE_SIZE) -ADD_ENUM1(ADD_COLPOLYGON_POINT) -ADD_ENUM1(REMOVE_COLPOLYGON_POINT) -ADD_ENUM1(UPDATE_COLPOLYGON_POINT) -IMPLEMENT_ENUM_END("eElementRPCFunctions") - -DECLARE_ENUM(CRPCFunctions::eRPCFunctions); -IMPLEMENT_ENUM_BEGIN(CRPCFunctions::eRPCFunctions) -ADD_ENUM1(CRPCFunctions::PLAYER_INGAME_NOTICE) -ADD_ENUM1(CRPCFunctions::INITIAL_DATA_STREAM) -ADD_ENUM1(CRPCFunctions::PLAYER_TARGET) -ADD_ENUM1(CRPCFunctions::PLAYER_WEAPON) -ADD_ENUM1(CRPCFunctions::KEY_BIND) -ADD_ENUM1(CRPCFunctions::CURSOR_EVENT) -ADD_ENUM1(CRPCFunctions::REQUEST_STEALTH_KILL) -IMPLEMENT_ENUM_END("eRPCFunctions") - -struct SRPCPacketStat -{ - int iCount; - int iTotalBytes; -}; - -/////////////////////////////////////////////////////////////// -// -// CPerfStatRPCPacketUsageImpl -// -// -// -/////////////////////////////////////////////////////////////// -class CPerfStatRPCPacketUsageImpl : public CPerfStatRPCPacketUsage -{ -public: - ZERO_ON_NEW - - CPerfStatRPCPacketUsageImpl(); - virtual ~CPerfStatRPCPacketUsageImpl(); - - // CPerfStatModule - virtual const SString& GetCategoryName(); - virtual void DoPulse(); - virtual void GetStats(CPerfStatResult* pOutResult, const std::map& optionMap, const SString& strFilter); - - // CPerfStatRPCPacketUsage - virtual void UpdatePacketUsageIn(uchar ucRpcId, uint uiSize); - virtual void UpdatePacketUsageOut(uchar ucRpcId, uint uiSize); - - // CPerfStatRPCPacketUsageImpl - void MaybeRecordStats(); - - int m_iStatsCleared; - CElapsedTime m_TimeSinceGetStats; - long long m_llNextRecordTime; - SString m_strCategoryName; - SRPCPacketStat m_PrevPacketStatsIn[256]; - SRPCPacketStat m_PacketStatsIn[256]; - SRPCPacketStat m_PacketStatsLiveIn[256]; - SFixedArray m_ShownPacketStatsIn; - SRPCPacketStat m_PrevPacketStatsOut[256]; - SRPCPacketStat m_PacketStatsOut[256]; - SRPCPacketStat m_PacketStatsLiveOut[256]; - SFixedArray m_ShownPacketStatsOut; -}; - -/////////////////////////////////////////////////////////////// -// -// Temporary home for global object -// -// -// -/////////////////////////////////////////////////////////////// -static std::unique_ptr g_pPerfStatRPCPacketUsageImp; - -CPerfStatRPCPacketUsage* CPerfStatRPCPacketUsage::GetSingleton() -{ - if (!g_pPerfStatRPCPacketUsageImp) - g_pPerfStatRPCPacketUsageImp.reset(new CPerfStatRPCPacketUsageImpl()); - return g_pPerfStatRPCPacketUsageImp.get(); -} - -/////////////////////////////////////////////////////////////// -// -// CPerfStatRPCPacketUsageImpl::CPerfStatRPCPacketUsageImpl -// -// -// -/////////////////////////////////////////////////////////////// -CPerfStatRPCPacketUsageImpl::CPerfStatRPCPacketUsageImpl() -{ - m_strCategoryName = "RPC Packet usage"; - assert(sizeof(m_PacketStatsIn) == sizeof(m_PrevPacketStatsIn)); - assert(sizeof(m_PacketStatsIn) == sizeof(m_PacketStatsLiveIn)); -} - -/////////////////////////////////////////////////////////////// -// -// CPerfStatRPCPacketUsageImpl::CPerfStatRPCPacketUsageImpl -// -// -// -/////////////////////////////////////////////////////////////// -CPerfStatRPCPacketUsageImpl::~CPerfStatRPCPacketUsageImpl() -{ -} - -/////////////////////////////////////////////////////////////// -// -// CPerfStatRPCPacketUsageImpl::GetCategoryName -// -// -// -/////////////////////////////////////////////////////////////// -const SString& CPerfStatRPCPacketUsageImpl::GetCategoryName() -{ - return m_strCategoryName; -} - -/////////////////////////////////////////////////////////////// -// -// CPerfStatRPCPacketUsageImpl::DoPulse -// -// -// -/////////////////////////////////////////////////////////////// -void CPerfStatRPCPacketUsageImpl::DoPulse() -{ - MaybeRecordStats(); -} - -/////////////////////////////////////////////////////////////// -// -// CPerfStatRPCPacketUsageImpl::UpdatePacketUsageIn -// -// -// -/////////////////////////////////////////////////////////////// -void CPerfStatRPCPacketUsageImpl::UpdatePacketUsageIn(uchar ucRpcId, uint uiSize) -{ - SRPCPacketStat& stat = m_PacketStatsLiveIn[ucRpcId]; - stat.iCount++; - stat.iTotalBytes += uiSize; -} - -/////////////////////////////////////////////////////////////// -// -// CPerfStatRPCPacketUsageImpl::UpdatePacketUsageOut -// -// -// -/////////////////////////////////////////////////////////////// -void CPerfStatRPCPacketUsageImpl::UpdatePacketUsageOut(uchar ucRpcId, uint uiSize) -{ - SRPCPacketStat& stat = m_PacketStatsLiveOut[ucRpcId]; - stat.iCount++; - stat.iTotalBytes += uiSize; -} - -/////////////////////////////////////////////////////////////// -// -// CPerfStatRPCPacketUsageImpl::MaybeRecordStats -// -// -// -/////////////////////////////////////////////////////////////// -void CPerfStatRPCPacketUsageImpl::MaybeRecordStats() -{ - // Someone watching? - if (m_TimeSinceGetStats.Get() < 10000) - { - // Time for record update? // Copy and clear once every 5 seconds - long long llTime = GetTickCount64_(); - if (llTime >= m_llNextRecordTime) - { - m_llNextRecordTime = std::max(m_llNextRecordTime + 5000, llTime + 5000 / 10 * 9); - - // Save previous sample so we can calc the delta values - memcpy(m_PrevPacketStatsIn, m_PacketStatsIn, sizeof(m_PacketStatsIn)); - memcpy(m_PacketStatsIn, m_PacketStatsLiveIn, sizeof(m_PacketStatsIn)); - - memcpy(m_PrevPacketStatsOut, m_PacketStatsOut, sizeof(m_PacketStatsOut)); - memcpy(m_PacketStatsOut, m_PacketStatsLiveOut, sizeof(m_PacketStatsOut)); - - if (m_iStatsCleared == 1) - { - // Prime if was zeroed - memcpy(m_PrevPacketStatsIn, m_PacketStatsIn, sizeof(m_PacketStatsIn)); - memcpy(m_PrevPacketStatsOut, m_PacketStatsOut, sizeof(m_PacketStatsOut)); - m_iStatsCleared = 2; - } - else if (m_iStatsCleared == 2) - m_iStatsCleared = 0; - } - } - else - { - // No one watching - if (!m_iStatsCleared) - { - memset(m_PrevPacketStatsIn, 0, sizeof(m_PacketStatsIn)); - memset(m_PacketStatsIn, 0, sizeof(m_PacketStatsIn)); - memset(m_PrevPacketStatsOut, 0, sizeof(m_PacketStatsOut)); - memset(m_PacketStatsOut, 0, sizeof(m_PacketStatsOut)); - m_iStatsCleared = 1; - } - } -} - -/////////////////////////////////////////////////////////////// -// -// CPerfStatRPCPacketUsageImpl::GetStats -// -// -// -/////////////////////////////////////////////////////////////// -void CPerfStatRPCPacketUsageImpl::GetStats(CPerfStatResult* pResult, const std::map& strOptionMap, const SString& strFilter) -{ - m_TimeSinceGetStats.Reset(); - MaybeRecordStats(); - - // - // Set option flags - // - bool bHelp = MapContains(strOptionMap, "h"); - - // - // Process help - // - if (bHelp) - { - pResult->AddColumn("RPC Packet usage help"); - pResult->AddRow()[0] = "Option h - This help"; - return; - } - - // Add columns - pResult->AddColumn("Packet type"); - pResult->AddColumn("Incoming.msgs/sec"); - pResult->AddColumn("Incoming.bytes/sec"); - pResult->AddColumn("Incoming.cpu"); - pResult->AddColumn("Outgoing.msgs/sec"); - pResult->AddColumn("Outgoing.bytes/sec"); - pResult->AddColumn("Outgoing.cpu"); - - if (m_iStatsCleared) - { - pResult->AddRow()[0] = "Sampling... Please wait"; - } - - long long llTickCountNow = CTickCount::Now().ToLongLong(); - - // Fill rows - for (uint i = 0; i < 256; i++) - { - // Calc incoming delta values - SPacketStat statInDelta; - { - const SRPCPacketStat& statInPrev = m_PrevPacketStatsIn[i]; - const SRPCPacketStat& statInNow = m_PacketStatsIn[i]; - statInDelta.iCount = statInNow.iCount - statInPrev.iCount; - statInDelta.iTotalBytes = statInNow.iTotalBytes - statInPrev.iTotalBytes; - // statInDelta.totalTime = statInNow.totalTime - statInPrev.totalTime; - } - - if (!statInDelta.iCount) - { - // Once displayed, keep a row displayed for at least 20 seconds - if (llTickCountNow - m_ShownPacketStatsIn[i] > 20000) - continue; - } - else - { - m_ShownPacketStatsIn[i] = llTickCountNow; - } - - // Add row - SString* row = pResult->AddRow(); - - int c = 0; - // Turn "CRPCFunctions::PLAYER_WEAPON" into "64_Player_weapon" - SString strPacketDesc = EnumToString((CRPCFunctions::eRPCFunctions)i).SplitRight("CRPCFunctions::", NULL, -1).ToLower(); - row[c++] = SString("%d_", i) + strPacketDesc.Left(1).ToUpper() + strPacketDesc.SubStr(1); - - if (statInDelta.iCount) - { - row[c++] = SString("%d", (statInDelta.iCount + 4) / 5); - row[c++] = CPerfStatManager::GetScaledByteString((statInDelta.iTotalBytes + 4) / 5); - row[c++] = "n/a"; - } - else - { - row[c++] = "-"; - row[c++] = "-"; - row[c++] = "-"; - } - - row[c++] = "-"; - row[c++] = "-"; - row[c++] = "-"; - } - - // Fill rows - for (uint i = 0; i < 256; i++) - { - // Calc outgoing delta values - SRPCPacketStat statOutDelta; - { - const SRPCPacketStat& statOutPrev = m_PrevPacketStatsOut[i]; - const SRPCPacketStat& statOutNow = m_PacketStatsOut[i]; - statOutDelta.iCount = statOutNow.iCount - statOutPrev.iCount; - statOutDelta.iTotalBytes = statOutNow.iTotalBytes - statOutPrev.iTotalBytes; - } - - if (!statOutDelta.iCount) - { - // Once displayed, keep a row displayed for at least 20 seconds - if (llTickCountNow - m_ShownPacketStatsOut[i] > 20000) - continue; - } - else - { - m_ShownPacketStatsOut[i] = llTickCountNow; - } - - // Add row - SString* row = pResult->AddRow(); - - int c = 0; - // Turn "SET_WEAPON_OWNER" into "64_Set_weapon_owner" - SString strPacketDesc = EnumToString((eElementRPCFunctions)i).ToLower(); - row[c++] = SString("%d_", i) + strPacketDesc.Left(1).ToUpper() + strPacketDesc.SubStr(1); - - row[c++] = "-"; - row[c++] = "-"; - row[c++] = "-"; - - if (statOutDelta.iCount) - { - row[c++] = SString("%d", (statOutDelta.iCount + 4) / 5); - row[c++] = CPerfStatManager::GetScaledByteString((statOutDelta.iTotalBytes + 4) / 5); - row[c++] = "n/a"; - } - else - { - row[c++] = "-"; - row[c++] = "-"; - row[c++] = "-"; - } - } -} +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/CPerfStat.PacketUsage.cpp + * PURPOSE: Performance stats manager class + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" + +DECLARE_ENUM(eElementRPCFunctions); +IMPLEMENT_ENUM_BEGIN(eElementRPCFunctions) +ADD_ENUM1(DONT_USE_0) +ADD_ENUM1(SET_TIME) +ADD_ENUM1(SET_WEATHER) +ADD_ENUM1(SET_WEATHER_BLENDED) +ADD_ENUM1(SET_MINUTE_DURATION) +ADD_ENUM1(SET_ELEMENT_PARENT) +ADD_ENUM1(SET_ELEMENT_DATA) +ADD_ENUM1(SET_ELEMENT_POSITION) +ADD_ENUM1(SET_ELEMENT_VELOCITY) +ADD_ENUM1(SET_ELEMENT_INTERIOR) +ADD_ENUM1(SET_ELEMENT_DIMENSION) +ADD_ENUM1(ATTACH_ELEMENTS) +ADD_ENUM1(DETACH_ELEMENTS) +ADD_ENUM1(SET_ELEMENT_ALPHA) +ADD_ENUM1(SET_ELEMENT_NAME) +ADD_ENUM1(SET_ELEMENT_HEALTH) +ADD_ENUM1(SET_ELEMENT_MODEL) +ADD_ENUM1(SET_ELEMENT_ATTACHED_OFFSETS) +ADD_ENUM1(SET_PLAYER_MONEY) +ADD_ENUM1(SHOW_PLAYER_HUD_COMPONENT) +ADD_ENUM1(FORCE_PLAYER_MAP) +ADD_ENUM1(SET_PLAYER_NAMETAG_TEXT) +ADD_ENUM1(REMOVE_PLAYER_NAMETAG_COLOR) +ADD_ENUM1(SET_PLAYER_NAMETAG_COLOR) +ADD_ENUM1(SET_PLAYER_NAMETAG_SHOWING) +ADD_ENUM1(SET_PED_ARMOR) +ADD_ENUM1(SET_PED_ROTATION) +ADD_ENUM1(GIVE_PED_JETPACK) +ADD_ENUM1(REMOVE_PED_JETPACK) +ADD_ENUM1(REMOVE_PED_CLOTHES) +ADD_ENUM1(SET_PED_GRAVITY) +ADD_ENUM1(SET_PED_CHOKING) +ADD_ENUM1(SET_PED_FIGHTING_STYLE) +ADD_ENUM1(SET_PED_MOVE_ANIM) +ADD_ENUM1(WARP_PED_INTO_VEHICLE) +ADD_ENUM1(REMOVE_PED_FROM_VEHICLE) +ADD_ENUM1(SET_PED_DOING_GANG_DRIVEBY) +ADD_ENUM1(SET_PED_ANIMATION) +ADD_ENUM1(SET_PED_ANIMATION_PROGRESS) +ADD_ENUM1(SET_PED_ANIMATION_SPEED) +ADD_ENUM1(SET_PED_ON_FIRE) +ADD_ENUM1(SET_PED_HEADLESS) +ADD_ENUM1(SET_PED_FROZEN) +ADD_ENUM1(RELOAD_PED_WEAPON) +ADD_ENUM1(DESTROY_ALL_VEHICLES) +ADD_ENUM1(FIX_VEHICLE) +ADD_ENUM1(BLOW_VEHICLE) +ADD_ENUM1(SET_VEHICLE_ROTATION) +ADD_ENUM1(SET_VEHICLE_TURNSPEED) +ADD_ENUM1(SET_VEHICLE_COLOR) +ADD_ENUM1(SET_VEHICLE_LOCKED) +ADD_ENUM1(SET_VEHICLE_DOORS_UNDAMAGEABLE) +ADD_ENUM1(SET_VEHICLE_SIRENE_ON) +ADD_ENUM1(SET_VEHICLE_LANDING_GEAR_DOWN) +ADD_ENUM1(SET_HELICOPTER_ROTOR_SPEED) +ADD_ENUM1(ADD_VEHICLE_UPGRADE) +ADD_ENUM1(ADD_ALL_VEHICLE_UPGRADES) +ADD_ENUM1(REMOVE_VEHICLE_UPGRADE) +ADD_ENUM1(SET_VEHICLE_DAMAGE_STATE) +ADD_ENUM1(SET_VEHICLE_OVERRIDE_LIGHTS) +ADD_ENUM1(SET_VEHICLE_ENGINE_STATE) +ADD_ENUM1(SET_VEHICLE_DIRT_LEVEL) +ADD_ENUM1(SET_VEHICLE_DAMAGE_PROOF) +ADD_ENUM1(SET_VEHICLE_PAINTJOB) +ADD_ENUM1(SET_VEHICLE_FUEL_TANK_EXPLODABLE) +ADD_ENUM1(SET_VEHICLE_WHEEL_STATES) +ADD_ENUM1(SET_VEHICLE_FROZEN) +ADD_ENUM1(SET_TRAIN_DERAILED) +ADD_ENUM1(SET_TRAIN_DERAILABLE) +ADD_ENUM1(SET_TRAIN_DIRECTION) +ADD_ENUM1(SET_TRAIN_SPEED) +ADD_ENUM1(SET_TAXI_LIGHT_ON) +ADD_ENUM1(SET_VEHICLE_HEADLIGHT_COLOR) +ADD_ENUM1(SET_VEHICLE_DOOR_OPEN_RATIO) +ADD_ENUM1(GIVE_WEAPON) +ADD_ENUM1(TAKE_WEAPON) +ADD_ENUM1(TAKE_ALL_WEAPONS) +ADD_ENUM1(SET_WEAPON_AMMO) +ADD_ENUM1(SET_WEAPON_SLOT) +ADD_ENUM1(DESTROY_ALL_BLIPS) +ADD_ENUM1(SET_BLIP_ICON) +ADD_ENUM1(SET_BLIP_SIZE) +ADD_ENUM1(SET_BLIP_COLOR) +ADD_ENUM1(SET_BLIP_ORDERING) +ADD_ENUM1(DESTROY_ALL_OBJECTS) +ADD_ENUM1(SET_OBJECT_ROTATION) +ADD_ENUM1(MOVE_OBJECT) +ADD_ENUM1(STOP_OBJECT) +ADD_ENUM1(DESTROY_ALL_RADAR_AREAS) +ADD_ENUM1(SET_RADAR_AREA_SIZE) +ADD_ENUM1(SET_RADAR_AREA_COLOR) +ADD_ENUM1(SET_RADAR_AREA_FLASHING) +ADD_ENUM1(DESTROY_ALL_MARKERS) +ADD_ENUM1(SET_MARKER_TYPE) +ADD_ENUM1(SET_MARKER_COLOR) +ADD_ENUM1(SET_MARKER_SIZE) +ADD_ENUM1(SET_MARKER_TARGET) +ADD_ENUM1(SET_MARKER_ICON) +ADD_ENUM1(DESTROY_ALL_PICKUPS) +ADD_ENUM1(SET_PICKUP_TYPE) +ADD_ENUM1(SET_PICKUP_VISIBLE) +ADD_ENUM1(PLAY_SOUND) +ADD_ENUM1(BIND_KEY) +ADD_ENUM1(UNBIND_KEY) +ADD_ENUM1(BIND_COMMAND) +ADD_ENUM1(UNBIND_COMMAND) +ADD_ENUM1(TOGGLE_CONTROL_ABILITY) +ADD_ENUM1(TOGGLE_ALL_CONTROL_ABILITY) +ADD_ENUM1(SET_CONTROL_STATE) +ADD_ENUM1(FORCE_RECONNECT) +ADD_ENUM1(SET_TEAM_NAME) +ADD_ENUM1(SET_TEAM_COLOR) +ADD_ENUM1(SET_PLAYER_TEAM) +ADD_ENUM1(SET_TEAM_FRIENDLY_FIRE) +ADD_ENUM1(SET_WANTED_LEVEL) +ADD_ENUM1(SET_CAMERA_MATRIX) +ADD_ENUM1(SET_CAMERA_TARGET) +ADD_ENUM1(SET_CAMERA_INTERIOR) +ADD_ENUM1(FADE_CAMERA) +ADD_ENUM1(SHOW_CURSOR) +ADD_ENUM1(SHOW_CHAT) +ADD_ENUM1(SET_GRAVITY) +ADD_ENUM1(SET_GAME_SPEED) +ADD_ENUM1(SET_WAVE_HEIGHT) +ADD_ENUM1(SET_SKY_GRADIENT) +ADD_ENUM1(RESET_SKY_GRADIENT) +ADD_ENUM1(SET_HEAT_HAZE) +ADD_ENUM1(RESET_HEAT_HAZE) +ADD_ENUM1(SET_BLUR_LEVEL) +ADD_ENUM1(SET_FPS_LIMIT) +ADD_ENUM1(SET_GARAGE_OPEN) +ADD_ENUM1(RESET_MAP_INFO) +ADD_ENUM1(SET_GLITCH_ENABLED) +ADD_ENUM1(SET_CLOUDS_ENABLED) +ADD_ENUM1(REMOVE_ELEMENT_DATA) +ADD_ENUM1(SET_VEHICLE_HANDLING) +ADD_ENUM1(SET_VEHICLE_HANDLING_PROPERTY) +ADD_ENUM1(RESET_VEHICLE_HANDLING_PROPERTY) +ADD_ENUM1(RESET_VEHICLE_HANDLING) +ADD_ENUM1(TOGGLE_DEBUGGER) +ADD_ENUM1(SET_ELEMENT_WATER_LEVEL) +ADD_ENUM1(SET_ALL_ELEMENT_WATER_LEVEL) +ADD_ENUM1(SET_WORLD_WATER_LEVEL) +ADD_ENUM1(RESET_WORLD_WATER_LEVEL) +ADD_ENUM1(SET_WATER_VERTEX_POSITION) +ADD_ENUM1(SET_ELEMENT_DOUBLESIDED) +ADD_ENUM1(SET_TRAFFIC_LIGHT_STATE) +ADD_ENUM1(SET_VEHICLE_TURRET_POSITION) +ADD_ENUM1(SET_OBJECT_SCALE) +ADD_ENUM1(SET_ELEMENT_COLLISIONS_ENABLED) +ADD_ENUM1(SET_JETPACK_MAXHEIGHT) +ADD_ENUM1(SET_WATER_COLOR) +ADD_ENUM1(RESET_WATER_COLOR) +ADD_ENUM1(SET_ELEMENT_FROZEN) +ADD_ENUM1(SET_LOW_LOD_ELEMENT) +ADD_ENUM1(SET_BLIP_VISIBLE_DISTANCE) +ADD_ENUM1(SET_INTERIOR_SOUNDS_ENABLED) +ADD_ENUM1(SET_RAIN_LEVEL) +ADD_ENUM1(SET_SUN_SIZE) +ADD_ENUM1(SET_SUN_COLOR) +ADD_ENUM1(SET_WIND_VELOCITY) +ADD_ENUM1(SET_FAR_CLIP_DISTANCE) +ADD_ENUM1(SET_FOG_DISTANCE) +ADD_ENUM1(RESET_RAIN_LEVEL) +ADD_ENUM1(RESET_SUN_SIZE) +ADD_ENUM1(RESET_SUN_COLOR) +ADD_ENUM1(RESET_WIND_VELOCITY) +ADD_ENUM1(RESET_FAR_CLIP_DISTANCE) +ADD_ENUM1(RESET_FOG_DISTANCE) +ADD_ENUM1(SET_AIRCRAFT_MAXHEIGHT) +ADD_ENUM1(SET_WEAPON_PROPERTY) +ADD_ENUM1(SET_VEHICLE_VARIANT) +ADD_ENUM1(REMOVE_WORLD_MODEL) +ADD_ENUM1(RESTORE_WORLD_MODEL) +ADD_ENUM1(RESTORE_ALL_WORLD_MODELS) +ADD_ENUM1(TAKE_PLAYER_SCREEN_SHOT) +ADD_ENUM1(SET_OCCLUSIONS_ENABLED) +ADD_ENUM1(GIVE_VEHICLE_SIRENS) +ADD_ENUM1(REMOVE_VEHICLE_SIRENS) +ADD_ENUM1(SET_VEHICLE_SIRENS) +ADD_ENUM1(SET_SYNC_INTERVALS) +ADD_ENUM1(SET_JETPACK_WEAPON_ENABLED) +ADD_ENUM1(FIRE_CUSTOM_WEAPON) +ADD_ENUM1(SET_CUSTOM_WEAPON_STATE) +ADD_ENUM1(SET_CUSTOM_WEAPON_AMMO) +ADD_ENUM1(SET_CUSTOM_WEAPON_CLIP_AMMO) +ADD_ENUM1(SET_CUSTOM_WEAPON_TARGET) +ADD_ENUM1(RESET_CUSTOM_WEAPON_TARGET) +ADD_ENUM1(SET_CUSTOM_WEAPON_FLAGS) +ADD_ENUM1(SET_CUSTOM_WEAPON_FIRING_RATE) +ADD_ENUM1(RESET_CUSTOM_WEAPON_FIRING_RATE) +ADD_ENUM1(SET_WEAPON_OWNER) +ADD_ENUM1(SET_AIRCRAFT_MAXVELOCITY) +ADD_ENUM1(RESET_MOON_SIZE) +ADD_ENUM1(SET_MOON_SIZE) +ADD_ENUM1(SET_VEHICLE_PLATE_TEXT) +ADD_ENUM1(SET_PROPAGATE_CALLS_ENABLED) +ADD_ENUM1(SET_TRAIN_TRACK) +ADD_ENUM1(SET_TRAIN_POSITION) +ADD_ENUM1(SET_ELEMENT_ANGULAR_VELOCITY) +ADD_ENUM1(SET_COLSHAPE_RADIUS) +ADD_ENUM1(SET_COLSHAPE_SIZE) +ADD_ENUM1(ADD_COLPOLYGON_POINT) +ADD_ENUM1(REMOVE_COLPOLYGON_POINT) +ADD_ENUM1(UPDATE_COLPOLYGON_POINT) +ADD_ENUM1(SET_DISCORD_JOIN_PARAMETERS) +IMPLEMENT_ENUM_END("eElementRPCFunctions") + +DECLARE_ENUM(CRPCFunctions::eRPCFunctions); +IMPLEMENT_ENUM_BEGIN(CRPCFunctions::eRPCFunctions) +ADD_ENUM1(CRPCFunctions::PLAYER_INGAME_NOTICE) +ADD_ENUM1(CRPCFunctions::INITIAL_DATA_STREAM) +ADD_ENUM1(CRPCFunctions::PLAYER_TARGET) +ADD_ENUM1(CRPCFunctions::PLAYER_WEAPON) +ADD_ENUM1(CRPCFunctions::KEY_BIND) +ADD_ENUM1(CRPCFunctions::CURSOR_EVENT) +ADD_ENUM1(CRPCFunctions::REQUEST_STEALTH_KILL) +IMPLEMENT_ENUM_END("eRPCFunctions") + +struct SRPCPacketStat +{ + int iCount; + int iTotalBytes; +}; + +/////////////////////////////////////////////////////////////// +// +// CPerfStatRPCPacketUsageImpl +// +// +// +/////////////////////////////////////////////////////////////// +class CPerfStatRPCPacketUsageImpl : public CPerfStatRPCPacketUsage +{ +public: + ZERO_ON_NEW + + CPerfStatRPCPacketUsageImpl(); + virtual ~CPerfStatRPCPacketUsageImpl(); + + // CPerfStatModule + virtual const SString& GetCategoryName(); + virtual void DoPulse(); + virtual void GetStats(CPerfStatResult* pOutResult, const std::map& optionMap, const SString& strFilter); + + // CPerfStatRPCPacketUsage + virtual void UpdatePacketUsageIn(uchar ucRpcId, uint uiSize); + virtual void UpdatePacketUsageOut(uchar ucRpcId, uint uiSize); + + // CPerfStatRPCPacketUsageImpl + void MaybeRecordStats(); + + int m_iStatsCleared; + CElapsedTime m_TimeSinceGetStats; + long long m_llNextRecordTime; + SString m_strCategoryName; + SRPCPacketStat m_PrevPacketStatsIn[256]; + SRPCPacketStat m_PacketStatsIn[256]; + SRPCPacketStat m_PacketStatsLiveIn[256]; + SFixedArray m_ShownPacketStatsIn; + SRPCPacketStat m_PrevPacketStatsOut[256]; + SRPCPacketStat m_PacketStatsOut[256]; + SRPCPacketStat m_PacketStatsLiveOut[256]; + SFixedArray m_ShownPacketStatsOut; +}; + +/////////////////////////////////////////////////////////////// +// +// Temporary home for global object +// +// +// +/////////////////////////////////////////////////////////////// +static std::unique_ptr g_pPerfStatRPCPacketUsageImp; + +CPerfStatRPCPacketUsage* CPerfStatRPCPacketUsage::GetSingleton() +{ + if (!g_pPerfStatRPCPacketUsageImp) + g_pPerfStatRPCPacketUsageImp.reset(new CPerfStatRPCPacketUsageImpl()); + return g_pPerfStatRPCPacketUsageImp.get(); +} + +/////////////////////////////////////////////////////////////// +// +// CPerfStatRPCPacketUsageImpl::CPerfStatRPCPacketUsageImpl +// +// +// +/////////////////////////////////////////////////////////////// +CPerfStatRPCPacketUsageImpl::CPerfStatRPCPacketUsageImpl() +{ + m_strCategoryName = "RPC Packet usage"; + assert(sizeof(m_PacketStatsIn) == sizeof(m_PrevPacketStatsIn)); + assert(sizeof(m_PacketStatsIn) == sizeof(m_PacketStatsLiveIn)); +} + +/////////////////////////////////////////////////////////////// +// +// CPerfStatRPCPacketUsageImpl::CPerfStatRPCPacketUsageImpl +// +// +// +/////////////////////////////////////////////////////////////// +CPerfStatRPCPacketUsageImpl::~CPerfStatRPCPacketUsageImpl() +{ +} + +/////////////////////////////////////////////////////////////// +// +// CPerfStatRPCPacketUsageImpl::GetCategoryName +// +// +// +/////////////////////////////////////////////////////////////// +const SString& CPerfStatRPCPacketUsageImpl::GetCategoryName() +{ + return m_strCategoryName; +} + +/////////////////////////////////////////////////////////////// +// +// CPerfStatRPCPacketUsageImpl::DoPulse +// +// +// +/////////////////////////////////////////////////////////////// +void CPerfStatRPCPacketUsageImpl::DoPulse() +{ + MaybeRecordStats(); +} + +/////////////////////////////////////////////////////////////// +// +// CPerfStatRPCPacketUsageImpl::UpdatePacketUsageIn +// +// +// +/////////////////////////////////////////////////////////////// +void CPerfStatRPCPacketUsageImpl::UpdatePacketUsageIn(uchar ucRpcId, uint uiSize) +{ + SRPCPacketStat& stat = m_PacketStatsLiveIn[ucRpcId]; + stat.iCount++; + stat.iTotalBytes += uiSize; +} + +/////////////////////////////////////////////////////////////// +// +// CPerfStatRPCPacketUsageImpl::UpdatePacketUsageOut +// +// +// +/////////////////////////////////////////////////////////////// +void CPerfStatRPCPacketUsageImpl::UpdatePacketUsageOut(uchar ucRpcId, uint uiSize) +{ + SRPCPacketStat& stat = m_PacketStatsLiveOut[ucRpcId]; + stat.iCount++; + stat.iTotalBytes += uiSize; +} + +/////////////////////////////////////////////////////////////// +// +// CPerfStatRPCPacketUsageImpl::MaybeRecordStats +// +// +// +/////////////////////////////////////////////////////////////// +void CPerfStatRPCPacketUsageImpl::MaybeRecordStats() +{ + // Someone watching? + if (m_TimeSinceGetStats.Get() < 10000) + { + // Time for record update? // Copy and clear once every 5 seconds + long long llTime = GetTickCount64_(); + if (llTime >= m_llNextRecordTime) + { + m_llNextRecordTime = std::max(m_llNextRecordTime + 5000, llTime + 5000 / 10 * 9); + + // Save previous sample so we can calc the delta values + memcpy(m_PrevPacketStatsIn, m_PacketStatsIn, sizeof(m_PacketStatsIn)); + memcpy(m_PacketStatsIn, m_PacketStatsLiveIn, sizeof(m_PacketStatsIn)); + + memcpy(m_PrevPacketStatsOut, m_PacketStatsOut, sizeof(m_PacketStatsOut)); + memcpy(m_PacketStatsOut, m_PacketStatsLiveOut, sizeof(m_PacketStatsOut)); + + if (m_iStatsCleared == 1) + { + // Prime if was zeroed + memcpy(m_PrevPacketStatsIn, m_PacketStatsIn, sizeof(m_PacketStatsIn)); + memcpy(m_PrevPacketStatsOut, m_PacketStatsOut, sizeof(m_PacketStatsOut)); + m_iStatsCleared = 2; + } + else if (m_iStatsCleared == 2) + m_iStatsCleared = 0; + } + } + else + { + // No one watching + if (!m_iStatsCleared) + { + memset(m_PrevPacketStatsIn, 0, sizeof(m_PacketStatsIn)); + memset(m_PacketStatsIn, 0, sizeof(m_PacketStatsIn)); + memset(m_PrevPacketStatsOut, 0, sizeof(m_PacketStatsOut)); + memset(m_PacketStatsOut, 0, sizeof(m_PacketStatsOut)); + m_iStatsCleared = 1; + } + } +} + +/////////////////////////////////////////////////////////////// +// +// CPerfStatRPCPacketUsageImpl::GetStats +// +// +// +/////////////////////////////////////////////////////////////// +void CPerfStatRPCPacketUsageImpl::GetStats(CPerfStatResult* pResult, const std::map& strOptionMap, const SString& strFilter) +{ + m_TimeSinceGetStats.Reset(); + MaybeRecordStats(); + + // + // Set option flags + // + bool bHelp = MapContains(strOptionMap, "h"); + + // + // Process help + // + if (bHelp) + { + pResult->AddColumn("RPC Packet usage help"); + pResult->AddRow()[0] = "Option h - This help"; + return; + } + + // Add columns + pResult->AddColumn("Packet type"); + pResult->AddColumn("Incoming.msgs/sec"); + pResult->AddColumn("Incoming.bytes/sec"); + pResult->AddColumn("Incoming.cpu"); + pResult->AddColumn("Outgoing.msgs/sec"); + pResult->AddColumn("Outgoing.bytes/sec"); + pResult->AddColumn("Outgoing.cpu"); + + if (m_iStatsCleared) + { + pResult->AddRow()[0] = "Sampling... Please wait"; + } + + long long llTickCountNow = CTickCount::Now().ToLongLong(); + + // Fill rows + for (uint i = 0; i < 256; i++) + { + // Calc incoming delta values + SPacketStat statInDelta; + { + const SRPCPacketStat& statInPrev = m_PrevPacketStatsIn[i]; + const SRPCPacketStat& statInNow = m_PacketStatsIn[i]; + statInDelta.iCount = statInNow.iCount - statInPrev.iCount; + statInDelta.iTotalBytes = statInNow.iTotalBytes - statInPrev.iTotalBytes; + // statInDelta.totalTime = statInNow.totalTime - statInPrev.totalTime; + } + + if (!statInDelta.iCount) + { + // Once displayed, keep a row displayed for at least 20 seconds + if (llTickCountNow - m_ShownPacketStatsIn[i] > 20000) + continue; + } + else + { + m_ShownPacketStatsIn[i] = llTickCountNow; + } + + // Add row + SString* row = pResult->AddRow(); + + int c = 0; + // Turn "CRPCFunctions::PLAYER_WEAPON" into "64_Player_weapon" + SString strPacketDesc = EnumToString((CRPCFunctions::eRPCFunctions)i).SplitRight("CRPCFunctions::", NULL, -1).ToLower(); + row[c++] = SString("%d_", i) + strPacketDesc.Left(1).ToUpper() + strPacketDesc.SubStr(1); + + if (statInDelta.iCount) + { + row[c++] = SString("%d", (statInDelta.iCount + 4) / 5); + row[c++] = CPerfStatManager::GetScaledByteString((statInDelta.iTotalBytes + 4) / 5); + row[c++] = "n/a"; + } + else + { + row[c++] = "-"; + row[c++] = "-"; + row[c++] = "-"; + } + + row[c++] = "-"; + row[c++] = "-"; + row[c++] = "-"; + } + + // Fill rows + for (uint i = 0; i < 256; i++) + { + // Calc outgoing delta values + SRPCPacketStat statOutDelta; + { + const SRPCPacketStat& statOutPrev = m_PrevPacketStatsOut[i]; + const SRPCPacketStat& statOutNow = m_PacketStatsOut[i]; + statOutDelta.iCount = statOutNow.iCount - statOutPrev.iCount; + statOutDelta.iTotalBytes = statOutNow.iTotalBytes - statOutPrev.iTotalBytes; + } + + if (!statOutDelta.iCount) + { + // Once displayed, keep a row displayed for at least 20 seconds + if (llTickCountNow - m_ShownPacketStatsOut[i] > 20000) + continue; + } + else + { + m_ShownPacketStatsOut[i] = llTickCountNow; + } + + // Add row + SString* row = pResult->AddRow(); + + int c = 0; + // Turn "SET_WEAPON_OWNER" into "64_Set_weapon_owner" + SString strPacketDesc = EnumToString((eElementRPCFunctions)i).ToLower(); + row[c++] = SString("%d_", i) + strPacketDesc.Left(1).ToUpper() + strPacketDesc.SubStr(1); + + row[c++] = "-"; + row[c++] = "-"; + row[c++] = "-"; + + if (statOutDelta.iCount) + { + row[c++] = SString("%d", (statOutDelta.iCount + 4) / 5); + row[c++] = CPerfStatManager::GetScaledByteString((statOutDelta.iTotalBytes + 4) / 5); + row[c++] = "n/a"; + } + else + { + row[c++] = "-"; + row[c++] = "-"; + row[c++] = "-"; + } + } +} diff --git a/Server/mods/deathmatch/logic/CPlayer.h b/Server/mods/deathmatch/logic/CPlayer.h index 759992a3691..c1f626d927d 100644 --- a/Server/mods/deathmatch/logic/CPlayer.h +++ b/Server/mods/deathmatch/logic/CPlayer.h @@ -87,15 +87,17 @@ class CPlayer : public CPed, public CClient const char* GetNick() { return m_strNick; }; void SetNick(const char* szNick); - int GetGameVersion() { return m_iGameVersion; }; - void SetGameVersion(int iGameVersion) { m_iGameVersion = iGameVersion; }; - unsigned short GetMTAVersion() { return m_usMTAVersion; }; - void SetMTAVersion(unsigned short usMTAVersion) { m_usMTAVersion = usMTAVersion; }; - unsigned short GetBitStreamVersion() { return m_usBitStreamVersion; }; - void SetBitStreamVersion(unsigned short usBitStreamVersion) { m_usBitStreamVersion = usBitStreamVersion; }; + int GetGameVersion() { return m_iGameVersion; }; + void SetGameVersion(int iGameVersion) { m_iGameVersion = iGameVersion; }; + unsigned short GetMTAVersion() { return m_usMTAVersion; }; + void SetMTAVersion(unsigned short usMTAVersion) { m_usMTAVersion = usMTAVersion; }; + unsigned short GetBitStreamVersion() { return m_usBitStreamVersion; }; + void SetBitStreamVersion(unsigned short usBitStreamVersion) { m_usBitStreamVersion = usBitStreamVersion; }; void SetPlayerVersion(const CMtaVersion& strPlayerVersion); const CMtaVersion& GetPlayerVersion() { return m_strPlayerVersion; }; bool ShouldIgnoreMinClientVersionChecks(); + void SetDiscordJoinSecret(const SString& joinSecret) { m_strDiscordJoinSecret = joinSecret; } + SString GetDiscordJoinSecret() const { return m_strDiscordJoinSecret; } bool IsMuted() { return m_bIsMuted; }; void SetMuted(bool bSetMuted) { m_bIsMuted = bSetMuted; }; @@ -356,6 +358,7 @@ class CPlayer : public CPed, public CClient bool m_bIsMuted; bool m_bIsLeavingServer; bool m_bIsJoined; + SString m_strDiscordJoinSecret; bool m_bNametagColorOverridden; diff --git a/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp b/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index 894697688dc..a8ae89726b7 100644 --- a/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp +++ b/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp @@ -3299,6 +3299,34 @@ bool CStaticFunctionDefinitions::SetPlayerBlurLevel(CElement* pElement, unsigned return false; } +bool CStaticFunctionDefinitions::SetPlayerDiscordJoinParams(CElement* pElement, SString& strKey, SString& strPartyId, uint uiPartySize, uint uiPartyMax) +{ + assert(pElement); + + if (uiPartyMax > m_pMainConfig->GetMaxPlayers() || uiPartySize > uiPartyMax || strKey.length() > 64 || strPartyId.length() > 64 || strKey.find(' ') != SString::npos || strPartyId.find(' ') != SString::npos) + return false; + + RUN_CHILDREN(SetPlayerDiscordJoinParams(*iter, strKey, strPartyId, uiPartySize, uiPartyMax)) + + if (IS_PLAYER(pElement)) + { + CPlayer* pPlayer = static_cast(pElement); + + if (pPlayer->GetBitStreamVersion() < 0x06D) + return false; + + CBitStream bitStream; + bitStream.pBitStream->WriteString(strKey); + bitStream.pBitStream->WriteString(strPartyId); + bitStream.pBitStream->Write(uiPartySize); + bitStream.pBitStream->Write(uiPartyMax); + pPlayer->Send(CLuaPacket(SET_DISCORD_JOIN_PARAMETERS, *bitStream.pBitStream)); + + return true; + } + return false; +} + bool CStaticFunctionDefinitions::RedirectPlayer(CElement* pElement, const char* szHost, unsigned short usPort, const char* szPassword) { if (IS_PLAYER(pElement)) @@ -9979,6 +10007,7 @@ bool CStaticFunctionDefinitions::SetMaxPlayers(unsigned int uiMax) return false; m_pMainConfig->SetSoftMaxPlayers(uiMax); g_pNetServer->SetMaximumIncomingConnections(uiMax); + g_pGame->GetPlayerManager()->BroadcastOnlyJoined(CServerInfoSyncPacket(SERVER_INFO_FLAG_MAX_PLAYERS)); return true; } diff --git a/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.h b/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.h index 9de6cc1cc0f..9c33e82ca0f 100644 --- a/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.h +++ b/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.h @@ -143,6 +143,7 @@ class CStaticFunctionDefinitions unsigned short usDimension, CTeam* pTeam = NULL); static bool SetPlayerMuted(CElement* pElement, bool bMuted); static bool SetPlayerBlurLevel(CElement* pElement, unsigned char ucLevel); + static bool SetPlayerDiscordJoinParams(CElement* pElement, SString& strKey, SString& strPartyId, uint uiPartySize, uint uiPartyMax); static bool RedirectPlayer(CElement* pElement, const char* szHost, unsigned short usPort, const char* szPassword); static bool SetPlayerName(CElement* pElement, const char* szName); static bool DetonateSatchels(CElement* pElement); diff --git a/Server/mods/deathmatch/logic/luadefs/CLuaPlayerDefs.cpp b/Server/mods/deathmatch/logic/luadefs/CLuaPlayerDefs.cpp index 5c2bf0f5bc8..eee839360d6 100644 --- a/Server/mods/deathmatch/logic/luadefs/CLuaPlayerDefs.cpp +++ b/Server/mods/deathmatch/logic/luadefs/CLuaPlayerDefs.cpp @@ -60,6 +60,7 @@ void CLuaPlayerDefs::LoadFunctions() {"setPlayerNametagShowing", SetPlayerNametagShowing}, {"setPlayerMuted", SetPlayerMuted}, {"setPlayerBlurLevel", SetPlayerBlurLevel}, + {"setPlayerDiscordJoinParams", SetPlayerDiscordJoinParams}, {"redirectPlayer", RedirectPlayer}, {"setPlayerName", SetPlayerName}, {"detonateSatchels", DetonateSatchels}, @@ -141,6 +142,7 @@ void CLuaPlayerDefs::AddClass(lua_State* luaVM) lua_classfunction(luaVM, "setMuted", "setPlayerMuted"); lua_classfunction(luaVM, "setName", "setPlayerName"); lua_classfunction(luaVM, "setBlurLevel", "setPlayerBlurLevel"); + lua_classfunction(luaVM, "setDiscordJoinParams", "setPlayerDiscordJoinParams"); lua_classfunction(luaVM, "setWantedLevel", "setPlayerWantedLevel"); lua_classfunction(luaVM, "setMoney", "setPlayerMoney"); lua_classfunction(luaVM, "setNametagText", "setPlayerNametagText"); @@ -1187,6 +1189,38 @@ int CLuaPlayerDefs::SetPlayerBlurLevel(lua_State* luaVM) return 1; } +int CLuaPlayerDefs::SetPlayerDiscordJoinParams(lua_State* luaVM) +{ + CElement* pElement; + SString strKey; + SString strPartyId; + uint uiPartySize; + uint uiPartyMax; + + CScriptArgReader argStream(luaVM); + argStream.ReadUserData(pElement); + argStream.ReadString(strKey); + argStream.ReadString(strPartyId); + argStream.ReadNumber(uiPartySize); + argStream.ReadNumber(uiPartyMax); + + if (!argStream.HasErrors()) + { + LogWarningIfPlayerHasNotJoinedYet(luaVM, pElement); + + if (CStaticFunctionDefinitions::SetPlayerDiscordJoinParams(pElement, strKey, strPartyId, uiPartySize, uiPartyMax)) + { + lua_pushboolean(luaVM, true); + return 1; + } + } + else + return luaL_error(luaVM, argStream.GetFullErrorMessage()); + + lua_pushboolean(luaVM, false); + return 1; +} + int CLuaPlayerDefs::RedirectPlayer(lua_State* luaVM) { CPlayer* pElement; diff --git a/Server/mods/deathmatch/logic/luadefs/CLuaPlayerDefs.h b/Server/mods/deathmatch/logic/luadefs/CLuaPlayerDefs.h index b25e686a626..c1cc811e6f5 100644 --- a/Server/mods/deathmatch/logic/luadefs/CLuaPlayerDefs.h +++ b/Server/mods/deathmatch/logic/luadefs/CLuaPlayerDefs.h @@ -61,6 +61,7 @@ class CLuaPlayerDefs : public CLuaDefs LUA_DECLARE(SetPlayerNametagShowing); LUA_DECLARE(SetPlayerMuted); LUA_DECLARE(SetPlayerBlurLevel); + LUA_DECLARE(SetPlayerDiscordJoinParams); LUA_DECLARE(RedirectPlayer); LUA_DECLARE(SetPlayerName); LUA_DECLARE(DetonateSatchels); diff --git a/Server/mods/deathmatch/logic/packets/CDiscordJoinPacket.cpp b/Server/mods/deathmatch/logic/packets/CDiscordJoinPacket.cpp new file mode 100644 index 00000000000..c3be7b72ddc --- /dev/null +++ b/Server/mods/deathmatch/logic/packets/CDiscordJoinPacket.cpp @@ -0,0 +1,16 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/packets/CDiscordJoinPacket.cpp + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" + +bool CDiscordJoinPacket::Read(NetBitStreamInterface& BitStream) +{ + return BitStream.ReadString(m_secretKey); +} diff --git a/Server/mods/deathmatch/logic/packets/CDiscordJoinPacket.h b/Server/mods/deathmatch/logic/packets/CDiscordJoinPacket.h new file mode 100644 index 00000000000..64c7016602d --- /dev/null +++ b/Server/mods/deathmatch/logic/packets/CDiscordJoinPacket.h @@ -0,0 +1,30 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/packets/CDiscordJoinPacket.h + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +#include "CPacket.h" + +class CDiscordJoinPacket : public CPacket +{ +public: + CDiscordJoinPacket() {} + + ePacketID GetPacketID() const { return PACKET_ID_DISCORD_JOIN; } + unsigned long GetFlags() const { return PACKET_LOW_PRIORITY | PACKET_RELIABLE | PACKET_SEQUENCED; } + virtual ePacketOrdering GetPacketOrdering() const { return PACKET_ORDERING_DEFAULT; } + + bool Read(NetBitStreamInterface& BitStream); + + SString GetSecret() const { return m_secretKey; } + +private: + SString m_secretKey; +}; diff --git a/Server/mods/deathmatch/logic/packets/CPlayerJoinDataPacket.cpp b/Server/mods/deathmatch/logic/packets/CPlayerJoinDataPacket.cpp index 4afae18c827..8d93c5e43d8 100644 --- a/Server/mods/deathmatch/logic/packets/CPlayerJoinDataPacket.cpp +++ b/Server/mods/deathmatch/logic/packets/CPlayerJoinDataPacket.cpp @@ -30,6 +30,13 @@ bool CPlayerJoinDataPacket::Read(NetBitStreamInterface& BitStream) // Shrink string sizes to fit m_strNick = *m_strNick; m_strSerialUser = *m_strSerialUser; + + if (m_usBitStreamVersion >= 0x06D) + { + if (!BitStream.ReadString(m_strDiscordSecret)) + return false; + } + return true; } return false; diff --git a/Server/mods/deathmatch/logic/packets/CPlayerJoinDataPacket.h b/Server/mods/deathmatch/logic/packets/CPlayerJoinDataPacket.h index 2d854a988aa..c7089fa9c14 100644 --- a/Server/mods/deathmatch/logic/packets/CPlayerJoinDataPacket.h +++ b/Server/mods/deathmatch/logic/packets/CPlayerJoinDataPacket.h @@ -29,8 +29,8 @@ class CPlayerJoinDataPacket : public CPacket void SetNetVersion(unsigned short usNetVersion) { m_usNetVersion = usNetVersion; }; void SetGameVersion(unsigned char ucGameVersion) { m_ucGameVersion = ucGameVersion; }; - unsigned short GetMTAVersion() { return m_usMTAVersion; }; - unsigned short GetBitStreamVersion() { return m_usBitStreamVersion; }; + unsigned short GetMTAVersion() { return m_usMTAVersion; }; + unsigned short GetBitStreamVersion() { return m_usBitStreamVersion; }; const CMtaVersion& GetPlayerVersion() { return m_strPlayerVersion; }; const char* GetNick() { return m_strNick; }; @@ -42,6 +42,8 @@ class CPlayerJoinDataPacket : public CPacket const char* GetSerialUser() { return m_strSerialUser; } void SetSerialUser(const char* szSerialUser) { m_strSerialUser.AssignLeft(szSerialUser, MAX_SERIAL_LENGTH); } + const char* GetDiscordJoinSecret() const { return (m_strDiscordSecret.length() > 64 ? "" : m_strDiscordSecret); } + bool IsOptionalUpdateInfoRequired() { return m_bOptionalUpdateInfoRequired; } private: @@ -54,4 +56,5 @@ class CPlayerJoinDataPacket : public CPacket MD5 m_Password; SString m_strSerialUser; CMtaVersion m_strPlayerVersion; + SString m_strDiscordSecret; }; diff --git a/Server/mods/deathmatch/logic/packets/CServerInfoSyncPacket.cpp b/Server/mods/deathmatch/logic/packets/CServerInfoSyncPacket.cpp new file mode 100644 index 00000000000..5a9db498bbb --- /dev/null +++ b/Server/mods/deathmatch/logic/packets/CServerInfoSyncPacket.cpp @@ -0,0 +1,30 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/packets/CServerInfoSyncPacket.cpp + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" + +bool CServerInfoSyncPacket::Write(NetBitStreamInterface& BitStream) const +{ + if (m_ActualInfo) // Flag is set + { + BitStream.Write(m_ActualInfo); + + // Check the flags one by one & write in order + if (maxPlayers) + BitStream.Write(static_cast( + CStaticFunctionDefinitions::GetMaxPlayers())); // static_cast ensures the type is uint in case it's changed in future + + // other info + + return true; + } + + return false; +} diff --git a/Server/mods/deathmatch/logic/packets/CServerInfoSyncPacket.h b/Server/mods/deathmatch/logic/packets/CServerInfoSyncPacket.h new file mode 100644 index 00000000000..a704e443c39 --- /dev/null +++ b/Server/mods/deathmatch/logic/packets/CServerInfoSyncPacket.h @@ -0,0 +1,42 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/packets/CServerInfoSyncPacket.h + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +#include "CPacket.h" + +enum EServerInfoSyncFlag : uint8 +{ + SERVER_INFO_FLAG_ALL = 0xFF, // 0b11111111 + SERVER_INFO_FLAG_MAX_PLAYERS = 1, // 0b00000001 + SERVER_INFO_FLAG_RESERVED = 1 << 1 // 0b00000010 and so on +}; + +class CServerInfoSyncPacket : public CPacket +{ +public: + CServerInfoSyncPacket(uint8 flags) { m_ActualInfo = flags; } + + ePacketID GetPacketID() const { return PACKET_ID_SERVER_INFO_SYNC; } + unsigned long GetFlags() const { return PACKET_LOW_PRIORITY | PACKET_RELIABLE | PACKET_SEQUENCED; } + virtual ePacketOrdering GetPacketOrdering() const { return PACKET_ORDERING_DEFAULT; } + + bool Write(NetBitStreamInterface& BitStream) const; + +private: + union { + uint8 m_ActualInfo; + struct + { + bool maxPlayers : 1; + bool reserved : 7; // for future? + }; + }; +}; diff --git a/Shared/data/MTA San Andreas/MTA/.gitignore b/Shared/data/MTA San Andreas/MTA/.gitignore new file mode 100644 index 00000000000..d4002bd7667 --- /dev/null +++ b/Shared/data/MTA San Andreas/MTA/.gitignore @@ -0,0 +1 @@ +discord_game_sdk.dll diff --git a/Shared/data/data files installer.nsi b/Shared/data/data files installer.nsi index 140e3f98d4b..048798f544f 100644 --- a/Shared/data/data files installer.nsi +++ b/Shared/data/data files installer.nsi @@ -147,6 +147,8 @@ Section "Data files" SEC01 File "${FILES_ROOT}\MTA San Andreas\mta\libEGL.dll" File "${FILES_ROOT}\MTA San Andreas\mta\libGLESv2.dll" + File "${FILES_ROOT}\MTA San Andreas\mta\discord_game_sdk.dll" + SetOutPath "$INSTDIR\MTA\config" File "${FILES_ROOT}\MTA San Andreas\mta\config\chatboxpresets.xml" diff --git a/Shared/installer/nightly.nsi b/Shared/installer/nightly.nsi index 105971bfa1f..f96d5a6117d 100644 --- a/Shared/installer/nightly.nsi +++ b/Shared/installer/nightly.nsi @@ -725,6 +725,8 @@ SectionGroup /e "$(INST_SEC_CLIENT)" SECGCLIENT File "${FILES_ROOT}\mta\basswma.dll" File "${FILES_ROOT}\mta\tags.dll" + File "${FILES_ROOT}\mta\discord_game_sdk.dll" + SetOutPath "$INSTDIR\MTA" File "${FILES_ROOT}\mta\chrome_elf.dll" File "${FILES_ROOT}\mta\libcef.dll" diff --git a/Shared/mods/deathmatch/logic/Enums.cpp b/Shared/mods/deathmatch/logic/Enums.cpp index 8afce3aa372..9dd96ff1758 100644 --- a/Shared/mods/deathmatch/logic/Enums.cpp +++ b/Shared/mods/deathmatch/logic/Enums.cpp @@ -161,4 +161,8 @@ ADD_ENUM1(PACKET_ID_WEAPON_BULLETSYNC) ADD_ENUM1(PACKET_ID_PED_TASK) ADD_ENUM1(PACKET_ID_PLAYER_NO_SOCKET) ADD_ENUM1(PACKET_ID_PLAYER_NETWORK_STATUS) +ADD_ENUM1(PACKET_ID_PLAYER_ACINFO) +ADD_ENUM1(PACKET_ID_CHAT_CLEAR) +ADD_ENUM1(PACKET_ID_SERVER_INFO_SYNC) +ADD_ENUM1(PACKET_ID_DISCORD_JOIN) IMPLEMENT_ENUM_END("ePacketID") diff --git a/Shared/sdk/SharedUtil.Misc.h b/Shared/sdk/SharedUtil.Misc.h index 1ddeae2b50b..4b82d078858 100644 --- a/Shared/sdk/SharedUtil.Misc.h +++ b/Shared/sdk/SharedUtil.Misc.h @@ -47,6 +47,11 @@ namespace SharedUtil int MessageBoxUTF8(HWND hWnd, SString lpText, SString lpCaption, UINT uType); #endif + // + // Return full path and filename of parent exe + // + SString GetParentProcessPathFilename(int pid); + // // Get startup directory as saved in the registry by the launcher // Used in the Win32 Client only diff --git a/Shared/sdk/SharedUtil.Misc.hpp b/Shared/sdk/SharedUtil.Misc.hpp index f0c83f37db9..6b67f65cfab 100644 --- a/Shared/sdk/SharedUtil.Misc.hpp +++ b/Shared/sdk/SharedUtil.Misc.hpp @@ -81,7 +81,7 @@ int SharedUtil::MessageBoxUTF8(HWND hWnd, SString lpText, SString lpCaption, UIN // // Return full path and filename of parent exe // -SString GetParentProcessPathFilename(int pid) +SString SharedUtil::GetParentProcessPathFilename(int pid) { HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); PROCESSENTRY32W pe = {sizeof(PROCESSENTRY32W)}; diff --git a/Shared/sdk/SharedUtil.Thread.h b/Shared/sdk/SharedUtil.Thread.h index 1e006ea11eb..c7d91e23eae 100644 --- a/Shared/sdk/SharedUtil.Thread.h +++ b/Shared/sdk/SharedUtil.Thread.h @@ -8,6 +8,8 @@ * *****************************************************************************/ +#pragma once + #include #include diff --git a/Shared/sdk/net/Packets.h b/Shared/sdk/net/Packets.h index 9b9b903b0c7..214d1902c7c 100644 --- a/Shared/sdk/net/Packets.h +++ b/Shared/sdk/net/Packets.h @@ -148,4 +148,6 @@ enum ePacketID PACKET_ID_PLAYER_NETWORK_STATUS, PACKET_ID_PLAYER_ACINFO, PACKET_ID_CHAT_CLEAR, + PACKET_ID_SERVER_INFO_SYNC, + PACKET_ID_DISCORD_JOIN, }; diff --git a/Shared/sdk/net/rpc_enums.h b/Shared/sdk/net/rpc_enums.h index 1b0f6615b19..7501a97d5ec 100644 --- a/Shared/sdk/net/rpc_enums.h +++ b/Shared/sdk/net/rpc_enums.h @@ -268,6 +268,8 @@ enum eElementRPCFunctions ADD_COLPOLYGON_POINT, REMOVE_COLPOLYGON_POINT, UPDATE_COLPOLYGON_POINT, + + SET_DISCORD_JOIN_PARAMETERS, NUM_RPC_FUNCS // Add above this line }; diff --git a/premake5.lua b/premake5.lua index 819672ea640..da238309393 100644 --- a/premake5.lua +++ b/premake5.lua @@ -4,6 +4,7 @@ require "compose_files" require "install_data" require "install_resources" require "install_cef" +require "install_discord" -- Set CI Build global local ci = os.getenv("CI") @@ -133,6 +134,7 @@ workspace "MTASA" include "vendor/tinygettext" include "vendor/pthreads" include "vendor/libspeex" + include "vendor/discordgsdk" end filter {} diff --git a/utils/buildactions/install_discord.lua b/utils/buildactions/install_discord.lua new file mode 100644 index 00000000000..7c53273068a --- /dev/null +++ b/utils/buildactions/install_discord.lua @@ -0,0 +1,52 @@ +require 'utils' + +premake.modules.install_discord = {} + +-- Config variables +local DISCORD_BASEURL = "https://github.com/multitheftauto/discord_game_sdk/releases/download/" +local DISCORD_FILENAME = "cpp_x86.zip" +local DISCORD_VENDOR = "vendor/discordgsdk" + +-- Change these to update the version +local DISCORD_TAG = "v2020-11-02_21-48-56" +local DISCORD_MD5 = "3d7b86f7fee560d85de3d6e9bac1efbb" + +newaction { + trigger = "install_discord", + description = "Downloads and installs Discord SDK", + + execute = function() + -- Only execute on Windows + if os.host() ~= "windows" then return end + + -- Check md5 + local archive_path = DISCORD_VENDOR.."/"..DISCORD_FILENAME + if os.isfile(archive_path) and os.md5_file(archive_path) == DISCORD_MD5 then + print("Discord Game SDK is up to date.") + return + end + + -- Download Discord + print("Downloading Discord Game SDK...") + if not http.download_print_errors(DISCORD_BASEURL..DISCORD_TAG.."/"..DISCORD_FILENAME, archive_path) then + return + end + + -- Delete old stuff + os.rmdir(DISCORD_VENDOR.."/cpp") + os.rmdir(DISCORD_VENDOR.."/lib") + os.remove("Shared/data/MTA San Andreas/MTA/discord_game_sdk.dll") + + -- We cannot use zip.extract here because it ends up being read only + -- see https://github.com/premake/premake-core/blob/master/src/host/zip_extract.c#L206-L210 + os.executef("powershell -Command Expand-Archive \"%s\" \"%s\"", archive_path, DISCORD_VENDOR) + + -- Move DLL to shared data folder + os.copyfile(DISCORD_VENDOR.."/lib/x86/discord_game_sdk.dll", "Shared/data/MTA San Andreas/MTA/discord_game_sdk.dll") + os.remove(DISCORD_VENDOR.."lib/x86/discord_game_sdk.dll") + + print("Discord Game SDK updated. Don't forget to run win-install-data.bat.") + end +} + +return premake.modules.install_discord diff --git a/utils/buildactions/utils.lua b/utils/buildactions/utils.lua index 25748c4f662..9a36c9b0041 100644 --- a/utils/buildactions/utils.lua +++ b/utils/buildactions/utils.lua @@ -105,10 +105,12 @@ end function http.download_print_errors(url, file, options) local result_str, response_code = http.download(url, file, options) if result_str ~= "OK" then - print( "\nERROR: Failed to download " .. url .. "\n" .. result_str ) + print( "\nERROR: Failed to download " .. url .. "\n" .. result_str .. " (" .. response_code .. ")" ) if response_code == 0 then -- No response code means server was unreachable print( "Check premake5 is not blocked by firewall rules" ) end + return false end + return true end diff --git a/vendor/discordgsdk/.gitignore b/vendor/discordgsdk/.gitignore new file mode 100644 index 00000000000..8c6f691d817 --- /dev/null +++ b/vendor/discordgsdk/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!premake5.lua diff --git a/vendor/discordgsdk/premake5.lua b/vendor/discordgsdk/premake5.lua new file mode 100644 index 00000000000..1de51686307 --- /dev/null +++ b/vendor/discordgsdk/premake5.lua @@ -0,0 +1,16 @@ +project "discordgsdk" + language "C++" + kind "StaticLib" + targetname "discordgsdk" + + vpaths { + ["Headers/*"] = "cpp/*.h", + ["Sources/*"] = "cpp/*.cpp", + ["*"] = "premake5.lua" + } + + files { + "premake5.lua", + "cpp/*.cpp", + "cpp/*.h", + } diff --git a/win-create-projects.bat b/win-create-projects.bat index 4f757ed9a4b..222a15b0d44 100644 --- a/win-create-projects.bat +++ b/win-create-projects.bat @@ -3,15 +3,18 @@ rem Update CEF eventually utils\premake5.exe install_cef +rem Install Discord SDK +utils\premake5.exe install_discord + rem Generate solutions utils\premake5.exe vs2019 rem Create a shortcut to the solution - http://superuser.com/questions/392061/how-to-make-a-shortcut-from-cmd set SCRIPTFILE="%TEMP%\CreateMyShortcut.vbs" ( - echo Set oWS = WScript.CreateObject^("WScript.Shell"^) + echo Set oWS = WScript.CreateObject^("WScript.Shell"^) echo sLinkFile = oWS.ExpandEnvironmentStrings^("MTASA.sln - Shortcut.lnk"^) - echo Set oLink = oWS.CreateShortcut^(sLinkFile^) + echo Set oLink = oWS.CreateShortcut^(sLinkFile^) echo oLink.TargetPath = oWS.ExpandEnvironmentStrings^("%~dp0\Build\MTASA.sln"^) echo oLink.Save ) 1>%SCRIPTFILE%