diff --git a/mp/src/game/server/neo/neo_player.cpp b/mp/src/game/server/neo/neo_player.cpp index 4fcd685f3..a82ec3d49 100644 --- a/mp/src/game/server/neo/neo_player.cpp +++ b/mp/src/game/server/neo/neo_player.cpp @@ -15,6 +15,7 @@ #include "neo_model_manager.h" #include "gib.h" +#include "world.h" #include "weapon_ghost.h" #include "weapon_supa7.h" @@ -2468,11 +2469,45 @@ int CNEO_Player::OnTakeDamage_Alive(const CTakeDamageInfo& info) ++iDamage; flDmgAccumlator -= 1.0f; } + + // Mirror team-damage + const bool bIsTeamDmg = (attackerIdx != entindex() && attacker->GetTeamNumber() == GetTeamNumber()); + if (bIsTeamDmg) + { + const float flMirrorMult = NEORules()->MirrorDamageMultiplier(); + if (flMirrorMult > 0.0f) + { + // Attacker own-team-inflicting - Mirror the damage + // NEO NOTE (nullsystem): DO NOT call OnTakeDamage directly as that'll crash the game, instead + // let the gamerule handle this situation by causing the attacker to suicide when health gets + // depliciated + const float flInflict = iDamage * flMirrorMult; + attacker->OnTakeDamage_Alive(CTakeDamageInfo(GetWorldEntity(), GetWorldEntity(), + flInflict, DMG_SLASH)); + if (attacker->m_iHealth <= 0) + { + attacker->m_bKilledInflicted = true; + } + + if (neo_sv_mirror_teamdamage_immunity.GetBool()) + { + // Give immunity to the victim and don't go through the OnTakeDamage_Alive + return 0; + } + } + } + + // Apply damages/hits numbers if (iDamage > 0) { m_rfAttackersScores.GetForModify(attackerIdx) += iDamage; m_rfAttackersAccumlator.Set(attackerIdx, flDmgAccumlator); m_rfAttackersHits.GetForModify(attackerIdx) += 1; + + if (bIsTeamDmg && neo_sv_teamdamage_kick.GetBool() && NEORules()->GetRoundStatus() == NeoRoundStatus::RoundLive) + { + attacker->m_iTeamDamageInflicted += iDamage; + } } } } diff --git a/mp/src/game/server/neo/neo_player.h b/mp/src/game/server/neo/neo_player.h index 644380b4e..a63e5289e 100644 --- a/mp/src/game/server/neo/neo_player.h +++ b/mp/src/game/server/neo/neo_player.h @@ -233,6 +233,10 @@ class CNEO_Player : public CHL2MP_Player CNetworkVar(bool, m_bClientWantNeoName); bool m_bIsPendingSpawnForThisRound; + bool m_bKilledInflicted = false; // Server-side var only + int m_iTeamDamageInflicted = 0; + int m_iTeamKillsInflicted = 0; + bool m_bIsPendingTKKick = false; // To not spam the kickid ConCommand private: bool m_bFirstDeathTick; diff --git a/mp/src/game/shared/neo/neo_gamerules.cpp b/mp/src/game/shared/neo/neo_gamerules.cpp index c6e907f55..5b070df7c 100644 --- a/mp/src/game/shared/neo/neo_gamerules.cpp +++ b/mp/src/game/shared/neo/neo_gamerules.cpp @@ -52,6 +52,19 @@ ConVar neo_sv_build_integrity_check_allow_debug("neo_sv_build_integrity_check_al "If enabled, when the server checks the client hashes, it'll also allow debug" " builds which has a given special bit to bypass the check.", true, 0.0f, true, 1.0f); + +#ifdef DEBUG +static constexpr char TEAMDMG_MULTI[] = "0"; +#else +static constexpr char TEAMDMG_MULTI[] = "2"; +#endif +ConVar neo_sv_mirror_teamdamage_multiplier("neo_sv_mirror_teamdamage_multiplier", TEAMDMG_MULTI, FCVAR_REPLICATED, "The damage multiplier given to the friendly-firing individual. Set value to 0 to disable mirror team damage.", true, 0.0f, true, 100.0f); +ConVar neo_sv_mirror_teamdamage_duration("neo_sv_mirror_teamdamage_duration", "7", FCVAR_REPLICATED, "How long in seconds the mirror damage is active for the start of each round. Set to 0 for the entire round.", true, 0.0f, true, 10000.0f); +ConVar neo_sv_mirror_teamdamage_immunity("neo_sv_mirror_teamdamage_immunity", "1", FCVAR_REPLICATED, "If enabled, the victim will not take damage from a teammate during the mirror team damage duration.", true, 0.0f, true, 1.0f); + +ConVar neo_sv_teamdamage_kick("neo_sv_teamdamage_kick", "0", FCVAR_REPLICATED, "If enabled, the friendly-firing individual will be kicked if damage is received during the neo_sv_mirror_teamdamage_duration, exceeds the neo_sv_teamdamage_kick_hp value, or executes a teammate.", true, 0.0f, true, 1.0f); +ConVar neo_sv_teamdamage_kick_hp("neo_sv_teamdamage_kick_hp", "900", FCVAR_REPLICATED, "The threshold for the amount of HP damage inflicted on teammates before the client is kicked.", true, 100.0f, false, 0.0f); +ConVar neo_sv_teamdamage_kick_kills("neo_sv_teamdamage_kick_kills", "6", FCVAR_REPLICATED, "The threshold for the amount of team kills before the client is kicked.", true, 1.0f, false, 0.0f); #endif REGISTER_GAMERULES_CLASS( CNEORules ); @@ -444,6 +457,20 @@ void CNEORules::ResetMapSessionCommon() m_flNeoNextRoundStartTime = 0.0f; #ifdef GAME_DLL m_pRestoredInfos.Purge(); + + for (int i = 1; i <= gpGlobals->maxClients; i++) + { + auto *pPlayer = static_cast(UTIL_PlayerByIndex(i)); + if (pPlayer) + { + pPlayer->m_iTeamDamageInflicted = 0; + pPlayer->m_iTeamKillsInflicted = 0; + pPlayer->m_bIsPendingTKKick = false; + pPlayer->m_bKilledInflicted = false; + } + } + m_flPrevThinkKick = 0.0f; + m_flPrevThinkMirrorDmg = 0.0f; #endif } @@ -529,6 +556,51 @@ void CNEORules::Think(void) BaseClass::Think(); #ifdef GAME_DLL + if (MirrorDamageMultiplier() > 0.0f && + gpGlobals->curtime > (m_flPrevThinkMirrorDmg + 0.25f)) + { + for (int i = 1; i <= gpGlobals->maxClients; ++i) + { + auto player = static_cast(UTIL_PlayerByIndex(i)); + if (player && player->IsAlive() && player->m_bKilledInflicted && player->m_iHealth <= 0) + { + player->CommitSuicide(false, true); + } + } + + m_flPrevThinkMirrorDmg = gpGlobals->curtime; + } + + if (neo_sv_teamdamage_kick.GetBool() && m_nRoundStatus == NeoRoundStatus::RoundLive && + gpGlobals->curtime > (m_flPrevThinkKick + 0.5f)) + { + const int iThresKickHp = neo_sv_teamdamage_kick_hp.GetInt(); + const int iThresKickKills = neo_sv_teamdamage_kick_kills.GetInt(); + + // Separate command from check so kick not affected by player index + int userIDsToKick[MAX_PLAYERS + 1] = {}; + int userIDsToKickSize = 0; + for (int i = 1; i <= gpGlobals->maxClients; ++i) + { + auto player = static_cast(UTIL_PlayerByIndex(i)); + if (player && (player->m_iTeamDamageInflicted >= iThresKickHp || + player->m_iTeamKillsInflicted >= iThresKickKills) && + !player->m_bIsPendingTKKick) + { + userIDsToKick[userIDsToKickSize++] = player->GetUserID(); + player->m_bIsPendingTKKick = true; + } + } + + for (int i = 0; i < userIDsToKickSize; ++i) + { + engine->ServerCommand(UTIL_VarArgs("kickid %d \"%s\"\n", userIDsToKick[i], + "Too much friendly-fire damage inflicted.")); + } + + m_flPrevThinkKick = gpGlobals->curtime; + } + if (IsRoundOver()) { // If the next round was not scheduled yet @@ -695,7 +767,7 @@ void CNEORules::AwardRankUp(CNEO_Player *pClient) } // Return remaining time in seconds. Zero means there is no time limit. -float CNEORules::GetRoundRemainingTime() +float CNEORules::GetRoundRemainingTime() const { if ((m_nRoundStatus != NeoRoundStatus::Warmup && neo_round_timelimit.GetFloat() == 0) || m_nRoundStatus == NeoRoundStatus::Idle) @@ -707,6 +779,25 @@ float CNEORules::GetRoundRemainingTime() return (m_flNeoRoundStartTime + roundTimeLimit) - gpGlobals->curtime; } +float CNEORules::GetRoundAccumulatedTime() const +{ + return gpGlobals->curtime - (m_flNeoRoundStartTime + mp_neo_preround_freeze_time.GetFloat()); +} + +#ifdef GAME_DLL +float CNEORules::MirrorDamageMultiplier() const +{ + if (m_nRoundStatus != NeoRoundStatus::RoundLive) + { + return 0.0f; + } + const float flAccTime = GetRoundAccumulatedTime(); + const float flMirrorMult = neo_sv_mirror_teamdamage_multiplier.GetFloat(); + const float flMirrorDur = neo_sv_mirror_teamdamage_duration.GetFloat(); + return (flMirrorDur == 0.0f || (0.0f <= flAccTime && flAccTime < flMirrorDur)) ? flMirrorMult : 0.0f; +} +#endif + void CNEORules::FireGameEvent(IGameEvent* event) { const char *type = event->GetName(); @@ -937,6 +1028,7 @@ void CNEORules::StartNextRound() continue; } + pPlayer->m_bKilledInflicted = false; if (pPlayer->GetActiveWeapon()) { pPlayer->GetActiveWeapon()->Holster(); @@ -958,11 +1050,16 @@ void CNEORules::StartNextRound() { pPlayer->Reset(); pPlayer->m_iXP.Set(0); + pPlayer->m_iTeamDamageInflicted = 0; + pPlayer->m_iTeamKillsInflicted = 0; } + pPlayer->m_bIsPendingTKKick = false; pPlayer->SetTestMessageVisible(false); } + m_flPrevThinkKick = 0.0f; + m_flPrevThinkMirrorDmg = 0.0f; m_flIntermissionEndTime = 0; m_flRestartGameTime = 0; m_bCompleteReset = false; @@ -1762,6 +1859,12 @@ void CNEORules::PlayerKilled(CBasePlayer *pVictim, const CTakeDamageInfo &info) if (attacker->GetTeamNumber() == victim->GetTeamNumber()) { attacker->m_iXP.GetForModify() -= 1; +#ifdef GAME_DLL + if (neo_sv_teamdamage_kick.GetBool() && m_nRoundStatus == NeoRoundStatus::RoundLive) + { + ++attacker->m_iTeamKillsInflicted; + } +#endif } // Enemy kill else @@ -2100,6 +2203,12 @@ bool CNEORules::FPlayerCanRespawn(CBasePlayer* pPlayer) Assert(false); } + // Do not let anyone who tried to team-kill during mirror damage + live round to respawn + if (static_cast(pPlayer)->m_bKilledInflicted) + { + return false; + } + // Did we make it in time to spawn for this round? if (GetRemainingPreRoundFreezeTime(false) + mp_neo_latespawn_max_time.GetFloat() > 0) { diff --git a/mp/src/game/shared/neo/neo_gamerules.h b/mp/src/game/shared/neo/neo_gamerules.h index eaa4702f2..6fb4e3048 100644 --- a/mp/src/game/shared/neo/neo_gamerules.h +++ b/mp/src/game/shared/neo/neo_gamerules.h @@ -82,6 +82,11 @@ class NEOViewVectors : public HL2MPViewVectors #ifdef GAME_DLL class CNEOGhostCapturePoint; class CNEO_Player; + +extern ConVar neo_sv_mirror_teamdamage_multiplier; +extern ConVar neo_sv_mirror_teamdamage_duration; +extern ConVar neo_sv_mirror_teamdamage_immunity; +extern ConVar neo_sv_teamdamage_kick; #else class C_NEO_Player; #endif @@ -176,7 +181,11 @@ class CNEORules : public CHL2MPRules, public CGameEventListener virtual bool CheckGameOver(void) OVERRIDE; - float GetRoundRemainingTime(); + float GetRoundRemainingTime() const; + float GetRoundAccumulatedTime() const; +#ifdef GAME_DLL + float MirrorDamageMultiplier() const; +#endif virtual void PlayerKilled(CBasePlayer *pVictim, const CTakeDamageInfo &info) OVERRIDE; @@ -262,6 +271,8 @@ class CNEORules : public CHL2MPRules, public CGameEventListener #ifdef GAME_DLL CUtlVector m_pGhostCaps; + float m_flPrevThinkKick = 0.0f; + float m_flPrevThinkMirrorDmg = 0.0f; #endif CNetworkVar(int, m_nRoundStatus); // NEO TODO (Rain): probably don't need to network this CNetworkVar(int, m_iRoundNumber);