From 5541017a5938cd156b45413f437e78a01f3a2c43 Mon Sep 17 00:00:00 2001 From: Jesse <69196954+ThisAMJ@users.noreply.github.com> Date: Fri, 21 Jun 2024 00:36:04 +1000 Subject: [PATCH] feat: portal color customize sar_portalcolor_enable 1 sar_portalcolor_sp_[1, 2] "" sar_portalcolor_mp[1, 2]_[1, 2] "" Changes color of crosshair, particles, and portals Singleplayer is limited support with particles and crosshair, portal color does not change. Multiplayer is full support, particle color changes when portal is moved/replaced. --- docs/cvars.md | 7 +++ src/Cheats.cpp | 2 +- src/Features/Hud/Crosshair.cpp | 28 +++++----- src/Features/PlayerTrace.cpp | 16 +++--- src/Modules/Client.cpp | 74 +++++++++++++++++++++++++++ src/Modules/Client.hpp | 2 + src/Utils.cpp | 20 ++++---- src/Utils/SDK/Color.hpp | 93 ++++++++++++++++++++++++++++++++++ 8 files changed, 212 insertions(+), 30 deletions(-) diff --git a/docs/cvars.md b/docs/cvars.md index c513f72e5..1d0b84785 100644 --- a/docs/cvars.md +++ b/docs/cvars.md @@ -387,6 +387,13 @@ |sar_pause_for|0|Pause for this amount of ticks.| |sar_pip_align|cmd|sar_pip_align \ \ - aligns the remote view.| |sar_placement_helper_hud|0|Visually displays all portal placement helpers (requires sv_cheats).| +|sar_portalcolor_enable|0|Enable custom portal colors.| +|sar_portalcolor_mp1_1|31 127 210|Portal color for Atlas (blue)'s left portal.| +|sar_portalcolor_mp1_2|19 0 210|Portal color for Atlas (blue)'s right portal.| +|sar_portalcolor_mp2_1|255 179 31|Portal color for P-Body (orange)'s left portal.| +|sar_portalcolor_mp2_2|57 2 2|Portal color for P-Body (orange)'s right portal.| +|sar_portalcolor_sp_1|64 160 255|Portal color for Chell's left portal.| +|sar_portalcolor_sp_2|255 160 32|Portal color for Chell's right portal.| |sar_portalgun_hud|0|Enables the portalgun HUD.| |sar_portalgun_hud_x|5|The x position of the portalgun HUD.| |sar_portalgun_hud_y|5|The y position of the portalgun HUD.| diff --git a/src/Cheats.cpp b/src/Cheats.cpp index 458052408..9c3041209 100644 --- a/src/Cheats.cpp +++ b/src/Cheats.cpp @@ -302,7 +302,7 @@ void Cheats::Init() { ui_transition_effect = Variable("ui_transition_effect"); ui_transition_time = Variable("ui_transition_time"); ui_pvplobby_show_offline = Variable("ui_pvplobby_show_offline"); - mm_session_sys_delay_create_host= Variable("mm_session_sys_delay_create_host"); + mm_session_sys_delay_create_host = Variable("mm_session_sys_delay_create_host"); hide_gun_when_holding = Variable("hide_gun_when_holding"); cl_viewmodelfov = Variable("cl_viewmodelfov"); r_flashlightbrightness = Variable("r_flashlightbrightness"); diff --git a/src/Features/Hud/Crosshair.cpp b/src/Features/Hud/Crosshair.cpp index e9ae0cf9b..11b89b3cc 100644 --- a/src/Features/Hud/Crosshair.cpp +++ b/src/Features/Hud/Crosshair.cpp @@ -254,18 +254,22 @@ void Crosshair::Paint(int slot) { } else if ((sar_quickhud_mode.GetInt() == 2 || sar_crosshair_P1.GetBool()) && this->isCustomQuickHudReady && portalGunUpgradeState) { // Quickhud from .png int width, height; - Color blue { 111, 184, 255 }; - Color orange { 255, 184, 86 }; - Color atlas_prim { 86, 161, 222 }; - Color atlas_sec { 77, 1, 222 }; - Color pbody_prim { 255, 199, 86 }; - Color pbody_sec { 106, 1, 1 }; - - Color real_prim = slot == 1 ? pbody_prim : engine->IsCoop() ? atlas_prim : blue; - Color real_sec = slot == 1 ? pbody_sec : engine->IsCoop() ? atlas_sec : orange; - - Color prim = (portalGunUpgradeState & 1) ? real_prim : real_sec; - Color sec = (portalGunUpgradeState & 2) ? real_sec : real_prim; + auto team = slot == 1 ? 2 : engine->IsCoop() ? 3 : 0; + Color prim = SARUTIL_Portal_Color(1, team); + Color sec = SARUTIL_Portal_Color(2, team); + + // HACKHACK: Stupid color adjustment to match vanilla crosshair + // https://www.desmos.com/calculator/7gd17bkmos +#define COLORADJUST(col) \ + if (col.r > 4) col.r = col.r * 0.75f + 64; \ + if (col.g > 4) col.g = col.g * 0.75f + 64; \ + if (col.b > 4) col.b = col.b * 0.75f + 64; + COLORADJUST(prim); + COLORADJUST(sec); +#undef COLORADJUST + + prim = (portalGunUpgradeState & 1) ? prim : sec; + sec = (portalGunUpgradeState & 2) ? sec : prim; bool prim_state = (portalGunUpgradeState & 1) ? bluePortalState : orangePortalState; bool sec_state = (portalGunUpgradeState & 2) ? orangePortalState : bluePortalState; diff --git a/src/Features/PlayerTrace.cpp b/src/Features/PlayerTrace.cpp index 1a467b468..ead701584 100644 --- a/src/Features/PlayerTrace.cpp +++ b/src/Features/PlayerTrace.cpp @@ -464,12 +464,12 @@ void PlayerTrace::DrawPortalsAt(int tick) const { localtick = trace.portals.size()-1; // Draw portals - Color blue { 111, 184, 255, 255 }; - Color orange { 255, 184, 86, 255 }; - Color atlas_prim { 32, 128, 210, 255 }; - Color atlas_second { 16, 0, 210, 255 }; - Color pbody_prim { 255, 180, 32, 255 }; - Color pbody_second { 58, 3, 3, 255 }; + Color blue = SARUTIL_Portal_Color(1, 0); + Color orange = SARUTIL_Portal_Color(2, 0); + Color atlas_prim = SARUTIL_Portal_Color(1, 3); + Color atlas_sec = SARUTIL_Portal_Color(2, 3); + Color pbody_prim = SARUTIL_Portal_Color(1, 2); + Color pbody_sec = SARUTIL_Portal_Color(2, 2); auto drawPortal = [&](Color portalColor, Vector origin, QAngle angles) { portalColor.a = (uint8_t)sar_trace_portal_opacity.GetInt(); @@ -537,9 +537,9 @@ void PlayerTrace::DrawPortalsAt(int tick) const { Color color; if (portal.is_coop) { if (portal.is_atlas) - color = portal.is_primary? atlas_prim: atlas_second; + color = portal.is_primary? atlas_prim: atlas_sec; else - color = portal.is_primary? pbody_prim: pbody_second; + color = portal.is_primary? pbody_prim: pbody_sec; } else { color = portal.is_primary? blue: orange; } diff --git a/src/Modules/Client.cpp b/src/Modules/Client.cpp index df67c3bd4..0f1604c90 100644 --- a/src/Modules/Client.cpp +++ b/src/Modules/Client.cpp @@ -74,6 +74,14 @@ Variable sar_patch_minor_angle_decay("sar_patch_minor_angle_decay", "0", "Patche Variable sar_unlocked_chapters("sar_unlocked_chapters", "-1", "Max unlocked chapter.\n"); +Variable sar_portalcolor_enable("sar_portalcolor_enable", "0", "Enable custom portal colors.\n"); +Variable sar_portalcolor_sp_1("sar_portalcolor_sp_1", "64 160 255", "Portal color for Chell's left portal.\n"); +Variable sar_portalcolor_sp_2("sar_portalcolor_sp_2", "255 160 32", "Portal color for Chell's right portal.\n"); +Variable sar_portalcolor_mp1_1("sar_portalcolor_mp1_1", "31 127 210", "Portal color for Atlas (blue)'s left portal.\n"); +Variable sar_portalcolor_mp1_2("sar_portalcolor_mp1_2", "19 0 210", "Portal color for Atlas (blue)'s right portal.\n"); +Variable sar_portalcolor_mp2_1("sar_portalcolor_mp2_1", "255 179 31", "Portal color for P-Body (orange)'s left portal.\n"); +Variable sar_portalcolor_mp2_2("sar_portalcolor_mp2_2", "57 2 2", "Portal color for P-Body (orange)'s right portal.\n"); + REDECL(Client::LevelInitPreEntity); REDECL(Client::CreateMove); REDECL(Client::CreateMove2); @@ -312,6 +320,60 @@ DETOUR_COMMAND(Client::openleaderboard) { } } +static SourceColor (*UTIL_Portal_Color)(int iPortal, int iTeamNumber); +extern Hook UTIL_Portal_Color_Hook; +static SourceColor UTIL_Portal_Color_Detour(int iPortal, int iTeamNumber) { + // FIXME: SP portal rendering does not use this but rather the + // texture's color itself. This does however work on the color + // of the SP *crosshair* and particles. + UTIL_Portal_Color_Hook.Disable(); + SourceColor ret = UTIL_Portal_Color(iPortal, iTeamNumber); + UTIL_Portal_Color_Hook.Enable(); + + if (sar_portalcolor_enable.GetBool()) { + std::optional modify; + // Yes, blue and orange are swapped. Mhm. + // Also 1 is unused. + if (iTeamNumber == 0) { + modify = Utils::GetColor(iPortal == 1 ? sar_portalcolor_sp_1.GetString() : sar_portalcolor_sp_2.GetString()); + } else if (iTeamNumber == 2) { + modify = Utils::GetColor(iPortal == 1 ? sar_portalcolor_mp2_1.GetString() : sar_portalcolor_mp2_2.GetString()); + } else if (iTeamNumber == 3) { + modify = Utils::GetColor(iPortal == 1 ? sar_portalcolor_mp1_1.GetString() : sar_portalcolor_mp1_2.GetString()); + } + if (modify.has_value()) ret = SourceColor(modify.value().r, modify.value().g, modify.value().b); + } + + return ret; +} +Hook UTIL_Portal_Color_Hook(&UTIL_Portal_Color_Detour); + +Color SARUTIL_Portal_Color(int iPortal, int iTeamNumber) { + SourceColor col = UTIL_Portal_Color(iPortal, iTeamNumber); + return Color(col.r(), col.g(), col.b()); +} + +static SourceColor (*UTIL_Portal_Color_Particles)(int iPortal, int iTeamNumber); +extern Hook UTIL_Portal_Color_Particles_Hook; +static SourceColor UTIL_Portal_Color_Particles_Detour(int iPortal, int iTeamNumber) { + UTIL_Portal_Color_Particles_Hook.Disable(); + SourceColor ret = UTIL_Portal_Color_Particles(iPortal, iTeamNumber); + UTIL_Portal_Color_Particles_Hook.Enable(); + + if (sar_portalcolor_enable.GetBool()) { + if (iTeamNumber == 0) { + // HACK: If we're at the default SP color, just let the function use its hardcoded return path. + // Otherwise, draw the particles according to portal colors + if (iPortal == 1 && !strcmp(sar_portalcolor_sp_1.GetString(), sar_portalcolor_sp_1.ThisPtr()->m_pszDefaultValue)) return ret; + if (iPortal == 2 && !strcmp(sar_portalcolor_sp_2.GetString(), sar_portalcolor_sp_2.ThisPtr()->m_pszDefaultValue)) return ret; + } + return UTIL_Portal_Color(iPortal, iTeamNumber); + } + + return ret; +} +Hook UTIL_Portal_Color_Particles_Hook(&UTIL_Portal_Color_Particles_Detour); + ON_INIT { NetMessage::RegisterHandler(LEADERBOARD_MESSAGE_TYPE, +[](const void *data, size_t size) { // TODO: Investigate why this sometimes doesn't work - AMJ 2024-04-25 @@ -994,6 +1056,18 @@ bool Client::Init() { #endif } +#ifdef _WIN32 + UTIL_Portal_Color = (decltype (UTIL_Portal_Color))Memory::Scan(client->Name(), "55 8B EC 56 8B 75 ? 85 F6 0F 84 ? ? ? ? 0F 8E"); + UTIL_Portal_Color_Particles = (decltype (UTIL_Portal_Color_Particles))Memory::Scan(client->Name(), "55 8B EC 51 8B 0D ? ? ? ? 8B 01 8B 90 ? ? ? ? FF D2 84 C0"); +#else + try { + UTIL_Portal_Color = (decltype (UTIL_Portal_Color))Memory::Scan(client->Name(), "56 53 83 EC 04 8B 44 24 ? 8B 74 24 ? 85 C0 74 ? 8D 58"); + UTIL_Portal_Color_Particles = (decltype (UTIL_Portal_Color_Particles))Memory::Scan(client->Name(), "53 83 EC 14 A1 ? ? ? ? 8B 5C 24 ? 8B 10 50 FF 92 ? ? ? ? 83 C4 10 84 C0 75"); + } catch (...) {} // Too lazy to check mods +#endif + UTIL_Portal_Color_Hook.SetFunc(UTIL_Portal_Color); + UTIL_Portal_Color_Particles_Hook.SetFunc(UTIL_Portal_Color_Particles); + g_AddShadowToReceiverHook.SetFunc(Client::AddShadowToReceiver); // Get at gamerules diff --git a/src/Modules/Client.hpp b/src/Modules/Client.hpp index cac6be42c..11dd6cea4 100644 --- a/src/Modules/Client.hpp +++ b/src/Modules/Client.hpp @@ -198,3 +198,5 @@ extern Variable snd_ducktovolume; extern Variable say; extern Command sar_workshop_skip; + +extern Color SARUTIL_Portal_Color(int iPortal, int iTeamNumber); diff --git a/src/Utils.cpp b/src/Utils.cpp index 83e5d2bfd..76b328091 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -88,21 +88,23 @@ std::optional Utils::GetColor(const char *str, bool to_linear) { unsigned r, g, b, a; int end; - if (len == 8 && sscanf(str, "%2x%2x%2x%2x%n", &r, &g, &b, &a, &end) == 4 && end >= 8) { - RET(r, g, b, a); - } + // This is before the hex checks because "19 0 210" is + // 8 characters long and apparently passes the hex scan + if (!had_hash) { + if (sscanf(str, "%u %u %u %u%n", &r, &g, &b, &a, &end) == 4 && (size_t)end >= len) { + RET(r, g, b, a); + } - if (len == 6 && sscanf(str, "%2x%2x%2x%n", &r, &g, &b, &end) == 3 && end >= 6) { - RET(r, g, b, 255); + if (sscanf(str, "%u %u %u%n", &r, &g, &b, &end) == 3 && (size_t)end >= len) { + RET(r, g, b, 255); + } } - if (had_hash) return {}; - - if (sscanf(str, "%u %u %u %u%n", &r, &g, &b, &a, &end) == 4 && (size_t)end >= len) { + if (len == 8 && sscanf(str, "%2x%2x%2x%2x%n", &r, &g, &b, &a, &end) == 4 && end >= 8) { RET(r, g, b, a); } - if (sscanf(str, "%u %u %u%n", &r, &g, &b, &end) == 3 && (size_t)end >= len) { + if (len == 6 && sscanf(str, "%2x%2x%2x%n", &r, &g, &b, &end) == 3 && end >= 6) { RET(r, g, b, 255); } diff --git a/src/Utils/SDK/Color.hpp b/src/Utils/SDK/Color.hpp index f714711d8..efd0c6d46 100644 --- a/src/Utils/SDK/Color.hpp +++ b/src/Utils/SDK/Color.hpp @@ -21,6 +21,99 @@ struct Color { } }; +struct color32 { + bool operator!=( const struct color32 &other ) const; + int r, g, b, a; + + inline unsigned *asInt(void) { return reinterpret_cast(this); } + inline const unsigned *asInt(void) const { return reinterpret_cast(this); } + inline void Copy(const color32 &rhs) { + *asInt() = *rhs.asInt(); + } + +}; + +class SourceColor { +public: + SourceColor() { + *((int *)this) = 0; + } + SourceColor(int _r, int _g, int _b) { + SetColor(_r, _g, _b, 0); + } + SourceColor(int _r, int _g, int _b, int _a) { + SetColor(_r, _g, _b, _a); + } + + void SetColor(int _r, int _g, int _b, int _a = 0) { + _color[0] = (unsigned char)_r; + _color[1] = (unsigned char)_g; + _color[2] = (unsigned char)_b; + _color[3] = (unsigned char)_a; + } + + void GetColor(int &_r, int &_g, int &_b, int &_a) const { + _r = _color[0]; + _g = _color[1]; + _b = _color[2]; + _a = _color[3]; + } + + void SetRawColor(int color32) { + *((int *)this) = color32; + } + + int GetRawColor() const { + return *((int *)this); + } + + inline int r() const { return _color[0]; } + inline int g() const { return _color[1]; } + inline int b() const { return _color[2]; } + inline int a() const { return _color[3]; } + + unsigned char &operator[](int index) { + return _color[index]; + } + + const unsigned char &operator[](int index) const { + return _color[index]; + } + + bool operator==(const SourceColor &rhs) const { + return (*((int *)this) == *((int *)&rhs)); + } + + bool operator!=(const SourceColor &rhs) const { + return !(operator==(rhs)); + } + + SourceColor &operator=(const SourceColor &rhs) { + SetRawColor(rhs.GetRawColor()); + return *this; + } + + SourceColor &operator=(const color32 &rhs) { + _color[0] = rhs.r; + _color[1] = rhs.g; + _color[2] = rhs.b; + _color[3] = rhs.a; + return *this; + } + + color32 ToColor32() const { + color32 newColor; + newColor.r = _color[0]; + newColor.g = _color[1]; + newColor.b = _color[2]; + newColor.a = _color[3]; + return newColor; + } + +private: + unsigned char _color[4]; +}; + enum class TextColor { PLAYERNAME = 3, GREEN = 4,