Skip to content
Draft
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -694,3 +694,7 @@ FodyWeavers.xsd

# Selected Background
/game/neo/scripts/[Cc]hapter[Bb]ackgrounds.txt

# clion stuff
/src/cmake-build-debug/
/.idea/
16 changes: 15 additions & 1 deletion game/bin/rebuild.fgd
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
6 : "JGR"
4 : "Empty"
5 : "Tutorial"
]
7 : "KOTH"
]

ForcedTeam(choices) : "Forced Team" : -1 : "Skip team menu and spawn as this team" =
[
Expand Down Expand Up @@ -461,3 +462,16 @@
[
radius(float) : "Radius" : 12 : ""
]

@SolidClass base(Trigger) = neo_trigger_koth_zone: "Trigger for zone capture detection. Can be grouped with other triggers into a single zone."
[
zone_name(target_destination) : "Zone name" : "" : "Name of the neo_info_koth_zone this trigger belongs to."
]

@PointClass base(Targetname) = neo_info_koth_zone : "Creates a zone to capture. Give it a name, then point one or more neo_trigger_koth_zone's zone_name at that name."
[
]

@PointClass = neo_koth_master : "Manages all neo_info_koth_zone entities on the map: picks which one is active and rotates between them. Place exactly one per map."
[
]
39 changes: 31 additions & 8 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 @@ -368,6 +371,10 @@ void CNEOHud_RoundState::UpdateStateForNeoHudElementDraw()
{
m_iWszRoundUCSize = V_swprintf_safe(m_wszRoundUnicode, L"DEATHMATCH");
}
else if (NEORules()->GetGameType() == NEO_GAME_TYPE_KOTH)
{
m_iWszRoundUCSize = V_swprintf_safe(m_wszRoundUnicode, L"FIRST TO %i WINS", sv_neo_koth_max_score.GetInt());
}
else
{
m_iWszRoundUCSize = V_swprintf_safe(m_wszRoundUnicode, L"ROUND %i", NEORules()->roundNumber());
Expand Down Expand Up @@ -396,13 +403,21 @@ void CNEOHud_RoundState::UpdateStateForNeoHudElementDraw()
const int localPlayerTeam = GetLocalPlayerTeam();
if (NEORules()->IsTeamplay())
{
if (cl_neo_hud_team_swap_sides.GetBool() && (localPlayerTeam == TEAM_JINRAI || localPlayerTeam == TEAM_NSF)) {
V_snwprintf(m_wszLeftTeamScore, 3, L"%i", GetGlobalTeam(localPlayerTeam)->GetRoundsWon());
V_snwprintf(m_wszRightTeamScore, 3, L"%i", GetGlobalTeam(NEORules()->GetOpposingTeam(localPlayerTeam))->GetRoundsWon());
const bool bSwapToLocal = cl_neo_hud_team_swap_sides.GetBool() && (localPlayerTeam == TEAM_JINRAI || localPlayerTeam == TEAM_NSF);
const int leftScoreTeam = bSwapToLocal ? localPlayerTeam : TEAM_JINRAI;
const int rightScoreTeam = bSwapToLocal ? NEORules()->GetOpposingTeam(localPlayerTeam) : TEAM_NSF;

if (NEORules()->GetGameType() == NEO_GAME_TYPE_KOTH)
{
const int leftScore = (leftScoreTeam == TEAM_JINRAI) ? NEORules()->GetKothTimeJinrai() : NEORules()->GetKothTimeNSF();
const int rightScore = (rightScoreTeam == TEAM_JINRAI) ? NEORules()->GetKothTimeJinrai() : NEORules()->GetKothTimeNSF();
V_snwprintf(m_wszLeftTeamScore, ARRAYSIZE(m_wszLeftTeamScore), L"%i", leftScore);
V_snwprintf(m_wszRightTeamScore, ARRAYSIZE(m_wszRightTeamScore), L"%i", rightScore);
}
else {
V_snwprintf(m_wszLeftTeamScore, 3, L"%i", GetGlobalTeam(TEAM_JINRAI)->GetRoundsWon());
V_snwprintf(m_wszRightTeamScore, 3, L"%i", GetGlobalTeam(TEAM_NSF)->GetRoundsWon());
else
{
V_snwprintf(m_wszLeftTeamScore, ARRAYSIZE(m_wszLeftTeamScore), L"%i", GetGlobalTeam(leftScoreTeam)->GetRoundsWon());
V_snwprintf(m_wszRightTeamScore, ARRAYSIZE(m_wszRightTeamScore), L"%i", GetGlobalTeam(rightScoreTeam)->GetRoundsWon());
}
}
else
Expand Down Expand Up @@ -433,6 +448,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 (wip) or detect KOTH maps (removed this feature)
// else if (NEORules()->GetGameType() == NEO_GAME_TYPE_KOTH)
// {
// V_sprintf_safe(szPlayersAliveANSI, "J:%d N:%d",
// NEORules()->GetKothTimeJinrai(),
// NEORules()->GetKothTimeNSF());
// }
else
{
V_sprintf_safe(szPlayersAliveANSI, "%i vs %i", m_iLeftPlayersAlive, m_iRightPlayersAlive);
Expand Down Expand Up @@ -550,12 +572,13 @@ void CNEOHud_RoundState::DrawNeoHudElement()
surface()->DrawSetTextFont(m_hOCRLargeFont);
surface()->DrawSetTextPos(m_posLeftTeamScore.x - (fontWidth / 2), m_posLeftTeamScore.y);
surface()->DrawSetTextColor(leftTeamInfo.color);
surface()->DrawPrintText(m_wszLeftTeamScore, 2);
surface()->DrawPrintText(m_wszLeftTeamScore, V_wcslen(m_wszLeftTeamScore));

surface()->GetTextSize(m_hOCRLargeFont, m_wszRightTeamScore, fontWidth, fontHeight);
surface()->DrawSetTextFont(m_hOCRLargeFont);
surface()->DrawSetTextPos(m_posRightTeamScore.x - (fontWidth / 2), m_posRightTeamScore.y);
surface()->DrawSetTextColor(rightTeamInfo.color);
surface()->DrawPrintText(m_wszRightTeamScore, 2);
surface()->DrawPrintText(m_wszRightTeamScore, V_wcslen(m_wszRightTeamScore));
}

m_iLeftPlayersAlive = m_iLeftPlayersTotal = m_iRightPlayersAlive = m_iRightPlayersTotal = 0;
Expand Down
6 changes: 3 additions & 3 deletions src/game/client/neo/ui/neo_hud_round_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,11 @@ class CNEOHud_RoundState : public CNEOHud_ChildElement, public CHudElement, publ
int m_iSmallFontHeight = 0;

// Center Box info
wchar_t m_wszRoundUnicode[12] = {};
wchar_t m_wszRoundUnicode[32] = {};
int m_iWszRoundUCSize = 0;
wchar_t m_wszTime[6] = {};
wchar_t m_wszLeftTeamScore[3] = {};
wchar_t m_wszRightTeamScore[3] = {};
wchar_t m_wszLeftTeamScore[12] = {};
wchar_t m_wszRightTeamScore[12] = {};
wchar_t m_wszPlayersAliveUnicode[16] = {};
const wchar_t *m_pWszStatusUnicode = nullptr;
int m_iStatusUnicodeSize = 0;
Expand Down
6 changes: 6 additions & 0 deletions src/game/server/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1498,6 +1498,12 @@ target_sources_grouped(
neo/neo_te_tocflash.h
neo/neo_tracefilter_collisiongroupdelta.h
neo/neo_dm_spawn.h
neo/neo_trigger_koth_zone.h
neo/neo_info_koth_zone.h
neo/neo_koth_master.h
neo/neo_trigger_koth_zone.cpp
neo/neo_info_koth_zone.cpp
neo/neo_koth_master.cpp
neo/neo_gamerules_restore.h
${UNITY_SOURCE_NEO_SRC_SERVER}
)
Expand Down
226 changes: 226 additions & 0 deletions src/game/server/neo/neo_info_koth_zone.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
#include "neo_info_koth_zone.h"
#include "neo_trigger_koth_zone.h"
#include "neo_koth_master.h"
#include "neo_player.h"
#include "neo_player_shared.h"

#define KOTHZONE_PRUNE_INTERVAL 0.5f
#define KOTHZONE_SCORE_CONTEXT "KothZoneScoreThink"
#define KOTHZONE_SCORE_INTERVAL 0.05f

BEGIN_DATADESC(CNEO_InfoKOTHZone)
DEFINE_THINKFUNC(Think),
DEFINE_THINKFUNC(ScoreThink),
END_DATADESC()

void CNEO_InfoKOTHZone::Spawn()
{
BaseClass::Spawn();

SetThink(&CNEO_InfoKOTHZone::Think);
SetNextThink(gpGlobals->curtime + KOTHZONE_PRUNE_INTERVAL);

SetContextThink(&CNEO_InfoKOTHZone::ScoreThink, gpGlobals->curtime + KOTHZONE_SCORE_INTERVAL, KOTHZONE_SCORE_CONTEXT);
}

void CNEO_InfoKOTHZone::Activate()
{
BaseClass::Activate();

CNEO_KOTHMaster *pMaster = static_cast<CNEO_KOTHMaster *>( // NOLINT
gEntList.FindEntityByClassname(nullptr, "neo_koth_master")
);

// neo TODO: do something when we see that there are several masters
if (pMaster == nullptr)
Warning("neo_info_koth_zone found with no neo_koth_master on the map!\n");
else
pMaster->RegisterZone(this);
}

void CNEO_InfoKOTHZone::Think()
{
PruneStaleCaptors();
SetNextThink(gpGlobals->curtime + KOTHZONE_PRUNE_INTERVAL);
}

void CNEO_InfoKOTHZone::ScoreThink()
{
if (!NEORules()->IsRoundLive())
{
SetNextThink(gpGlobals->curtime + KOTHZONE_SCORE_INTERVAL, KOTHZONE_SCORE_CONTEXT);
return;
}

switch (m_State)
{
case KOTH_JINRAI:
m_flAccumulatorJinrai += KOTHZONE_SCORE_INTERVAL;
m_flAccumulatorNSF = 0.0f;
break;
case KOTH_NSF:
m_flAccumulatorNSF += KOTHZONE_SCORE_INTERVAL;
m_flAccumulatorJinrai = 0.0f;
break;
default:
// KOTH_NONE (empty) or KOTH_BOTH (contested) - nobody accumulates
m_flAccumulatorNSF = 0.0f;
m_flAccumulatorJinrai = 0.0f;
break;
}

const float flSecondsPerPoint = sv_neo_koth_seconds_per_point.GetFloat();
if (m_flAccumulatorJinrai >= flSecondsPerPoint)
{
const int points = int(m_flAccumulatorJinrai / flSecondsPerPoint);
m_flAccumulatorJinrai -= flSecondsPerPoint * points;
NEORules()->AddKothScore(TEAM_JINRAI, points);
}
if (m_flAccumulatorNSF >= flSecondsPerPoint)
{
const int points = int(m_flAccumulatorNSF / flSecondsPerPoint);
m_flAccumulatorNSF -= flSecondsPerPoint * points;
NEORules()->AddKothScore(TEAM_NSF, points);
}

SetNextThink(gpGlobals->curtime + KOTHZONE_SCORE_INTERVAL, KOTHZONE_SCORE_CONTEXT);
}

void CNEO_InfoKOTHZone::OnPlayerEnter(CNEO_Player *pPlayer)
{
if (!pPlayer)
return;

EHANDLE hPlayer = pPlayer;
for (int i = 0; i < m_Captors.Count(); ++i)
{
if (m_Captors[i].hPlayer == hPlayer)
{
// already standing in another trigger of this same zone
m_Captors[i].touchCount++;
return;
}
}

ZoneCaptor captor;
captor.hPlayer = hPlayer;
// if they're somehow dead on entry, don't credit either team
captor.team = pPlayer->IsAlive() ? pPlayer->GetTeamNumber() : TEAM_UNASSIGNED;
captor.touchCount = 1;
m_Captors.AddToTail(captor);

if (captor.team == TEAM_JINRAI)
m_iJinraiCount++;
else if (captor.team == TEAM_NSF)
m_iNSFCount++;

UpdateState();
}

void CNEO_InfoKOTHZone::OnPlayerLeave(CNEO_Player *pPlayer)
{
if (!pPlayer)
return;

EHANDLE hPlayer = pPlayer;
for (int i = 0; i < m_Captors.Count(); ++i)
{
if (m_Captors[i].hPlayer != hPlayer)
continue;

// still touching another trigger belonging to this zone
if (--m_Captors[i].touchCount > 0)
return;

if (m_Captors[i].team == TEAM_JINRAI)
m_iJinraiCount--;
else if (m_Captors[i].team == TEAM_NSF)
m_iNSFCount--;

m_Captors.Remove(i);
UpdateState();
return;
}
}

void CNEO_InfoKOTHZone::AddChildTrigger(CNEO_TriggerKOTHZone *pTrigger)
{
if (!pTrigger)
return;

m_ChildTriggers.AddToTail(pTrigger);
}

void CNEO_InfoKOTHZone::SetActivity(bool bActive)
{
if (bActive == m_bActive)
return;

m_bActive = bActive;

for (int i = 0; i < m_ChildTriggers.Count(); ++i)
{
CNEO_TriggerKOTHZone *pTrigger = m_ChildTriggers[i].Get();
if (!pTrigger)
continue;

if (bActive)
pTrigger->Enable();
else
pTrigger->Disable();
}
}

void CNEO_InfoKOTHZone::ResetCapture()
{
m_Captors.Purge();
m_iJinraiCount = 0;
m_iNSFCount = 0;
// neo TODO: it could lead to an incorrect result if players somehow spawn right inside the trigger, but who cares?
m_State = KOTH_NONE;
m_flAccumulatorNSF = 0.0f;
m_flAccumulatorJinrai = 0.0f;
}

void CNEO_InfoKOTHZone::PruneStaleCaptors()
{
bool bChanged = false;

// EndTouch won't reliably fire for players who disconnected or died without
// physically leaving the trigger volume (e.g. respawning elsewhere) - catch those here
for (int i = m_Captors.Count() - 1; i >= 0; --i)
{
CNEO_Player *pPlayer = static_cast<CNEO_Player *>(m_Captors[i].hPlayer.Get());
if (pPlayer && pPlayer->IsAlive())
continue;

if (m_Captors[i].team == TEAM_JINRAI)
m_iJinraiCount--;
else if (m_Captors[i].team == TEAM_NSF)
m_iNSFCount--;

m_Captors.Remove(i);
bChanged = true;
}

if (bChanged)
UpdateState();
}

void CNEO_InfoKOTHZone::UpdateState()
{
KothControllingTeams newState;
if (m_iJinraiCount > 0 && m_iNSFCount > 0)
newState = KOTH_BOTH;
else if (m_iJinraiCount > 0)
newState = KOTH_JINRAI;
else if (m_iNSFCount > 0)
newState = KOTH_NSF;
else
newState = KOTH_NONE;

if (newState == m_State)
return;

m_State = newState;
}
Loading