-
Notifications
You must be signed in to change notification settings - Fork 25
Cap zone ring #2119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Cap zone ring #2119
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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!!! | ||
|
|
@@ -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; | ||
|
|
@@ -142,6 +155,7 @@ int CNEOGhostCapturePoint::owningTeamAlternate() const | |
|
|
||
| void CNEOGhostCapturePoint::Spawn(void) | ||
| { | ||
| Precache(); | ||
| BaseClass::Spawn(); | ||
|
|
||
| AddEFlags(EFL_FORCE_CHECK_TRANSMIT); | ||
|
|
@@ -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); | ||
| SetNextClientThink(gpGlobals->curtime + NEO_GHOSTCAP_GRAPHICS_THINK_INTERVAL); | ||
| #endif | ||
| } | ||
|
|
@@ -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; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
The only solutions as far as I can tell are:
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.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. damn it's tough
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove