diff --git a/game/bin/rebuild.fgd b/game/bin/rebuild.fgd index 2b3ca6a12f..e00c38e697 100644 --- a/game/bin/rebuild.fgd +++ b/game/bin/rebuild.fgd @@ -129,10 +129,16 @@ output OnCap(void) : "Fires when capped here" ] -@PointClass base(Angles) studio("models/gameplay/w_ghost.mdl") = neo_ghostspawnpoint : +@PointClass base(Targetname, Angles, EnableDisable) studio("models/gameplay/w_ghost.mdl") = neo_ghostspawnpoint : "This entity marks the start point ghosts for CTG gametypes." [ - + StartDisabled(choices) : "Start Disabled" : 0 = + [ + 0 : "No" + 1 : "Yes" + ] + + output OnSpawnedHere(void) : "Fires when a ghost spawns here" ] @FilterClass base(BaseFilter) iconsprite("editor/filter_class.vmt") = filter_activator_neoclass : "A filter that filters by the class (Recon, Assault, Support, etc) of the player who activated it." @@ -346,8 +352,15 @@ output OnPlayerActivate(void) : "Juggernaut activated" ] -@PointClass base(Angles) studio("models/player/jgr.mdl") = neo_juggernautspawnpoint : "Juggernaut spawn point" +@PointClass base(Targetname, Angles, EnableDisable) studio("models/player/jgr.mdl") = neo_juggernautspawnpoint : "Juggernaut spawn point" [ + StartDisabled(choices) : "Start Disabled" : 0 = + [ + 0 : "No" + 1 : "Yes" + ] + + output OnSpawnedHere(void) : "Fires when a juggernaut spawns here" ] @PointClass base(Targetname) size(-8 -8 -8, 8 8 8) = point_broadcastclientcommand : diff --git a/src/game/server/neo/neo_ghost_spawn_point.cpp b/src/game/server/neo/neo_ghost_spawn_point.cpp index 50bdc2c7ba..befcbd05b2 100644 --- a/src/game/server/neo/neo_ghost_spawn_point.cpp +++ b/src/game/server/neo/neo_ghost_spawn_point.cpp @@ -6,6 +6,25 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +BEGIN_DATADESC(CNEOObjectiveSpawnPoint) + DEFINE_KEYFIELD(m_bStartDisabled, FIELD_BOOLEAN, "StartDisabled"), + + DEFINE_INPUTFUNC(FIELD_VOID, "Enable", InputEnable), + DEFINE_INPUTFUNC(FIELD_VOID, "Disable", InputDisable), + + DEFINE_OUTPUT(m_OnSpawnedHere, "OnSpawnedHere"), +END_DATADESC() + +void CNEOObjectiveSpawnPoint::Spawn() +{ + BaseClass::Spawn(); + + if (m_bStartDisabled) + { + m_bDisabled = true; + } +} + LINK_ENTITY_TO_CLASS(neo_ghostspawnpoint, CNEOGhostSpawnPoint); LINK_ENTITY_TO_CLASS(neo_juggernautspawnpoint, CNEOJuggernautSpawnPoint); @@ -14,5 +33,18 @@ void CNEOGhostSpawnPoint::Spawn() { BaseClass::Spawn(); - NEORules()->m_ghostSpawns.AddToTail(this); + if (!m_bDisabled) + { + NEORules()->m_ghostSpawns.AddToTail(this); + } } + +void CNEOJuggernautSpawnPoint::Spawn() +{ + BaseClass::Spawn(); + + if (!m_bDisabled) + { + NEORules()->m_jgrSpawns.AddToTail(this); + } +} \ No newline at end of file diff --git a/src/game/server/neo/neo_ghost_spawn_point.h b/src/game/server/neo/neo_ghost_spawn_point.h index a998aadf75..10602ab063 100644 --- a/src/game/server/neo/neo_ghost_spawn_point.h +++ b/src/game/server/neo/neo_ghost_spawn_point.h @@ -4,16 +4,36 @@ #pragma once #endif -class CNEOGhostSpawnPoint : public CPointEntity +class CNEOObjectiveSpawnPoint : public CPointEntity { - DECLARE_CLASS(CNEOGhostSpawnPoint, CPointEntity); + DECLARE_CLASS(CNEOObjectiveSpawnPoint, CPointEntity); + DECLARE_DATADESC(); + +protected: + void InputEnable(inputdata_t &inputdata) { m_bDisabled = false; } + void InputDisable(inputdata_t &inputdata) { m_bDisabled = true; } + + bool m_bStartDisabled = false; + public: virtual void Spawn() override; + + bool m_bDisabled = false; + COutputEvent m_OnSpawnedHere; }; -class CNEOJuggernautSpawnPoint : public CPointEntity +class CNEOGhostSpawnPoint : public CNEOObjectiveSpawnPoint { - DECLARE_CLASS(CNEOJuggernautSpawnPoint, CPointEntity); + DECLARE_CLASS(CNEOGhostSpawnPoint, CNEOObjectiveSpawnPoint); +public: + virtual void Spawn() override; +}; + +class CNEOJuggernautSpawnPoint : public CNEOObjectiveSpawnPoint +{ + DECLARE_CLASS(CNEOJuggernautSpawnPoint, CNEOObjectiveSpawnPoint); +public: + virtual void Spawn() override; }; #endif // NEO_GHOST_SPAWN_POINT_H diff --git a/src/game/shared/neo/neo_gamerules.cpp b/src/game/shared/neo/neo_gamerules.cpp index b30c44b1db..76eb216a2d 100644 --- a/src/game/shared/neo/neo_gamerules.cpp +++ b/src/game/shared/neo/neo_gamerules.cpp @@ -144,7 +144,6 @@ ConVar sv_neo_pausematch_enabled("sv_neo_pausematch_enabled", "0", FCVAR_REPLICA ConVar sv_neo_pausematch_unpauseimmediate("sv_neo_pausematch_unpauseimmediate", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "Testing only - If enabled, unpause will be immediate.", true, 0.0f, true, 1.0f); ConVar sv_neo_readyup_countdown("sv_neo_readyup_countdown", "5", FCVAR_REPLICATED, "Set the countdown from fully ready to start of match in seconds.", true, 0.0f, true, 120.0f); ConVar sv_neo_ghost_spawn_bias("sv_neo_ghost_spawn_bias", "0", FCVAR_REPLICATED, "Spawn ghost in the same location as the previous round on odd-indexed rounds (Round 1 = index 0)", true, 0, true, 1); -ConVar sv_neo_juggernaut_spawn_bias("sv_neo_juggernaut_spawn_bias", "0", FCVAR_REPLICATED, "Spawn juggernaut in the same location as the previous round on odd-indexed rounds (Round 1 = index 0)", true, 0, true, 1); ConVar sv_neo_teamdamage_assists("sv_neo_teamdamage_assists", "0", FCVAR_REPLICATED, "Whether to drain XP when assisting the death of a teammate.", true, 0.0f, true, 1.0f); ConVar sv_neo_client_autorecord("sv_neo_client_autorecord", "0", FCVAR_REPLICATED | FCVAR_DONTRECORD, "Record demos clientside", true, 0, true, 1); #ifdef CLIENT_DLL @@ -168,7 +167,6 @@ static void neoSvCompCallback(IConVar* var, const char* pOldValue, float flOldVa sv_neo_spraydisable.SetValue(bCurrentValue); sv_neo_pausematch_enabled.SetValue(bCurrentValue); sv_neo_ghost_spawn_bias.SetValue(bCurrentValue); - sv_neo_juggernaut_spawn_bias.SetValue(bCurrentValue); sv_neo_client_autorecord.SetValue(bCurrentValue); #ifdef GAME_DLL sv_neo_reject_opengl_mesa_check.SetValue(bCurrentValue); // NEO NOTE (nullsystem): See comment above variable declaration for reason @@ -586,6 +584,7 @@ CNEORules::CNEORules() #ifdef GAME_DLL m_bNextClientIsFakeClient = false; m_ghostSpawns.EnsureCapacity(10); + m_jgrSpawns.EnsureCapacity(10); Q_strncpy(g_Teams[TEAM_JINRAI]->m_szTeamname.GetForModify(), TEAM_STR_JINRAI, MAX_TEAM_NAME_LENGTH); @@ -1890,6 +1889,9 @@ void CNEORules::SpawnTheGhost(const Vector *origin) } else { + // NEO TODO DG: Create a way of dealing with removed / disabled spawns during the match + // I'm not touching this right now cuz I don't want to risk breaking the parity behaviour + Assert(!m_ghostSpawns.IsEmpty()); int desiredSpawn; // zero-indexed @@ -1916,7 +1918,7 @@ void CNEORules::SpawnTheGhost(const Vector *origin) Assert(desiredSpawn >= 0); Assert(desiredSpawn < m_ghostSpawns.Count()); - const auto* ghostSpawn = m_ghostSpawns[desiredSpawn].Get(); + auto *ghostSpawn = m_ghostSpawns[desiredSpawn].Get(); if (ghostSpawn) { if (m_pGhost->GetOwner()) @@ -1936,6 +1938,7 @@ void CNEORules::SpawnTheGhost(const Vector *origin) { m_pGhost->SetAbsOrigin(ghostSpawn->GetAbsOrigin()); m_pGhost->Drop(vec3_origin); + ghostSpawn->m_OnSpawnedHere.FireOutput(m_pGhost, m_pGhost); } } else @@ -1955,44 +1958,22 @@ void CNEORules::SpawnTheGhost(const Vector *origin) // Very similar to above. void CNEORules::SpawnTheJuggernaut(const Vector* origin) { - CBaseEntity* pEnt; - - // Get the amount of juggernaut spawns available to us - int numJgrSpawns = 0; - - pEnt = gEntList.FirstEnt(); - while (pEnt) - { - if (dynamic_cast(pEnt)) - { - numJgrSpawns++; - } - else if (auto* jgr = dynamic_cast(pEnt)) - { - if (!jgr->IsMarkedForDeletion()) - { - m_pJuggernautItem = jgr; - } - } - - pEnt = gEntList.NextEnt(pEnt); - } - - // No juggernaut spawns - if (numJgrSpawns == 0) + // No Juggernaut spawns and this map isn't named "_jgr". Probably not a JGR map. + if (m_jgrSpawns.IsEmpty() && (V_stristr(GameRules()->MapName(), "_jgr") == 0)) { m_pJuggernautItem = nullptr; return; } - bool spawnedJuggernautNow = false; + AssertMsg(!m_pJuggernautItem, "m_pJuggernautItem already exists before attempting spawn. Shouldn't happen!"); + if (!m_pJuggernautItem) { - m_pJuggernautItem = dynamic_cast(CreateEntityByName("neo_juggernaut", -1)); + m_pJuggernautItem = dynamic_cast(CreateEntityByName("neo_juggernaut")); if (!m_pJuggernautItem) { Assert(false); - Warning("Failed to spawn a juggernaut\n"); + Warning("Failed to spawn a new Juggernaut\n"); return; } @@ -2003,71 +1984,76 @@ void CNEORules::SpawnTheJuggernaut(const Vector* origin) return; } - spawnedJuggernautNow = true; + m_pJuggernautItem->NetworkStateChanged(); } + m_hJuggernaut = m_pJuggernautItem; m_bJuggernautItemExists = true; + m_pJuggernautItem->m_bLocked = true; Assert(UTIL_IsValidEntity(m_pJuggernautItem)); - m_hJuggernaut = m_pJuggernautItem; - m_pJuggernautItem->m_bLocked = true; - if (origin) { m_pJuggernautItem->SetAbsOrigin(*origin); } - // We didn't have any spawns, spawn jgr at origin - else if (numJgrSpawns == 0) + else if (m_jgrSpawns.IsEmpty()) { - Warning("No juggernaut spawns found! Spawning juggernaut at map origin, instead.\n"); + Warning("No Juggernaut spawns found! Spawning Juggernaut at map origin, instead.\n"); m_pJuggernautItem->SetAbsOrigin(vec3_origin); } - else if (sv_neo_juggernaut_spawn_bias.GetBool() == true && roundNumberIsEven()) - { - m_pJuggernautItem->SetAbsOrigin(m_vecPreviousJuggernautSpawn); - } else { - // Randomly decide on a juggernaut spawn point we want this time - const int desiredSpawn = RandomInt(1, numJgrSpawns); - int jgrSpawnIteration = 1; + // NEO TODO DG: Create a way of dealing with removed / disabled spawns during the match - pEnt = gEntList.FirstEnt(); - // Second iteration, we pick the ghost spawn we want - while (pEnt) - { - auto jgrSpawn = dynamic_cast(pEnt); + Assert(!m_jgrSpawns.IsEmpty()); + int desiredSpawn; // zero-indexed - if (jgrSpawn) + if (roundNumber() == 0) + { + desiredSpawn = RandomInt(0, m_jgrSpawns.Count()-1); + } + else + { + // Round numbers are one-indexed + Assert(roundNumber() > 0); + bool isFirstRound = (roundNumber() == 1); + if (isFirstRound) { - if (jgrSpawnIteration++ == desiredSpawn) - { - if (!jgrSpawn->GetAbsOrigin().IsValid()) - { - m_pJuggernautItem->SetAbsOrigin(vec3_origin); - Warning("Failed to get ghost spawn coords; spawning juggernaut at map origin instead!\n"); - Assert(false); - } - else - { - m_pJuggernautItem->SetAbsOrigin(jgrSpawn->GetAbsOrigin()); - m_pJuggernautItem->SetAbsAngles(QAngle(0, jgrSpawn->GetAbsAngles().y, 0)); - } - - break; - } + m_jgrSpawns.Shuffle(); } - pEnt = gEntList.NextEnt(pEnt); + desiredSpawn = roundNumber() % m_jgrSpawns.Count(); + } + Assert(desiredSpawn >= 0); + Assert(desiredSpawn < m_jgrSpawns.Count()); + + auto *jgrSpawn = m_jgrSpawns[desiredSpawn].Get(); + if (jgrSpawn) + { + if (!jgrSpawn->GetAbsOrigin().IsValid()) + { + m_pJuggernautItem->SetAbsOrigin(vec3_origin); + Warning("Failed to get Juggernaut spawn coords; spawning Juggernaut at map origin instead!\n"); + Assert(false); + } + else + { + m_pJuggernautItem->SetAbsOrigin(jgrSpawn->GetAbsOrigin()); + m_pJuggernautItem->SetAbsAngles(QAngle(0, jgrSpawn->GetAbsAngles().y, 0)); + jgrSpawn->m_OnSpawnedHere.FireOutput(m_pJuggernautItem, m_pJuggernautItem); + } + } + else + { + Assert(false); } } m_vecPreviousJuggernautSpawn = m_pJuggernautItem->GetAbsOrigin(); - DevMsg("%s juggernaut at coords:\n\t%.1f %.1f %.1f\n", - spawnedJuggernautNow ? "Spawned" : "Moved", - m_vecPreviousJuggernautSpawn.x, - m_vecPreviousJuggernautSpawn.y, - m_vecPreviousJuggernautSpawn.z); + DevMsg("Spawned Juggernaut at coords:\n\t%.1f %.1f %.1f\n", + m_vecPreviousJuggernautSpawn.x, + m_vecPreviousJuggernautSpawn.y, + m_vecPreviousJuggernautSpawn.z); } void CNEORules::SelectTheVIP() diff --git a/src/game/shared/neo/neo_gamerules.h b/src/game/shared/neo/neo_gamerules.h index c8cbfd2d3e..fd6044f2c8 100644 --- a/src/game/shared/neo/neo_gamerules.h +++ b/src/game/shared/neo/neo_gamerules.h @@ -461,8 +461,10 @@ class CNEORules : public CHL2MPRules, public CGameEventListener bool m_bGamemodeTypeBeenInitialized = false; friend class CNEO_GhostBoundary; friend class CNEOGhostSpawnPoint; + friend class CNEOJuggernautSpawnPoint; friend class CMultiplayRules; CUtlVector> m_ghostSpawns; + CUtlVector> m_jgrSpawns; Vector m_vecPreviousGhostSpawn = vec3_origin; Vector m_vecPreviousJuggernautSpawn = vec3_origin; bool m_bGotMatchWinner = false;