Skip to content

Commit

Permalink
Custom Crosshair - Coloring + tweakable crosshair (#651)
Browse files Browse the repository at this point in the history
* Basic default crosshair coloring functionality for textured crosshair
* Custom crosshair - Have the ability to tweak/adjust the parameters
  such as size, gap, dot, outline, etc...
* Custom crosshair - Add top line toggle + circle radius+segments
* NeoUI::SliderU8, unify setting with crosshair struct
* Abs/Screen half sizing, self managed NeoUI section
* Thickness to direct px control, odd will always have bias-sides
* Import/export crosshair
* fixes #604
  • Loading branch information
nullsystem authored Oct 10, 2024
1 parent 9457fce commit 68ffa08
Show file tree
Hide file tree
Showing 14 changed files with 608 additions and 30 deletions.
2 changes: 2 additions & 0 deletions mp/src/game/client/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1526,6 +1526,8 @@ target_sources_grouped(
neo/ui/neo_hud_childelement.h
neo/ui/neo_hud_compass.cpp
neo/ui/neo_hud_compass.h
neo/ui/neo_hud_crosshair.cpp
neo/ui/neo_hud_crosshair.h
neo/ui/neo_hud_damage_indicator.cpp
neo/ui/neo_hud_friendly_marker.cpp
neo/ui/neo_hud_friendly_marker.h
Expand Down
99 changes: 89 additions & 10 deletions mp/src/game/client/hud_crosshair.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "VGuiMatSurface/IMatSystemSurface.h"
#include "client_virtualreality.h"
#include "sourcevr/isourcevirtualreality.h"
#include "ui/neo_hud_crosshair.h"

#ifdef SIXENSE
#include "sixense/in_sixense.h"
Expand Down Expand Up @@ -46,9 +47,42 @@ int ScreenTransform( const Vector& point, Vector& screen );
DECLARE_HUDELEMENT_DEPTH(CHudCrosshair, 9999);
#endif

#ifdef NEO
static inline bool g_bInstalledCVarCallback = false;

void CVGlobal_NeoClCrosshair(IConVar *var, [[maybe_unused]] const char *pOldString, [[maybe_unused]] float flOldValue)
{
CHudCrosshair *crosshair = GET_HUDELEMENT(CHudCrosshair);
if (crosshair && V_strstr(var->GetName(), "neo_cl_crosshair"))
{
// NEO NOTE (nullsystem): Only mark for refresh, not immediate as they will likely be multiple of convars sent
// over
crosshair->m_bRefreshCrosshair = true;
}
}
#endif

CHudCrosshair::CHudCrosshair( const char *pElementName ) :
CHudElement( pElementName ), BaseClass( NULL, "HudCrosshair" )
{
#ifdef NEO
if (!g_bInstalledCVarCallback)
{
cvar->InstallGlobalChangeCallback(CVGlobal_NeoClCrosshair);
g_bInstalledCVarCallback = true;
}

for (int i = 0; i < CROSSHAIR_STYLE__TOTAL; ++i)
{
if (CROSSHAIR_FILES[i][0])
{
m_iTexXHId[i] = vgui::surface()->CreateNewTextureID();
vgui::surface()->DrawSetTextureFile(m_iTexXHId[i], CROSSHAIR_FILES[i], false, false);
}
}

#endif

vgui::Panel *pParent = g_pClientMode->GetViewport();
SetParent( pParent );

Expand All @@ -63,6 +97,9 @@ CHudCrosshair::CHudCrosshair( const char *pElementName ) :

CHudCrosshair::~CHudCrosshair()
{
#ifdef NEO
cvar->RemoveGlobalChangeCallback(CVGlobal_NeoClCrosshair);
#endif
}

void CHudCrosshair::ApplySchemeSettings( IScheme *scheme )
Expand Down Expand Up @@ -278,7 +315,7 @@ void CHudCrosshair::Paint( void )
if ( pWeapon )
{
// NEO HACK (Rain): this should get implemented in virtual pNeoWep->GetWeaponCrosshairScale
pNeoWep = dynamic_cast<CNEOBaseCombatWeapon*>(pWeapon);
pNeoWep = static_cast<CNEOBaseCombatWeapon *>(pWeapon);
if (pNeoWep && pNeoWep->GetNeoWepBits() & NEO_WEP_SCOPEDWEAPON)
{
int screenWidth, screenHeight;
Expand Down Expand Up @@ -327,22 +364,64 @@ void CHudCrosshair::Paint( void )

int iWidth = (int)( flWidth + 0.5f );
int iHeight = (int)( flHeight + 0.5f );

int iX = (int)( x + 0.5f );
int iY = (int)( y + 0.5f );

m_pCrosshair->DrawSelfCropped (
iX-(iWidth/2), iY-(iHeight/2),
0, 0,
iTextureW, iTextureH,
iWidth, iHeight,
// NEO TODO (nullsystem): Probably be better if the entire class refreshed to our own
// thing and can do away with that CHudTexture nonsense entirely
const bool bIsScoped = pNeoWep && pNeoWep->GetNeoWepBits() & NEO_WEP_SCOPEDWEAPON;
const int iXHairStyle = neo_cl_crosshair_style.GetInt();

if (bIsScoped)
{
m_pCrosshair->DrawSelfCropped (
iX-(iWidth/2), iY-(iHeight/2),
0, 0,
iTextureW, iTextureH,
iWidth, iHeight,
#ifdef TF_CLIENT_DLL
clr
clr
#else
m_clrCrosshair
m_clrCrosshair
#endif
);
);
}
else if (m_iTexXHId[iXHairStyle] > 0)
{
vgui::surface()->DrawSetTexture(m_iTexXHId[iXHairStyle]);
int iTexWide, iTexTall;
vgui::surface()->DrawGetTextureSize(m_iTexXHId[iXHairStyle], iTexWide, iTexTall);
iTexWide >>= 1;
iTexTall >>= 1;
vgui::surface()->DrawSetColor(m_clrCrosshair);
vgui::surface()->DrawTexturedRect(iX - iTexWide, iY - iTexTall, iX + iTexWide, iY + iTexTall);
}
else
{
if (m_bRefreshCrosshair)
{
m_crosshairInfo = CrosshairInfo{
.color = Color(
neo_cl_crosshair_color_r.GetInt(), neo_cl_crosshair_color_g.GetInt(),
neo_cl_crosshair_color_b.GetInt(), neo_cl_crosshair_color_a.GetInt()),
.iESizeType = neo_cl_crosshair_size_type.GetInt(),
.iSize = neo_cl_crosshair_size.GetInt(),
.flScrSize = neo_cl_crosshair_size_screen.GetFloat(),
.iThick = neo_cl_crosshair_thickness.GetInt(),
.iGap = neo_cl_crosshair_gap.GetInt(),
.iOutline = neo_cl_crosshair_outline.GetInt(),
.iCenterDot = neo_cl_crosshair_center_dot.GetInt(),
.bTopLine = neo_cl_crosshair_top_line.GetBool(),
.iCircleRad = neo_cl_crosshair_circle_radius.GetInt(),
.iCircleSegments = neo_cl_crosshair_circle_segments.GetInt(),
};
m_bRefreshCrosshair = false;
}
PaintCrosshair(m_crosshairInfo, iX, iY);
}

if (pNeoWep && pNeoWep->GetNeoWepBits() & NEO_WEP_SCOPEDWEAPON)
if (bIsScoped)
{
int screenWidth, screenHeight;
GetHudSize(screenWidth, screenHeight);
Expand Down
13 changes: 13 additions & 0 deletions mp/src/game/client/hud_crosshair.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
#include "hudelement.h"
#include <vgui_controls/Panel.h>

#ifdef NEO
#include "ui/neo_hud_crosshair.h"
#endif

namespace vgui
{
class IScheme;
Expand All @@ -29,6 +33,11 @@ class CHudCrosshair : public CHudElement, public vgui::Panel
CHudCrosshair( const char *pElementName );
virtual ~CHudCrosshair();

#ifdef NEO
bool m_bRefreshCrosshair = true;
CrosshairInfo m_crosshairInfo;
#endif

virtual void SetCrosshairAngle( const QAngle& angle );
virtual void SetCrosshair( CHudTexture *texture, const Color& clr );
virtual void ResetCrosshair();
Expand All @@ -48,6 +57,10 @@ class CHudCrosshair : public CHudElement, public vgui::Panel
Color m_clrCrosshair;
QAngle m_vecCrossHairOffsetAngle;

#ifdef NEO
int m_iTexXHId[CROSSHAIR_STYLE__TOTAL] = {};
#endif

CPanelAnimationVar( bool, m_bHideCrosshair, "never_draw", "false" );
};

Expand Down
7 changes: 0 additions & 7 deletions mp/src/game/client/neo/c_neo_player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1223,13 +1223,6 @@ void C_NEO_Player::Spawn( void )
engine->ClientCmd(teammenu.GetName());
}
}

#if(0)
// We could support crosshair customization/colors etc this way.
auto cross = GET_HUDELEMENT(CHudCrosshair);
Color color = Color(255, 255, 255, 255);
cross->SetCrosshair(NULL, color);
#endif
}

void C_NEO_Player::DoImpactEffect( trace_t &tr, int nDamageType )
Expand Down
187 changes: 187 additions & 0 deletions mp/src/game/client/neo/ui/neo_hud_crosshair.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
#include "neo_hud_crosshair.h"

#include "filesystem.h"
#include "vgui/ISurface.h"
#include "vgui_controls/Controls.h"
#include "ui/neo_root.h"

ConVar neo_cl_crosshair_style("neo_cl_crosshair_style", "0", FCVAR_ARCHIVE | FCVAR_USERINFO, "Set the crosshair style. 0 = default, 0 to 1 = textured, 2 = custom", true, 0.0f, true, CROSSHAIR_STYLE__TOTAL);
ConVar neo_cl_crosshair_color_r("neo_cl_crosshair_color_r", "255", FCVAR_ARCHIVE | FCVAR_USERINFO, "Set the red value of the crosshair color", true, 0.0f, true, UCHAR_MAX);
ConVar neo_cl_crosshair_color_g("neo_cl_crosshair_color_g", "255", FCVAR_ARCHIVE | FCVAR_USERINFO, "Set the green value of the crosshair color", true, 0.0f, true, UCHAR_MAX);
ConVar neo_cl_crosshair_color_b("neo_cl_crosshair_color_b", "255", FCVAR_ARCHIVE | FCVAR_USERINFO, "Set the blue value of the crosshair color", true, 0.0f, true, UCHAR_MAX);
ConVar neo_cl_crosshair_color_a("neo_cl_crosshair_color_a", "255", FCVAR_ARCHIVE | FCVAR_USERINFO, "Set the alpha value of the crosshair color", true, 0.0f, true, UCHAR_MAX);
ConVar neo_cl_crosshair_size("neo_cl_crosshair_size", "15", FCVAR_ARCHIVE | FCVAR_USERINFO, "Set the size of the crosshair (custom only)", true, 0.0f, true, CROSSHAIR_MAX_SIZE);
ConVar neo_cl_crosshair_size_screen("neo_cl_crosshair_size_screen", "0", FCVAR_ARCHIVE | FCVAR_USERINFO, "Set the size of the crosshair by half-screen scale (custom only)", true, 0.0f, true, 1.0f);
ConVar neo_cl_crosshair_size_type("neo_cl_crosshair_size_type", "0", FCVAR_ARCHIVE | FCVAR_USERINFO, "Set the size unit used for the crosshair. 0 = neo_cl_crosshair_size, 1 = neo_cl_crosshair_size_screen (custom only)", true, 0.0f, true, (CROSSHAIR_SIZETYPE__TOTAL - 1));
ConVar neo_cl_crosshair_thickness("neo_cl_crosshair_thickness", "1", FCVAR_ARCHIVE | FCVAR_USERINFO, "Set the thickness of the crosshair (custom only)", true, 0.0f, true, CROSSHAIR_MAX_THICKNESS);
ConVar neo_cl_crosshair_gap("neo_cl_crosshair_gap", "5", FCVAR_ARCHIVE | FCVAR_USERINFO, "Set the gap of the crosshair (custom only)", true, 0.0f, true, CROSSHAIR_MAX_GAP);
ConVar neo_cl_crosshair_outline("neo_cl_crosshair_outline", "0", FCVAR_ARCHIVE | FCVAR_USERINFO, "Set the crosshair outline (custom only)", true, 0.0f, true, CROSSHAIR_MAX_OUTLINE);
ConVar neo_cl_crosshair_center_dot("neo_cl_crosshair_center_dot", "0", FCVAR_ARCHIVE | FCVAR_USERINFO, "Set the size of the center dot (custom only)", true, 0.0f, true, CROSSHAIR_MAX_CENTER_DOT);
ConVar neo_cl_crosshair_top_line("neo_cl_crosshair_top_line", "1", FCVAR_ARCHIVE | FCVAR_USERINFO, "Set if the top line will be shown (custom only)", true, 0.0f, true, 1.0f);
ConVar neo_cl_crosshair_circle_radius("neo_cl_crosshair_circle_radius", "0", FCVAR_ARCHIVE | FCVAR_USERINFO, "Set the circle radius of the crosshair (custom only)", true, 0.0f, true, CROSSHAIR_MAX_CIRCLE_RAD);
ConVar neo_cl_crosshair_circle_segments("neo_cl_crosshair_circle_segments", "30", FCVAR_ARCHIVE | FCVAR_USERINFO, "Set the segments of the circle (custom only)", true, 0.0f, true, CROSSHAIR_MAX_CIRCLE_SEGMENTS);

static const char *INTERNAL_CROSSHAIR_FILES[CROSSHAIR_STYLE__TOTAL] = { "vgui/hud/crosshair", "vgui/hud/crosshair_b", "" };
const char **CROSSHAIR_FILES = INTERNAL_CROSSHAIR_FILES;

static const wchar_t *INTERNAL_CROSSHAIR_LABELS[CROSSHAIR_STYLE__TOTAL] = { L"Default", L"Alt", L"Custom" };
const wchar_t **CROSSHAIR_LABELS = INTERNAL_CROSSHAIR_LABELS;

static const wchar_t *INTERNAL_CROSSHAIR_SIZETYPE_LABELS[CROSSHAIR_SIZETYPE__TOTAL] = { L"Absolute", L"Screen halves" };
const wchar_t **CROSSHAIR_SIZETYPE_LABELS = INTERNAL_CROSSHAIR_SIZETYPE_LABELS;

void PaintCrosshair(const CrosshairInfo &crh, const int x, const int y)
{
if (crh.iSize > 0)
{
int iSize = crh.iSize;
if (crh.iESizeType == CROSSHAIR_SIZETYPE_SCREEN)
{
int wide, tall;
vgui::surface()->GetScreenSize(wide, tall);
iSize = (crh.flScrSize * (max(wide, tall) / 2));
}

const bool bOdd = ((crh.iThick % 2) == 1);
const int iHalf = crh.iThick / 2;
const int iStartThick = bOdd ? iHalf + 1 : iHalf;
const int iEndThick = iHalf;
vgui::IntRect iRects[4] = {
{ -iSize - crh.iGap, -iStartThick, -crh.iGap, iEndThick }, // Left
{ crh.iGap, -iStartThick, crh.iGap + iSize, iEndThick }, // Right
{ -iStartThick, crh.iGap, iEndThick, crh.iGap + iSize }, // Bottom
{ -iStartThick, -iSize - crh.iGap, iEndThick, -crh.iGap }, // Top (Must be last for bTopLine)
};
for (vgui::IntRect &rect : iRects)
{
rect.x0 += x;
rect.y0 += y;
rect.x1 += x;
rect.y1 += y;
}
const int iRectSize = (crh.bTopLine) ? 4 : 3;
if (crh.iOutline > 0)
{
vgui::IntRect iOutRects[4] = {};
V_memcpy(iOutRects, iRects, sizeof(vgui::IntRect) * 4);
for (vgui::IntRect &rect : iOutRects)
{
rect.x0 -= crh.iOutline;
rect.y0 -= crh.iOutline;
rect.x1 += crh.iOutline;
rect.y1 += crh.iOutline;
}
vgui::surface()->DrawSetColor(COLOR_BLACK);
vgui::surface()->DrawFilledRectArray(iOutRects, iRectSize);
}

vgui::surface()->DrawSetColor(crh.color);
vgui::surface()->DrawFilledRectArray(iRects, iRectSize);
}

if (crh.iCenterDot > 0)
{
const bool bOdd = ((crh.iCenterDot % 2) == 1);
const int iHalf = crh.iCenterDot / 2;
const int iStartCenter = bOdd ? iHalf + 1 : iHalf;
const int iEndCenter = iHalf;

if (crh.iOutline > 0)
{
vgui::surface()->DrawSetColor(COLOR_BLACK);
vgui::surface()->DrawFilledRect(-iStartCenter + x -crh.iOutline, -iStartCenter + y -crh.iOutline,
iEndCenter + x +crh.iOutline, iEndCenter + y +crh.iOutline);
}

vgui::surface()->DrawSetColor(crh.color);
vgui::surface()->DrawFilledRect(-iStartCenter + x, -iStartCenter + y, iEndCenter + x, iEndCenter + y);
}

if (crh.iCircleRad > 0 && crh.iCircleSegments > 0)
{
vgui::surface()->DrawOutlinedCircle(x, y, crh.iCircleRad, crh.iCircleSegments);
}
}

// NEO WARNING (nullsystem): When adding new/removing items to serialize, only append enum one after another
// before the NEOXHAIR_SERIAL__LATESTPLUSONE section!
// When working on this put in the current in-dev/unreleased version for the enum name.
enum NeoXHairSerial
{
NEOXHAIR_SERIAL_PREALPHA_V8_2 = 1,

NEOXHAIR_SERIAL__LATESTPLUSONE,
NEOXHAIR_SERIAL_CURRENT = NEOXHAIR_SERIAL__LATESTPLUSONE - 1,
};
static constexpr unsigned int MAGIC_NUMBER = 0x15AF104B;

void ImportCrosshair(CrosshairInfo *crh, const char *szFullpath)
{
if (!szFullpath)
{
return;
}

CUtlBuffer buf(0, 0, CUtlBuffer::READ_ONLY);

if (!filesystem->ReadFile(szFullpath, nullptr, buf))
{
Msg("[CROSSHAIR]: Failed to read file: %s", szFullpath);
return;
}

if (buf.GetUnsignedInt() != MAGIC_NUMBER)
{
return;
}

const int version = buf.GetInt();
if (version < 1 || version > NEOXHAIR_SERIAL_CURRENT)
{
Msg("[CROSSHAIR]: File corrupted, invalid version %d", version);
return;
}

crh->color.SetRawColor(buf.GetInt());
crh->iESizeType = buf.GetInt();
crh->iSize = buf.GetInt();
crh->flScrSize = buf.GetFloat();
crh->iThick = buf.GetInt();
crh->iGap = buf.GetInt();
crh->iOutline = buf.GetInt();
crh->iCenterDot = buf.GetInt();
crh->bTopLine = buf.GetChar();
crh->iCircleRad = buf.GetInt();
crh->iCircleSegments = buf.GetInt();

g_pNeoRoot->m_ns.bModified = true;
}

void ExportCrosshair(CrosshairInfo *crh, const char *szFullpath)
{
if (!szFullpath)
{
return;
}

CUtlBuffer buf;
buf.PutUnsignedInt(MAGIC_NUMBER);
buf.PutInt(NEOXHAIR_SERIAL_CURRENT);

buf.PutInt(crh->color.GetRawColor());
buf.PutInt(crh->iESizeType);
buf.PutInt(crh->iSize);
buf.PutFloat(crh->flScrSize);
buf.PutInt(crh->iThick);
buf.PutInt(crh->iGap);
buf.PutInt(crh->iOutline);
buf.PutInt(crh->iCenterDot);
buf.PutChar(crh->bTopLine);
buf.PutInt(crh->iCircleRad);
buf.PutInt(crh->iCircleSegments);

if (!filesystem->WriteFile(szFullpath, nullptr, buf))
{
Msg("[CROSSHAIR]: Failed to write file: %s", szFullpath);
}
}
Loading

0 comments on commit 68ffa08

Please sign in to comment.