Skip to content
10 changes: 10 additions & 0 deletions src/game/client/neo/ui/neo_hud_round_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,9 @@ void CNEOHud_RoundState::UpdateStateForNeoHudElementDraw()
case NEO_GAME_TYPE_JGR:
m_pWszStatusUnicode = L"Control the Juggernaut\n";
break;
case NEO_GAME_TYPE_KOTH:
m_pWszStatusUnicode = L"Capture the objective!\n";
break;
default:
m_pWszStatusUnicode = L"Await further orders\n";
break;
Expand Down Expand Up @@ -433,6 +436,13 @@ void CNEOHud_RoundState::UpdateStateForNeoHudElementDraw()
V_sprintf_safe(szPlayersAliveANSI, "%i:%i", GetGlobalTeam(TEAM_JINRAI)->Get_Score(), GetGlobalTeam(TEAM_NSF)->Get_Score());
}
}
// neo TODO: add KOTH gameType (smart way) or detect KOTH maps (dumb way)
else if (NEORules()->GetGameType() == NEO_GAME_TYPE_KOTH)
{
V_sprintf_safe(szPlayersAliveANSI, "J:%d N:%d",
NEORules()->m_iKothTimeJinrai.Get(),
NEORules()->m_iKothTimeNSF.Get());
}
else
{
V_sprintf_safe(szPlayersAliveANSI, "%i vs %i", m_iLeftPlayersAlive, m_iRightPlayersAlive);
Expand Down
195 changes: 194 additions & 1 deletion src/game/shared/neo/neo_gamerules.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ ConVar sv_neo_suicide_prevent_cap_punish("sv_neo_suicide_prevent_cap_punish", "1
"while the other team is holding the ghost, reward the ghost holder team "
"a rank up.",
true, 0.0f, true, 1.0f);
// koth
ConVar sv_neo_koth_seconds_per_point("sv_neo_koth_point_multiplyer", "1.75", FCVAR_REPLICATED, "Seconds to get 1 point");
ConVar sv_neo_koth_max_score("sv_neo_koth_max_score", "100", FCVAR_REPLICATED, "The points needed to win this round");

#define DEF_TEAMPLAYERTHRES 5
static_assert(DEF_TEAMPLAYERTHRES <= ((MAX_PLAYERS - 1) / 2));
Expand Down Expand Up @@ -291,6 +294,8 @@ BEGIN_NETWORK_TABLE_NOBASE( CNEORules, DT_NEORules )
RecvPropInt(RECVINFO(m_iLastAttacker)),
RecvPropInt(RECVINFO(m_iLastKiller)),
RecvPropInt(RECVINFO(m_iLastGhoster)),
RecvPropInt(RECVINFO(m_iKothTimeJinrai)),
RecvPropInt(RECVINFO(m_iKothTimeNSF)),
#else
SendPropTime(SENDINFO(m_flNeoNextRoundStartTime)),
SendPropTime(SENDINFO(m_flNeoRoundStartTime)),
Expand Down Expand Up @@ -325,6 +330,8 @@ BEGIN_NETWORK_TABLE_NOBASE( CNEORules, DT_NEORules )
SendPropInt(SENDINFO(m_iLastAttacker), NumBitsForCount(MAX_PLAYERS_ARRAY_SAFE), SPROP_UNSIGNED),
SendPropInt(SENDINFO(m_iLastKiller), NumBitsForCount(MAX_PLAYERS_ARRAY_SAFE), SPROP_UNSIGNED),
SendPropInt(SENDINFO(m_iLastGhoster), NumBitsForCount(MAX_PLAYERS_ARRAY_SAFE), SPROP_UNSIGNED),
SendPropInt(SENDINFO(m_iKothTimeJinrai)),
SendPropInt(SENDINFO(m_iKothTimeNSF)),
#endif
END_NETWORK_TABLE()

Expand Down Expand Up @@ -372,6 +379,7 @@ const NeoGameTypeSettings NEO_GAME_TYPE_SETTINGS[NEO_GAME_TYPE__TOTAL] = {
/*NEO_GAME_TYPE_EMT*/ {"EMT", true, false, true, false, false},
/*NEO_GAME_TYPE_TUT*/ {"TUT", true, false, false, false, false},
/*NEO_GAME_TYPE_JGR*/ {"JGR", true, true, false, true, false},
/*NEO_GAME_TYPE_KOTH*/ {"KOTH", true, true, false, false, false},
};

#ifdef CLIENT_DLL
Expand Down Expand Up @@ -486,6 +494,9 @@ ConVar neo_dm_round_timelimit("neo_dm_round_timelimit", "10.25", FCVAR_REPLICATE
ConVar neo_jgr_round_timelimit("neo_jgr_round_timelimit", "4.25", FCVAR_REPLICATED, "JGR round timelimit, in minutes.",
true, 0.0f, false, 600.0f);

ConVar neo_koth_round_timelimit("neo_koth_round_timelimit", "5", FCVAR_REPLICATED, "KOTH round timelimit, in minutes.",
true, 0.0f, false, 600.0f);

ConVar sv_neo_ignore_wep_xp_limit("sv_neo_ignore_wep_xp_limit", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "If true, allow equipping any loadout regardless of player XP.",
true, 0.0f, true, 1.0f);

Expand Down Expand Up @@ -1063,6 +1074,16 @@ void CNEORules::CheckGameType()
m_nGameTypeSelected = (pEntGameCfg) ? pEntGameCfg->m_GameType : NEO_GAME_TYPE_EMT;
} break;
}

// neo HACK: backward compability fix to import all _koth maps without editing them
if (m_nGameTypeSelected == NEO_GAME_TYPE_CTG)
{
if (gEntList.FindEntityByName(nullptr, "koth_point"))
{
m_nGameTypeSelected = NEO_GAME_TYPE_KOTH;
}
}

m_bGamemodeTypeBeenInitialized = true;
iStaticInitOnCmd = iGamemodeEnforce;
iStaticInitOnRandAllow = iGamemodeRandAllow;
Expand Down Expand Up @@ -1097,6 +1118,42 @@ bool CNEORules::CheckShouldNotThink()
return false;
}

#ifdef GAME_DLL
// neo hack: shitcoded input but who cares, ill fix it later TODO
void CNEORules::SetKothLeaderMapVisual(const int team) const
{
pSpriteNSF->AcceptInput("HideSprite", nullptr, nullptr, variant_t(), 0);
pSpriteJinrai->AcceptInput("HideSprite", nullptr, nullptr, variant_t(), 0);
pSpriteInactive->AcceptInput("HideSprite", nullptr, nullptr, variant_t(), 0);
pSpriteNone->AcceptInput("HideSprite", nullptr, nullptr, variant_t(), 0);

pBrushNSF->AcceptInput("Disable", nullptr, nullptr, variant_t(), 0);
pBrushJinrai->AcceptInput("Disable", nullptr, nullptr, variant_t(), 0);
pBrushInactive->AcceptInput("Disable", nullptr, nullptr, variant_t(), 0);
pBrushNone->AcceptInput("Disable", nullptr, nullptr, variant_t(), 0);

if (team==KOTH_INACTIVE) {
pSpriteInactive->AcceptInput("ShowSprite", nullptr, nullptr, variant_t(), 0);
pBrushInactive->AcceptInput("Enable", nullptr, nullptr, variant_t(), 0);
return;
}
if (team==KOTH_NONE) {
pSpriteNone->AcceptInput("ShowSprite", nullptr, nullptr, variant_t(), 0);
pBrushNone->AcceptInput("Enable", nullptr, nullptr, variant_t(), 0);
return;
}
if (team==KOTH_NSF) {
pSpriteNSF->AcceptInput("ShowSprite", nullptr, nullptr, variant_t(), 0);
pBrushNSF->AcceptInput("Enable", nullptr, nullptr, variant_t(), 0);
return;
}
if (team==KOTH_JINRAI) {
pSpriteJinrai->AcceptInput("ShowSprite", nullptr, nullptr, variant_t(), 0);
pBrushJinrai->AcceptInput("Enable", nullptr, nullptr, variant_t(), 0);
}
}
#endif

void CNEORules::Think(void)
{
#ifdef GAME_DLL
Expand Down Expand Up @@ -1390,6 +1447,23 @@ void CNEORules::Think(void)
return;
}
}
if (GetGameType() == NEO_GAME_TYPE_KOTH)
{
if (m_iKothTimeJinrai > m_iKothTimeNSF)
{
SetWinningTeam(TEAM_JINRAI, NEO_VICTORY_POINTS, false, true, false, false);
return;
}

if (m_iKothTimeNSF > m_iKothTimeJinrai)
{
SetWinningTeam(TEAM_NSF, NEO_VICTORY_POINTS, false, true, false, false);
return;
}

SetWinningTeam(TEAM_SPECTATOR, NEO_VICTORY_STALEMATE, false, true, true, false);
return;
}
else if (GetGameType() == NEO_GAME_TYPE_DM)
{
// Winning player
Expand Down Expand Up @@ -1541,6 +1615,71 @@ void CNEORules::Think(void)
m_pJuggernautItem->m_bLocked = false;
}
}
else if (pKothTrigger)
{
// neo TODO: rm pKothTrigger check and just check that curr gm is NEO_GAME_TYPE_KOTH
// neo TODO: prettify code to satisfy code-style requirements!!!
// neo TODO: add single check that allows us to know that there is players inside!!! (not a loop)
// neo TODO: add sending via net some HUD vars (score and controlling team)
// neo TODO: annul ALL variables on round restart (currntly im resetting only some of them)

bool jinrai_capturing = false,
nsf_capturing = false;
for (int i = 1; i <= gpGlobals->maxClients; i++)
{
CNEO_Player* pPlayer = static_cast<CNEO_Player*>(UTIL_PlayerByIndex(i));
// neo TODO: do i rlly need this check?
if (!pPlayer || !pPlayer->IsAlive())
continue;

// neo TODO: maybe instead of IsTouching use something like IsInside (if there is any similar events lol)??
// neo TODO: for-loop is probably VERY inefficient (check previous todo)
if (pKothTrigger->IsTouching(pPlayer))
{
int team = pPlayer->GetTeamNumber();
if (team==TEAM_JINRAI) {
jinrai_capturing = true;
} else if (team==TEAM_NSF) {
nsf_capturing = true;
}
}
}

int prevControllers = m_iKothControllingTeam;
if (jinrai_capturing && nsf_capturing || !(jinrai_capturing || nsf_capturing)) {
// both loses anyway
// neo TODO: should i really nullify these values each time? check it pls
m_flKothAccumulatorNSF = 0.0f;
m_flKothAccumulatorJinrai = 0.0f;
m_iKothControllingTeam = KOTH_NONE;
} else if (jinrai_capturing) {
// give jin some points
m_flKothAccumulatorNSF = 0.0f;
m_flKothAccumulatorJinrai += gpGlobals->frametime;
m_iKothControllingTeam = KOTH_JINRAI;
} else if (nsf_capturing) {
// give nsf some points
m_flKothAccumulatorNSF += gpGlobals->frametime;
m_flKothAccumulatorJinrai = 0.0f;
m_iKothControllingTeam = KOTH_NSF;
}
if (prevControllers != m_iKothControllingTeam)
{
SetKothLeaderMapVisual(m_iKothControllingTeam);
}

// DevMsg("JIN: %f, NSF: %f, ft delta: %f\n", m_flKothAccumulatorJinrai, m_flKothAccumulatorNSF, gpGlobals->frametime);
// DevMsg("JIN: %d, NSF: %d\n", jinrai_capturing, nsf_capturing);
// move it accumulation into the score if possible
if (m_flKothAccumulatorNSF > sv_neo_koth_seconds_per_point.GetFloat()) {
m_iKothTimeNSF += int(m_flKothAccumulatorNSF / sv_neo_koth_seconds_per_point.GetFloat());
m_flKothAccumulatorNSF -= int(m_flKothAccumulatorNSF);
}
if (m_flKothAccumulatorJinrai > sv_neo_koth_seconds_per_point.GetFloat()) {
m_iKothTimeJinrai += int(m_flKothAccumulatorJinrai / sv_neo_koth_seconds_per_point.GetFloat());
m_flKothAccumulatorJinrai -= int(m_flKothAccumulatorJinrai);
}
}

if (GetGameType() == NEO_GAME_TYPE_JGR && IsRoundLive())
{
Expand Down Expand Up @@ -1656,7 +1795,8 @@ void CNEORules::Think(void)
else if (IsRoundLive())
{
COMPILE_TIME_ASSERT(TEAM_JINRAI == 2 && TEAM_NSF == 3);
if (GetGameType() != NEO_GAME_TYPE_TDM && GetGameType() != NEO_GAME_TYPE_DM && GetGameType() != NEO_GAME_TYPE_JGR)
if (GetGameType() != NEO_GAME_TYPE_TDM && GetGameType() != NEO_GAME_TYPE_DM &&
GetGameType() != NEO_GAME_TYPE_JGR && GetGameType() != NEO_GAME_TYPE_KOTH)
{
auto jinraiAlive = GetGlobalTeam(TEAM_JINRAI)->GetAliveMembers();
auto nsfAlive = GetGlobalTeam(TEAM_NSF)->GetAliveMembers();
Expand All @@ -1681,6 +1821,29 @@ void CNEORules::Think(void)
SetWinningDMPlayer(pHighestPlayers[0]);
}
}
if (GetGameType() == NEO_GAME_TYPE_KOTH)
{
if (m_iKothTimeJinrai >= sv_neo_koth_max_score.GetInt() && m_iKothTimeNSF >= sv_neo_koth_max_score.GetInt())
{
// impossible but we should do something here...
SetWinningTeam(TEAM_SPECTATOR, NEO_VICTORY_STALEMATE, false, true, true, false);
return;
}

if (m_iKothTimeNSF >= sv_neo_koth_max_score.GetInt())
{
m_iKothTimeNSF = sv_neo_koth_max_score.GetInt();
SetWinningTeam(TEAM_NSF, NEO_VICTORY_POINTS, false, true, false, false);
return;
}

if (m_iKothTimeJinrai >= sv_neo_koth_max_score.GetInt())
{
m_iKothTimeJinrai = sv_neo_koth_max_score.GetInt();
SetWinningTeam(TEAM_JINRAI, NEO_VICTORY_POINTS, false, true, false, false);
return;
}
}
}
#endif
}
Expand Down Expand Up @@ -1836,6 +1999,9 @@ float CNEORules::GetRoundRemainingTime() const
case NEO_GAME_TYPE_JGR:
roundTimeLimit = neo_jgr_round_timelimit.GetFloat() * 60.f;
break;
case NEO_GAME_TYPE_KOTH:
roundTimeLimit = neo_koth_round_timelimit.GetFloat() * 60.f;
break;
default:
break;
}
Expand Down Expand Up @@ -3256,6 +3422,11 @@ void CNEORules::SetGameRelatedVars()
ResetJGR();
SpawnTheJuggernaut();
}

if (GetGameType() == NEO_GAME_TYPE_KOTH)
{
ResetKOTH();
}
}

void CNEORules::ResetTDM()
Expand Down Expand Up @@ -3306,6 +3477,28 @@ void CNEORules::ResetJGR()
}
}

void CNEORules::ResetKOTH() {
m_iKothTimeJinrai = 0;
m_iKothTimeNSF = 0;
// neo TODO: i dont think that i should send controllingTeam var through net
m_iKothControllingTeam = KOTH_INACTIVE;
m_flKothAccumulatorNSF = 0.0f;
m_flKothAccumulatorJinrai = 0.0f;
// TODO: give xp for holding the point?
pKothTrigger = dynamic_cast<CBaseTrigger*>(gEntList.FindEntityByName(nullptr, "koth_point"));

// sprite/brush things
pSpriteNSF->AcceptInput("HideSprite", nullptr, nullptr, variant_t(), 0);
pSpriteJinrai->AcceptInput("HideSprite", nullptr, nullptr, variant_t(), 0);
pSpriteInactive->AcceptInput("ShowSprite", nullptr, nullptr, variant_t(), 0);
pSpriteNone->AcceptInput("HideSprite", nullptr, nullptr, variant_t(), 0);

pBrushNSF->AcceptInput("Disable", nullptr, nullptr, variant_t(), 0);
pBrushJinrai->AcceptInput("Disable", nullptr, nullptr, variant_t(), 0);
pBrushInactive->AcceptInput("Enable", nullptr, nullptr, variant_t(), 0);
pBrushNone->AcceptInput("Disable", nullptr, nullptr, variant_t(), 0);
}

void CNEORules::RestartGame()
{
// bounds check
Expand Down
34 changes: 33 additions & 1 deletion src/game/shared/neo/neo_gamerules.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class NEOViewVectors : public HL2MPViewVectors
};

#ifdef GAME_DLL
#include "triggers.h" // для CBaseTrigger и IsTouching()
class CNEOGhostCapturePoint;
class CNEO_Player;
class CWeaponGhost;
Expand All @@ -104,6 +105,7 @@ enum NeoGameType {
NEO_GAME_TYPE_EMT,
NEO_GAME_TYPE_TUT,
NEO_GAME_TYPE_JGR,
NEO_GAME_TYPE_KOTH,

NEO_GAME_TYPE__TOTAL // Number of game types
};
Expand Down Expand Up @@ -275,6 +277,7 @@ class CNEORules : public CHL2MPRules, public CGameEventListener
void ResetGhost();
void ResetVIP();
void ResetJGR();
void ResetKOTH();

void CheckRestartGame();

Expand Down Expand Up @@ -356,7 +359,9 @@ class CNEORules : public CHL2MPRules, public CGameEventListener
bool JuggernautItemExists() const;
const Vector& GetJuggernautMarkerPos() const;
bool IsJuggernautLocked() const;

#ifdef GAME_DLL
void SetKothLeaderMapVisual(const int team) const;
#endif

int GetOpposingTeam(const int team) const
{
Expand Down Expand Up @@ -463,6 +468,7 @@ class CNEORules : public CHL2MPRules, public CGameEventListener
const int GetLastGhoster() const { return m_iLastGhoster; }
#ifdef GAME_DLL
private:
CBaseTrigger *pKothTrigger = nullptr;
CNEO_Juggernaut *m_pJuggernautItem = nullptr;
CNEO_Player *m_pJuggernautPlayer = nullptr;
float m_flJuggernautDeathTime = 0.0f;
Expand Down Expand Up @@ -497,6 +503,27 @@ class CNEORules : public CHL2MPRules, public CGameEventListener
Vector m_vecPreviousJuggernautSpawn = vec3_origin;
bool m_bGotMatchWinner = false;
int m_iMatchWinner = TEAM_UNASSIGNED;

// koth
float m_flKothAccumulatorNSF = 0.0f;
float m_flKothAccumulatorJinrai = 0.0f;
CBaseEntity* pSpriteNSF = gEntList.FindEntityByName(nullptr, "koth_sprite_nsf");
CBaseEntity* pSpriteJinrai = gEntList.FindEntityByName(nullptr, "koth_sprite_jin");
CBaseEntity* pSpriteInactive = gEntList.FindEntityByName(nullptr, "koth_sprite_inactive");
CBaseEntity* pSpriteNone = gEntList.FindEntityByName(nullptr, "koth_sprite_none");

CBaseEntity* pBrushNSF = gEntList.FindEntityByName(nullptr, "koth_brush_nsf");
CBaseEntity* pBrushJinrai = gEntList.FindEntityByName(nullptr, "koth_brush_jin");
CBaseEntity* pBrushInactive = gEntList.FindEntityByName(nullptr, "koth_brush_inactive");
CBaseEntity* pBrushNone = gEntList.FindEntityByName(nullptr, "koth_brush_none");

enum KothControllingTeams
{
KOTH_NONE = 0,
KOTH_INACTIVE,
KOTH_NSF,
KOTH_JINRAI,
};
#endif
CNetworkVar(int, m_nRoundStatus);
CNetworkVar(int, m_iHiddenHudElements);
Expand All @@ -521,6 +548,11 @@ class CNEORules : public CHL2MPRules, public CGameEventListener
CNetworkVar(float, m_flGhostLastHeld);
CNetworkHandle( CWeaponGhost, m_hGhost );

// KOTH networked variables
CNetworkVar(int, m_iKothTimeJinrai);
CNetworkVar(int, m_iKothTimeNSF);
CNetworkVar(int, m_iKothControllingTeam);

// Juggernaut networked variables
CNetworkVar(int, m_iJuggernautPlayerIndex);
CNetworkVar(bool, m_bJuggernautItemExists);
Expand Down
Loading