Skip to content

Commit

Permalink
feat: portal color customize
Browse files Browse the repository at this point in the history
sar_portalcolor_enable 1
sar_portalcolor_sp_[1, 2] "<color>"
sar_portalcolor_mp[1, 2]_[1, 2] "<color>"

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.
  • Loading branch information
ThisAMJ committed Jun 27, 2024
1 parent c873dcc commit 5541017
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 30 deletions.
7 changes: 7 additions & 0 deletions docs/cvars.md
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,13 @@
|sar_pause_for|0|Pause for this amount of ticks.|
|sar_pip_align|cmd|sar_pip_align \<top\|center\|bottom> \<left\|center\|right> - 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.|
Expand Down
2 changes: 1 addition & 1 deletion src/Cheats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
28 changes: 16 additions & 12 deletions src/Features/Hud/Crosshair.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
16 changes: 8 additions & 8 deletions src/Features/PlayerTrace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}
Expand Down
74 changes: 74 additions & 0 deletions src/Modules/Client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<Color> 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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/Modules/Client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
20 changes: 11 additions & 9 deletions src/Utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,21 +88,23 @@ std::optional<Color> 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);
}

Expand Down
93 changes: 93 additions & 0 deletions src/Utils/SDK/Color.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<unsigned*>(this); }
inline const unsigned *asInt(void) const { return reinterpret_cast<const unsigned*>(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,
Expand Down

0 comments on commit 5541017

Please sign in to comment.