Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/game/client/neo/ui/neo_root_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ static const wchar_t* AUTOMATIC_LEAN_LABELS[] = {
L"Always",
};

static const wchar_t* CAP_ZONE_EDGE_LABELS[] = {
L"Disabled",
L"Line",
L"Line + Logo",
};

static const wchar_t *CROSSHAIR_HIPFIRE_LABELS_DEFAULT[HIPFIREOPT__TOTAL] = {
L"Disabled",
L"Use default",
Expand Down Expand Up @@ -424,6 +430,7 @@ void NeoSettingsRestore(NeoSettings *ns, const NeoSettings::Keys::Flags flagsKey
pGeneral->bViewmodelRighthand = cvr->cl_righthand.GetBool();
pGeneral->bLeanViewmodelOnly = cvr->cl_neo_lean_viewmodel_only.GetBool();
pGeneral->iLeanAutomatic = cvr->cl_neo_lean_automatic.GetInt();
pGeneral->iCapZoneEdge = cvr->cl_neo_cap_zone_edge.GetInt();
pGeneral->iEquipUtilityPriority = cvr->cl_neo_equip_utility_priority.GetInt();
pGeneral->bWeaponFastSwitch = cvr->hud_fastswitch.GetBool();
pGeneral->bShowPlayerSprays = !(cvr->cl_spraydisable.GetBool()); // Inverse
Expand Down Expand Up @@ -792,6 +799,7 @@ void NeoSettingsSave(const NeoSettings *ns)
cvr->cl_righthand.SetValue(pGeneral->bViewmodelRighthand);
cvr->cl_neo_lean_viewmodel_only.SetValue(pGeneral->bLeanViewmodelOnly);
cvr->cl_neo_lean_automatic.SetValue(pGeneral->iLeanAutomatic);
cvr->cl_neo_cap_zone_edge.SetValue(pGeneral->iCapZoneEdge);
cvr->cl_neo_equip_utility_priority.SetValue(pGeneral->iEquipUtilityPriority);
cvr->hud_fastswitch.SetValue(pGeneral->bWeaponFastSwitch);
cvr->cl_spraydisable.SetValue(!pGeneral->bShowPlayerSprays); // Inverse
Expand Down Expand Up @@ -1110,6 +1118,7 @@ void NeoSettings_General(NeoSettings *ns)
NeoUI::RingBox(L"Utility slot equip priority", EQUIP_UTILITY_PRIORITY_LABELS, NeoSettings::EquipUtilityPriorityType::EQUIP_UTILITY_PRIORITY__TOTAL, &pGeneral->iEquipUtilityPriority);
NeoUI::RingBoxBool(L"Weapon fastswitch", &pGeneral->bWeaponFastSwitch);
NeoUI::RingBoxBool(L"Taking damage sounds", &pGeneral->bTakingDamageSounds);
NeoUI::RingBox(L"Cap zone edge", CAP_ZONE_EDGE_LABELS, ARRAYSIZE(CAP_ZONE_EDGE_LABELS), &pGeneral->iCapZoneEdge);

NeoUI::Divider(L"MAIN MENU");
NeoUI::RingBox(L"Selected Background", const_cast<const wchar_t **>(ns->p2WszCBList), ns->iCBListSize, &pGeneral->iBackground);
Expand Down
2 changes: 2 additions & 0 deletions src/game/client/neo/ui/neo_root_settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ struct NeoSettings
bool bViewmodelRighthand;
bool bLeanViewmodelOnly;
int iLeanAutomatic;
int iCapZoneEdge;
int iEquipUtilityPriority;
bool bWeaponFastSwitch;
bool bShowPlayerSprays;
Expand Down Expand Up @@ -260,6 +261,7 @@ struct NeoSettings
CONVARREF_DEF(cl_righthand);
CONVARREF_DEF(cl_neo_lean_viewmodel_only);
CONVARREF_DEF(cl_neo_lean_automatic);
CONVARREF_DEF(cl_neo_cap_zone_edge);
CONVARREF_DEF(cl_neo_squad_hud_original);
CONVARREF_DEF(cl_neo_hud_health_mode);
CONVARREF_DEF(cl_neo_hud_worldpos_verbose);
Expand Down
192 changes: 192 additions & 0 deletions src/game/shared/neo/neo_ghost_cap_point.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

#ifdef CLIENT_DLL
#include "ui/neo_hud_ghost_cap_point.h"
#include "materialsystem/imaterialsystem.h"
#include <math.h>
#endif

// memdbgon must be the last include file in a .cpp file!!!
Expand Down Expand Up @@ -76,6 +78,17 @@ BEGIN_DATADESC(CNEOGhostCapturePoint)
#endif
END_DATADESC()

enum NeoCapEdgeType
{
NEO_CAP_EDGE_OFF = 0, // disabled
NEO_CAP_EDGE_LINE, // show only a line around the capzone
NEO_CAP_EDGE_LINE_LOGO, // show the line + a team logo
};

#ifdef CLIENT_DLL
ConVar cl_neo_cap_zone_edge("cl_neo_cap_zone_edge", "2", FCVAR_ARCHIVE, "Cap zone edge rendering", true, 0, true, NEO_CAP_EDGE_LINE_LOGO);
#endif

CNEOGhostCapturePoint::CNEOGhostCapturePoint()
{
m_flCapzoneRadius = -1;
Expand Down Expand Up @@ -142,6 +155,7 @@ int CNEOGhostCapturePoint::owningTeamAlternate() const

void CNEOGhostCapturePoint::Spawn(void)
{
Precache();
BaseClass::Spawn();

AddEFlags(EFL_FORCE_CHECK_TRANSMIT);
Expand Down Expand Up @@ -202,6 +216,10 @@ void CNEOGhostCapturePoint::Spawn(void)
SetContextThink(&CNEOGhostCapturePoint::Think_CheckMyRadius,
gpGlobals->curtime, "CheckMyRadius");
#else
m_pRingMaterial = materials->FindMaterial("effects/cap_zone_ring", TEXTURE_GROUP_CLIENT_EFFECTS);
m_pRingNsfMaterial = materials->FindMaterial("effects/cap_zone_ring_nsf", TEXTURE_GROUP_CLIENT_EFFECTS);
m_pRingJinraiMaterial = materials->FindMaterial("effects/cap_zone_ring_jinrai", TEXTURE_GROUP_CLIENT_EFFECTS);
AddToLeafSystem(RENDER_GROUP_TRANSLUCENT_ENTITY);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

SetNextClientThink(gpGlobals->curtime + NEO_GHOSTCAP_GRAPHICS_THINK_INTERVAL);
#endif
}
Expand Down Expand Up @@ -298,13 +316,187 @@ void CNEOGhostCapturePoint::ClientThink(void)

SetNextClientThink(gpGlobals->curtime + NEO_GHOSTCAP_GRAPHICS_THINK_INTERVAL);
}

bool CNEOGhostCapturePoint::ShouldDraw()
{
return m_bIsActive;
}

RenderGroup_t CNEOGhostCapturePoint::GetRenderGroup()
{
return RENDER_GROUP_TRANSLUCENT_ENTITY;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cap boundary being translucent is an issue given its size. Returning RENDER_GROUP_TRANSLUCENT_ENTITY means the cap boundary will be sorted against other translucents using the cap center as the origin leading to some funky behavior. The funkiest of behaviors lies in its interaction with smoke grenades. If a smoke grenade smoke particle emitter's origin is further from the camera than the center of the cap zone, it will render before the cap boundary, meaning parts of the boundary that are behind the smoke will still render on top. Because the boundary is translucent however, it will still render later than opaque models like non-cloaked players. This means you will be able to, in specific circumstances, spot players through smoke grenades using gaps in the cap boundary.

Image

The only solutions as far as I can tell are:

  1. Make the boundary opaque. I was having trouble getting CMeshBuilder to write to the depth buffer so not sure if this is possible. Theoretically theres $alphatest with $alphatestreference and $allowalphatocoverage but that will still look not great unless players are using MSAA, and even then the fade out over distance will look bad, and again couldn't get this to work probably for the same reasons as a fully opaque CMeshBuilder.
  2. Draw the boundary in several steps. CClientLeafSystem::SortEntities determines the order in which translucent entities are drawn. If DrawModel took an argument and only drew the portion of the model specified by that argument, SortEntities could instead sort entity, argument pairs where the origin also depends on the argument. Anyway I'm bikeshedding here, this definitely shouldn't be done in this PR. What you could do maybe is create a clientside only entity that corresponds to a small section of the border. These would be connected to the cap entity in that deleting the cap would also delete all the smaller border entities, but they would exist separately in the clientleafsystem and would be sorted more thoroughly.
  3. Implement order independent transparency. Again bikeshedding here.

All in all its probably unrealistic for this pr to solve this issue, so we should instead consider how much impact it will have on the game, and whether its worth it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upon further investigation I realized I forgot to remove the Sine material proxy that wrote to the material's alpha. With that commented out, it is indeed possible to use alphatest for a cap border that sorts correctly against translucent entities. The problem with this is the fading out of the cap over distance. Since we cannot change the alpha of the border smoothly with this (even with MSAA), the next be st solution might be to simply always render the cap border, and carefully edit the mipmaps so that at the lowest levels the texture is very unobtrusive
image

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively just always render the border, I don't think it's that obtrusive to begin with, and it will not render when outside the pvs anyway. Alphatest is much more performant that transparent, so this might even be better performance wise

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

damn it's tough

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for testing and analysis, appreciated it

}

void CNEOGhostCapturePoint::GetRenderBoundsWorldspace(Vector& mins, Vector& maxs)
{
const Vector& origin = GetAbsOrigin();
const float r = m_flCapzoneRadius;
mins = origin + Vector(-r, -r, -16.0f);
maxs = origin + Vector(r, r, 16.0f);
}

int CNEOGhostCapturePoint::DrawModel(int flags)
{
int cl_neo_cap_zone_edge_value = cl_neo_cap_zone_edge.GetInt();

if (!m_bIsActive || !m_pRingMaterial || !m_pRingJinraiMaterial || !m_pRingNsfMaterial || cl_neo_cap_zone_edge_value == NEO_CAP_EDGE_OFF) {
return 0;
}

// Mirror the arrow color logic from CNEOHud_GhostCapPoint::DrawNeoHudElement exactly
const int capTeam = owningTeamAlternate();
Color ringColor = (capTeam == TEAM_ANY) ? COLOR_SPEC : ((capTeam == TEAM_JINRAI) ? COLOR_JINRAI : COLOR_NSF);

auto *player = C_NEO_Player::GetLocalNEOPlayer();

const Vector& capOrigin = GetAbsOrigin();
int ringOpacity = 128;
constexpr float MAX_VISIBILITY_RANGE = 768.0f;

if (player)
{
const int playerTeam = player->GetTeamNumber();
const bool playerIsPlaying = (playerTeam == TEAM_JINRAI || playerTeam == TEAM_NSF);

if (playerIsPlaying && capTeam != TEAM_ANY && playerTeam != capTeam)
{
ringColor = COLOR_RED;
}

const Vector& playerOrigin = player->GetAbsOrigin();
const float distanceToCap = playerOrigin.DistTo(capOrigin);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const float distanceToCap = max(0.f, playerOrigin.DistTo(capOrigin) - m_flCapzoneRadius);

So distanceToCap remains constant while inside the cap, and we don't get surprised by the cap suddenly appearing next to us when the cap radius is very large (or the boundary of the cap not appearing until we are already inside if the radius of the cap is greater than 768hu)


if (distanceToCap > MAX_VISIBILITY_RANGE) {
return 0; // Don't draw the ring if the player is too far away
}

// smooth fade out of ring opacity based on distance to player
const float opacityCoef = distanceToCap / MAX_VISIBILITY_RANGE;
ringOpacity = static_cast<int>(128 * (1.0f - opacityCoef));
}

ringColor[3] = ringOpacity;

// draw bars ring
constexpr float SEGMENT_SIZE = 8.0f;
constexpr float RING_BOTTOM = 4.0f;

const int segments = (int)round(M_PI_F * 2.0f * m_flCapzoneRadius / SEGMENT_SIZE);
const float zBottom = capOrigin.z + RING_BOTTOM;
const float radius = m_flCapzoneRadius;

CMatRenderContextPtr pRenderContext(materials);
pRenderContext->Bind(m_pRingMaterial);
IMesh *pMesh = pRenderContext->GetDynamicMesh(true);
CMeshBuilder meshBuilder;
meshBuilder.Begin(pMesh, MATERIAL_QUADS, segments);

this->DrawBarRing(meshBuilder, segments, zBottom, SEGMENT_SIZE, radius, capOrigin, ringColor);

meshBuilder.End(false, true);

if (cl_neo_cap_zone_edge_value != NEO_CAP_EDGE_LINE_LOGO || capTeam == TEAM_ANY) {
return 1;
}

// draw logo ring
constexpr float LOGO_SIZE = 16.0f;
constexpr float LOGO_BOTTOM = 2.0f;
const float logoRadius = m_flCapzoneRadius - 1.0f;
// draw logo only 5 times
constexpr int LOGO_SEGMENTS = 5;
const float zLogoBottom = capOrigin.z + LOGO_BOTTOM;

pRenderContext->Bind(capTeam == TEAM_JINRAI ? m_pRingJinraiMaterial : m_pRingNsfMaterial);
IMesh* pMeshTeam = pRenderContext->GetDynamicMesh(true);
CMeshBuilder meshBuilderTeam;
meshBuilderTeam.Begin(pMeshTeam, MATERIAL_QUADS, LOGO_SEGMENTS);

this->DrawLogoRing(meshBuilderTeam, LOGO_SEGMENTS, zLogoBottom, LOGO_SIZE, logoRadius, capOrigin, ringColor);

meshBuilderTeam.End(false, true);

return 1;
}

void CNEOGhostCapturePoint::DrawBarRing(CMeshBuilder& builder, int segments, float zBottom, float segmentSize, float radius, Vector capOrigin, Color ringColor)
{
// for portal effect add currentRotationInRad to angle0, maybe use it for the cap effect?
//constexpr float ROTATION_SPEED = 45.0f;
//const float currentRotationInRad = ROTATION_SPEED * gpGlobals->curtime * (M_PI_F / 180.0f);
constexpr float CIRCLE_LENGTH = M_PI_F * 2.0f;

for (int i = 0; i < segments; i++)
{
const float angle0 = ((float)i / segments) * CIRCLE_LENGTH;
const float angle1 = ((float)(i + 1) / segments) * CIRCLE_LENGTH;
this->DrawSegment(builder, angle0, angle1, zBottom, zBottom + segmentSize, radius, capOrigin, ringColor);
}
}

void CNEOGhostCapturePoint::DrawLogoRing(CMeshBuilder& builder, int segmentsToFill, float zBottom, float segmentSize, float radius, Vector capOrigin, Color ringColor)
{
constexpr float ROTATION_SPEED = 5.0f;
constexpr float CIRCLE_LENGTH = M_PI_F * 2.0f;
const float currentRotationInRad = ROTATION_SPEED * gpGlobals->curtime * (M_PI_F / 180.0f);
// calculate the number of segments to fill based on the segment size and radius
const int segmentsCountBySize = (int)round(CIRCLE_LENGTH * radius / segmentSize);

for (int i = 0; i < segmentsToFill; i++)
{
const float angle0 = ((float)i / segmentsToFill) * CIRCLE_LENGTH + currentRotationInRad;
// we take angle0 and add angle increment based on single segment size to get angle1
// that way we are achieving a partial fill of the ring based on the segment size and radius
const float angle1 = angle0 + ((float)1 / segmentsCountBySize) * CIRCLE_LENGTH;
this->DrawSegment(builder, angle0, angle1, zBottom, zBottom + segmentSize, radius, capOrigin, ringColor);
}
}

void CNEOGhostCapturePoint::DrawSegment(CMeshBuilder& builder, float angle0, float angle1, float zBottom, float zTop, float radius, Vector capOrigin, Color ringColor) {
const float cos0 = cosf(angle0), sin0 = sinf(angle0);
const float cos1 = cosf(angle1), sin1 = sinf(angle1);

// Bottom at angle0
builder.Position3f(capOrigin.x + cos0 * radius, capOrigin.y + sin0 * radius, zBottom);
builder.Normal3f(cos0, sin0, 0.0f);
builder.Color4ub(ringColor.r(), ringColor.g(), ringColor.b(), ringColor.a());
builder.TexCoord2f(0, 0.0f, 1.0f);
builder.AdvanceVertex();

// Bottom at angle1
builder.Position3f(capOrigin.x + cos1 * radius, capOrigin.y + sin1 * radius, zBottom);
builder.Normal3f(cos1, sin1, 0.0f);
builder.Color4ub(ringColor.r(), ringColor.g(), ringColor.b(), ringColor.a());
builder.TexCoord2f(0, 1.0f, 1.0f);
builder.AdvanceVertex();

// Top at angle1
builder.Position3f(capOrigin.x + cos1 * radius, capOrigin.y + sin1 * radius, zTop);
builder.Normal3f(cos1, sin1, 0.0f);
builder.Color4ub(ringColor.r(), ringColor.g(), ringColor.b(), ringColor.a());
builder.TexCoord2f(0, 1.0f, 0.0f);
builder.AdvanceVertex();

// Top at angle0
builder.Position3f(capOrigin.x + cos0 * radius, capOrigin.y + sin0 * radius, zTop);
builder.Normal3f(cos0, sin0, 0.0f);
builder.Color4ub(ringColor.r(), ringColor.g(), ringColor.b(), ringColor.a());
builder.TexCoord2f(0, 0.0f, 0.0f);
builder.AdvanceVertex();
}
#endif

void CNEOGhostCapturePoint::Precache(void)
{
BaseClass::Precache();

AddEFlags(EFL_FORCE_CHECK_TRANSMIT);

#ifdef CLIENT_DLL
PrecacheMaterial("effects/cap_zone_ring");
PrecacheMaterial("effects/cap_zone_ring_nsf");
PrecacheMaterial("effects/cap_zone_ring_jinrai");
#endif
}

#ifdef GAME_DLL
Expand Down
14 changes: 14 additions & 0 deletions src/game/shared/neo/neo_ghost_cap_point.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include "vphysics_interface.h"
#include "c_neo_player.h"
#include "ienginevgui.h"
#include "materialsystem/imaterial.h"
#include "materialsystem/imesh.h"
#endif

#ifdef CLIENT_DLL
Expand Down Expand Up @@ -94,6 +96,18 @@ class CNEOGhostCapturePoint : public CBaseEntity
bool m_bIsActive;

CNEOHud_GhostCapPoint *m_pHUDCapPoint = nullptr;
IMaterial *m_pRingMaterial = nullptr;
IMaterial* m_pRingNsfMaterial = nullptr;
IMaterial* m_pRingJinraiMaterial = nullptr;

public:
virtual bool ShouldDraw() override;
virtual RenderGroup_t GetRenderGroup() override;
virtual void GetRenderBoundsWorldspace(Vector& mins, Vector& maxs) override;
virtual int DrawModel(int flags) override;
virtual void DrawBarRing(CMeshBuilder& builder, int segments, float zBottom, float segmentSize, float radius, Vector capOrigin, Color ringColor);
virtual void DrawLogoRing(CMeshBuilder& builder, int segments, float zBottom, float segmentSize, float radius, Vector capOrigin, Color ringColor);
virtual void DrawSegment(CMeshBuilder& builder, float angle0, float angle1, float zBottom, float zTop, float radius, Vector capOrigin, Color ringColor);
#endif
};

Expand Down