From 5bb83cb24a500b66656024ee5dd7c64dcff8ed8f Mon Sep 17 00:00:00 2001 From: DESTROYGIRL <170364626+DESTROYGIRL@users.noreply.github.com> Date: Tue, 13 Jan 2026 12:36:32 +0000 Subject: [PATCH 1/3] initial --- src/game/server/CMakeLists.txt | 1 + src/game/server/neo/neo_npc_targetsystem.cpp | 535 +++++++++---------- src/game/server/neo/neo_npc_targetsystem.h | 61 +++ src/game/shared/baseentity_shared.cpp | 68 ++- src/game/shared/neo/neo_shot_manipulator.h | 16 +- 5 files changed, 370 insertions(+), 311 deletions(-) create mode 100644 src/game/server/neo/neo_npc_targetsystem.h diff --git a/src/game/server/CMakeLists.txt b/src/game/server/CMakeLists.txt index b7e4b3c741..a101598df1 100644 --- a/src/game/server/CMakeLists.txt +++ b/src/game/server/CMakeLists.txt @@ -1391,6 +1391,7 @@ target_sources_grouped( neo/neo_npc_dummy.cpp neo/neo_npc_dummy.h neo/neo_npc_targetsystem.cpp + neo/neo_npc_targetsystem.h neo/neo_player.cpp neo/neo_player.h neo/neo_smokelineofsightblocker.cpp diff --git a/src/game/server/neo/neo_npc_targetsystem.cpp b/src/game/server/neo/neo_npc_targetsystem.cpp index f5a5308727..9d2cec79a9 100644 --- a/src/game/server/neo/neo_npc_targetsystem.cpp +++ b/src/game/server/neo/neo_npc_targetsystem.cpp @@ -1,96 +1,40 @@ -#include "cbase.h" -#include "baseentity.h" -#include "filters.h" +#include "neo_npc_targetsystem.h" #include "neo_player.h" +#include "ammodef.h" + #include "tier0/memdbgon.h" #define CLOAKED_VELOCITY_THRESHOLD 32400 // 180 horizontal velocity. Slightly under sprint/wigglerun speed -class CNEO_NPCTargetSystem : public CBaseEntity -{ -public: - DECLARE_CLASS(CNEO_NPCTargetSystem, CBaseEntity); - DECLARE_DATADESC(); - -private: - // KVs - float m_flFOV = 90.0f; - float m_flTopClip = 60.0f; - float m_flBottomClip = -100.0f; - float m_flMaxViewDistance = 800.0f; - float m_flDeadzone = 100.0f; - float m_flMiddleBoundsHalf = 20.0f; - string_t m_iFilterName = NULL_STRING; - bool m_bStartDisabled = false; - bool m_bMotionVision = true; - // Damage trace - string_t m_strDamageSourceName = NULL_STRING; - float m_flDamage = 10.0f; - float m_flFireRate = 1.0f; - - CBaseFilter *m_pFilter = nullptr; - EHANDLE m_hDamageSource = nullptr; - - COutputEvent m_OnSpotted; - COutputEvent m_OnRight; - COutputEvent m_OnLeft; - COutputEvent m_OnMiddle; - COutputEvent m_OnMiddleIgnore; - COutputEvent m_OnExit; - COutputEvent m_OnExitMiddle; - - void InputEnable(inputdata_t &inputData); - void InputDisable(inputdata_t &inputData); - -public: - void Spawn(); - void Think(); - -private: - enum TargetZone_e - { - ZONE_NONE = 0, - ZONE_LEFT, - ZONE_MIDDLE, - ZONE_RIGHT - }; - - TargetZone_e m_iLastZone = ZONE_NONE; - bool m_bTargetAcquired = false; - bool m_bMiddleIgnoreActive = false; - float m_flNextFireTime = 0; - CBasePlayer *m_pLastBestTarget = nullptr; -}; - LINK_ENTITY_TO_CLASS(neo_npc_targetsystem, CNEO_NPCTargetSystem); BEGIN_DATADESC(CNEO_NPCTargetSystem) - DEFINE_KEYFIELD(m_flFOV, FIELD_FLOAT, "fov"), - DEFINE_KEYFIELD(m_flTopClip, FIELD_FLOAT, "topclip"), - DEFINE_KEYFIELD(m_flBottomClip, FIELD_FLOAT, "bottomclip"), - DEFINE_KEYFIELD(m_flMaxViewDistance, FIELD_FLOAT, "maxviewdistance"), - DEFINE_KEYFIELD(m_flDeadzone, FIELD_FLOAT, "deadzone"), - DEFINE_KEYFIELD(m_flMiddleBoundsHalf, FIELD_FLOAT, "middlebounds"), - DEFINE_KEYFIELD(m_iFilterName, FIELD_STRING, "filtername"), - DEFINE_KEYFIELD(m_bStartDisabled, FIELD_BOOLEAN, "StartDisabled"), - DEFINE_KEYFIELD(m_bMotionVision, FIELD_BOOLEAN, "motionvision"), - - DEFINE_KEYFIELD(m_strDamageSourceName, FIELD_STRING, "damagesource"), - DEFINE_KEYFIELD(m_flDamage, FIELD_FLOAT, "damage"), - DEFINE_KEYFIELD(m_flFireRate, FIELD_FLOAT, "firerate"), - - DEFINE_INPUTFUNC(FIELD_VOID, "Enable", InputEnable), - DEFINE_INPUTFUNC(FIELD_VOID, "Disable", InputDisable), - - DEFINE_OUTPUT(m_OnSpotted, "OnSpotted"), - DEFINE_OUTPUT(m_OnRight, "OnRight"), - DEFINE_OUTPUT(m_OnLeft, "OnLeft"), - DEFINE_OUTPUT(m_OnMiddle, "OnMiddle"), - DEFINE_OUTPUT(m_OnMiddleIgnore, "OnMiddleIgnore"), - DEFINE_OUTPUT(m_OnExit, "OnExit"), - DEFINE_OUTPUT(m_OnExitMiddle, "OnExitMiddle"), - - DEFINE_THINKFUNC(Think), + DEFINE_KEYFIELD(m_flFOV, FIELD_FLOAT, "fov"), + DEFINE_KEYFIELD(m_flTopClip, FIELD_FLOAT, "topclip"), + DEFINE_KEYFIELD(m_flBottomClip, FIELD_FLOAT, "bottomclip"), + DEFINE_KEYFIELD(m_flMaxViewDistance, FIELD_FLOAT, "maxviewdistance"), + DEFINE_KEYFIELD(m_flDeadzone, FIELD_FLOAT, "deadzone"), + DEFINE_KEYFIELD(m_flMiddleBoundsHalf, FIELD_FLOAT, "middlebounds"), + DEFINE_KEYFIELD(m_iFilterName, FIELD_STRING, "filtername"), + DEFINE_KEYFIELD(m_bStartDisabled, FIELD_BOOLEAN, "StartDisabled"), + DEFINE_KEYFIELD(m_bMotionVision, FIELD_BOOLEAN, "motionvision"), + + DEFINE_KEYFIELD(m_strDamageSourceName, FIELD_STRING, "damagesource"), + DEFINE_KEYFIELD(m_flDamage, FIELD_FLOAT, "damage"), + DEFINE_KEYFIELD(m_flFireRate, FIELD_FLOAT, "firerate"), + + DEFINE_INPUTFUNC(FIELD_VOID, "Enable", InputEnable), + DEFINE_INPUTFUNC(FIELD_VOID, "Disable", InputDisable), + + DEFINE_OUTPUT(m_OnSpotted, "OnSpotted"), + DEFINE_OUTPUT(m_OnRight, "OnRight"), + DEFINE_OUTPUT(m_OnLeft, "OnLeft"), + DEFINE_OUTPUT(m_OnMiddle, "OnMiddle"), + DEFINE_OUTPUT(m_OnMiddleIgnore, "OnMiddleIgnore"), + DEFINE_OUTPUT(m_OnExit, "OnExit"), + DEFINE_OUTPUT(m_OnExitMiddle, "OnExitMiddle"), + + DEFINE_THINKFUNC(Think), END_DATADESC() // This entity is a built-for-purpose recreation of the HT tank's "vision" @@ -106,223 +50,230 @@ END_DATADESC() void CNEO_NPCTargetSystem::Spawn() { - if (m_iFilterName != NULL_STRING) - { - m_pFilter = dynamic_cast(gEntList.FindEntityByName(nullptr, m_iFilterName)); - } - - if (m_strDamageSourceName != NULL_STRING) - { - m_hDamageSource = gEntList.FindEntityByName(nullptr, m_strDamageSourceName); - } - - SetThink(&CNEO_NPCTargetSystem::Think); - if (m_bStartDisabled) - { - SetNextThink(TICK_NEVER_THINK); - } - else - { - SetNextThink(gpGlobals->curtime + 0.05f); - } + if (m_iFilterName != NULL_STRING) + { + m_pFilter = dynamic_cast(gEntList.FindEntityByName(nullptr, m_iFilterName)); + } + + if (m_strDamageSourceName != NULL_STRING) + { + m_hDamageSource = gEntList.FindEntityByName(nullptr, m_strDamageSourceName); + } + + SetThink(&CNEO_NPCTargetSystem::Think); + if (m_bStartDisabled) + { + SetNextThink(TICK_NEVER_THINK); + } + else + { + SetNextThink(gpGlobals->curtime + 0.05f); + } } void CNEO_NPCTargetSystem::Think() { - float flBestLateral = FLT_MAX; - float flBestForward = 0.0f; - Vector vBestLateral; - CBasePlayer *pBestTarget = nullptr; - - const float flMaxViewDistanceSqr = m_flMaxViewDistance * m_flMaxViewDistance; - const float flTanFOV = tan(DEG2RAD(m_flFOV * 0.5f)); - - Vector vecEye = EyePosition(); // Despite not using a model mappers are able to set their own eye offsets - - Vector vecForward, vecRight; - AngleVectors(GetAbsAngles(), &vecForward, &vecRight, nullptr); - - for (int i = 1; i <= gpGlobals->maxClients; i++) - { - CBasePlayer* pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer || !pPlayer->IsAlive()) - { - continue; - } - - if (m_pFilter && !m_pFilter->PassesFilter(this, pPlayer)) - { - continue; - } - - if (m_bMotionVision) - { - if (ToNEOPlayer(pPlayer)->m_bInThermOpticCamo) - { - if (pPlayer->GetAbsVelocity().Length2DSqr() < CLOAKED_VELOCITY_THRESHOLD) - { - continue; - } - } - } - - // The player's eye position is used for detection. - // The entity might be able to see the player's body up to their neck and they won't be detected - Vector vecPlayerEye = pPlayer->EyePosition(); - Vector vecTargetPos = vecPlayerEye - vecEye; - - Vector vecTarget2DPos = vecTargetPos; - vecTarget2DPos.z = 0; - - float flDistanceSqr = vecTarget2DPos.LengthSqr(); - if (flDistanceSqr > flMaxViewDistanceSqr) // Ignore if out of range - { - continue; - } - - float flZDiff = vecPlayerEye.z - vecEye.z; - if (flZDiff > m_flTopClip || flZDiff < m_flBottomClip) // Ignore if head is out of high/low bounds - { - continue; - } - - if (!FVisible(pPlayer, MASK_BLOCKLOS, nullptr)) // Draw a trace to check if we can actually see the player - { - continue; - } - - float flForwardDist = DotProduct(vecTarget2DPos, vecForward); - if (flForwardDist <= 0) // Is the player behind? - { - continue; - } - - // How close is this player to the center of our view - Vector vLateral = vecTarget2DPos - (flForwardDist * vecForward); - float flLateral = vLateral.Length(); - - if (flLateral > flTanFOV * flForwardDist) // Ignore if they are outside our FOV - { - continue; - } - - // The player is valid. But let's focus the one closest to us & our line of fire!! - if (flLateral < flBestLateral) - { - flBestLateral = flLateral; - flBestForward = flForwardDist; - vBestLateral = vLateral; - pBestTarget = pPlayer; - m_pLastBestTarget = pPlayer; - } - } - - // Determine the zone this valid player is in - enum TargetZone_e iNewZone = ZONE_NONE; - if (pBestTarget) - { - if (flBestForward >= m_flDeadzone && flBestLateral <= m_flMiddleBoundsHalf) // If the player is outside the deadzone and inside the middle volume - { - iNewZone = ZONE_MIDDLE; - } - else if (flBestLateral > m_flMiddleBoundsHalf) - { - float flDotRight = DotProduct(vBestLateral, vecRight); - iNewZone = (flDotRight > 0.0f) ? ZONE_RIGHT : ZONE_LEFT; // If the dot product is negative, the playa is on the left side - } - } - - bool bMiddleIgnore = pBestTarget && (flBestLateral <= m_flMiddleBoundsHalf); // Its just the middle zone ignoring the deadzone. - - // Fire outputs - if (pBestTarget) - { - if (!m_bTargetAcquired) - { - m_OnSpotted.FireOutput(pBestTarget, this); - m_bTargetAcquired = true; - } - - if (bMiddleIgnore && !m_bMiddleIgnoreActive) - { - m_OnMiddleIgnore.FireOutput(pBestTarget, this); - m_bMiddleIgnoreActive = true; - } - else if (!bMiddleIgnore && m_bMiddleIgnoreActive) - { - m_bMiddleIgnoreActive = false; - } - - if (iNewZone != m_iLastZone) // If the zone has changed - { - if (m_iLastZone == ZONE_MIDDLE && iNewZone != ZONE_MIDDLE) - { - m_OnExitMiddle.FireOutput(pBestTarget, this); - } - switch (iNewZone) - { - case ZONE_MIDDLE: - m_OnMiddle.FireOutput(pBestTarget, this); - break; - case ZONE_LEFT: - m_OnLeft.FireOutput(pBestTarget, this); - break; - case ZONE_RIGHT: - m_OnRight.FireOutput(pBestTarget, this); - break; - default: - break; - } - } - } - else - { - if (m_bTargetAcquired) - { - if (m_iLastZone == ZONE_MIDDLE) - { - m_OnExitMiddle.FireOutput(m_pLastBestTarget, this); - } - m_OnExit.FireOutput(m_pLastBestTarget, this); - m_bTargetAcquired = false; - } - // Reset state when no target is found - m_bMiddleIgnoreActive = false; - } - - // Optional - damage the target - if (m_hDamageSource && pBestTarget && (iNewZone == ZONE_MIDDLE)) - { - if (gpGlobals->curtime >= m_flNextFireTime) - { - trace_t tr; - UTIL_TraceLine(m_hDamageSource->GetAbsOrigin(), pBestTarget->EyePosition(), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr); - - if (tr.m_pEnt) - { - CTakeDamageInfo info(this, this, m_flDamage, DMG_BULLET); - info.SetDamagePosition(m_hDamageSource->GetAbsOrigin()); - info.SetDamageForce(Vector(0, 0, 1)); // These are just to fill the fields. Probably never used here - tr.m_pEnt->TakeDamage(info); - } - - if (m_flFireRate > 0) - { - m_flNextFireTime = gpGlobals->curtime + (1.0f / m_flFireRate); - } - } - } - - m_iLastZone = iNewZone; - SetNextThink(gpGlobals->curtime + 0.05f); + float flBestLateral = FLT_MAX; + float flBestForward = 0.0f; + Vector vBestLateral; + CBasePlayer *pBestTarget = nullptr; + + const float flMaxViewDistanceSqr = m_flMaxViewDistance * m_flMaxViewDistance; + const float flTanFOV = tan(DEG2RAD(m_flFOV * 0.5f)); + + Vector vecEye = EyePosition(); // Despite not using a model mappers are able to set their own eye offsets + + Vector vecForward, vecRight; + AngleVectors(GetAbsAngles(), &vecForward, &vecRight, nullptr); + + for (int i = 1; i <= gpGlobals->maxClients; i++) + { + CBasePlayer* pPlayer = UTIL_PlayerByIndex(i); + if (!pPlayer || !pPlayer->IsAlive()) + { + continue; + } + + if (m_pFilter && !m_pFilter->PassesFilter(this, pPlayer)) + { + continue; + } + + if (m_bMotionVision) + { + if (ToNEOPlayer(pPlayer)->m_bInThermOpticCamo) + { + if (pPlayer->GetAbsVelocity().Length2DSqr() < CLOAKED_VELOCITY_THRESHOLD) + { + continue; + } + } + } + + // The player's eye position is used for detection. + // The entity might be able to see the player's body up to their neck and they won't be detected + Vector vecPlayerEye = pPlayer->EyePosition(); + Vector vecTargetPos = vecPlayerEye - vecEye; + + Vector vecTarget2DPos = vecTargetPos; + vecTarget2DPos.z = 0; + + float flDistanceSqr = vecTarget2DPos.LengthSqr(); + if (flDistanceSqr > flMaxViewDistanceSqr) // Ignore if out of range + { + continue; + } + + float flZDiff = vecPlayerEye.z - vecEye.z; + if (flZDiff > m_flTopClip || flZDiff < m_flBottomClip) // Ignore if head is out of high/low bounds + { + continue; + } + + if (!FVisible(pPlayer, MASK_BLOCKLOS, nullptr)) // Draw a trace to check if we can actually see the player + { + continue; + } + + float flForwardDist = DotProduct(vecTarget2DPos, vecForward); + if (flForwardDist <= 0) // Is the player behind? + { + continue; + } + + // How close is this player to the center of our view + Vector vLateral = vecTarget2DPos - (flForwardDist * vecForward); + float flLateral = vLateral.Length(); + + if (flLateral > flTanFOV * flForwardDist) // Ignore if they are outside our FOV + { + continue; + } + + // The player is valid. But let's focus the one closest to us & our line of fire!! + if (flLateral < flBestLateral) + { + flBestLateral = flLateral; + flBestForward = flForwardDist; + vBestLateral = vLateral; + pBestTarget = pPlayer; + m_pLastBestTarget = pPlayer; + } + } + + // Determine the zone this valid player is in + enum TargetZone_e iNewZone = ZONE_NONE; + if (pBestTarget) + { + if (flBestForward >= m_flDeadzone && flBestLateral <= m_flMiddleBoundsHalf) // If the player is outside the deadzone and inside the middle volume + { + iNewZone = ZONE_MIDDLE; + } + else if (flBestLateral > m_flMiddleBoundsHalf) + { + float flDotRight = DotProduct(vBestLateral, vecRight); + iNewZone = (flDotRight > 0.0f) ? ZONE_RIGHT : ZONE_LEFT; // If the dot product is negative, the playa is on the left side + } + } + + bool bMiddleIgnore = pBestTarget && (flBestLateral <= m_flMiddleBoundsHalf); // Its just the middle zone ignoring the deadzone. + + // Fire outputs + if (pBestTarget) + { + if (!m_bTargetAcquired) + { + m_OnSpotted.FireOutput(pBestTarget, this); + m_bTargetAcquired = true; + } + + if (bMiddleIgnore && !m_bMiddleIgnoreActive) + { + m_OnMiddleIgnore.FireOutput(pBestTarget, this); + m_bMiddleIgnoreActive = true; + } + else if (!bMiddleIgnore && m_bMiddleIgnoreActive) + { + m_bMiddleIgnoreActive = false; + } + + if (iNewZone != m_iLastZone) // If the zone has changed + { + if (m_iLastZone == ZONE_MIDDLE && iNewZone != ZONE_MIDDLE) + { + m_OnExitMiddle.FireOutput(pBestTarget, this); + } + switch (iNewZone) + { + case ZONE_MIDDLE: + m_OnMiddle.FireOutput(pBestTarget, this); + break; + case ZONE_LEFT: + m_OnLeft.FireOutput(pBestTarget, this); + break; + case ZONE_RIGHT: + m_OnRight.FireOutput(pBestTarget, this); + break; + default: + break; + } + } + } + else + { + if (m_bTargetAcquired) + { + if (m_iLastZone == ZONE_MIDDLE) + { + m_OnExitMiddle.FireOutput(m_pLastBestTarget, this); + } + m_OnExit.FireOutput(m_pLastBestTarget, this); + m_bTargetAcquired = false; + } + // Reset state when no target is found + m_bMiddleIgnoreActive = false; + } + + // Optional - damage the target + if (m_hDamageSource && pBestTarget && (iNewZone == ZONE_MIDDLE)) + { + if (gpGlobals->curtime >= m_flNextFireTime) + { + Vector const vecSrc = m_hDamageSource.Get()->GetAbsOrigin(); + Vector const vecTarget = pBestTarget->EyePosition(); + Vector vecDir = vecTarget - vecSrc; + VectorNormalize(vecDir); + + FireBulletsInfo_t info( 1, vecSrc, vecDir, vec3_origin, MAX_TRACE_LENGTH, GetAmmoDef()->Index("AMMO_PRI"), 28.0f, true ); + info.m_flDamage = m_flDamage; + FireBullets(info); + + if (m_flFireRate > 0) + { + m_flNextFireTime = gpGlobals->curtime + (1.0f / m_flFireRate); + } + } + } + + m_iLastZone = iNewZone; + SetNextThink(gpGlobals->curtime + 0.05f); +} + +bool CNEO_NPCTargetSystem::CanSee(CBaseEntity *pEntity) +{ + if (m_pFilter && m_pFilter->PassesFilter(this, pEntity)) + { + return true; + } + return false; } void CNEO_NPCTargetSystem::InputEnable(inputdata_t &inputData) { - SetNextThink(gpGlobals->curtime + 0.05f); + SetNextThink(gpGlobals->curtime + 0.05f); } void CNEO_NPCTargetSystem::InputDisable(inputdata_t &inputData) { - SetNextThink(TICK_NEVER_THINK); + SetNextThink(TICK_NEVER_THINK); } diff --git a/src/game/server/neo/neo_npc_targetsystem.h b/src/game/server/neo/neo_npc_targetsystem.h new file mode 100644 index 0000000000..620f6ad597 --- /dev/null +++ b/src/game/server/neo/neo_npc_targetsystem.h @@ -0,0 +1,61 @@ +#pragma once +#include "cbase.h" +#include "filters.h" + +class CNEO_NPCTargetSystem : public CBaseEntity +{ +public: + DECLARE_CLASS(CNEO_NPCTargetSystem, CBaseEntity); + DECLARE_DATADESC(); + +private: + // KVs + float m_flFOV = 90.0f; + float m_flTopClip = 60.0f; + float m_flBottomClip = -100.0f; + float m_flMaxViewDistance = 800.0f; + float m_flDeadzone = 100.0f; + float m_flMiddleBoundsHalf = 20.0f; + string_t m_iFilterName = NULL_STRING; + bool m_bStartDisabled = false; + bool m_bMotionVision = true; + // Damage trace + string_t m_strDamageSourceName = NULL_STRING; + float m_flDamage = 10.0f; + float m_flFireRate = 1.0f; + + CBaseFilter *m_pFilter = nullptr; + EHANDLE m_hDamageSource = nullptr; + + COutputEvent m_OnSpotted; + COutputEvent m_OnRight; + COutputEvent m_OnLeft; + COutputEvent m_OnMiddle; + COutputEvent m_OnMiddleIgnore; + COutputEvent m_OnExit; + COutputEvent m_OnExitMiddle; + + void InputEnable(inputdata_t &inputData); + void InputDisable(inputdata_t &inputData); + +public: + virtual void Spawn() override; + virtual void Think() override; + + bool CanSee(CBaseEntity *pEntity); + +private: + enum TargetZone_e + { + ZONE_NONE = 0, + ZONE_LEFT, + ZONE_MIDDLE, + ZONE_RIGHT + }; + + TargetZone_e m_iLastZone = ZONE_NONE; + bool m_bTargetAcquired = false; + bool m_bMiddleIgnoreActive = false; + float m_flNextFireTime = 0; + CBasePlayer *m_pLastBestTarget = nullptr; +}; \ No newline at end of file diff --git a/src/game/shared/baseentity_shared.cpp b/src/game/shared/baseentity_shared.cpp index 47409a932f..ea1c59c654 100644 --- a/src/game/shared/baseentity_shared.cpp +++ b/src/game/shared/baseentity_shared.cpp @@ -1642,6 +1642,9 @@ void NormalizeAngles(QAngle& angles) #ifdef CLIENT_DLL ConVar cl_neo_bullet_trace("cl_neo_bullet_trace", "0", FCVAR_CHEAT, "Show bullet trace", true, 0, true, 1); ConVar cl_neo_bullet_trace_max_pen("cl_neo_bullet_trace_max_pen", "65", FCVAR_CHEAT, "How much pen does a bullet need to have to show up as a solid red line. Configure to the current weapon used, or leave on default to see differences in pen between weapons", true, 0.1, true, 999.f); +#else +ConVar sv_neo_bullet_trace("sv_neo_bullet_trace", "0", FCVAR_CHEAT, "Show bullet trace", true, 0, true, 1); +ConVar sv_neo_bullet_trace_max_pen("sv_neo_bullet_trace_max_pen", "65", FCVAR_CHEAT, "How much pen does a bullet need to have to show up as a solid red line. Configure to the current weapon used, or leave on default to see differences in pen between weapons", true, 0.1, true, 999.f); #endif // CLIENT_DLL #endif // NEO void CBaseEntity::FireBullets( const FireBulletsInfo_t &info ) @@ -1742,16 +1745,24 @@ void CBaseEntity::FireBullets( const FireBulletsInfo_t &info ) // Set up our shot manipulator. //----------------------------------------------------- #ifdef NEO - auto pNeoAttacker = dynamic_cast(this); - Assert(pNeoAttacker); - pNeoAttacker->m_bIneligibleForLoadoutPick = true; + CNEOBaseCombatWeapon *neoWeapon = nullptr; + int numShotsFired = 0; - auto neoWeapon = dynamic_cast(pNeoAttacker->GetActiveWeapon()); - Assert(neoWeapon); + if ( IsPlayer() ) + { + Assert(pAttacker == this); + + auto pNeoAttacker = dynamic_cast(this); + Assert(pNeoAttacker); + pNeoAttacker->m_bIneligibleForLoadoutPick = true; + + neoWeapon = dynamic_cast(pNeoAttacker->GetActiveWeapon()); + Assert(neoWeapon); - const int numShotsFired = neoWeapon ? neoWeapon->GetNumShotsFired() : 0; + numShotsFired = neoWeapon ? neoWeapon->GetNumShotsFired() : 0; + } - CNEOShotManipulator Manipulator(numShotsFired, info.m_vecDirShooting, pNeoAttacker, neoWeapon); + CNEOShotManipulator Manipulator(numShotsFired, info.m_vecDirShooting, pAttacker, neoWeapon); #else CShotManipulator Manipulator( info.m_vecDirShooting ); #endif @@ -1952,9 +1963,15 @@ void CBaseEntity::FireBullets( const FireBulletsInfo_t &info ) UpdateShotStatistics( tr ); #ifdef NEO + bool bWeaponAutomatic = true; + if ( IsPlayer() ) + { + bWeaponAutomatic = neoWeapon->IsAutomatic(); + } + const int soundEntChannel = (info.m_nFlags & FIRE_BULLETS_TEMPORARY_DANGER_SOUND) ? SOUNDENT_CHANNEL_BULLET_IMPACT - : (neoWeapon->IsAutomatic() ? SOUNDENT_CHANNEL_REPEATING : SOUNDENT_CHANNEL_WEAPON); + : (bWeaponAutomatic ? SOUNDENT_CHANNEL_REPEATING : SOUNDENT_CHANNEL_WEAPON); #else // For shots that don't need persistance int soundEntChannel = ( info.m_nFlags&FIRE_BULLETS_TEMPORARY_DANGER_SOUND ) ? SOUNDENT_CHANNEL_BULLET_IMPACT : SOUNDENT_CHANNEL_UNSPECIFIED; @@ -2052,6 +2069,11 @@ void CBaseEntity::FireBullets( const FireBulletsInfo_t &info ) { DebugDrawLine(info.m_vecSrc, tr.endpos, 255, 255 * (1 - (info.m_flPenetration / cl_neo_bullet_trace_max_pen.GetFloat())), 0, 1, 30.f); } +#else + if (sv_neo_bullet_trace.GetBool()) + { + DebugDrawLine(info.m_vecSrc, tr.endpos, 255, 255 * (1 - (info.m_flPenetration / sv_neo_bullet_trace_max_pen.GetFloat())), 0, 1, 30.f); + } #endif // CLIENT_DLL #endif // NEO @@ -2187,6 +2209,16 @@ void CBaseEntity::HandleShotPenetration(const FireBulletsInfo_t& info, DebugDrawLine(x1, x1 + Vector(0, 1, 0), 255, 255, 0, 1, 30.f); DebugDrawLine(x2, x2 + Vector(0, 0, 1), 255, 255, 0, 1, 30.f); } +#else + if (sv_neo_bullet_trace.GetBool()) + { // Bullet Block (Yellow STAR) + Vector x0 = tr.endpos + Vector(-0.5, 0, 0); + Vector x1 = tr.endpos + Vector(0, -0.5, 0); + Vector x2 = tr.endpos + Vector(0, 0, -0.5); + DebugDrawLine(x0, x0 + Vector(1, 0, 0), 255, 255, 0, 1, 30.f); + DebugDrawLine(x1, x1 + Vector(0, 1, 0), 255, 255, 0, 1, 30.f); + DebugDrawLine(x2, x2 + Vector(0, 0, 1), 255, 255, 0, 1, 30.f); + } #endif // CLIENT_DLL return; } @@ -2201,6 +2233,16 @@ void CBaseEntity::HandleShotPenetration(const FireBulletsInfo_t& info, DebugDrawLine(x1, x1 + Vector(0, 1, 0), 0, 255, 0, 1, 30.f); DebugDrawLine(x2, x2 + Vector(0, 0, 1), 0, 255, 0, 1, 30.f); } +#else + if (sv_neo_bullet_trace.GetBool()) + { // Entrance (GREEN STAR) + Vector x0 = tr.endpos + Vector(-0.5, 0, 0); + Vector x1 = tr.endpos + Vector(0, -0.5, 0); + Vector x2 = tr.endpos + Vector(0, 0, -0.5); + DebugDrawLine(x0, x0 + Vector(1, 0, 0), 0, 255, 0, 1, 30.f); + DebugDrawLine(x1, x1 + Vector(0, 1, 0), 0, 255, 0, 1, 30.f); + DebugDrawLine(x2, x2 + Vector(0, 0, 1), 0, 255, 0, 1, 30.f); + } #endif // CLIENT_DLL if (tr.m_pEnt && tr.m_pEnt->IsPlayer()) @@ -2267,6 +2309,16 @@ void CBaseEntity::HandleShotPenetration(const FireBulletsInfo_t& info, DebugDrawLine(x1, x1 + Vector(0, 1, 0), 255, 0, 0, 1, 30.f); DebugDrawLine(x2, x2 + Vector(0, 0, 1), 255, 0, 0, 1, 30.f); } +#else + if (sv_neo_bullet_trace.GetBool()) + { // Exit (RED STAR) + Vector x0 = penetrationTrace.endpos + Vector(-0.5, 0, 0); + Vector x1 = penetrationTrace.endpos + Vector(0, -0.5, 0); + Vector x2 = penetrationTrace.endpos + Vector(0, 0, -0.5); + DebugDrawLine(x0, x0 + Vector(1, 0, 0), 255, 0, 0, 1, 30.f); + DebugDrawLine(x1, x1 + Vector(0, 1, 0), 255, 0, 0, 1, 30.f); + DebugDrawLine(x2, x2 + Vector(0, 0, 1), 255, 0, 0, 1, 30.f); + } #endif //CLIENT_DLL CBaseEntity::FireBullets(behindMaterialInfo); diff --git a/src/game/shared/neo/neo_shot_manipulator.h b/src/game/shared/neo/neo_shot_manipulator.h index 27c414eae2..c5ea5e642e 100644 --- a/src/game/shared/neo/neo_shot_manipulator.h +++ b/src/game/shared/neo/neo_shot_manipulator.h @@ -7,21 +7,15 @@ #include "shot_manipulator.h" #include "weapon_neobasecombatweapon.h" -#ifdef CLIENT_DLL -#include "c_neo_player.h" -#else -#include "neo_player.h" -#endif - class CNEOBaseCombatWeapon; class CNEOShotManipulator : public CShotManipulator { public: - CNEOShotManipulator(int numBullet, const Vector& vecForward, CNEO_Player* player, CNEOBaseCombatWeapon* neoWep = NULL) + CNEOShotManipulator(int numBullet, const Vector& vecForward, CBaseEntity* ent, CNEOBaseCombatWeapon* neoWep = nullptr) : CShotManipulator(vecForward) { - Assert(player); - m_pPlayer = player; + Assert(ent); + m_pEnt = ent; // Not always a player m_pWeapon = neoWep; // we're ok with a nullptr here (ie. not an NT gun); just handle as a non-recoiled weapon then. @@ -49,7 +43,7 @@ class CNEOShotManipulator : public CShotManipulator { Vector m_vecRecoilDirection; - CNEO_Player* m_pPlayer; + CBaseEntity* m_pEnt; CNEOBaseCombatWeapon* m_pWeapon; }; @@ -71,7 +65,7 @@ inline const Vector& CNEOShotManipulator::ApplyRecoil(const Vector& vecSpread, f QAngle myangles; VectorAngles(m_vecShotDirection, myangles); - QAngle worldangles = TransformAnglesToWorldSpace(myangles, m_pPlayer->EntityToWorldTransform()); + QAngle worldangles = TransformAnglesToWorldSpace(myangles, m_pEnt->EntityToWorldTransform()); matrix3x4_t attachedToWorld; AngleMatrix(worldangles, attachedToWorld); From 1a490667efbebe9ee8115dc420c8ec6bedb510de Mon Sep 17 00:00:00 2001 From: DESTROYGIRL <170364626+DESTROYGIRL@users.noreply.github.com> Date: Sat, 17 Jan 2026 13:10:20 +0000 Subject: [PATCH 2/3] neo_worldpos_marker --- src/game/client/CMakeLists.txt | 4 + .../ui/neo_hud_worldpos_marker_generic.cpp | 184 ++++++++++++++++++ .../neo/ui/neo_hud_worldpos_marker_generic.h | 34 ++++ src/game/server/CMakeLists.txt | 2 + src/game/server/neo/neo_npc_targetsystem.cpp | 5 +- src/game/shared/neo/neo_gamerules.h | 1 + src/game/shared/neo/neo_worldpos_marker.cpp | 176 +++++++++++++++++ src/game/shared/neo/neo_worldpos_marker.h | 81 ++++++++ 8 files changed, 485 insertions(+), 2 deletions(-) create mode 100644 src/game/client/neo/ui/neo_hud_worldpos_marker_generic.cpp create mode 100644 src/game/client/neo/ui/neo_hud_worldpos_marker_generic.h create mode 100644 src/game/shared/neo/neo_worldpos_marker.cpp create mode 100644 src/game/shared/neo/neo_worldpos_marker.h diff --git a/src/game/client/CMakeLists.txt b/src/game/client/CMakeLists.txt index 0b34d76710..ddce75a7ef 100644 --- a/src/game/client/CMakeLists.txt +++ b/src/game/client/CMakeLists.txt @@ -1611,6 +1611,8 @@ target_sources_grouped( neo/ui/neo_hud_startup_sequence.h neo/ui/neo_hud_worldpos_marker.cpp neo/ui/neo_hud_worldpos_marker.h + neo/ui/neo_hud_worldpos_marker_generic.cpp + neo/ui/neo_hud_worldpos_marker_generic.h neo/ui/neo_scoreboard.cpp neo/ui/neo_scoreboard.h neo/ui/neo_hud_context_hint.cpp @@ -1648,6 +1650,8 @@ target_sources_grouped( ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_misc.h ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_weapon_loadout.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_weapon_loadout.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_worldpos_marker.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_worldpos_marker.h ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_enums.h ) diff --git a/src/game/client/neo/ui/neo_hud_worldpos_marker_generic.cpp b/src/game/client/neo/ui/neo_hud_worldpos_marker_generic.cpp new file mode 100644 index 0000000000..7bb7fe89de --- /dev/null +++ b/src/game/client/neo/ui/neo_hud_worldpos_marker_generic.cpp @@ -0,0 +1,184 @@ +#include "neo_hud_worldpos_marker_generic.h" +#include "neo_worldpos_marker.h" +#include "iclientmode.h" +#include + +#include "c_neo_player.h" +#include "neo_gamerules.h" + +extern ConVar neo_ghost_cap_point_hud_scale_factor; +extern ConVar cl_neo_hud_center_ghost_cap_size; + +NEO_HUD_ELEMENT_DECLARE_FREQ_CVAR( WorldPosMarker_Generic, 0 ) + +CNEOHud_WorldPosMarker_Generic::CNEOHud_WorldPosMarker_Generic( const char *pElementName, C_NEOWorldPosMarkerEnt *src, vgui::Panel *parent ) + : CNEOHud_WorldPosMarker (pElementName, parent ) +{ + SetAutoDelete(true); + m_iHideHudElementNumber = NEO_HUD_ELEMENT_WORLDPOS_MARKER_ENT; + + if (parent) + { + SetParent(parent); + } + else + { + SetParent(g_pClientMode->GetViewport()); + } + + vgui::surface()->GetScreenSize(m_iPosX, m_iPosY); + SetBounds(0, 0, m_iPosX, m_iPosY); + + m_pSource = src; + + for ( int i = 0; i < MAX_SCREEN_OVERLAYS; i++ ) + { + if ( m_pSource->m_iszSpriteNames[i][0] ) + { + m_hSprite[i] = vgui::surface()->CreateNewTextureID(); + Assert(m_hSprite[i] > 0); + vgui::surface()->DrawSetTextureFile( m_hSprite[i], m_pSource->m_iszSpriteNames[i], 1, false ); + } + } + + SetVisible(false); +} + +void CNEOHud_WorldPosMarker_Generic::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings(pScheme); + + m_hFont = pScheme->GetFont("NHudOCRSmall"); + + vgui::surface()->GetScreenSize(m_iPosX, m_iPosY); + SetBounds(0, 0, m_iPosX, m_iPosY); + + const int widerAxis = max(m_viewWidth, m_viewHeight); + m_viewCentreSize = widerAxis * (cl_neo_hud_center_ghost_cap_size.GetFloat() / 100.0f); +} + +void CNEOHud_WorldPosMarker_Generic::UpdateStateForNeoHudElementDraw() +{ + auto *player = C_NEO_Player::GetLocalNEOPlayer(); + if ( !player || !m_pSource ) + { + return; + } + + m_vecMyPos = m_pSource->GetAbsOrigin(); + m_flDistance = METERS_PER_INCH * player->GetAbsOrigin().DistTo( m_vecMyPos ); + + g_pVGuiLocalize->ConvertANSIToUnicode( m_pSource->m_szText, m_wszMarkerTextUnicode, sizeof( m_wszMarkerTextUnicode ) ); + if ( m_wszMarkerTextUnicode[0] != L'\0' && m_pSource->m_bShowDistance ) + { + wchar_t wszTemp[ARRAYSIZE(m_wszMarkerTextUnicode)]; + V_wcsncpy( wszTemp, m_wszMarkerTextUnicode, sizeof( wszTemp ) ); + + V_snwprintf( m_wszMarkerTextUnicode, ARRAYSIZE( m_wszMarkerTextUnicode ), L"%ls %.0f m", wszTemp, m_flDistance ); + } +} + +void CNEOHud_WorldPosMarker_Generic::DrawNeoHudElement() +{ + if ( !ShouldDraw() ) + { + return; + } + + auto *player = C_NEO_Player::GetLocalNEOPlayer(); + const int playerTeam = player->GetTeamNumber(); + + const color32 rCol = m_pSource->GetRenderColor(); + Color targetColor( rCol.r, rCol.g, rCol.b, rCol.a ); + + const bool playerIsPlaying = ( playerTeam == TEAM_JINRAI || playerTeam == TEAM_NSF ); + + int x, y; + GetVectorInScreenSpace(m_vecMyPos, x, y); + + const float scale = neo_ghost_cap_point_hud_scale_factor.GetFloat() * ( m_pSource->m_flScale / 0.5f ); + + constexpr float HALF_BASE_TEX_LENGTH = 64; + if ( playerIsPlaying && m_wszMarkerTextUnicode[0] != L'\0' ) + { + const float fadeTextMultiplier = GetFadeValueTowardsScreenCentre(x, y); + if (m_flDistance > 0.2f && fadeTextMultiplier > 0.001f) + { + int xWide = 0; + int yTall = 0; + vgui::surface()->GetTextSize(m_hFont, m_wszMarkerTextUnicode, xWide, yTall); + vgui::surface()->DrawSetColor(COLOR_TRANSPARENT); + vgui::surface()->DrawSetTextColor(FadeColour(COLOR_TINTGREY, fadeTextMultiplier)); + vgui::surface()->DrawSetTextFont(m_hFont); + vgui::surface()->DrawSetTextPos(x - (xWide / 2), y + (HALF_BASE_TEX_LENGTH * scale)); + vgui::surface()->DrawPrintText(m_wszMarkerTextUnicode, V_wcslen(m_wszMarkerTextUnicode)); + } + } + + vgui::surface()->DrawSetTexture( m_hSprite[m_pSource->m_iCurrentSprite] ); + if ( m_pSource->m_bCapzoneEffect ) + { + for (int i = 0; i < 4; i++) { + m_fMarkerScalesCurrent[i] = (remainder(gpGlobals->curtime, 2) / 2) + 0.5 + m_fMarkerScalesStart[i]; + if (m_fMarkerScalesCurrent[i] > 1) + m_fMarkerScalesCurrent[i] -= 1; + + int alpha = 32; + if (m_fMarkerScalesCurrent[i] > 0.5) + alpha *= ((0.5 - (m_fMarkerScalesCurrent[i] - 0.5)) * 2); + + targetColor[3] = alpha; + vgui::surface()->DrawSetColor(targetColor); + + const float halfArrowLength = HALF_BASE_TEX_LENGTH * m_fMarkerScalesCurrent[i] * scale; + vgui::surface()->DrawTexturedRect( + x - halfArrowLength, + y - halfArrowLength, + x + halfArrowLength, + y + halfArrowLength); + } + + float alpha6 = remainder(gpGlobals->curtime, 6) + 3; + alpha6 = alpha6 / 6; + if (alpha6 > 0.5) + alpha6 = 1 - alpha6; + alpha6 = 255 * alpha6; + + targetColor[3] = alpha6; + + const float halfArrowLength = HALF_BASE_TEX_LENGTH * scale; + vgui::surface()->DrawSetColor(targetColor); + vgui::surface()->DrawTexturedRect( + x - halfArrowLength, + y - halfArrowLength, + x + halfArrowLength, + y + halfArrowLength); + } + else + { + const float halfArrowLength = HALF_BASE_TEX_LENGTH * scale; + vgui::surface()->DrawSetColor( targetColor ); + vgui::surface()->DrawTexturedRect( + x - halfArrowLength, + y - halfArrowLength, + x + halfArrowLength, + y + halfArrowLength); + } +} + +void CNEOHud_WorldPosMarker_Generic::Paint() +{ + SetFgColor( COLOR_TRANSPARENT ); + SetBgColor( COLOR_TRANSPARENT ); + BaseClass::Paint(); + PaintNeoElement(); +} + +bool CNEOHud_WorldPosMarker_Generic::ShouldDraw() +{ + if ( !BaseClass::ShouldDraw() || NEORules()->IsRoundOver() || !m_pSource || !m_pSource->m_bEnabled ) + { + return false; + } + return true; +} diff --git a/src/game/client/neo/ui/neo_hud_worldpos_marker_generic.h b/src/game/client/neo/ui/neo_hud_worldpos_marker_generic.h new file mode 100644 index 0000000000..dc123dd4aa --- /dev/null +++ b/src/game/client/neo/ui/neo_hud_worldpos_marker_generic.h @@ -0,0 +1,34 @@ +#pragma once +#include "neo_hud_worldpos_marker.h" + +class C_NEOWorldPosMarkerEnt; + +class CNEOHud_WorldPosMarker_Generic : public CNEOHud_WorldPosMarker +{ + DECLARE_CLASS_SIMPLE( CNEOHud_WorldPosMarker_Generic, CNEOHud_WorldPosMarker ); + +public: + CNEOHud_WorldPosMarker_Generic( const char *pElementName, C_NEOWorldPosMarkerEnt *src, vgui::Panel *parent = nullptr ); + CNEOHud_WorldPosMarker_Generic( const CNEOHud_WorldPosMarker_Generic &other ) = delete; + +protected: + virtual void ApplySchemeSettings(vgui::IScheme *pScheme) override; + virtual void Paint() override; + virtual void UpdateStateForNeoHudElementDraw() override; + virtual void DrawNeoHudElement() override; + virtual ConVar *GetUpdateFrequencyConVar() const override; + + virtual bool ShouldDraw() override; + +private: + C_NEOWorldPosMarkerEnt *m_pSource = nullptr; + int m_iPosX = 0; + int m_iPosY = 0; + float m_flDistance = 0.0f; + float m_fMarkerScalesStart[4] = { 0.78f, 0.6f, 0.38f, 0.0f }; + float m_fMarkerScalesCurrent[4] = { 0.78f, 0.6f, 0.38f, 0.0f }; + wchar_t m_wszMarkerTextUnicode[64 + 1] = {}; + Vector m_vecMyPos = vec3_origin; + vgui::HFont m_hFont = 0UL; + vgui::HTexture m_hSprite[MAX_SCREEN_OVERLAYS] = {}; +}; diff --git a/src/game/server/CMakeLists.txt b/src/game/server/CMakeLists.txt index a101598df1..a525631bb4 100644 --- a/src/game/server/CMakeLists.txt +++ b/src/game/server/CMakeLists.txt @@ -1371,6 +1371,8 @@ target_sources_grouped( ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_misc.h ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_weapon_loadout.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_weapon_loadout.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_worldpos_marker.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_worldpos_marker.h ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_enums.h neo/neo_bloom_controller.cpp neo/neo_client.cpp diff --git a/src/game/server/neo/neo_npc_targetsystem.cpp b/src/game/server/neo/neo_npc_targetsystem.cpp index 9d2cec79a9..afa1810de8 100644 --- a/src/game/server/neo/neo_npc_targetsystem.cpp +++ b/src/game/server/neo/neo_npc_targetsystem.cpp @@ -239,11 +239,12 @@ void CNEO_NPCTargetSystem::Think() { if (gpGlobals->curtime >= m_flNextFireTime) { - Vector const vecSrc = m_hDamageSource.Get()->GetAbsOrigin(); - Vector const vecTarget = pBestTarget->EyePosition(); + const Vector vecSrc = m_hDamageSource.Get()->GetAbsOrigin(); + const Vector vecTarget = pBestTarget->EyePosition(); Vector vecDir = vecTarget - vecSrc; VectorNormalize(vecDir); + // This intentionally should not create any tracer / decal effects being a server side only operation FireBulletsInfo_t info( 1, vecSrc, vecDir, vec3_origin, MAX_TRACE_LENGTH, GetAmmoDef()->Index("AMMO_PRI"), 28.0f, true ); info.m_flDamage = m_flDamage; FireBullets(info); diff --git a/src/game/shared/neo/neo_gamerules.h b/src/game/shared/neo/neo_gamerules.h index 7a788e885e..4415502b2f 100644 --- a/src/game/shared/neo/neo_gamerules.h +++ b/src/game/shared/neo/neo_gamerules.h @@ -150,6 +150,7 @@ enum NeoHudElements : NEO_HUD_BITS_UNDERLYING_TYPE { NEO_HUD_ELEMENT_WORLDPOS_MARKER = (static_cast(1) << 13), NEO_HUD_ELEMENT_SCOREBOARD = (static_cast(1) << 14), NEO_HUD_ELEMENT_PLAYER_PING = (static_cast(1) << 15), + NEO_HUD_ELEMENT_WORLDPOS_MARKER_ENT = (static_cast(1) << 16), }; class CNEORules : public CHL2MPRules, public CGameEventListener diff --git a/src/game/shared/neo/neo_worldpos_marker.cpp b/src/game/shared/neo/neo_worldpos_marker.cpp new file mode 100644 index 0000000000..950b745f81 --- /dev/null +++ b/src/game/shared/neo/neo_worldpos_marker.cpp @@ -0,0 +1,176 @@ +#include "neo_worldpos_marker.h" + +#ifdef CLIENT_DLL +#include "ui/neo_hud_worldpos_marker_generic.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +LINK_ENTITY_TO_CLASS( neo_worldpos_marker, CNEOWorldPosMarkerEnt ); +#ifdef GAME_DLL +LINK_ENTITY_TO_CLASS( neo_controlpoint, CNEOWorldPosMarkerEnt ); +#endif + +#ifdef GAME_DLL +extern void SendProxy_StringT_To_String( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID ); + +IMPLEMENT_SERVERCLASS_ST( CNEOWorldPosMarkerEnt, DT_NEOWorldPosMarkerEnt ) + SendPropArray( SendPropString( SENDINFO_ARRAY( m_iszSpriteNames ), 0, SendProxy_StringT_To_String ), m_iszSpriteNames ), + SendPropInt(SENDINFO( m_iCurrentSprite ), NumBitsForCount( MAX_SCREEN_OVERLAYS ), SPROP_UNSIGNED ), + SendPropString(SENDINFO( m_szText )), + SendPropFloat(SENDINFO( m_flScale )), + SendPropBool(SENDINFO( m_bShowDistance )), + SendPropBool(SENDINFO( m_bCapzoneEffect )), + SendPropBool(SENDINFO( m_bEnabled )) +END_SEND_TABLE() +#else +#ifdef CNEOWorldPosMarkerEnt +#undef CNEOWorldPosMarkerEnt +#endif +IMPLEMENT_CLIENTCLASS_DT( C_NEOWorldPosMarkerEnt, DT_NEOWorldPosMarkerEnt, CNEOWorldPosMarkerEnt ) + RecvPropArray( RecvPropString( RECVINFO( m_iszSpriteNames[0]) ), m_iszSpriteNames ), + RecvPropInt(RECVINFO( m_iCurrentSprite )), + RecvPropString(RECVINFO( m_szText )), + RecvPropFloat(RECVINFO( m_flScale )), + RecvPropBool(RECVINFO( m_bShowDistance )), + RecvPropBool(RECVINFO( m_bCapzoneEffect )), + RecvPropBool(RECVINFO( m_bEnabled )) +END_RECV_TABLE() +#define CNEOWorldPosMarkerEnt C_NEOWorldPosMarkerEnt +#endif + +BEGIN_DATADESC( CNEOWorldPosMarkerEnt ) +#ifdef GAME_DLL + DEFINE_KEYFIELD( m_iszSpriteNames[0], FIELD_STRING, "SpriteName1" ), + DEFINE_KEYFIELD( m_iszSpriteNames[1], FIELD_STRING, "SpriteName2" ), + DEFINE_KEYFIELD( m_iszSpriteNames[2], FIELD_STRING, "SpriteName3" ), + DEFINE_KEYFIELD( m_iszSpriteNames[3], FIELD_STRING, "SpriteName4" ), + DEFINE_KEYFIELD( m_iszSpriteNames[4], FIELD_STRING, "SpriteName5" ), + DEFINE_KEYFIELD( m_iszSpriteNames[5], FIELD_STRING, "SpriteName6" ), + DEFINE_KEYFIELD( m_iszSpriteNames[6], FIELD_STRING, "SpriteName7" ), + DEFINE_KEYFIELD( m_iszSpriteNames[7], FIELD_STRING, "SpriteName8" ), + DEFINE_KEYFIELD( m_iszSpriteNames[8], FIELD_STRING, "SpriteName9" ), + DEFINE_KEYFIELD( m_iszSpriteNames[9], FIELD_STRING, "SpriteName10" ), + DEFINE_KEYFIELD( m_flScale, FIELD_FLOAT, "scale" ), + DEFINE_KEYFIELD( m_szKVText, FIELD_STRING, "Name" ), + DEFINE_KEYFIELD( m_iAlpha, FIELD_INTEGER, "alpha" ), + DEFINE_KEYFIELD( m_bStartDisabled, FIELD_BOOLEAN, "StartDisabled" ), + DEFINE_KEYFIELD( m_bShowDistance, FIELD_BOOLEAN, "showdistance" ), + DEFINE_KEYFIELD( m_bCapzoneEffect, FIELD_BOOLEAN, "capzoneeffect" ), + + // Legacy, for neo_controlpoint + DEFINE_KEYFIELD( m_iIcon, FIELD_INTEGER, "Icon" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSprite", InputSetSprite ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetText", InputSetText ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), +#endif +END_DATADESC() + +static const char *ICON_PATHS[] = +{ + "vgui/hud/cp/cp_1", + "vgui/hud/cp/cp_2", + "vgui/hud/cp/cp_3", + "vgui/hud/cp/cp_4", + "vgui/hud/cp/cp_5", + "vgui/hud/cp/cp_6", + "vgui/hud/cp/cp_a", + "vgui/hud/cp/cp_b", + "vgui/hud/cp/cp_c", + "vgui/hud/cp/cp_d", + "vgui/hud/cp/cp_e", + "vgui/hud/cp/cp_f", +}; + +CNEOWorldPosMarkerEnt::CNEOWorldPosMarkerEnt() +{ +#ifdef GAME_DLL + m_szText.GetForModify()[0] = 0; +#endif + m_iCurrentSprite = 0; + m_flScale = 0.5f; + m_bShowDistance = true; + m_bCapzoneEffect = false; + m_bEnabled = true; +} + +CNEOWorldPosMarkerEnt::~CNEOWorldPosMarkerEnt() +{ +#ifdef CLIENT_DLL + if ( m_pHUD_WorldPosMarker ) + { + m_pHUD_WorldPosMarker->DeletePanel(); + m_pHUD_WorldPosMarker = nullptr; + } +#endif +} + +#ifdef GAME_DLL +void CNEOWorldPosMarkerEnt::Spawn() +{ + if ( m_bStartDisabled ) + { + m_bEnabled = false; + } + + Assert( ICON_TOTAL == ARRAYSIZE(ICON_PATHS) ); + if ( m_iIcon > ICON_INVALID && m_iIcon < ICON_TOTAL ) + { + KeyValue( "SpriteName1", ICON_PATHS[m_iIcon] ); + } + + V_strncpy( m_szText.GetForModify(), STRING( m_szKVText ), 64 ); + + SetRenderColorA( clamp( m_iAlpha, 0, 255 ) ); +} + +int CNEOWorldPosMarkerEnt::UpdateTransmitState() +{ + return SetTransmitState( FL_EDICT_ALWAYS ); +} + +void CNEOWorldPosMarkerEnt::InputSetSprite( inputdata_t &inputdata ) +{ + int iCorrectedIndex = inputdata.value.Int() - 1; + iCorrectedIndex = abs( iCorrectedIndex ); + + if ( m_iszSpriteNames[iCorrectedIndex] == NULL_STRING ) + { + Warning("neo_worldpos_marker %s has no sprite to display at index %d.\n", STRING(GetEntityName()), inputdata.value.Int() ); + return; + } + + m_iCurrentSprite = iCorrectedIndex; +} + +void CNEOWorldPosMarkerEnt::InputSetText( inputdata_t &inputdata ) +{ + V_strncpy( m_szText.GetForModify(), inputdata.value.String(), 64 ); +} + +void CNEOWorldPosMarkerEnt::InputEnable( inputdata_t &inputdata ) +{ + m_bEnabled = true; +} + +void CNEOWorldPosMarkerEnt::InputDisable( inputdata_t &inputdata ) +{ + m_bEnabled = false; +} +#else +void CNEOWorldPosMarkerEnt::PostDataUpdate( DataUpdateType_t updateType ) +{ + BaseClass::PostDataUpdate( updateType ); + + if ( !m_pHUD_WorldPosMarker && updateType == DATA_UPDATE_DATATABLE_CHANGED ) + { + Assert( !m_pHUD_WorldPosMarker ); + m_pHUD_WorldPosMarker = new CNEOHud_WorldPosMarker_Generic( "hudWPMent", this ); + m_pHUD_WorldPosMarker->SetVisible( true ); + } +} +#endif diff --git a/src/game/shared/neo/neo_worldpos_marker.h b/src/game/shared/neo/neo_worldpos_marker.h new file mode 100644 index 0000000000..0c5d9a76f2 --- /dev/null +++ b/src/game/shared/neo/neo_worldpos_marker.h @@ -0,0 +1,81 @@ +#pragma once +#include "cbase.h" + +#ifdef CLIENT_DLL +#define CNEOWorldPosMarkerEnt C_NEOWorldPosMarkerEnt +class CNEOHud_WorldPosMarker_Generic; +#endif + +enum +{ + ICON_INVALID = -1, + ICON_1 = 0, + ICON_2, + ICON_3, + ICON_4, + ICON_5, + ICON_6, + ICON_A, + ICON_B, + ICON_C, + ICON_D, + ICON_E, + ICON_F, + + ICON_TOTAL +}; + +class CNEOWorldPosMarkerEnt : public CBaseEntity +{ + DECLARE_CLASS( CNEOWorldPosMarkerEnt, CBaseEntity ); + +public: +#ifdef CLIENT_DLL + DECLARE_CLIENTCLASS(); +#else + DECLARE_SERVERCLASS(); +#endif + DECLARE_DATADESC(); + + CNEOWorldPosMarkerEnt(); + virtual ~CNEOWorldPosMarkerEnt(); + +#ifdef GAME_DLL + virtual void Spawn(); + virtual int UpdateTransmitState() override; +#else + virtual void PostDataUpdate( DataUpdateType_t updateType ) override; +#endif + +private: +#ifdef GAME_DLL + void InputSetSprite( inputdata_t &inputdata ); + void InputSetText( inputdata_t &inputdata ); + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); +#else + friend class CNEOHud_WorldPosMarker_Generic; +#endif + +#ifdef GAME_DLL + CNetworkArray( string_t, m_iszSpriteNames, MAX_SCREEN_OVERLAYS ); + CNetworkString( m_szText, 64 ); +#else + char m_iszSpriteNames[ MAX_SCREEN_OVERLAYS ][ MAX_PATH ]; + char m_szText[64]; +#endif + CNetworkVar( int, m_iCurrentSprite ); + CNetworkVar( float, m_flScale ); + CNetworkVar( bool, m_bShowDistance ); + CNetworkVar( bool, m_bCapzoneEffect ); + CNetworkVar( bool, m_bEnabled ); + +#ifdef GAME_DLL + string_t m_szKVText = NULL_STRING; + int m_iAlpha = 100; + int m_iIcon = ICON_INVALID; + bool m_bStartDisabled = false; +#else + CNEOHud_WorldPosMarker_Generic *m_pHUD_WorldPosMarker = nullptr; +#endif +}; \ No newline at end of file From 4afb475a2c60baa589aa80b70412a85eab320bf7 Mon Sep 17 00:00:00 2001 From: DESTROYGIRL <170364626+DESTROYGIRL@users.noreply.github.com> Date: Sat, 17 Jan 2026 13:21:01 +0000 Subject: [PATCH 3/3] Update rebuild.fgd --- game/bin/rebuild.fgd | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/game/bin/rebuild.fgd b/game/bin/rebuild.fgd index 78563404e2..5e23dc1193 100644 --- a/game/bin/rebuild.fgd +++ b/game/bin/rebuild.fgd @@ -411,3 +411,42 @@ [ targetname(target_source) : "Name" : : "The name that other entities refer to this entity by." ] + +@PointClass base(Targetname, Parentname) iconsprite("vgui/hud/cp/cp_1.vmt") = neo_worldpos_marker : "Custom Marker" +[ + StartDisabled(choices) : "Start Disabled" : 0 = + [ + 0 : "No" + 1 : "Yes" + ] + scale(string) : "Scale" : "0.5" : "Scale multiplier of the sprite." + rendercolor(color255) : "Sprite Color (R G B)" : "255 255 255" + Name(string) : "Text to Display" : "" + showdistance(choices) : "Show Distance" : 1 = + [ + 0 : "No" + 1 : "Yes" + ] + capzoneeffect(choices) : "Capture Point FX" : 0 = + [ + 0 : "No" + 1 : "Yes" + ] + SpriteName1(sprite) : "Sprite 1" : "" : "Material of the sprite to be drawn." + SpriteName2(sprite) : "Sprite 2" : "" : "Material of the sprite to be drawn." + SpriteName3(sprite) : "Sprite 3" : "" : "Material of the sprite to be drawn." + SpriteName4(sprite) : "Sprite 4" : "" : "Material of the sprite to be drawn." + SpriteName5(sprite) : "Sprite 5" : "" : "Material of the sprite to be drawn." + SpriteName6(sprite) : "Sprite 6" : "" : "Material of the sprite to be drawn." + SpriteName7(sprite) : "Sprite 7" : "" : "Material of the sprite to be drawn." + SpriteName8(sprite) : "Sprite 8" : "" : "Material of the sprite to be drawn." + SpriteName9(sprite) : "Sprite 9" : "" : "Material of the sprite to be drawn." + SpriteName10(sprite) : "Sprite 10" : "" : "Material of the sprite to be drawn." + + input Enable(void) : "Turn on." + input Disable(void) : "Turn off." + input SetSprite(integer) : "Switch to a sprite from the SpriteName KVs." + input SetText(string) : "Set the message text." + input Alpha(integer) : "Sets the sprite's alpha (0 - 255)." + input Color(color255) : "Sets the sprite's render color (R G B)." +]