diff --git a/game/bin/rebuild.fgd b/game/bin/rebuild.fgd index 19d69818fa..4f5aa29b1e 100644 --- a/game/bin/rebuild.fgd +++ b/game/bin/rebuild.fgd @@ -40,6 +40,7 @@ 2 : "VIP" 3 : "DM" 6 : "JGR" + 7 : "ATK 4 : "Empty" 5 : "Tutorial" ] diff --git a/src/game/client/CMakeLists.txt b/src/game/client/CMakeLists.txt index 19c2458a4c..8ef7ef7ae7 100644 --- a/src/game/client/CMakeLists.txt +++ b/src/game/client/CMakeLists.txt @@ -21,6 +21,7 @@ target_include_directories(client ${CMAKE_SOURCE_DIR}/game/shared/hl2mp ${CMAKE_SOURCE_DIR}/game/shared/Multiplayer ${CMAKE_SOURCE_DIR}/game/shared/neo + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons ${CMAKE_SOURCE_DIR}/public ${CMAKE_SOURCE_DIR}/thirdparty/sixensesdk/include @@ -1629,8 +1630,6 @@ target_sources_grouped( FILES ${CMAKE_SOURCE_DIR}/game/shared/neo/achievements_neo.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/achievements_neo.h - ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_gamerules.cpp - ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_gamerules.h ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_ghost_cap_point.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_ghost_cap_point.h ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_juggernaut.cpp @@ -1663,6 +1662,30 @@ target_sources_grouped( ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_serial.h ) +target_sources_grouped( + TARGET client + NAME "Source Files\\Shared\\Gamerules" + FILES + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_atk.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_atk.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_ctg.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_ctg.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_dm.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_dm.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_emt.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_emt.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_jgr.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_jgr.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_tdm.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_tdm.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_tut.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_tut.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_vip.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_vip.h +) + target_sources_grouped( TARGET client NAME "Source Files\\Shared\\Weapons" diff --git a/src/game/client/neo/ui/neo_hud_childelement.h b/src/game/client/neo/ui/neo_hud_childelement.h index 896c3f41c2..10f4bdae40 100644 --- a/src/game/client/neo/ui/neo_hud_childelement.h +++ b/src/game/client/neo/ui/neo_hud_childelement.h @@ -4,7 +4,7 @@ #pragma once #endif -#include "neo_gamerules.h" +#include "gamerules/neo_gamerules.h" class C_NEO_Player; diff --git a/src/game/client/neo/ui/neo_hud_friendly_marker.h b/src/game/client/neo/ui/neo_hud_friendly_marker.h index 9f83af606a..8efb4d21d8 100644 --- a/src/game/client/neo/ui/neo_hud_friendly_marker.h +++ b/src/game/client/neo/ui/neo_hud_friendly_marker.h @@ -3,7 +3,7 @@ #include "hudelement.h" #include -#include "neo_gamerules.h" +#include "gamerules/neo_gamerules.h" #include "neo_hud_worldpos_marker.h" enum NeoIFFMarkerSegment diff --git a/src/game/client/neo/ui/neo_hud_round_state.cpp b/src/game/client/neo/ui/neo_hud_round_state.cpp index aec87e7174..9609db4f0f 100644 --- a/src/game/client/neo/ui/neo_hud_round_state.cpp +++ b/src/game/client/neo/ui/neo_hud_round_state.cpp @@ -20,6 +20,7 @@ #include "c_playerresource.h" #include "vgui_avatarimage.h" #include "neo_scoreboard.h" +#include "neo_gamerules_dm.h" #include "hltvcamera.h" @@ -258,6 +259,7 @@ void CNEOHud_RoundState::UpdateStateForNeoHudElementDraw() const bool inDoOrDie = NEORules()->RoundIsDoOrDie(); const bool inMatchPoint = !inDoOrDie && NEORules()->RoundIsMatchPoint(); // we don't care about matchpoint if in do or die + // NEO TODO (Adam) move this to neogamerules? m_pWszStatusUnicode = L""; if (roundStatus == NeoRoundStatus::Idle) { @@ -298,7 +300,7 @@ void CNEOHud_RoundState::UpdateStateForNeoHudElementDraw() m_pWszStatusUnicode = L"Capture the Ghost\n"; break; case NEO_GAME_TYPE_VIP: - if (GetLocalPlayerTeam() == NEORules()->m_iEscortingTeam.Get()) + if (GetLocalPlayerTeam() == NEORules()->GetEscortingTeam()) { if (NEORules()->GhostExists()) { @@ -309,7 +311,7 @@ void CNEOHud_RoundState::UpdateStateForNeoHudElementDraw() m_pWszStatusUnicode = L"Escort the VIP\n"; } } - else + else // NEO TODO (Adam) check if opposite team to escorting team, else different message for spectators { if (NEORules()->GhostExists()) { @@ -324,6 +326,20 @@ void CNEOHud_RoundState::UpdateStateForNeoHudElementDraw() case NEO_GAME_TYPE_JGR: m_pWszStatusUnicode = L"Control the Juggernaut\n"; break; + case NEO_GAME_TYPE_ATK: + if (GetLocalPlayerTeam() == NEORules()->GetAttackingTeam()) + { + m_pWszStatusUnicode = L"Capture the Ghost\n"; + } + else if (GetLocalPlayerTeam() == NEORules()->GetDefendingTeam()) + { + m_pWszStatusUnicode = L"Protect the Ghost\n"; + } + else + { + m_pWszStatusUnicode = L"Capture/Protect the Ghost\n"; + } + break; default: m_pWszStatusUnicode = L"Await further orders\n"; break; @@ -378,9 +394,9 @@ void CNEOHud_RoundState::UpdateStateForNeoHudElementDraw() roundTimeLeft = NEORules()->GetRemainingPreRoundFreezeTime(true); int secsTotal = 0.0f; - if (roundStatus == NeoRoundStatus::Overtime && NEORules()->GetGameType() == NEO_GAME_TYPE_CTG) + if (roundStatus == NeoRoundStatus::Overtime && (NEORules()->GetGameType() == NEO_GAME_TYPE_CTG || NEORules()->GetGameType() == NEO_GAME_TYPE_ATK)) { - secsTotal = RoundFloatToInt(NEORules()->GetCTGOverTime()); + secsTotal = RoundFloatToInt(NEORules()->GetRoundRemainingTime()); } else { @@ -408,7 +424,7 @@ void CNEOHud_RoundState::UpdateStateForNeoHudElementDraw() else { [[maybe_unused]] int iDMHighestTotal; - NEORules()->GetDMHighestScorers(&iDMHighestTotal, &iDMHighestXP); + NEORulesDM()->GetDMHighestScorers(&iDMHighestTotal, &iDMHighestXP); } char szPlayersAliveANSI[ARRAYSIZE(m_wszPlayersAliveUnicode)] = {}; diff --git a/src/game/server/CMakeLists.txt b/src/game/server/CMakeLists.txt index fe219a20cc..e264a8b0e5 100644 --- a/src/game/server/CMakeLists.txt +++ b/src/game/server/CMakeLists.txt @@ -42,6 +42,7 @@ target_include_directories(server ${CMAKE_SOURCE_DIR}/game/shared/hl2mp ${CMAKE_SOURCE_DIR}/game/shared/Multiplayer ${CMAKE_SOURCE_DIR}/game/shared/neo + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons ${CMAKE_SOURCE_DIR}/utils/common ${CMAKE_SOURCE_DIR}/public @@ -1346,8 +1347,6 @@ target_sources_grouped( FILES ${CMAKE_SOURCE_DIR}/game/shared/neo/achievements_neo.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/achievements_neo.h - ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_gamerules.cpp - ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_gamerules.h ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_ghost_cap_point.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_ghost_cap_point.h ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_juggernaut.cpp @@ -1410,8 +1409,6 @@ target_sources_grouped( neo/neo_te_tocflash.h neo/neo_tracefilter_collisiongroupdelta.h neo/neo_typeconverter.cpp - neo/neo_dm_spawn.cpp - neo/neo_dm_spawn.h ) target_sources_grouped( @@ -1517,6 +1514,30 @@ target_sources_grouped( neo/bot/map_entities/neo_bot_proxy.h ) +target_sources_grouped( + TARGET server + NAME "NEO\\Gamerules" + FILES + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_atk.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_atk.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_ctg.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_ctg.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_dm.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_dm.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_emt.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_emt.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_jgr.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_jgr.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_tdm.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_tdm.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_tut.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_tut.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_vip.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/gamerules/neo_gamerules_vip.h +) + target_sources_grouped( TARGET server NAME "NEO\\Weapons" diff --git a/src/game/server/neo/neo_client.cpp b/src/game/server/neo/neo_client.cpp index d1e6b3b167..b3ed95b99e 100644 --- a/src/game/server/neo/neo_client.cpp +++ b/src/game/server/neo/neo_client.cpp @@ -1,5 +1,6 @@ #include "cbase.h" #include "neo_player.h" +#include "neo_game_config.h" #include "neo_gamerules.h" #include "gamerules.h" #include "teamplay_gamerules.h" @@ -393,5 +394,5 @@ void GameStartFrame( void ) //========================================================= void InstallGameRules() { - CreateGameRulesObject( "CNEORules" ); + CreateGameRulesObject( "CNEORulesEMT" ); } diff --git a/src/game/server/neo/neo_dm_spawn.cpp b/src/game/server/neo/neo_dm_spawn.cpp deleted file mode 100644 index e1fe2a4d2a..0000000000 --- a/src/game/server/neo/neo_dm_spawn.cpp +++ /dev/null @@ -1,255 +0,0 @@ -#include "neo_dm_spawn.h" - -#include -#include "cbase.h" -#include "inetchannelinfo.h" -#include "neo_player.h" -#include "neo_misc.h" -#include "util_shared.h" -#include "filesystem.h" -#include "utlbuffer.h" - -#include "tier0/memdbgon.h" - -static inline CUtlVector gDMSpawnLocs; -static inline int gDMSpawnCur = 0; -static inline CUtlVector gDMSpawnUsed; -static inline int gDMSpawnUsedTally = 0; - -namespace DMSpawn -{ -bool HasDMSpawn() -{ - return !gDMSpawnLocs.IsEmpty(); -} - -Info GiveNextSpawn() -{ - if (gDMSpawnLocs.IsEmpty()) return Info{}; - - // Try to give the next available spawn rather than just giving only a random number - const int iSpawnTotal = gDMSpawnLocs.Size(); - if (gDMSpawnUsedTally >= iSpawnTotal) - { - gDMSpawnUsed.SetCount(iSpawnTotal); - gDMSpawnUsedTally = 0; - } - - // iChecked for sanity check so it never inf. loop - static bool bCheckLeft = false; // Alternate check directions per spawning - int iPickSpawn = RandomInt(0, iSpawnTotal - 1); - for (int iChecked = 0; gDMSpawnUsed[iPickSpawn] && iChecked < iSpawnTotal; ++iChecked) - { - iPickSpawn = LoopAroundInArray(iPickSpawn + (bCheckLeft ? -1 : +1), iSpawnTotal); - } - bCheckLeft = !bCheckLeft; - - gDMSpawnUsed[iPickSpawn] = true; - ++gDMSpawnUsedTally; - return gDMSpawnLocs[iPickSpawn]; -} -} - -static CNEO_Player *LoopbackPlayer() -{ - for (int i = 1; i <= gpGlobals->maxClients; ++i) - { - auto *pPlayer = static_cast(UTIL_PlayerByIndex(i)); - if (!pPlayer || pPlayer->IsBot()) - { - continue; - } - INetChannelInfo *nci = engine->GetPlayerNetInfo(i); - Assert(nci); - if (nci) - { - if (nci->IsLoopback()) - { - return pPlayer; - } - } - } - return nullptr; -} - -static void DMSpawnComCallbackCreate() -{ - if (auto *player = LoopbackPlayer()) - { - const Vector newLoc = player->GetAbsOrigin(); - const QAngle lookAngle = player->GetAbsAngles(); - bool bAlreadyApplied = false; - for (const auto &spawn : gDMSpawnLocs) - { - if (spawn.pos == newLoc) - { - bAlreadyApplied = true; - break; - } - } - if (bAlreadyApplied) - { - Msg("DM Spawn already applied\n"); - } - else - { - gDMSpawnLocs.AddToTail(DMSpawn::Info{newLoc, lookAngle.y}); - gDMSpawnUsed.AddToTail(false); - Msg("DM Spawn: %.2f %.2f %.2f (%.2f) created\n", newLoc.x, newLoc.y, newLoc.z, lookAngle.y); - } - } -} - -static void DMSpawnComCallbackRemoveAll() -{ - gDMSpawnLocs.RemoveAll(); - gDMSpawnUsed.RemoveAll(); -} - -static void DMSpawnComCallbackRemoveOne(const CCommand &command) -{ - static constexpr char USAGE_MSG[] = "Usage: sv_neo_dmspawn_removeone [spawn index]\n"; - if (command.ArgC() != 2) - { - Msg(USAGE_MSG); - return; - } - const auto optIdx = StrToInt(command.Arg(1)); - if (!optIdx) - { - Msg(USAGE_MSG); - return; - } - - const int idx = *optIdx; - if (!IN_BETWEEN_AR(0, idx, gDMSpawnLocs.Size())) - { - Msg("Error: Index %d is not within 0 to %d.\n", idx, gDMSpawnLocs.Size() - 1); - return; - } - - gDMSpawnLocs.Remove(idx); - gDMSpawnUsed.Remove(idx); - Msg("Removed spawn %d\n", idx); -} - -static void DMSpawnComCallbackPrintLocs() -{ - for (int i = 0; i < gDMSpawnLocs.Size(); ++i) - { - const auto &spawn = gDMSpawnLocs[i]; - Msg("DM Spawn [%d]: %.2f %.2f %.2f (%.2f)\n", i, spawn.pos.x, spawn.pos.y, spawn.pos.z, spawn.lookY); - } -} - -static constexpr unsigned int MAGIC_NUMBER = 0x15AF73DA; -static constexpr int DMSPAWN_SERIAL_CURRENT = 1; - -void DMSpawnComCallbackSave([[maybe_unused]] const CCommand &command) -{ - if (gDMSpawnLocs.IsEmpty()) - { - return; - } - - CUtlBuffer buf; - buf.PutUnsignedInt(MAGIC_NUMBER); - buf.PutInt(DMSPAWN_SERIAL_CURRENT); - buf.PutInt(gDMSpawnLocs.Size()); - for (const auto &spawn : gDMSpawnLocs) - { - buf.PutFloat(spawn.pos.x); - buf.PutFloat(spawn.pos.y); - buf.PutFloat(spawn.pos.z); - buf.PutFloat(spawn.lookY); - } - - char szCurrentMapName[MAX_MAP_NAME + 1]; - V_strcpy_safe(szCurrentMapName, STRING(gpGlobals->mapname)); - filesystem->CreateDirHierarchy("maps/dm_locs"); - - char szFName[512]; - V_sprintf_safe(szFName, "maps/dm_locs/%s.loc", szCurrentMapName); - if (!filesystem->WriteFile(szFName, nullptr, buf)) - { - Msg("Failed to write file: %s\n", szFName); - return; - } - Msg("DMSpawn file saved: %s\n", szFName); -} - -void DMSpawnComCallbackLoad([[maybe_unused]] const CCommand &command) -{ - gDMSpawnLocs.RemoveAll(); - - char szCurrentMapName[MAX_MAP_NAME + 1]; - V_strcpy_safe(szCurrentMapName, STRING(gpGlobals->mapname)); - - CUtlBuffer buf(0, 0, CUtlBuffer::READ_ONLY); - char szFName[512]; - V_sprintf_safe(szFName, "maps/dm_locs/%s.loc", szCurrentMapName); - if (!filesystem->ReadFile(szFName, nullptr, buf)) - { - Msg("DMSpawn file not found: %s\n", szFName); - return; - } - - if (buf.GetUnsignedInt() != MAGIC_NUMBER) - { - return; - } - - /*const int version =*/ (void)buf.GetInt(); - const int locsSize = buf.GetInt(); - for (int i = 0; i < locsSize && buf.IsValid(); ++i) - { - DMSpawn::Info spawn = {}; - spawn.pos.x = buf.GetFloat(); - spawn.pos.y = buf.GetFloat(); - spawn.pos.z = buf.GetFloat(); - spawn.lookY = buf.GetFloat(); - gDMSpawnLocs.AddToTail(spawn); - } - gDMSpawnUsed.SetCount(gDMSpawnLocs.Size()); - Msg("DMSpawn file loaded: %s\n", szFName); -} - -static void DMSpawnComCallbackTeleportNext() -{ - if (gDMSpawnLocs.IsEmpty()) return; - gDMSpawnCur = LoopAroundInArray(gDMSpawnCur + 1, gDMSpawnLocs.Size()); - if (auto *pPlayer = LoopbackPlayer()) - { - const auto &spawn = gDMSpawnLocs[gDMSpawnCur]; - const QAngle spawnAngle{0, spawn.lookY, 0}; - pPlayer->SetAbsOrigin(spawn.pos); - pPlayer->SetAbsAngles(spawnAngle); - pPlayer->SnapEyeAngles(spawnAngle); - Msg("Teleported to spawn %d: %.2f %.2f %.2f (%.2f)\n", gDMSpawnCur, spawn.pos.x, spawn.pos.y, spawn.pos.z, spawn.lookY); - } -} - -static void DMSpawnComCallbackMapinfo() -{ - char szCurrentMapName[MAX_MAP_NAME + 1]; - V_strcpy_safe(szCurrentMapName, STRING(gpGlobals->mapname)); - - int iEntCount = 0; - CBaseEntity *entDMSpawn = nullptr; - while ((entDMSpawn = gEntList.FindEntityByClassname(entDMSpawn, "info_player_deathmatch"))) - { - ++iEntCount; - } - - Msg("Deathmatch spawns for: %s\ndmspawn spawns count: %d\ninfo_player_deathmatch count: %d\nAllow deathmatch: %s", - szCurrentMapName, gDMSpawnLocs.Size(), iEntCount, (iEntCount > 0 || !gDMSpawnLocs.IsEmpty()) ? "YES" : "NO"); -} - -ConCommand sv_neo_dmspawn_create("sv_neo_dmspawn_create", &DMSpawnComCallbackCreate, "DMSpawn - Create a new spawn", FCVAR_USERINFO | FCVAR_CHEAT); -ConCommand sv_neo_dmspawn_removeallspawns("sv_neo_dmspawn_removeallspawns", &DMSpawnComCallbackRemoveAll, "DMSpawn - Remove all spawns", FCVAR_USERINFO | FCVAR_CHEAT); -ConCommand sv_neo_dmspawn_removeone("sv_neo_dmspawn_removeone", &DMSpawnComCallbackRemoveOne, "DMSpawn - Remove one spawn by a given index", FCVAR_USERINFO | FCVAR_CHEAT); -ConCommand sv_neo_dmspawn_printlocs("sv_neo_dmspawn_printlocs", &DMSpawnComCallbackPrintLocs, "DMSpawn - Print locations and Y-angle of all spawns", FCVAR_USERINFO | FCVAR_CHEAT); -ConCommand sv_neo_dmspawn_save("sv_neo_dmspawn_save", &DMSpawnComCallbackSave, "DMSpawn - Save spawn file to filesystem", FCVAR_USERINFO | FCVAR_CHEAT); -ConCommand sv_neo_dmspawn_load("sv_neo_dmspawn_load", &DMSpawnComCallbackLoad, "DMSpawn - Load spawn file from filesystem", FCVAR_USERINFO | FCVAR_CHEAT); -ConCommand sv_neo_dmspawn_teleportnext("sv_neo_dmspawn_teleportnext", &DMSpawnComCallbackTeleportNext, "DMSpawn - Teleport to the next spawn", FCVAR_USERINFO | FCVAR_CHEAT); -ConCommand sv_neo_dmspawn_mapinfo("sv_neo_dmspawn_mapinfo", &DMSpawnComCallbackMapinfo, "DMSpawn - Map deathmatch spawns check and information", FCVAR_USERINFO); diff --git a/src/game/server/neo/neo_dm_spawn.h b/src/game/server/neo/neo_dm_spawn.h deleted file mode 100644 index 6c483bf726..0000000000 --- a/src/game/server/neo/neo_dm_spawn.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "mathlib/vector.h" -#include "convar.h" - -namespace DMSpawn -{ - -struct Info -{ - Vector pos; - float lookY; -}; - -bool HasDMSpawn(); -Info GiveNextSpawn(); - -} - -void DMSpawnComCallbackLoad([[maybe_unused]] const CCommand &command = CCommand{}); diff --git a/src/game/server/neo/neo_game_config.cpp b/src/game/server/neo/neo_game_config.cpp index dab9ada034..b35dd1eebc 100644 --- a/src/game/server/neo/neo_game_config.cpp +++ b/src/game/server/neo/neo_game_config.cpp @@ -1,4 +1,6 @@ #include "neo_game_config.h" +#include "neo_gamerules.h" +#include "neo_gamerules_dm.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -26,12 +28,35 @@ END_DATADESC() extern ConVar sv_neo_comp; +CNEOGameConfig *g_pNEOGameConfig = nullptr; + +CNEOGameConfig::CNEOGameConfig() +{ + Assert( !g_pNEOGameConfig ); + g_pNEOGameConfig = this; +} + +CNEOGameConfig::~CNEOGameConfig() +{ + g_pNEOGameConfig = nullptr; +} + void CNEOGameConfig::Spawn() { if (sv_neo_comp.GetBool()) { m_OnCompetitive.FireOutput(nullptr, this); } + + // Create new gamerules object + { + for (int i = g_Teams.Count() - 1; i >= 0; i--) + { + UTIL_RemoveImmediate(g_Teams[i]); + } + + CreateGameRulesObject(NEO_GAME_TYPE_CLASS_NAMES[m_GameType]); + } } // Inputs @@ -44,7 +69,7 @@ void CNEOGameConfig::InputFireTeamWin(inputdata_t& inputData) void CNEOGameConfig::InputFireDMPlayerWin(inputdata_t& inputData) { - CBasePlayer* pPlayer = NULL; + CBasePlayer* pPlayer = nullptr; if (inputData.pActivator && inputData.pActivator->IsPlayer()) { @@ -53,7 +78,7 @@ void CNEOGameConfig::InputFireDMPlayerWin(inputdata_t& inputData) if (pPlayer) { - NEORules()->SetWinningDMPlayer(static_cast(pPlayer)); + NEORulesDM()->SetWinningDMPlayer(static_cast(pPlayer)); } } diff --git a/src/game/server/neo/neo_game_config.h b/src/game/server/neo/neo_game_config.h index c9a8729c88..65c0969b36 100644 --- a/src/game/server/neo/neo_game_config.h +++ b/src/game/server/neo/neo_game_config.h @@ -1,18 +1,19 @@ #pragma once #include "cbase.h" -#include "baseentity.h" #include "neo_gamerules.h" class CNEOGameConfig : public CLogicalEntity { - DECLARE_CLASS(CNEOGameConfig, CBaseEntity); + DECLARE_CLASS(CNEOGameConfig, CLogicalEntity); DECLARE_DATADESC(); public: + CNEOGameConfig(); + ~CNEOGameConfig(); virtual void Spawn() override; - int m_GameType = NEO_GAME_TYPE_TDM; + int m_GameType = NEO_GAME_TYPE_EMT; int m_HiddenHudElements = 0; int m_ForcedTeam = -1; int m_ForcedClass = -1; @@ -30,3 +31,9 @@ class CNEOGameConfig : public CLogicalEntity COutputEvent m_OnRoundStart; COutputEvent m_OnCompetitive; }; + +extern CNEOGameConfig *g_pNEOGameConfig; +inline CNEOGameConfig *NEOGameConfig() +{ + return g_pNEOGameConfig; +} \ No newline at end of file diff --git a/src/game/server/neo/neo_player.cpp b/src/game/server/neo/neo_player.cpp index 6102275c60..616868e98e 100644 --- a/src/game/server/neo/neo_player.cpp +++ b/src/game/server/neo/neo_player.cpp @@ -156,8 +156,6 @@ extern ConVar sv_stickysprint; extern ConVar sv_neo_dev_loadout; extern ConVar neo_bot_difficulty; -ConVar sv_neo_can_change_classes_anytime("sv_neo_can_change_classes_anytime", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "Can players change classes at any moment, even mid-round?", - true, 0.0f, true, 1.0f); ConVar sv_neo_change_suicide_player("sv_neo_change_suicide_player", "0", FCVAR_REPLICATED, "Kill the player if they change the team and they're alive.", true, 0.0f, true, 1.0f); ConVar sv_neo_change_threshold_interval("sv_neo_change_threshold_interval", "0.25", FCVAR_REPLICATED, "The interval threshold limit in seconds before the player is allowed to change team.", true, 0.0f, true, 1000.0f); ConVar sv_neo_dm_max_class_dur("sv_neo_dm_max_class_dur", "10", FCVAR_REPLICATED, "The time in seconds when the player can change class on respawn during deathmatch.", true, 0.0f, true, 60.0f); @@ -243,12 +241,7 @@ void CNEO_Player::RequestSetClass(int newClass) return; } - const bool bIsTypeDM = (NEORules()->GetGameType() == NEO_GAME_TYPE_TDM || NEORules()->GetGameType() == NEO_GAME_TYPE_DM); - const NeoRoundStatus status = NEORules()->GetRoundStatus(); - if (IsDead() || sv_neo_can_change_classes_anytime.GetBool() || - (!m_bIneligibleForLoadoutPick && NEORules()->GetRemainingPreRoundFreezeTime(false) > 0) || - (bIsTypeDM && !m_bIneligibleForLoadoutPick && GetAliveDuration() < sv_neo_dm_max_class_dur.GetFloat()) || - (status == NeoRoundStatus::Idle || status == NeoRoundStatus::Warmup || status == NeoRoundStatus::Countdown)) + if (NEORules()->PlayerCanChangeLoadout(this)) { m_iNeoClass = newClass; m_iNextSpawnClassChoice = NEO_CLASS_RANDOM; @@ -279,13 +272,9 @@ void CNEO_Player::RequestSetClass(int newClass) void CNEO_Player::RequestSetSkin(int newSkin) { - const NeoRoundStatus roundStatus = NEORules()->GetRoundStatus(); - bool canChangeImmediately = ((roundStatus != NeoRoundStatus::RoundLive) && (roundStatus != NeoRoundStatus::Overtime) && (roundStatus != NeoRoundStatus::PostRound)) || !IsAlive(); - - if (canChangeImmediately) + if (NEORules()->PlayerCanChangeSkin(this)) { m_iNeoSkin = newSkin; - SetPlayerTeamModel(); } //else NEOTODO Set for next spawn @@ -2546,7 +2535,7 @@ void CNEO_Player::SpawnSpecificGibs(float vMinVelocity, float vMaxVelocity, cons { CGib* pGib = CREATE_ENTITY(CGib, "gib"); - if (NEORules()->CanRespawnAnyTime()) + if (NEORules()->RespawnsEnabled()) { constexpr float GIB_LIFETIME = 30.f; pGib->Spawn(cModelName, GIB_LIFETIME); @@ -3099,10 +3088,10 @@ bool CNEO_Player::ProcessTeamSwitchRequest(int iTeam) changedTeams = true; // Spawn the player immediately if its a single life game mode - spawnImmediately = !NEORules()->CanRespawnAnyTime() && NEORules()->FPlayerCanRespawn(this); + spawnImmediately = !NEORules()->RespawnsEnabled() && NEORules()->FPlayerCanRespawn(this); if (!spawnImmediately) { - if (NEORules()->CanRespawnAnyTime() || IsFakeClient()) + if (NEORules()->RespawnsEnabled() || IsFakeClient()) { // Stop observer mode so we spawn in anyway after a short delay, bots crash when transitioning to observer mode StopObserverMode(); } diff --git a/src/game/server/neo/neo_spawn_manager.cpp b/src/game/server/neo/neo_spawn_manager.cpp index 6c1a92ea48..ba751756ad 100644 --- a/src/game/server/neo/neo_spawn_manager.cpp +++ b/src/game/server/neo/neo_spawn_manager.cpp @@ -122,7 +122,7 @@ namespace NeoSpawnManager return backup; } - if (!rules->CanRespawnAnyTime()) + if (!rules->RespawnsEnabled()) { // We only care if it's been used before or not if there are no respawns manager.m_spawns[idx].isUsed = true; diff --git a/src/game/server/subs.cpp b/src/game/server/subs.cpp index 92e27d7e09..f49201c969 100644 --- a/src/game/server/subs.cpp +++ b/src/game/server/subs.cpp @@ -55,7 +55,7 @@ class CBaseDMStart : public CPointEntity CBaseDMStart() : CNEOSpawnPoint() { - m_iOwningTeam = TEAM_ANY; + m_eSide = E_TeamSide::Unspecified; } #else DECLARE_CLASS( CBaseDMStart, CPointEntity ); diff --git a/src/game/shared/hl2mp/hl2mp_gamerules.h b/src/game/shared/hl2mp/hl2mp_gamerules.h index 38b2dbfa51..f3ce6683c6 100644 --- a/src/game/shared/hl2mp/hl2mp_gamerules.h +++ b/src/game/shared/hl2mp/hl2mp_gamerules.h @@ -86,6 +86,14 @@ class CHL2MPRules : public CTeamplayRules #ifdef NEO friend class CNEORules; + friend class CNEORulesATK; + friend class CNEORulesCTG; + friend class CNEORulesDM; + friend class CNEORulesEMT; + friend class CNEORulesJGR; + friend class CNEORulesTDM; + friend class CNEORulesTUT; + friend class CNEORulesVIP; #endif #ifdef CLIENT_DLL diff --git a/src/game/shared/multiplay_gamerules.cpp b/src/game/shared/multiplay_gamerules.cpp index 1ed5244286..bb95ec3aa9 100644 --- a/src/game/shared/multiplay_gamerules.cpp +++ b/src/game/shared/multiplay_gamerules.cpp @@ -92,6 +92,9 @@ void MPTimeLimitCallback( IConVar *var, const char *pOldString, float flOldValue #endif ConVar mp_timelimit( "mp_timelimit", "0", FCVAR_NOTIFY|FCVAR_REPLICATED, "game time per map in minutes" +#ifdef NEO + , true, 0.0f, false, 0.0f +#endif // NEO #ifdef GAME_DLL , MPTimeLimitCallback #endif diff --git a/src/game/shared/neo/neo_gamerules.cpp b/src/game/shared/neo/gamerules/neo_gamerules.cpp similarity index 76% rename from src/game/shared/neo/neo_gamerules.cpp rename to src/game/shared/neo/gamerules/neo_gamerules.cpp index 0ddbbb19ab..d27b5df9ad 100644 --- a/src/game/shared/neo/neo_gamerules.cpp +++ b/src/game/shared/neo/gamerules/neo_gamerules.cpp @@ -30,7 +30,6 @@ #include "hl2mp_gameinterface.h" #include "player_resource.h" #include "inetchannelinfo.h" -#include "neo_dm_spawn.h" #include "neo_game_config.h" #include "nav_mesh.h" #include "neo_npc_dummy.h" @@ -48,6 +47,8 @@ ConVar sv_neo_preround_freeze_time("sv_neo_preround_freeze_time", "15", FCVAR_RE ConVar sv_neo_latespawn_max_time("sv_neo_latespawn_max_time", "15", FCVAR_REPLICATED, "How many seconds late are players still allowed to spawn.", true, 0.0, false, 0); ConVar sv_neo_wep_dmg_modifier("sv_neo_wep_dmg_modifier", "1.485", FCVAR_REPLICATED, "Temp global weapon damage modifier.", true, 0.0, true, 100.0); +ConVar sv_neo_can_change_classes_anytime("sv_neo_can_change_classes_anytime", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "Can players change classes at any moment, even mid-round?", + true, 0.0f, true, 1.0f); ConVar sv_neo_player_restore("sv_neo_player_restore", "1", FCVAR_REPLICATED, "If enabled, the server will save players XP and deaths per match session and restore them if they reconnect.", true, 0.0f, true, 1.0f); ConVar sv_neo_spraydisable("sv_neo_spraydisable", "0", FCVAR_REPLICATED, "If enabled, disables the players ability to spray.", true, 0.0f, true, 1.0f); @@ -63,37 +64,9 @@ ConVar sv_neo_clantag_allow("sv_neo_clantag_allow", "1", FCVAR_REPLICATED, "", t ConVar sv_neo_dev_test_clantag("sv_neo_dev_test_clantag", "", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Debug-mode only - Override all clantags with this value."); #endif -#define STR_GAMEOPTS "TDM=0, CTG=1, VIP=2, DM=3" -#define STR_GAMEBWOPTS "TDM=1, CTG=2, VIP=4, DM=8" #ifdef CLIENT_DLL -ConVar neo_vote_game_mode("neo_vote_game_mode", "1", FCVAR_USERINFO, "Vote on game mode to play. " STR_GAMEOPTS, true, 0, true, NEO_GAME_TYPE__TOTAL - 1); ConVar neo_vip_eligible("cl_neo_vip_eligible", "1", FCVAR_ARCHIVE, "Eligible for VIP", true, 0, true, 1); #endif // CLIENT_DLL -#ifdef GAME_DLL -ConVar sv_neo_vip_ctg_on_death("sv_neo_vip_ctg_on_death", "0", FCVAR_ARCHIVE, "Spawn Ghost when VIP dies, continue the game", true, 0, true, 1); -ConVar sv_neo_jgr_max_points("sv_neo_jgr_max_points", "20", FCVAR_GAMEDLL, "Maximum points required for a team to win in JGR", true, 1, false, 0); -#endif - -#ifdef GAME_DLL -// NEO TODO (nullsystem): Change how voting done from convar to menu selection -enum eGamemodeEnforcement -{ - GAMEMODE_ENFORCEMENT_MAP = 0, // Only use the gamemode enforced by the map - GAMEMODE_ENFORCEMENT_SINGLE, // Only use the single gamemode enforced by the server - GAMEMODE_ENFORCEMENT_RAND, // Randomly choose a gamemode on each map initialization based on a list - GAMEMODE_ENFORCEMENT_VOTE, // Allow vote by players on pre-match - - GAMEMODE_ENFORCEMENT__TOTAL, -}; -ConVar sv_neo_gamemode_enforcement("sv_neo_gamemode_enforcement", "0", FCVAR_REPLICATED, - "How the gamemode are determined. 0 = By map, 1 = By sv_neo_gamemode_single, 2 = Random, 3 = Pre-match voting", - true, 0.0f, true, GAMEMODE_ENFORCEMENT__TOTAL - 1); -ConVar sv_neo_gamemode_single("sv_neo_gamemode_single", "3", FCVAR_REPLICATED, "The gamemode that is enforced by the server. " STR_GAMEOPTS, - true, 0.0f, true, NEO_GAME_TYPE__TOTAL - 1); -ConVar sv_neo_gamemode_random_allow("sv_neo_gamemode_random_allow", "11", FCVAR_REPLICATED, - "In bitwise, the gamemodes that are allowed for random selection. Default = TDM+CTG+DM. " STR_GAMEBWOPTS, - true, 1.0f, true, (1 << NEO_GAME_TYPE__TOTAL)); // Can't be zero, minimum has to set to a bitwise value -#endif #ifdef GAME_DLL #ifdef DEBUG @@ -253,8 +226,6 @@ void CNEOGameRulesProxy::OnDataChanged(DataUpdateType_t updateType) } #endif // CLIENT_DLL -REGISTER_GAMERULES_CLASS( CNEORules ); - BEGIN_NETWORK_TABLE_NOBASE( CNEORules, DT_NEORules ) // NEO TODO (Rain): NEO specific game modes var (CTG/TDM/...) #ifdef CLIENT_DLL @@ -262,7 +233,6 @@ BEGIN_NETWORK_TABLE_NOBASE( CNEORules, DT_NEORules ) RecvPropTime(RECVINFO(m_flNeoRoundStartTime)), RecvPropTime(RECVINFO(m_flPauseEnd)), RecvPropInt(RECVINFO(m_nRoundStatus)), - RecvPropInt(RECVINFO(m_nGameTypeSelected)), RecvPropInt(RECVINFO(m_iRoundNumber)), RecvPropBool(RECVINFO(m_bIsMatchPoint)), RecvPropBool(RECVINFO(m_bIsDoOrDie)), @@ -296,7 +266,6 @@ BEGIN_NETWORK_TABLE_NOBASE( CNEORules, DT_NEORules ) SendPropTime(SENDINFO(m_flNeoRoundStartTime)), SendPropTime(SENDINFO(m_flPauseEnd)), SendPropInt(SENDINFO(m_nRoundStatus), NumBitsForCount(RoundStatusTotal), SPROP_UNSIGNED), - SendPropInt(SENDINFO(m_nGameTypeSelected), NumBitsForCount(NEO_GAME_TYPE__TOTAL), SPROP_UNSIGNED), SendPropInt(SENDINFO(m_iRoundNumber)), SendPropBool(SENDINFO(m_bIsMatchPoint)), SendPropBool(SENDINFO(m_bIsDoOrDie)), @@ -354,24 +323,16 @@ static NEOViewVectors g_NEOViewVectors( Vector(16, 16, 60) //VEC_CROUCH_TRACE_MAX (m_vCrouchTraceMax) ); -struct NeoGameTypeSettings { - const char* gameTypeName; - bool respawns; - bool neoRulesThink; - bool changeTeamClassLoadoutWhenAlive; - bool comp; - bool capPrevent; -}; - -const NeoGameTypeSettings NEO_GAME_TYPE_SETTINGS[NEO_GAME_TYPE__TOTAL] = { -// gametypeName respawns neoRulesThink changeTeamClassLoadoutWhenAlive comp capPrevent -/*NEO_GAME_TYPE_TDM*/ {"TDM", true, true, false, false, false}, -/*NEO_GAME_TYPE_CTG*/ {"CTG", false, true, false, true, true}, -/*NEO_GAME_TYPE_VIP*/ {"VIP", false, true, false, true, true}, -/*NEO_GAME_TYPE_DM*/ {"DM", true, true, false, false, false}, -/*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}, +const char* NEO_GAME_TYPE_CLASS_NAMES[NEO_GAME_TYPE__TOTAL] = +{ + "CNEORulesTDM", + "CNEORulesCTG", + "CNEORulesVIP", + "CNEORulesDM", + "CNEORulesEMT", + "CNEORulesTUT", + "CNEORulesJGR", + "CNEORulesATK" }; #ifdef CLIENT_DLL @@ -417,85 +378,9 @@ const NeoGameTypeSettings NEO_GAME_TYPE_SETTINGS[NEO_GAME_TYPE__TOTAL] = { // convert a velocity in ft/sec and a mass in grains to an impulse in kg in/s #define BULLET_IMPULSE(grains, ftpersec) ((ftpersec)*12*BULLET_MASS_GRAINS_TO_KG(grains)*BULLET_IMPULSE_EXAGGERATION) -extern ConVar neo_score_limit; -extern ConVar neo_round_limit; -extern ConVar sv_neo_ctg_score_limit; -extern ConVar sv_neo_ctg_round_limit; - -static void neoScoreLimitLegacyCallback(IConVar* var, const char* pOldValue, float flOldValue) -{ - Warning("Using legacy neo_score_limit cvar. Use sv_neo_[gamemode]_score_limit instead!\n"); - sv_neo_ctg_score_limit.SetValue(neo_score_limit.GetInt()); -} - -static void neoRoundLimitLegacyCallback(IConVar* var, const char* pOldValue, float flOldValue) -{ - Warning("Using legacy neo_round_limit cvar. Use sv_neo_[gamemode]_round_limit instead!\n"); - sv_neo_ctg_round_limit.SetValue(neo_round_limit.GetInt()); -} - -ConVar neo_score_limit("neo_score_limit", "7", FCVAR_REPLICATED | FCVAR_HIDDEN, "(Legacy) Neo score limit.", true, 0.0f, true, 99.0f -#ifdef GAME_DLL - , neoScoreLimitLegacyCallback -#endif -); -ConVar neo_round_limit("neo_round_limit", "0", FCVAR_REPLICATED | FCVAR_HIDDEN, "(Legacy) Max amount of rounds, 0 for no limit.", true, 0.0f, false, 0.0f -#ifdef GAME_DLL - , neoRoundLimitLegacyCallback -#endif -); - -ConVar neo_round_sudden_death("neo_round_sudden_death", "1", FCVAR_REPLICATED, "If neo_round_limit is not 0 and round is past " - "neo_round_limit, go into sudden death where match won't end until a team won.", true, 0.0f, true, 1.0f); - -// Score Limit -ConVar sv_neo_tdm_score_limit("sv_neo_tdm_score_limit", "1", FCVAR_REPLICATED, "TDM score limit", true, 0.0f, true, 99.0f); - -ConVar sv_neo_ctg_score_limit("sv_neo_ctg_score_limit", "7", FCVAR_REPLICATED, "CTG score limit", true, 0.0f, true, 99.0f); - -ConVar sv_neo_vip_score_limit("sv_neo_vip_score_limit", "7", FCVAR_REPLICATED, "VIP score limit", true, 0.0f, true, 99.0f); - -ConVar sv_neo_dm_score_limit("sv_neo_dm_score_limit", "7", FCVAR_REPLICATED, "DM score limit", true, 0.0f, true, 99.0f); - -ConVar sv_neo_jgr_score_limit("sv_neo_jgr_score_limit", "0", FCVAR_REPLICATED, "JGR score limit", true, 0.0f, true, 99.0f); - -// Round Limit -ConVar sv_neo_tdm_round_limit("sv_neo_tdm_round_limit", "0", FCVAR_REPLICATED, "TDM max amount of rounds, 0 for no limit.", true, 0.0f, false, 0.0f); - -ConVar sv_neo_ctg_round_limit("sv_neo_ctg_round_limit", "0", FCVAR_REPLICATED, "CTG max amount of rounds, 0 for no limit.", true, 0.0f, false, 0.0f); - -ConVar sv_neo_vip_round_limit("sv_neo_vip_round_limit", "0", FCVAR_REPLICATED, "VIP max amount of rounds, 0 for no limit.", true, 0.0f, false, 0.0f); +ConVar sv_neo_round_sudden_death("sv_neo_round_sudden_death", "1", FCVAR_REPLICATED, "If the game is past the round limit, go into sudden death where the match won't end until a team wins.", true, 0.0f, true, 1.0f); -ConVar sv_neo_dm_round_limit("sv_neo_dm_round_limit", "0", FCVAR_REPLICATED, "DM max amount of rounds, 0 for no limit.", true, 0.0f, false, 0.0f); - -ConVar sv_neo_jgr_round_limit("sv_neo_jgr_round_limit", "5", FCVAR_REPLICATED, "JGR max amount of rounds, 0 for no limit.", true, 0.0f, false, 0.0f); - -// Round Time Limit (make these sv_neo at some point) -ConVar neo_tdm_round_timelimit("neo_tdm_round_timelimit", "10.25", FCVAR_REPLICATED, "TDM round timelimit, in minutes.", - true, 0.0f, false, 600.0f); - -ConVar neo_ctg_round_timelimit("neo_ctg_round_timelimit", "3.25", FCVAR_REPLICATED, "CTG round timelimit, in minutes.", - true, 0.0f, false, 600.0f); - -ConVar neo_vip_round_timelimit("neo_vip_round_timelimit", "3.25", FCVAR_REPLICATED, "VIP round timelimit, in minutes.", - true, 0.0f, false, 600.0f); - -ConVar neo_dm_round_timelimit("neo_dm_round_timelimit", "10.25", FCVAR_REPLICATED, "DM round timelimit, in minutes.", - true, 0.0f, false, 600.0f); - -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 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); - -ConVar sv_neo_dm_win_xp("sv_neo_dm_win_xp", "50", FCVAR_REPLICATED, "The XP limit to win the match.", - true, 0.0f, true, 1000.0f); - -ConVar sv_neo_ctg_ghost_overtime_enabled("sv_neo_ctg_ghost_overtime_enabled", "0", FCVAR_REPLICATED, "Enable ghost overtime.", true, 0, true, 1); -ConVar sv_neo_ctg_ghost_overtime("sv_neo_ctg_ghost_overtime", "45", FCVAR_REPLICATED, "Adds up to this many seconds to the round while the ghost is held.", true, 0, true, 120); -ConVar sv_neo_ctg_ghost_overtime_grace("sv_neo_ctg_ghost_overtime_grace", "10", FCVAR_REPLICATED, "Number of seconds left in the round when the ghost is dropped in overtime.", true, 0, true, 30); -ConVar sv_neo_ctg_ghost_overtime_grace_decay("sv_neo_ctg_ghost_overtime_grace_decay", "0", FCVAR_REPLICATED, "Slowly reduce the grace time as overtime goes on.", true, 0, true, 1); +ConVar sv_neo_ignore_wep_xp_limit("sv_neo_ignore_wep_xp_limit", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "Allow equipping any loadout regardless of player XP.", true, 0.0f, true, 1.0f); #ifdef CLIENT_DLL extern ConVar neo_fov; @@ -640,20 +525,34 @@ static void CvarChanged_WeaponStay(IConVar* convar, const char* pOldVal, float f wep = gEntList.NextEntByClass(wep); } } - -static CNEOGameConfig *GetActiveGameConfig() -{ - return static_cast(gEntList.FindEntityByClassname(nullptr, "neo_game_config")); -} #endif CNEORules::CNEORules() { #ifdef GAME_DLL - m_bNextClientIsFakeClient = false; m_ghostSpawns.EnsureCapacity(10); m_jgrSpawns.EnsureCapacity(10); + CBaseEntity* ghostSpawn = nullptr; + do + { + ghostSpawn = gEntList.FindEntityByClassname(ghostSpawn, "neo_ghostspawnpoint"); + if (ghostSpawn) + { + m_ghostSpawns.AddToTail(static_cast(ghostSpawn)); + } + } while (ghostSpawn); + + CBaseEntity* jgrSpawn = nullptr; + do + { + jgrSpawn = gEntList.FindEntityByClassname(jgrSpawn, "neo_juggernautspawnpoint"); + if (jgrSpawn) + { + m_jgrSpawns.AddToTail(static_cast(jgrSpawn)); + } + } while (jgrSpawn); + Q_strncpy(g_Teams[TEAM_JINRAI]->m_szTeamname.GetForModify(), TEAM_STR_JINRAI, MAX_TEAM_NAME_LENGTH); @@ -670,7 +569,7 @@ CNEORules::CNEORules() } } - m_nGameTypeSelected = NEO_GAME_TYPE_CTG; + ResetMapSessionCommon(); #endif m_iHiddenHudElements = 0; m_iForcedTeam = -1; @@ -679,7 +578,6 @@ CNEORules::CNEORules() m_iForcedWeapon = -1; m_bCyberspaceLevel = false; - ResetMapSessionCommon(); ListenForGameEvent("round_start"); ListenForGameEvent("game_end"); @@ -838,7 +736,6 @@ extern ConVar mp_chattime; void CNEORules::ResetMapSessionCommon() { - SetRoundStatus(NeoRoundStatus::Idle); m_iRoundNumber = 0; m_bIsMatchPoint = false; m_bIsDoOrDie = false; @@ -855,6 +752,7 @@ void CNEORules::ResetMapSessionCommon() m_flNeoNextRoundStartTime = 0.0f; m_flGhostLastHeld = 0.0f; #ifdef GAME_DLL + SetRoundStatus(NeoRoundStatus::Idle); m_pRestoredInfos.Purge(); m_readyAccIDs.Purge(); m_bIgnoreOverThreshold = false; @@ -876,7 +774,6 @@ void CNEORules::ResetMapSessionCommon() m_bTeamBeenAwardedDueToCapPrevent = false; V_memset(m_arrayiEntPrevCap, 0, sizeof(m_arrayiEntPrevCap)); m_iEntPrevCapSize = 0; - DMSpawnComCallbackLoad(); m_vecPreviousGhostSpawn = vec3_origin; m_vecPreviousJuggernautSpawn = vec3_origin; m_pJuggernautItem = nullptr; @@ -920,230 +817,42 @@ bool CNEORules::CheckGameOver(void) return gameOver; } -void CNEORules::GetDMHighestScorers( #ifdef GAME_DLL - CNEO_Player *(*pHighestPlayers)[MAX_PLAYERS + 1], -#endif - int *iHighestPlayersTotal, - int *iHighestXP) const +void CNEORules::UpdateFromGameConfig() { - *iHighestPlayersTotal = 0; - *iHighestXP = 0; -#ifdef GAME_DLL - for (int i = 1; i <= gpGlobals->maxClients; ++i) -#else - if (!g_PR) + if (const auto pEntGameCfg = NEOGameConfig()) { - return; + m_iHiddenHudElements = pEntGameCfg->m_HiddenHudElements; + m_iForcedTeam = pEntGameCfg->m_ForcedTeam; + m_iForcedClass = pEntGameCfg->m_ForcedClass; + m_iForcedSkin = pEntGameCfg->m_ForcedSkin; + m_iForcedWeapon = pEntGameCfg->m_ForcedWeapon; + m_bCyberspaceLevel = pEntGameCfg->m_Cyberspace; } - - for (int i = 0; i < (MAX_PLAYERS + 1); ++i) -#endif - { - int iXP = 0; - -#ifdef GAME_DLL - auto pCmpPlayer = static_cast(UTIL_PlayerByIndex(i)); - if (!pCmpPlayer) - { - continue; - } - iXP = pCmpPlayer->m_iXP; -#else - if (!g_PR->IsConnected(i)) - { - continue; - } - iXP = g_PR->GetXP(i); -#endif - - if (iXP == *iHighestXP) - { -#ifdef GAME_DLL - (*pHighestPlayers)[(*iHighestPlayersTotal)++] = pCmpPlayer; -#else - (*iHighestPlayersTotal)++; -#endif - } - else if (iXP > *iHighestXP) - { - *iHighestPlayersTotal = 0; - *iHighestXP = iXP; -#ifdef GAME_DLL - (*pHighestPlayers)[(*iHighestPlayersTotal)++] = pCmpPlayer; -#else - (*iHighestPlayersTotal)++; -#endif - } - } -} - -#ifdef GAME_DLL -void CNEORules::CheckGameType() -{ - // Static as CNEORules doesn't persists through map changes - static int iStaticInitOnCmd = -1; - static int iStaticInitOnRandAllow = -1; - static bool staticGamemodesCanPick[NEO_GAME_TYPE__TOTAL] = {}; - static int iStaticLastPick = -1; // Mostly so it doesn't repeat on array refresh - - const int iGamemodeEnforce = sv_neo_gamemode_enforcement.GetInt(); - const int iGamemodeRandAllow = sv_neo_gamemode_random_allow.GetInt(); - // Update on what to select on first map load or server operator changes sv_neo_gamemode_enforcement - const bool bCheckOnGameType = (!m_bGamemodeTypeBeenInitialized || iGamemodeEnforce != iStaticInitOnCmd || - iGamemodeRandAllow != iStaticInitOnRandAllow); - if (!bCheckOnGameType) - { - return; - } - - // NEO NOTE (nullsystem): CNEORules always recreated on map change, yet entities properly found - // happens later. So checking and init on game type will execute here once. - switch (iGamemodeEnforce) - { - case GAMEMODE_ENFORCEMENT_SINGLE: - { - m_nGameTypeSelected = sv_neo_gamemode_single.GetInt(); - } break; - case GAMEMODE_ENFORCEMENT_RAND: - { - const int iBWAllow = sv_neo_gamemode_random_allow.GetInt(); // Min of 1, cannot be zero - Assert(iBWAllow > 0); - - // Check if all are used up - { - int iAllowsPicks = 0; - for (int i = 0; i < NEO_GAME_TYPE__TOTAL; ++i) - { - iAllowsPicks += staticGamemodesCanPick[i]; - } - if (iAllowsPicks == 0 || iGamemodeRandAllow != iStaticInitOnRandAllow) - { -#ifdef DEBUG - DevMsg("Array reset!\n"); -#endif - // Preset true to those not-allowed, preset false to those allowed - int iTotalPicks = 0; - for (int i = 0; i < NEO_GAME_TYPE__TOTAL; ++i) - { - const bool bCanPick = (iBWAllow & (1 << i)); - iTotalPicks += bCanPick; - staticGamemodesCanPick[i] = bCanPick; - } - if (iTotalPicks <= 1) - { - iStaticLastPick = -1; - } - } - } - - m_nGameTypeSelected = RandomInt(0, NEO_GAME_TYPE__TOTAL - 1); - for (int iWalk = 0; - (!staticGamemodesCanPick[m_nGameTypeSelected] || m_nGameTypeSelected == iStaticLastPick) && - iWalk < NEO_GAME_TYPE__TOTAL; - ++iWalk) - { - m_nGameTypeSelected = LoopAroundInArray(m_nGameTypeSelected + 1, NEO_GAME_TYPE__TOTAL); - } - -#ifdef DEBUG - for (int i = 0; i < NEO_GAME_TYPE__TOTAL; ++i) - { - DevMsg("%d | %s: %s\n", i, NEO_GAME_TYPE_DESC_STRS[i].szStr, staticGamemodesCanPick[i] ? "Allowed" : "Not allowed"); - } - DevMsg("Pick: %d | Prev: %d\n", m_nGameTypeSelected.Get(), iStaticLastPick); -#endif - - staticGamemodesCanPick[m_nGameTypeSelected] = false; - iStaticLastPick = m_nGameTypeSelected; - } break; - default: - { - const auto pEntGameCfg = GetActiveGameConfig(); - m_nGameTypeSelected = (pEntGameCfg) ? pEntGameCfg->m_GameType : NEO_GAME_TYPE_EMT; - } break; - } - m_bGamemodeTypeBeenInitialized = true; - iStaticInitOnCmd = iGamemodeEnforce; - iStaticInitOnRandAllow = iGamemodeRandAllow; -} - -void CNEORules::CheckGameConfig() -{ - CheckGameType(); - - const auto pEntGameCfg = GetActiveGameConfig(); - m_iHiddenHudElements = (pEntGameCfg) ? pEntGameCfg->m_HiddenHudElements : 0; - - m_iForcedTeam = (pEntGameCfg) ? pEntGameCfg->m_ForcedTeam : -1; - m_iForcedClass = (pEntGameCfg) ? pEntGameCfg->m_ForcedClass : -1; - m_iForcedSkin = (pEntGameCfg) ? pEntGameCfg->m_ForcedSkin : -1; - m_iForcedWeapon = (pEntGameCfg) ? pEntGameCfg->m_ForcedWeapon : -1; - - m_bCyberspaceLevel = (pEntGameCfg) ? pEntGameCfg->m_Cyberspace : false; -} -#endif - -bool CNEORules::CheckShouldNotThink() -{ -#ifdef GAME_DLL - if (gpGlobals->eLoadType == MapLoad_Background || !NEO_GAME_TYPE_SETTINGS[m_nGameTypeSelected].neoRulesThink) -#else // CLIENT_DLL - if (engine->IsLevelMainMenuBackground() || !NEO_GAME_TYPE_SETTINGS[m_nGameTypeSelected].neoRulesThink) -#endif // GAME_DLL || CLIENT_DLL - { - return true; - } - return false; } -void CNEORules::Think(void) +bool CNEORules::RoundStartFromIdleOrPausedThink() { -#ifdef GAME_DLL - CheckGameConfig(); - if (CheckShouldNotThink()) - { - // This is kind of wonky, but we only need it for the tutorial, in order to play the dummy beacon sounds... - if (!m_pGhost && GetGameType() == NEO_GAME_TYPE_TUT) - { - auto pEnt = gEntList.FirstEnt(); - while (pEnt) - { - if (dynamic_cast(pEnt)) - { - m_pGhost = static_cast(pEnt); - m_hGhost = m_pGhost; - return; - } - pEnt = gEntList.NextEnt(pEnt); - } - } - if (m_pGhost) m_pGhost->UpdateNearestGhostBeaconDist(); - return; - } + UpdateFromGameConfig(); - const bool bIsIdleState = m_nRoundStatus == NeoRoundStatus::Idle - || m_nRoundStatus == NeoRoundStatus::Warmup - || m_nRoundStatus == NeoRoundStatus::Countdown; - bool bIsPause = m_nRoundStatus == NeoRoundStatus::Pause; - if (bIsIdleState && gpGlobals->curtime > m_flNeoNextRoundStartTime) + if (IsRoundIdle() && gpGlobals->curtime > m_flNeoNextRoundStartTime) { StartNextRound(); - return; + return true; } // Make the pause instant if we're still in freeze time - if (m_nRoundStatus == NeoRoundStatus::PreRoundFreeze && m_flPauseDur > 0.0f && - m_iRoundNumber == (m_iPausingRound - 1)) + if (NeoRoundStatus::PreRoundFreeze == m_nRoundStatus && + m_flPauseDur > 0.0f && + m_iPausingRound - 1 == m_iRoundNumber) { SetRoundStatus(NeoRoundStatus::Pause); - bIsPause = true; m_bPausedByPreRoundFreeze = true; m_flNeoNextRoundStartTime = 0.0f; m_flPauseEnd = gpGlobals->curtime + m_flPauseDur; } - if (bIsPause) + if (IsRoundPaused()) { if (gpGlobals->curtime >= m_flPauseEnd) { @@ -1160,7 +869,7 @@ void CNEORules::Think(void) m_flPauseDur = 0.0f; m_flPauseEnd = 0.0f; StartNextRound(); - return; + return true; } else if (gpGlobals->curtime > m_flNeoNextRoundStartTime) { @@ -1169,120 +878,106 @@ void CNEORules::Think(void) } } - // Allow respawn if it's an idle, warmup round, pausing, or deathmatch-type gamemode - const bool bIsDMType = (m_nGameTypeSelected == NEO_GAME_TYPE_DM || m_nGameTypeSelected == NEO_GAME_TYPE_TDM || m_nGameTypeSelected == NEO_GAME_TYPE_JGR); - if (bIsDMType || bIsIdleState || bIsPause) - { - CRecipientFilter filter; - filter.MakeReliable(); - - for (int i = 1; i <= gpGlobals->maxClients; i++) - { - auto player = static_cast(UTIL_PlayerByIndex(i)); - if (player && player->IsDead() && (bIsPause || player->DeathCount() > 0)) - { - const int playerTeam = player->GetTeamNumber(); - if ((playerTeam == TEAM_JINRAI || playerTeam == TEAM_NSF) && RespawnWithRet(player, false)) - { - player->m_bInAim = false; - player->m_bCarryingGhost = false; - player->m_bInThermOpticCamo = false; - player->m_bInVision = false; - player->m_bIneligibleForLoadoutPick = false; - player->SetTestMessageVisible(false); - - if (!bIsIdleState && !bIsPause && bIsDMType) - { - engine->ClientCommand(player->edict(), "loadoutmenu"); - } - else - { - filter.AddRecipient(player); - } - } - } - } + return false; +} - if (filter.GetRecipientCount() > 0 && bIsIdleState) - { - UserMessageBegin(filter, "IdleRespawnShowMenu"); - MessageEnd(); - } - } +void CNEORules::PlayerRespawnThink() +{ + if (!(IsRoundIdle() || IsRoundPaused())) + return; - if (m_bThinkCheckClantags) + CRecipientFilter filter; + for (int i = 1; i <= gpGlobals->maxClients; i++) { - m_bThinkCheckClantags = false; - int iHasClantags[TEAM__TOTAL] = {}; - bool bClantagSet[TEAM__TOTAL] = {}; - char szTeamClantags[TEAM__TOTAL][NEO_MAX_CLANTAG_LENGTH] = {}; - for (int i = 1; i <= gpGlobals->maxClients; ++i) + auto player = static_cast(UTIL_PlayerByIndex(i)); + if (player && player->IsDead() && (IsRoundPaused() || player->DeathCount() > 0)) { - auto pNeoPlayer = static_cast(UTIL_PlayerByIndex(i)); - if (pNeoPlayer) + const int playerTeam = player->GetTeamNumber(); + if ((playerTeam == TEAM_JINRAI || playerTeam == TEAM_NSF) && RespawnWithRet(player, false)) { - const int iTeam = pNeoPlayer->GetTeamNumber(); - if (!bClantagSet[iTeam]) + player->m_bInAim = false; + player->m_bCarryingGhost = false; + player->m_bInThermOpticCamo = false; + player->m_bInVision = false; + player->m_bIneligibleForLoadoutPick = false; + player->SetTestMessageVisible(false); + + if (!IsRoundIdle() && !IsRoundPaused()) { - bClantagSet[iTeam] = true; - V_strcpy_safe(szTeamClantags[iTeam], pNeoPlayer->GetNeoClantag()), - ++iHasClantags[iTeam]; + engine->ClientCommand(player->edict(), "loadoutmenu"); } else { - iHasClantags[iTeam] += (V_strcmp(szTeamClantags[iTeam], pNeoPlayer->GetNeoClantag()) == 0); + filter.AddRecipient(player); } } } - - char *pszClantagMod[TEAM__TOTAL] = {}; - pszClantagMod[TEAM_JINRAI] = m_szNeoJinraiClantag.GetForModify(); - pszClantagMod[TEAM_NSF] = m_szNeoNSFClantag.GetForModify(); - for (const int i : {TEAM_JINRAI, TEAM_NSF}) - { - V_strncpy(pszClantagMod[i], (iHasClantags[i] == GetGlobalTeam(i)->GetNumPlayers()) ? - szTeamClantags[i] : "", NEO_MAX_CLANTAG_LENGTH); - } } - if (bIsPause) + if (filter.GetRecipientCount() > 0 && IsRoundIdle()) { - return; + filter.MakeReliable(); + UserMessageBegin(filter, "IdleRespawnShowMenu"); + MessageEnd(); } +} - if (m_nGameTypeSelected == NEO_GAME_TYPE_CTG) +static bool bCheckClantags = false; +void CNEORules::CheckClantagsThink() +{ + if (!bCheckClantags) + return; + + bCheckClantags = false; + int iHasClantags[TEAM__TOTAL] = {}; + bool bClantagSet[TEAM__TOTAL] = {}; + char szTeamClantags[TEAM__TOTAL][NEO_MAX_CLANTAG_LENGTH] = {}; + for (int i = 1; i <= gpGlobals->maxClients; ++i) { - if (sv_neo_ctg_ghost_overtime_enabled.GetBool() && m_nRoundStatus == NeoRoundStatus::RoundLive && m_iGhosterPlayer && - (m_flNeoRoundStartTime + (neo_ctg_round_timelimit.GetFloat() * 60) - sv_neo_ctg_ghost_overtime_grace.GetFloat()) < gpGlobals->curtime) + auto pNeoPlayer = static_cast(UTIL_PlayerByIndex(i)); + if (pNeoPlayer) { - m_nRoundStatus = NeoRoundStatus::Overtime; - } - - if (m_nRoundStatus == NeoRoundStatus::Overtime && m_iGhosterPlayer) - { - m_flGhostLastHeld = gpGlobals->curtime; + const int iTeam = pNeoPlayer->GetTeamNumber(); + if (!bClantagSet[iTeam]) + { + bClantagSet[iTeam] = true; + V_strcpy_safe(szTeamClantags[iTeam], pNeoPlayer->GetNeoClantag()), + ++iHasClantags[iTeam]; + } + else + { + iHasClantags[iTeam] += (V_strcmp(szTeamClantags[iTeam], pNeoPlayer->GetNeoClantag()) == 0); + } } } - if (g_fGameOver) // someone else quit the game already + char *pszClantagMod[TEAM__TOTAL] = {}; + pszClantagMod[TEAM_JINRAI] = m_szNeoJinraiClantag.GetForModify(); + pszClantagMod[TEAM_NSF] = m_szNeoNSFClantag.GetForModify(); + for (const int i : {TEAM_JINRAI, TEAM_NSF}) { - // check to see if we should change levels now - if (m_flIntermissionEndTime < gpGlobals->curtime) - { - if (!m_bChangelevelDone) - { - m_bChangelevelDone = true; - ChangeLevel(); // intermission is over - } - } + V_strncpy(pszClantagMod[i], (iHasClantags[i] == GetGlobalTeam(i)->GetNumPlayers()) ? + szTeamClantags[i] : "", NEO_MAX_CLANTAG_LENGTH); + } +} - return; +bool CNEORules::GameOverThink() +{ + if (!g_fGameOver) + return false; + + // check to see if we should change levels now + if (m_flIntermissionEndTime < gpGlobals->curtime && !m_bChangelevelDone) + { + m_bChangelevelDone = true; + ChangeLevel(); // intermission is over } -#endif - BaseClass::Think(); + return true; +} -#ifdef GAME_DLL +void CNEORules::TeamDamageThink() +{ if (MirrorDamageMultiplier() > 0.0f && gpGlobals->curtime > (m_flPrevThinkMirrorDmg + 0.25f)) { @@ -1313,332 +1008,78 @@ void CNEORules::Think(void) 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 - if (m_flNeoNextRoundStartTime == 0) - { - m_flNeoNextRoundStartTime = gpGlobals->curtime + mp_chattime.GetFloat(); - DevMsg("Round is over\n"); - - m_pGhost = nullptr; - m_iGhosterTeam = TEAM_UNASSIGNED; - m_iGhosterPlayer = 0; - m_pJuggernautItem = nullptr; - m_bJuggernautItemExists = false; - m_pJuggernautPlayer = nullptr; - m_iJuggernautPlayerIndex = 0; - } - // Else if it's time to start the next round - else if (gpGlobals->curtime >= m_flNeoNextRoundStartTime) - { - if (m_bGotMatchWinner) - { - IGameEvent *event = gameeventmanager->CreateEvent("game_end"); - if (event) - { - event->SetInt("winner", m_iMatchWinner); - gameeventmanager->FireEvent(event); - } - - if (sv_neo_readyup_lobby.GetBool() && !sv_neo_readyup_autointermission.GetBool()) - { - ResetMapSessionCommon(); - } - else - { - GoToIntermission(); - } - } - else - { - StartNextRound(); - } - } - - return; - } - // Note that exactly zero here means infinite round time. - else if (GetRoundRemainingTime() < 0) - { - if (GetGameType() == NEO_GAME_TYPE_TDM) - { - if (GetGlobalTeam(TEAM_JINRAI)->GetScore() > GetGlobalTeam(TEAM_NSF)->GetScore()) - { - SetWinningTeam(TEAM_JINRAI, NEO_VICTORY_POINTS, false, true, false, false); - return; - } - - if (GetGlobalTeam(TEAM_NSF)->GetScore() > GetGlobalTeam(TEAM_JINRAI)->GetScore()) - { - SetWinningTeam(TEAM_NSF, NEO_VICTORY_POINTS, false, true, false, false); - return; - } - } - else if (GetGameType() == NEO_GAME_TYPE_DM) - { - // Winning player - CNEO_Player *pWinners[MAX_PLAYERS + 1] = {}; - int iWinnersTotal = 0; - int iWinnerXP = 0; - GetDMHighestScorers(&pWinners, &iWinnersTotal, &iWinnerXP); - if (iWinnersTotal == 1) - { - SetWinningDMPlayer(pWinners[0]); - return; - } - // Otherwise go into overtime - } - else if (GetGameType() == NEO_GAME_TYPE_JGR) - { - if ((!m_pJuggernautPlayer && m_pJuggernautItem && !m_pJuggernautItem->IsBeingActivatedByLosingTeam()) || - (!m_pJuggernautPlayer && !m_pJuggernautItem)) // Juggernaut is absent entirely - { - if (GetGlobalTeam(TEAM_JINRAI)->GetScore() > GetGlobalTeam(TEAM_NSF)->GetScore()) - { - SetWinningTeam(TEAM_JINRAI, NEO_VICTORY_POINTS, false, true, false, false); - return; - } - - if (GetGlobalTeam(TEAM_NSF)->GetScore() > GetGlobalTeam(TEAM_JINRAI)->GetScore()) - { - SetWinningTeam(TEAM_NSF, NEO_VICTORY_POINTS, false, true, false, false); - return; - } - } - else - { - if (m_nRoundStatus == NeoRoundStatus::RoundLive) - { - m_nRoundStatus = NeoRoundStatus::Overtime; - } - - if (m_pJuggernautPlayer) - { - const int jgrTeam = m_pJuggernautPlayer->GetTeamNumber(); - const int oppositeTeam = (m_pJuggernautPlayer->GetTeamNumber() == TEAM_JINRAI ? TEAM_NSF : TEAM_JINRAI); - if (GetGlobalTeam(jgrTeam)->GetScore() > GetGlobalTeam(oppositeTeam)->GetScore()) - { - SetWinningTeam(jgrTeam, NEO_VICTORY_POINTS, false, true, false, false); - return; - } - } - - return; - } - } - - if (IsTeamplay()) - { - SetWinningTeam(TEAM_SPECTATOR, NEO_VICTORY_STALEMATE, false, false, true, false); - } - } - - if (m_pGhost) - { - // Update ghosting team info - int nextGhosterTeam = TEAM_UNASSIGNED; - int nextGhosterPlayerIdx = 0; - CNEO_Player *pGhosterPlayer = static_cast(m_pGhost->GetOwner()); - if (pGhosterPlayer) - { - nextGhosterTeam = pGhosterPlayer->GetTeamNumber(); - nextGhosterPlayerIdx = pGhosterPlayer->entindex(); - Assert(nextGhosterTeam == TEAM_JINRAI || nextGhosterTeam == TEAM_NSF); - m_pGhost->UpdateNearestGhostBeaconDist(); - } - m_iGhosterTeam = nextGhosterTeam; - m_iGhosterPlayer = nextGhosterPlayerIdx; - - Assert(UTIL_IsValidEntity(m_pGhost)); - - if (m_pGhost->GetAbsOrigin().IsValid()) - { - // Someone's carrying it - m_vecGhostMarkerPos = (nextGhosterTeam == TEAM_JINRAI || nextGhosterTeam == TEAM_NSF) ? - // NEO NOTE (Adam) GetGhostMarkerPos() can return m_vecGhostMarkerPos if m_pGhost is invalid, but we've checked for m_pGhost above so should be fine? - GetGhostMarkerPos() : m_pGhost->GetAbsOrigin(); - } - else - { - Assert(false); - } - - // Check if the ghost was capped during this Think - int captorTeam, captorClient; - for (int i = 0; i < m_pGhostCaps.Count(); i++) - { - auto pGhostCap = dynamic_cast(UTIL_EntityByIndex(m_pGhostCaps[i])); - if (!pGhostCap) - { - Assert(false); - continue; - } - - // If a ghost was captured - if (pGhostCap->IsGhostCaptured(captorTeam, captorClient)) - { - // Turn off all capzones - for (int i = 0; i < m_pGhostCaps.Count(); i++) - { - auto pGhostCap = dynamic_cast(UTIL_EntityByIndex(m_pGhostCaps[i])); - if (!pGhostCap) - { - Assert(false); - continue; - } - pGhostCap->SetActive(false); - } - - IGameEvent* event = gameeventmanager->CreateEvent("ghost_capture"); - if (event) - { - CBasePlayer* pCaptorClient = UTIL_PlayerByIndex(captorClient); - event->SetInt("userid", pCaptorClient ? pCaptorClient->GetUserID() : INVALID_USER_ID); - gameeventmanager->FireEvent(event); - } - - // And then announce team victory - SetWinningTeam(captorTeam, NEO_VICTORY_GHOST_CAPTURE, false, true, false, false); - - break; - } - } - } - else if (m_pJuggernautItem) - { - if (IsRoundLive() && (gpGlobals->curtime > (m_flNeoRoundStartTime + sv_neo_preround_freeze_time.GetFloat()) + 20.0f) && IsJuggernautLocked()) - { - UTIL_CenterPrintAll("- JUGGERNAUT ENABLED -\n"); - - EmitSound_t soundParams; - soundParams.m_pSoundName = "HUD.GhostPickUp"; - soundParams.m_nChannel = CHAN_USER_BASE; - soundParams.m_bWarnOnDirectWaveReference = false; - soundParams.m_bEmitCloseCaption = false; - soundParams.m_SoundLevel = ATTN_TO_SNDLVL(ATTN_NONE); - - CRecipientFilter soundFilter; - soundFilter.AddAllPlayers(); - soundFilter.MakeReliable(); - m_pJuggernautItem->EmitSound(soundFilter, m_pJuggernautItem->entindex(), soundParams); - - m_pJuggernautItem->m_bLocked = false; + { + userIDsToKick[userIDsToKickSize++] = player->GetUserID(); + player->m_bIsPendingTKKick = true; + } } - } - if (GetGameType() == NEO_GAME_TYPE_JGR && IsRoundLive()) - { - if (GetGlobalTeam(TEAM_JINRAI)->GetScore() >= sv_neo_jgr_max_points.GetInt()) + for (int i = 0; i < userIDsToKickSize; ++i) { - SetWinningTeam(TEAM_JINRAI, NEO_VICTORY_POINTS, false, true, false, false); - return; + engine->ServerCommand(UTIL_VarArgs("kickid %d \"%s\"\n", userIDsToKick[i], + "Too much friendly-fire damage inflicted.")); } - if (GetGlobalTeam(TEAM_NSF)->GetScore() >= sv_neo_jgr_max_points.GetInt()) - { - SetWinningTeam(TEAM_NSF, NEO_VICTORY_POINTS, false, true, false, false); - return; - } + m_flPrevThinkKick = gpGlobals->curtime; } +} - if (GetGameType() == NEO_GAME_TYPE_VIP && IsRoundLive() && !m_pGhost) +bool CNEORules::RoundOverThink() +{ + if (IsRoundOver()) { - if (!m_pVIP) - { - if (sv_neo_vip_ctg_on_death.GetBool()) - { - UTIL_CenterPrintAll("- HVT DOWN - RECOVER THE GHOST -\n"); - SpawnTheGhost(); - } - else - { - // Assume vip player disconnected, forfeit round - SetWinningTeam(GetOpposingTeam(m_iEscortingTeam), NEO_VICTORY_FORFEIT, false, true, false, false); - } - IGameEvent* event = gameeventmanager->CreateEvent("vip_death"); - if (event) - { - gameeventmanager->FireEvent(event); - } - } - else if (!m_pVIP->IsAlive()) + // If the next round was not scheduled yet + if (m_flNeoNextRoundStartTime == 0) { - if (sv_neo_vip_ctg_on_death.GetBool()) - { - UTIL_CenterPrintAll("- HVT DOWN - RECOVER THE GHOST -\n"); - SpawnTheGhost(&m_pVIP->GetAbsOrigin()); - } - else - { - // VIP was killed, end round - SetWinningTeam(GetOpposingTeam(m_iEscortingTeam), NEO_VICTORY_VIP_ELIMINATION, false, true, false, false); - } - IGameEvent* event = gameeventmanager->CreateEvent("vip_death"); - if (event) - { - event->SetInt("userid", m_pVIP->GetUserID()); - gameeventmanager->FireEvent(event); - } - } + m_flNeoNextRoundStartTime = gpGlobals->curtime + mp_chattime.GetFloat(); + DevMsg("Round is over\n"); - // Check if the vip was escorted during this Think - int captorTeam, captorClient; - for (int i = 0; i < m_pGhostCaps.Count(); i++) + m_pGhost = nullptr; + m_iGhosterTeam = TEAM_UNASSIGNED; + m_iGhosterPlayer = 0; + m_pJuggernautItem = nullptr; + m_bJuggernautItemExists = false; + m_pJuggernautPlayer = nullptr; + m_iJuggernautPlayerIndex = 0; + } + // Else if it's time to start the next round + else if (gpGlobals->curtime >= m_flNeoNextRoundStartTime) { - auto pGhostCap = dynamic_cast(UTIL_EntityByIndex(m_pGhostCaps[i])); - if (!pGhostCap) - { - Assert(false); - continue; - } - - // If vip was escorted - if (pGhostCap->IsGhostCaptured(captorTeam, captorClient)) + if (m_bGotMatchWinner) { - // Turn off all capzones - for (int i = 0; i < m_pGhostCaps.Count(); i++) + if (IGameEvent *event = gameeventmanager->CreateEvent("game_end")) { - auto pGhostCap = dynamic_cast(UTIL_EntityByIndex(m_pGhostCaps[i])); - if (!pGhostCap) - { - Assert(false); - continue; - } - pGhostCap->SetActive(false); + event->SetInt("winner", m_iMatchWinner); + gameeventmanager->FireEvent(event); } - // And then announce team victory - SetWinningTeam(captorTeam, NEO_VICTORY_VIP_ESCORT, false, true, false, false); - - IGameEvent* event = gameeventmanager->CreateEvent("vip_extract"); - if (event) + if (sv_neo_readyup_lobby.GetBool() && !sv_neo_readyup_autointermission.GetBool()) { - CBasePlayer* pCaptorClient = UTIL_PlayerByIndex(captorClient); - event->SetInt("userid", pCaptorClient ? pCaptorClient->GetUserID() : INVALID_USER_ID); - gameeventmanager->FireEvent(event); + ResetMapSessionCommon(); } - - break; + else + { + GoToIntermission(); + } + } + else + { + StartNextRound(); } } + + return true; + } + // Note that exactly zero here means infinite round time. + else if (GetRoundRemainingTime() < 0) + { + RoundTimeout(); } + return false; +} +void CNEORules::RoundStatusThink() +{ if (m_nRoundStatus == NeoRoundStatus::PreRoundFreeze) { if (IsRoundOver()) @@ -1653,119 +1094,99 @@ 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) - { - auto jinraiAlive = GetGlobalTeam(TEAM_JINRAI)->GetAliveMembers(); - auto nsfAlive = GetGlobalTeam(TEAM_NSF)->GetAliveMembers(); - - if (jinraiAlive == 0 && nsfAlive == 0) { - SetWinningTeam(TEAM_SPECTATOR, NEO_VICTORY_STALEMATE, false, false, true, false); - } - else if(jinraiAlive == 0 || nsfAlive == 0) { - auto winningTeam = jinraiAlive > nsfAlive ? TEAM_JINRAI : TEAM_NSF; - SetWinningTeam(winningTeam, NEO_VICTORY_TEAM_ELIMINATION, false, true, false, false); - } - } - if (GetGameType() == NEO_GAME_TYPE_DM && sv_neo_dm_win_xp.GetInt() > 0) - { - // End game early if there's already a player past the winning XP - CNEO_Player *pHighestPlayers[MAX_PLAYERS + 1] = {}; - int iWinningTotal = 0; - int iWinningXP = 0; - GetDMHighestScorers(&pHighestPlayers, &iWinningTotal, &iWinningXP); - if (iWinningXP >= sv_neo_dm_win_xp.GetInt() && iWinningTotal == 1) - { - SetWinningDMPlayer(pHighestPlayers[0]); - } - } - } -#endif } -#ifdef GAME_DLL -void CNEORules::SetWinningDMPlayer(CNEO_Player *pWinner) +void CNEORules::CheckWinByElimination() { - if (IsRoundOver()) - { + if (!IsRoundLive()) return; - } - - if (auto pEntGameCfg = GetActiveGameConfig()) - { - pEntGameCfg->m_OnDMRoundEnd.FireOutput(pWinner, pEntGameCfg); - } - SetRoundStatus(NeoRoundStatus::PostRound); - char victoryMsg[128]; - // TODO: Per client since client has neo_name settings - V_sprintf_safe(victoryMsg, "%s is the winner of the deathmatch!\n", pWinner->GetNeoPlayerName()); - - CRecipientFilter filter; - filter.AddAllPlayers(); - UserMessageBegin(filter, "RoundResult"); - WRITE_STRING("tie"); - WRITE_FLOAT(gpGlobals->curtime); - WRITE_STRING(victoryMsg); - MessageEnd(); + const int jinraiAlive = GetGlobalTeam(TEAM_JINRAI)->GetAliveMembers(); + const int nsfAlive = GetGlobalTeam(TEAM_NSF)->GetAliveMembers(); - EmitSound_t soundParams; - soundParams.m_nChannel = CHAN_AUTO; - soundParams.m_SoundLevel = SNDLVL_NONE; - soundParams.m_flVolume = 0.33f; - // Differing between Jinrai/NSF only as a sound cosmetic (no affect on DM) - const int team = pWinner->GetTeamNumber(); - soundParams.m_pSoundName = (team == TEAM_JINRAI) ? "gameplay/jinrai.mp3" : (team == TEAM_NSF) ? "gameplay/nsf.mp3" : "gameplay/draw.mp3"; - soundParams.m_bWarnOnDirectWaveReference = false; - soundParams.m_bEmitCloseCaption = false; + if (jinraiAlive == 0 && nsfAlive == 0) { + SetWinningTeam(TEAM_SPECTATOR, NEO_VICTORY_STALEMATE, false, false, true, false); + } + else if(jinraiAlive == 0 || nsfAlive == 0) { + auto winningTeam = jinraiAlive > nsfAlive ? TEAM_JINRAI : TEAM_NSF; + SetWinningTeam(winningTeam, NEO_VICTORY_TEAM_ELIMINATION, false, true, false, false); + } +} +#endif // GAME_DLL - for (int i = 1; i <= gpGlobals->maxClients; ++i) +COMPILE_TIME_ASSERT(TEAM_JINRAI == 2 && TEAM_NSF == 3); +bool CNEORules::CHL2MPRulesThink() +{ +#ifdef GAME_DLL + if ( g_fGameOver ) // someone else quit the game already { - CBasePlayer* basePlayer = UTIL_PlayerByIndex(i); - auto player = static_cast(basePlayer); - if (player) + // check to see if we should change levels now + if ( m_flIntermissionEndTime < gpGlobals->curtime ) { - if (!player->IsBot() || player->IsHLTV()) + if ( !m_bChangelevelDone ) { - const char* volStr = engine->GetClientConVarValue(i, "snd_victory_volume"); - const float jingleVolume = volStr ? atof(volStr) : 0.33f; - soundParams.m_flVolume = jingleVolume; - - CRecipientFilter soundFilter; - soundFilter.AddRecipient(basePlayer); - soundFilter.MakeReliable(); - player->EmitSound(soundFilter, i, soundParams); + ChangeLevel(); // intermission is over + m_bChangelevelDone = true; } } - } - GoToIntermission(); + return true; + } - IGameEvent *event = gameeventmanager->CreateEvent("game_end"); - if (event) + if ( GetMapRemainingTime() < 0 ) { - event->SetInt("winner", pWinner->GetUserID()); - gameeventmanager->FireEvent(event); + GoToIntermission(); + return true; + } + + if ( gpGlobals->curtime > m_tmNextPeriodicThink ) + { + CheckAllPlayersReady(); + CheckRestartGame(); + m_tmNextPeriodicThink = gpGlobals->curtime + 1.0; } + + ManageObjectRelocation(); +#endif // GAME_DLL + + return false; } -#endif -void CNEORules::AwardRankUp(int client) +void CNEORules::Think(void) { - auto player = UTIL_PlayerByIndex(client); - if (player) - { - AwardRankUp(static_cast(player)); - } +#ifdef GAME_DLL + CGameRules::Think(); + + UpdateFromGameConfig(); + + if (RoundStartFromIdleOrPausedThink()) + return; + + PlayerRespawnThink(); + + CheckClantagsThink(); + + if (IsRoundPaused()) + return; + + GameOverThink(); + + CheckOvertime(); + + if (CHL2MPRulesThink()) + return; + + TeamDamageThink(); + + if (RoundOverThink()) + return; + + RoundStatusThink(); +#endif } -#ifdef CLIENT_DLL -void CNEORules::AwardRankUp(C_NEO_Player *pClient) -#else +#ifdef GAME_DLL void CNEORules::AwardRankUp(CNEO_Player *pClient) -#endif { Assert(m_nRoundStatus != NeoRoundStatus::Pause); if (m_nRoundStatus == NeoRoundStatus::Pause) @@ -1791,79 +1212,64 @@ void CNEORules::AwardRankUp(CNEO_Player *pClient) // If we're beyond max rank, just award +1 point. pClient->AddPoints(1, false, true); } +#endif -// Return remaining time in seconds. Zero means there is no time limit. float CNEORules::GetRoundRemainingTime() const { - if (m_nRoundStatus == NeoRoundStatus::Idle) + Assert(false); // Implement in the derived gamemode class + constexpr float DEFAULT_ROUND_TIMELIMIT = 3.25f; + return GetRoundRemainingTime(DEFAULT_ROUND_TIMELIMIT); +} + +// Return remaining time in seconds. Zero means there is no time limit. +float CNEORules::GetRoundRemainingTime(float flGameTypeRoundTimeLimit) const +{ + if (NeoRoundStatus::Idle == m_nRoundStatus) { - return 0; + return 0.f; } - if (m_nRoundStatus == NeoRoundStatus::PostRound) + if (NeoRoundStatus::PostRound == m_nRoundStatus) { return m_flNeoNextRoundStartTime - gpGlobals->curtime; } float roundTimeLimit = 0.f; - if (m_nRoundStatus == NeoRoundStatus::Warmup) + if (NeoRoundStatus::Warmup == m_nRoundStatus) { roundTimeLimit = sv_neo_warmup_round_time.GetFloat(); } - else if (m_nRoundStatus == NeoRoundStatus::Countdown) + else if (NeoRoundStatus::Countdown == m_nRoundStatus) { roundTimeLimit = sv_neo_readyup_countdown.GetFloat(); } else { - switch (m_nGameTypeSelected) { - case NEO_GAME_TYPE_TDM: - roundTimeLimit = neo_tdm_round_timelimit.GetFloat() * 60.f; - break; - case NEO_GAME_TYPE_CTG: - roundTimeLimit = neo_ctg_round_timelimit.GetFloat() * 60.f; - if (m_nRoundStatus == NeoRoundStatus::Overtime) - { - return GetCTGOverTime(); - } - break; - case NEO_GAME_TYPE_VIP: - roundTimeLimit = neo_vip_round_timelimit.GetFloat() * 60.f; - break; - case NEO_GAME_TYPE_DM: - roundTimeLimit = neo_dm_round_timelimit.GetFloat() * 60.f; - break; - case NEO_GAME_TYPE_JGR: - roundTimeLimit = neo_jgr_round_timelimit.GetFloat() * 60.f; - break; - default: - break; - } + roundTimeLimit = flGameTypeRoundTimeLimit * 60.f; } return (m_flNeoRoundStartTime + roundTimeLimit) - gpGlobals->curtime; } -float CNEORules::GetCTGOverTime() const +float CNEORules::GetOverTime(float flRoundTimeLimit, float flOvertimeBaseAmount, float flOvertimeGrace, float flGraceDecay) const { - float roundTimeLimit = neo_ctg_round_timelimit.GetFloat() * 60.f; - float overtime = (m_flNeoRoundStartTime + roundTimeLimit + sv_neo_ctg_ghost_overtime.GetFloat()) - gpGlobals->curtime; + float overtime = (m_flNeoRoundStartTime + flRoundTimeLimit + flOvertimeBaseAmount) - gpGlobals->curtime; - if (sv_neo_ctg_ghost_overtime_grace_decay.GetBool()) + if (flGraceDecay) { - if (m_iGhosterPlayer) + if (m_iGhosterPlayer) // NEO TODO (Adam) gamemode specific check? { return overtime; } else { - float overtimeAtGhostDrop = (m_flNeoRoundStartTime + roundTimeLimit + sv_neo_ctg_ghost_overtime.GetFloat()) - m_flGhostLastHeld; - return (overtimeAtGhostDrop * sv_neo_ctg_ghost_overtime_grace.GetFloat() / (sv_neo_ctg_ghost_overtime.GetFloat() + sv_neo_ctg_ghost_overtime_grace.GetFloat())) - (gpGlobals->curtime - m_flGhostLastHeld); + float overtimeAtGhostDrop = (m_flNeoRoundStartTime + flRoundTimeLimit + flOvertimeBaseAmount) - m_flGhostLastHeld; + return (overtimeAtGhostDrop * flOvertimeGrace / (flOvertimeBaseAmount + flOvertimeGrace)) - (gpGlobals->curtime - m_flGhostLastHeld); } } else { - float grace = sv_neo_ctg_ghost_overtime_grace.GetFloat() - (gpGlobals->curtime - m_flGhostLastHeld); + float grace = flOvertimeGrace - (gpGlobals->curtime - m_flGhostLastHeld); if (m_iGhosterPlayer || overtime < grace) { return overtime; @@ -1938,8 +1344,8 @@ void CNEORules::FireGameEvent(IGameEvent* event) // Purpose: Spawns one ghost at a randomly chosen Neo ghost spawn point. void CNEORules::SpawnTheGhost(const Vector *origin) { - // No ghost spawns and this map isn't named "_ctg". Probably not a CTG map. - if (m_ghostSpawns.IsEmpty() && (V_stristr(GameRules()->MapName(), "_ctg") == 0)) + // No ghost spawns + if (m_ghostSpawns.IsEmpty()) { m_pGhost = nullptr; return; @@ -2262,40 +1668,6 @@ void CNEORules::JuggernautTotalRemoval(CNEO_Juggernaut *pJuggernaut) } } -void CNEORules::GatherGameTypeVotes() -{ - int gameTypes[NEO_GAME_TYPE__TOTAL] = {}; - - for (int i = 1; i <= gpGlobals->maxClients; i++) - { - if (CBasePlayer* pPlayer = static_cast(UTIL_PlayerByIndex(i))) - { - if (pPlayer->IsBot()) - continue; - const char *clientGameTypeVote = engine->GetClientConVarValue(i, "neo_vote_game_mode"); - if (!clientGameTypeVote) - continue; - if (!clientGameTypeVote[0]) - continue; - int gameType = atoi(clientGameTypeVote); - gameTypes[gameType]++; - } - } - - int mostVotes = gameTypes[0]; - int mostPopularGameType = 0; - for (int i = 1; i < NEO_GAME_TYPE__TOTAL; i++) - { - if (gameTypes[i] > mostVotes) // NEOTODO (Adam) Handle draws - { - mostVotes = gameTypes[i]; - mostPopularGameType = i; - } - } - - m_nGameTypeSelected = mostPopularGameType; -} - bool CNEORules::ReadyUpPlayerIsReady(CNEO_Player *pNeoPlayer) const { if (!pNeoPlayer) return false; @@ -2337,7 +1709,7 @@ void CNEORules::CheckChatCommand(CNEO_Player *pNeoCmdPlayer, const char *pSzChat return; } - const bool bNonCmdGameType = !NEO_GAME_TYPE_SETTINGS[GetGameType()].comp; + const bool bNonCmdGameType = !GetCompEnabled(); if (sv_neo_readyup_lobby.GetBool() && (bNonCmdGameType || m_nRoundStatus != NeoRoundStatus::Idle)) { @@ -2623,56 +1995,6 @@ CNEORules::ReadyPlayers CNEORules::FetchReadyPlayers() const return readyPlayers; } -const int CNEORules::GetScoreLimit() const -{ - switch (m_nGameTypeSelected) - { - case NEO_GAME_TYPE_TDM: - return sv_neo_tdm_score_limit.GetInt(); - break; - case NEO_GAME_TYPE_CTG: - return sv_neo_ctg_score_limit.GetInt(); - break; - case NEO_GAME_TYPE_VIP: - return sv_neo_vip_score_limit.GetInt(); - break; - case NEO_GAME_TYPE_DM: - return sv_neo_dm_score_limit.GetInt(); - break; - case NEO_GAME_TYPE_JGR: - return sv_neo_jgr_score_limit.GetInt(); - break; - default: - return sv_neo_ctg_score_limit.GetInt(); - break; - } -} - -const int CNEORules::GetRoundLimit() const -{ - switch (m_nGameTypeSelected) - { - case NEO_GAME_TYPE_TDM: - return sv_neo_tdm_round_limit.GetInt(); - break; - case NEO_GAME_TYPE_CTG: - return sv_neo_ctg_round_limit.GetInt(); - break; - case NEO_GAME_TYPE_VIP: - return sv_neo_vip_round_limit.GetInt(); - break; - case NEO_GAME_TYPE_DM: - return sv_neo_dm_round_limit.GetInt(); - break; - case NEO_GAME_TYPE_JGR: - return sv_neo_jgr_round_limit.GetInt(); - break; - default: - return sv_neo_ctg_round_limit.GetInt(); - break; - } -} - void CNEORules::StartNextRound() { // Only check ready-up on idle state @@ -2692,7 +2014,8 @@ void CNEORules::StartNextRound() { if (sv_neo_readyup_lobby.GetBool()) { - bool bPrintHelpInfo = (m_iPrintHelpCounter == 0); + static int iPrintHelpCounter = 0; + bool bPrintHelpInfo = (0 == iPrintHelpCounter); if (!m_bIgnoreOverThreshold && (readyPlayers.array[TEAM_JINRAI] > iThres || readyPlayers.array[TEAM_NSF] > iThres)) { char szPrint[128]; @@ -2721,7 +2044,7 @@ void CNEORules::StartNextRound() UTIL_ClientPrintAll(HUD_PRINTTALK, szPrint); } static constexpr int HELP_COUNT_NEXT_PRINT = 3; - m_iPrintHelpCounter = LoopAroundInArray(m_iPrintHelpCounter + 1, HELP_COUNT_NEXT_PRINT); + iPrintHelpCounter = LoopAroundInArray(iPrintHelpCounter + 1, HELP_COUNT_NEXT_PRINT); } else { @@ -2819,11 +2142,6 @@ void CNEORules::StartNextRound() const bool bFromStarting = (m_nRoundStatus == NeoRoundStatus::Warmup || m_nRoundStatus == NeoRoundStatus::Countdown); - if (sv_neo_gamemode_enforcement.GetInt() == GAMEMODE_ENFORCEMENT_VOTE && bFromStarting) - { - GatherGameTypeVotes(); - } - // NEO TODO (nullsystem): There should be a more sophisticated logic to be able to restore XP // for when moving from idle to preroundfreeze, or in the future, competitive with whatever // extra stuff in there. But to keep it simple: just clear if it was a warmup. @@ -2905,8 +2223,8 @@ void CNEORules::StartNextRound() FireLegacyEvent_NeoRoundEnd(); - char RoundMsg[27]; - static_assert(sizeof(RoundMsg) == sizeof("- CTG ROUND 99 STARTED -\n\0"), "RoundMsg requires to fit round numbers up to 2 digits"); + char RoundMsg[26]; + static_assert(sizeof(RoundMsg) == sizeof("- KOTH ROUND 99 STARTED -"), "RoundMsg requires to fit round numbers up to 2 digits"); V_sprintf_safe(RoundMsg, "- %s ROUND %d STARTED -\n", GetGameTypeName(), Min(99, m_iRoundNumber.Get())); UTIL_CenterPrintAll(RoundMsg); @@ -2935,33 +2253,76 @@ bool CNEORules::IsRoundPreRoundFreeze() const bool CNEORules::IsRoundOver() const { -#ifdef GAME_DLL - // We don't want to start preparing for a new round - // if the game has ended for the current map. - if (g_fGameOver) +#ifdef GAME_DLL + // We don't want to start preparing for a new round + // if the game has ended for the current map. + if (g_fGameOver) + { + return false; + } +#endif + + // Next round start has been scheduled, so current round must be over. + if (m_flNeoNextRoundStartTime != 0) + { + Assert((m_flNeoNextRoundStartTime < 0) == false); + return true; + } + + return false; +} + +bool CNEORules::IsRoundLive() const +{ + switch (m_nRoundStatus) + { + case NeoRoundStatus::RoundLive: + case NeoRoundStatus::Overtime: + return true; + } + return false; +} + +bool CNEORules::IsRoundActive() const +{ + switch (m_nRoundStatus) { - return false; + case NeoRoundStatus::RoundLive: + case NeoRoundStatus::Overtime: + case NeoRoundStatus::PostRound: + return true; } -#endif + return false; +} - // Next round start has been scheduled, so current round must be over. - if (m_flNeoNextRoundStartTime != 0) +bool CNEORules::IsRoundOn() const +{ + switch (m_nRoundStatus) { - Assert((m_flNeoNextRoundStartTime < 0) == false); + case NeoRoundStatus::PreRoundFreeze: + case NeoRoundStatus::RoundLive: + case NeoRoundStatus::Overtime: + case NeoRoundStatus::PostRound: return true; } - return false; } -bool CNEORules::IsRoundLive() const +bool CNEORules::IsRoundIdle() const { - return (m_nRoundStatus == NeoRoundStatus::RoundLive || m_nRoundStatus == NeoRoundStatus::Overtime); + switch (m_nRoundStatus) + { + case NeoRoundStatus::Idle: + case NeoRoundStatus::Warmup: + case NeoRoundStatus::Countdown: + return true; + } + return false; } -bool CNEORules::IsRoundOn() const +bool CNEORules::IsRoundPaused() const { - return (m_nRoundStatus == NeoRoundStatus::PreRoundFreeze) || IsRoundLive() || (m_nRoundStatus == NeoRoundStatus::PostRound); + return NeoRoundStatus::Pause == m_nRoundStatus; } void CNEORules::CreateStandardEntities(void) @@ -2980,20 +2341,6 @@ void CNEORules::CreateStandardEntities(void) #endif } -const SZWSZTexts NEO_GAME_TYPE_DESC_STRS[NEO_GAME_TYPE__TOTAL] = { - SZWSZ_INIT("Team Deathmatch"), - SZWSZ_INIT("Capture the Ghost"), - SZWSZ_INIT("Extract or Kill the VIP"), - SZWSZ_INIT("Deathmatch"), - SZWSZ_INIT("Free Roam"), - SZWSZ_INIT("Training"), -}; - -const char *CNEORules::GetGameDescription(void) -{ - return NEO_GAME_TYPE_DESC_STRS[GetGameType()].szStr; -} - const CViewVectors *CNEORules::GetViewVectors() const { return &g_NEOViewVectors; @@ -3053,7 +2400,7 @@ void CNEORules::CleanUpMap() CNEOBaseCombatWeapon *pWeapon = dynamic_cast(pCur); if (pWeapon) { - UTIL_Remove(pCur); + UTIL_Remove(pCur); // NEO TODO (Adam) are there any weapons in s_NeoPreserveEnts that wouldn't be removed if we didnt check for cneobasecombatweapon here? } // remove entities that has to be restored on roundrestart (breakables etc) else if (!FindInList(s_NeoPreserveEnts, pCur->GetClassname())) @@ -3141,23 +2488,13 @@ void CNEORules::CleanUpMap() ResetGhostCapPoints(); // OnCompetitive needs to fire every time the map resets, along with all the entities, props, etc. - auto pEntGameCfg = GetActiveGameConfig(); - if (pEntGameCfg && sv_neo_comp.GetBool()) + if (auto pEntGameCfg = NEOGameConfig(); + pEntGameCfg && sv_neo_comp.GetBool()) { pEntGameCfg->m_OnCompetitive.FireOutput(nullptr, pEntGameCfg); } } -void CNEORules::CheckRestartGame() -{ - BaseClass::CheckRestartGame(); -} - -void CNEORules::PurgeGhostCapPoints() -{ - m_pGhostCaps.Purge(); -} - void CNEORules::ResetGhostCapPoints() { m_pGhostCaps.Purge(); @@ -3202,63 +2539,7 @@ void CNEORules::ResetGhostCapPoints() } } -void CNEORules::SetGameRelatedVars() -{ - ResetTDM(); - - ResetGhost(); - if (GetGameType() == NEO_GAME_TYPE_CTG) - { - SpawnTheGhost(); - } - - ResetVIP(); - if (GetGameType() == NEO_GAME_TYPE_VIP) - { - if (!m_iEscortingTeam) - { - m_iEscortingTeam.Set(RandomInt(TEAM_JINRAI, TEAM_NSF)); - } - else - { - m_iEscortingTeam.Set(m_iEscortingTeam.Get() == TEAM_JINRAI ? TEAM_NSF : TEAM_JINRAI); - } - - SelectTheVIP(); - } - else - { - m_iEscortingTeam.Set(0); - } - - if (GetGameType() == NEO_GAME_TYPE_TDM) - { - for (int i = 0; i < GetNumberOfTeams(); i++) - { - GetGlobalTeam(i)->SetScore(0); - } - } - - if (GetGameType() == NEO_GAME_TYPE_DM) - { - for (int i = 1; i <= gpGlobals->maxClients; ++i) - { - auto pPlayer = static_cast(UTIL_PlayerByIndex(i)); - if (pPlayer) - { - pPlayer->m_iXP.GetForModify() = 0; - } - } - } - - if (GetGameType() == NEO_GAME_TYPE_JGR) - { - ResetJGR(); - SpawnTheJuggernaut(); - } -} - -void CNEORules::ResetTDM() +void CNEORules::ResetTeamScores() { for (int i = 0; i < GetNumberOfTeams(); i++) { @@ -3308,11 +2589,6 @@ void CNEORules::ResetJGR() void CNEORules::RestartGame() { - // bounds check - if (mp_timelimit.GetInt() < 0) - { - mp_timelimit.SetValue(0); - } m_flGameStartTime = gpGlobals->curtime; if (!IsFinite(m_flGameStartTime.Get())) { @@ -3361,19 +2637,6 @@ void CNEORules::RestartGame() m_bCompleteReset = false; ResetMapSessionCommon(); - - // NEO FIXME (Rain): this GatherGameTypeVotes business seems a bit wonky, - // since it'll just gather the clients' "neo_vote_game_mode" cvar values - // without prompting for some kind of a vote. So the most likely result - // is the map just switching to the "neo_vote_game_mode" default value (CTG), - // which may not be appropriate for the map. - const bool bFromStarting = (m_nRoundStatus == NeoRoundStatus::Warmup - || m_nRoundStatus == NeoRoundStatus::Countdown); - if (sv_neo_gamemode_enforcement.GetInt() == GAMEMODE_ENFORCEMENT_VOTE && bFromStarting) - { - GatherGameTypeVotes(); - } - SetGameRelatedVars(); IGameEvent * event = gameeventmanager->CreateEvent("round_start"); @@ -3545,7 +2808,7 @@ void CNEORules::ClientSettingsChanged(CBasePlayer *pPlayer) V_strncpy(pNEOPlayer->m_szNeoClantag.GetForModify(), (FStrEq(pszNeoClantag, "#empty") ? "" : pszNeoClantag), NEO_MAX_CLANTAG_LENGTH); - m_bThinkCheckClantags = true; + bCheckClantags = true; } const char *pszClNeoCrosshair = engine->GetClientConVarValue(pNEOPlayer->entindex(), "cl_neo_crosshair"); @@ -3695,7 +2958,7 @@ void CNEORules::SetWinningTeam(int team, int iWinReason, bool bForceMapReset, bo return; } - if (auto pEntGameCfg = GetActiveGameConfig()) + if (auto pEntGameCfg = NEOGameConfig()) { pEntGameCfg->m_OnRoundEnd.Set(team, nullptr, pEntGameCfg); } @@ -3763,7 +3026,7 @@ void CNEORules::SetWinningTeam(int team, int iWinReason, bool bForceMapReset, bo if (teamJinrai->GetRoundsWon() == teamNSF->GetRoundsWon()) { // Sudden death - Don't end the match until we get a winning team - if (neo_round_sudden_death.GetBool()) + if (sv_neo_round_sudden_death.GetBool()) { V_sprintf_safe(victoryMsg, "Next round: Sudden death!\n"); isSuddenDeath = true; @@ -3801,6 +3064,9 @@ void CNEORules::SetWinningTeam(int team, int iWinReason, bool bForceMapReset, bo case NEO_VICTORY_TIMEOUT_WIN_BY_NUMBERS: V_sprintf_safe(victoryMsg, "Team %s wins by numbers!\n", (team == TEAM_JINRAI ? "Jinrai" : "NSF")); break; + case NEO_VICTORY_ATK_TIMEOUT: + V_sprintf_safe(victoryMsg, "Team %s wins by defending the ghost!\n", (team == TEAM_JINRAI ? "Jinrai" : "NSF")); + break; case NEO_VICTORY_POINTS: V_sprintf_safe(victoryMsg, "Team %s wins by highest score!\n", (team == TEAM_JINRAI ? "Jinrai" : "NSF")); break; @@ -3827,7 +3093,7 @@ void CNEORules::SetWinningTeam(int team, int iWinReason, bool bForceMapReset, bo WRITE_FLOAT(gpGlobals->curtime); // when did they win if(iWinReason != NEO_VICTORY_MAPIO) { - WRITE_STRING(victoryMsg); // extra message (who capped or last kill or who got the most points or whatever) + WRITE_STRING(victoryMsg); // extra message (who capped or last kill or who got the most points or whatever) } MessageEnd(); @@ -3893,7 +3159,7 @@ void CNEORules::SetWinningTeam(int team, int iWinReason, bool bForceMapReset, bo } } } - else if (GetGameType() == NEO_GAME_TYPE_CTG || GetGameType() == NEO_GAME_TYPE_VIP) + else if (GetGameType() == NEO_GAME_TYPE_CTG || GetGameType() == NEO_GAME_TYPE_VIP || GetGameType() == NEO_GAME_TYPE_ATK) { if (sv_neo_survivor_bonus.GetBool() && player->IsAlive()) { @@ -3963,7 +3229,7 @@ static CNEO_Player* FetchAssists(CNEO_Player* attacker, CNEO_Player* victim) #ifdef GAME_DLL void CNEORules::CheckIfCapPrevent(CNEO_Player *capPreventerPlayer) { - if (!NEO_GAME_TYPE_SETTINGS[GetGameType()].capPrevent) + if (!GetCapPreventEnabled()) { return; } @@ -4019,103 +3285,154 @@ void CNEORules::CheckIfCapPrevent(CNEO_Player *capPreventerPlayer) m_bTeamBeenAwardedDueToCapPrevent = (bOtherTeamPlayingGhost && iTallyAlive[iPreventerTeam] == 0 && iTallyAlive[iOppositeTeam] > 0); } + +bool CNEORules::GhostTeamUpdateCheckWinCondition() +{ + if (!m_pGhost) + return false; + + // Update ghosting team info + int nextGhosterTeam = TEAM_UNASSIGNED; + int nextGhosterPlayerIdx = 0; + CNEO_Player *pGhosterPlayer = static_cast(m_pGhost->GetOwner()); + if (pGhosterPlayer) + { + nextGhosterTeam = pGhosterPlayer->GetTeamNumber(); + nextGhosterPlayerIdx = pGhosterPlayer->entindex(); + Assert(nextGhosterTeam == TEAM_JINRAI || nextGhosterTeam == TEAM_NSF); + m_pGhost->UpdateNearestGhostBeaconDist(); + } + m_iGhosterTeam = nextGhosterTeam; + m_iGhosterPlayer = nextGhosterPlayerIdx; + + Assert(UTIL_IsValidEntity(m_pGhost)); + + if (m_pGhost->GetAbsOrigin().IsValid()) + { + // Someone's carrying it + m_vecGhostMarkerPos = (nextGhosterTeam == TEAM_JINRAI || nextGhosterTeam == TEAM_NSF) ? GetGhostMarkerPos() + : m_pGhost->GetAbsOrigin(); + } + else + { + Assert(false); + } + + // Check if the ghost was capped during this Think + int captorTeam, captorClient; + for (int i = 0; i < m_pGhostCaps.Count(); i++) + { + auto pGhostCap = dynamic_cast(UTIL_EntityByIndex(m_pGhostCaps[i])); + if (!pGhostCap) + { + Assert(false); + continue; + } + + // If a ghost was captured + if (pGhostCap->IsGhostCaptured(captorTeam, captorClient)) + { + // Turn off all capzones + for (int i = 0; i < m_pGhostCaps.Count(); i++) + { + auto pGhostCap = dynamic_cast(UTIL_EntityByIndex(m_pGhostCaps[i])); + if (!pGhostCap) + { + Assert(false); + continue; + } + pGhostCap->SetActive(false); + } + + IGameEvent* event = gameeventmanager->CreateEvent("ghost_capture"); + if (event) + { + CBasePlayer* pCaptorClient = UTIL_PlayerByIndex(captorClient); + event->SetInt("userid", pCaptorClient ? pCaptorClient->GetUserID() : INVALID_USER_ID); + gameeventmanager->FireEvent(event); + } + + // And then announce team victory + SetWinningTeam(captorTeam, NEO_VICTORY_GHOST_CAPTURE, false, true, false, false); + + return true; + } + } + + return false; +} #endif void CNEORules::PlayerKilled(CBasePlayer *pVictim, const CTakeDamageInfo &info) { +#ifdef GAME_DLL BaseClass::PlayerKilled(pVictim, info); - auto attacker = dynamic_cast(info.GetAttacker()); - auto victim = dynamic_cast(pVictim); - auto grenade = dynamic_cast(info.GetInflictor()); + auto pNEOAttacker = dynamic_cast(info.GetAttacker()); + auto pNEOVictim = dynamic_cast(pVictim); + auto pGrenade = dynamic_cast(info.GetInflictor()); - if (!victim) + if (!pNEOVictim) { + Assert(false); return; } - if (m_nRoundStatus == NeoRoundStatus::Pause) + if (NeoRoundStatus::Pause == m_nRoundStatus) { -#ifdef GAME_DLL - // Counter-act the death count for pausing state - victim->IncrementDeathCount(-1); -#endif + // restore the death count during pause + pNEOVictim->IncrementDeathCount(-1); return; } // Suicide or suicide by environment (non-grenade as grenade is likely from a player) - if (attacker == victim || (!attacker && !grenade)) + if (pNEOAttacker == pNEOVictim || (!pNEOAttacker && !pGrenade)) { - victim->AddPoints(-1, true); -#ifdef GAME_DLL - CheckIfCapPrevent(victim); -#endif + pNEOVictim->AddPoints(-1, true); + CheckIfCapPrevent(pNEOVictim); } -#ifdef GAME_DLL - else if (!attacker && grenade && grenade->GetTeamNumber() == victim->GetTeamNumber()) + else if (!pNEOAttacker && pGrenade && pGrenade->GetTeamNumber() == pNEOVictim->GetTeamNumber()) { // Death by own team's grenade, but the player is already disconnected. Check for cap prevent. - CheckIfCapPrevent(victim); + CheckIfCapPrevent(pNEOVictim); } -#endif - else if (attacker) + else if (pNEOAttacker) { // Team kill - if (IsTeamplay() && attacker->GetTeamNumber() == victim->GetTeamNumber()) + if (IsTeamplay() && pNEOAttacker->GetTeamNumber() == pNEOVictim->GetTeamNumber()) { - attacker->AddPoints(-1, true); -#ifdef GAME_DLL + pNEOAttacker->AddPoints(-1, true); if (sv_neo_teamdamage_kick.GetBool() && IsRoundLive()) { - ++attacker->m_iTeamKillsInflicted; + ++pNEOAttacker->m_iTeamKillsInflicted; } for (int i = 0; i < m_iEntPrevCapSize; ++i) { - if (m_arrayiEntPrevCap[i] == attacker->entindex()) + if (m_arrayiEntPrevCap[i] == pNEOAttacker->entindex()) { // Posthumous teamkill to prevent ghost cap scenario: // Player-A throws nade at Player-B, Player-A suicides right after, // Player-B gets killed from the nade - This dodges the general case // as Player-A is not the final player, but it was Player-A's intention // to prevent the ghost cap. - CheckIfCapPrevent(victim); + CheckIfCapPrevent(pNEOVictim); break; } } -#endif } // Enemy kill else { - attacker->AddPoints(1, false); -#ifdef GAME_DLL - if (GetGameType() == NEO_GAME_TYPE_JGR && IsRoundLive()) - { - if (attacker->GetClass() == NEO_CLASS_JUGGERNAUT) - { - auto jgrTeam = attacker->GetTeam(); - jgrTeam->SetScore(Min(jgrTeam->GetScore() + 2, sv_neo_jgr_max_points.GetInt())); - } - else if (m_pJuggernautPlayer) - { - const int attackerTeam = attacker->GetTeamNumber(); - const int jgrTeam = m_pJuggernautPlayer->GetTeamNumber(); - - if (attackerTeam == jgrTeam) - { - attacker->GetTeam()->AddScore(1); - } - } - } -#endif + pNEOAttacker->AddPoints(1, false); + EnemyPlayerKilled(pNEOVictim, pNEOAttacker, info); } } - if (auto *assister = FetchAssists(attacker, victim)) + if (auto *assister = FetchAssists(pNEOAttacker, pNEOVictim)) { // Team kill assist - if (assister->GetTeamNumber() == victim->GetTeamNumber()) + if (assister->GetTeamNumber() == pNEOVictim->GetTeamNumber()) { if (sv_neo_teamdamage_assists.GetBool()) { @@ -4128,6 +3445,7 @@ void CNEORules::PlayerKilled(CBasePlayer *pVictim, const CTakeDamageInfo &info) assister->AddPoints(1, false); } } +#endif // GAME_DLL } #ifdef GAME_DLL @@ -4147,6 +3465,24 @@ float CNEORules::FlPlayerFallDamage(CBasePlayer* pPlayer) return pPlayer->m_Local.m_flFallVelocity * DAMAGE_FOR_FALL_SPEED * sv_neo_falldmg_scale.GetFloat(); } +bool CNEORules::PlayerCanChangeLoadout(CNEO_Player* pPlayer) +{ + if (sv_neo_can_change_classes_anytime.GetBool() || + pPlayer->IsDead() || + IsRoundIdle() || + !pPlayer->m_bIneligibleForLoadoutPick && GetRemainingPreRoundFreezeTime(false) > 0) + { + return true; + } + return false; +} + +bool CNEORules::PlayerCanChangeSkin(CNEO_Player* pPlayer) +{ + return (!IsRoundActive() || !pPlayer->IsAlive()); +} + + const char* CNEORules::GetChatFormat(bool bTeamOnly, CBasePlayer* pPlayer) { if (!pPlayer) // dedicated server output @@ -4214,9 +3550,9 @@ const char* CNEORules::GetChatFormat(bool bTeamOnly, CBasePlayer* pPlayer) } #endif -#ifdef GAME_DLL void CNEORules::DeathNotice(CBasePlayer* pVictim, const CTakeDamageInfo& info) { +#ifdef GAME_DLL // Work out what killed the player, and send a message to all clients about it const char* killer_weapon_name = "world"; // by default, the player is killed by the world int killer_ID = 0; @@ -4338,8 +3674,8 @@ void CNEORules::DeathNotice(CBasePlayer* pVictim, const CTakeDamageInfo& info) gameeventmanager->FireEvent(event); } +#endif // GAME_DLL } -#endif #ifdef GAME_DLL void CNEORules::ClientDisconnected(edict_t* pClient) @@ -4412,110 +3748,40 @@ void CNEORules::ClientDisconnected(edict_t* pClient) BaseClass::ClientDisconnected(pClient); } -#endif - -bool CNEORules::GetTeamPlayEnabled() const -{ - return m_nGameTypeSelected != NEO_GAME_TYPE_DM; -} -#ifdef GAME_DLL bool CNEORules::FPlayerCanRespawn(CBasePlayer* pPlayer) { - // Special case for spectator player takeover CNEO_Player* pNeoPlayer = ToNEOPlayer(pPlayer); - Assert(pNeoPlayer); - if (pNeoPlayer->GetSpectatorTakeoverPlayerPending()) - { - return true; - } - - auto gameType = GetGameType(); - - if (gameType == NEO_GAME_TYPE_JGR && (pPlayer->GetTeamNumber() == m_iLastJuggernautTeam)) + if (!pNeoPlayer) { - if (m_pJuggernautPlayer || (gpGlobals->curtime - m_flJuggernautDeathTime) <= 8.0f) - { - return false; - } + Assert(false); + return false; } - - if (CanRespawnAnyTime()) - { - if (GetRoundStatus() == PostRound) - { - return false; - } - + + // Special case for spectator player takeover + if (pNeoPlayer->GetSpectatorTakeoverPlayerPending()) return true; - } - else if (gameType == NEO_GAME_TYPE_TUT) - { - if (pPlayer->IsAlive()) - { - return false; - } + if (!IsRoundOn()) return true; - } - - auto jinrai = GetGlobalTeam(TEAM_JINRAI); - auto nsf = GetGlobalTeam(TEAM_NSF); - - if (jinrai && nsf) - { - if (!IsRoundOn()) - { - return true; - } - - if (pNeoPlayer->m_bSpawnedThisRound && !IsRoundPreRoundFreeze()) - { - return false; - } - } - else - { - Assert(false); - } + if (pNeoPlayer->m_bSpawnedThisRound && !IsRoundPreRoundFreeze()) + return false; + // Do not let anyone who tried to team-kill during mirror damage + live round to respawn - if (static_cast(pPlayer)->m_bKilledInflicted) - { + if (pNeoPlayer->m_bKilledInflicted) return false; - } - + // Did we make it in time to spawn for this round? if (GetRemainingPreRoundFreezeTime(false) + sv_neo_latespawn_max_time.GetFloat() > 0) - { return true; - } return false; } -CBaseEntity *CNEORules::GetPlayerSpawnSpot(CBasePlayer *pPlayer) -{ - // NEO NOTE (nullsystem): If available + DM, instead of by entity, player spawn - // by set position. It doesn't seem anything utilizes what returned anyway. - if (m_nGameTypeSelected == NEO_GAME_TYPE_DM && DMSpawn::HasDMSpawn()) - { - const auto spawn = DMSpawn::GiveNextSpawn(); - const QAngle spawnAngle{0, spawn.lookY, 0}; - pPlayer->SetLocalOrigin(spawn.pos + Vector(0,0,1)); - pPlayer->SetAbsVelocity(vec3_origin); - pPlayer->SetLocalAngles(spawnAngle); - pPlayer->m_Local.m_vecPunchAngle = vec3_angle; - pPlayer->m_Local.m_vecPunchAngleVel = vec3_angle; - pPlayer->SnapEyeAngles(spawnAngle); - return nullptr; - } - - return BaseClass::GetPlayerSpawnSpot(pPlayer); -} - #endif +#ifdef GAME_DLL void CNEORules::SetRoundStatus(NeoRoundStatus status) { if (status != NeoRoundStatus::PreRoundFreeze && status != NeoRoundStatus::PostRound) @@ -4529,20 +3795,17 @@ void CNEORules::SetRoundStatus(NeoRoundStatus status) player->RemoveNeoFlag(NEO_FL_FREEZETIME); } } -#ifdef GAME_DLL if (status == NeoRoundStatus::RoundLive) { UTIL_CenterPrintAll("- GO! GO! GO! -\n"); - if (auto pEntGameCfg = GetActiveGameConfig()) + if (auto pEntGameCfg = NEOGameConfig()) { pEntGameCfg->m_OnRoundStart.FireOutput(nullptr, pEntGameCfg); } } -#endif } -#ifdef GAME_DLL if (status == NeoRoundStatus::PreRoundFreeze) { // we clear these so people who rejoin on a different round to the round when they left aren't prevented from spawning. This is done before all players are // spawned on the new round so these values will be overwritten for those players who are still in the game @@ -4558,66 +3821,16 @@ void CNEORules::SetRoundStatus(NeoRoundStatus status) m_bIsMatchPoint = RoundIsMatchPoint(); m_bIsInSuddenDeath = RoundIsInSuddenDeath(); } -#endif // GAME_DLL m_nRoundStatus = status; } +#endif NeoRoundStatus CNEORules::GetRoundStatus() const { return static_cast(m_nRoundStatus.Get()); } -int CNEORules::GetGameType(void) -{ - return m_nGameTypeSelected; -} - -int CNEORules::GetHiddenHudElements(void) -{ - return m_iHiddenHudElements; -} - -int CNEORules::GetForcedTeam(void) -{ - return m_iForcedTeam.Get(); -} - -int CNEORules::GetForcedClass(void) -{ - return m_iForcedClass; -} - -int CNEORules::GetForcedSkin(void) -{ - return m_iForcedSkin; -} - -int CNEORules::GetForcedWeapon(void) -{ - return m_iForcedWeapon; -} - -bool CNEORules::IsCyberspace() -{ - return m_bCyberspaceLevel; -} - -inline const char* CNEORules::GetGameTypeName(void) -{ - return NEO_GAME_TYPE_SETTINGS[GetGameType()].gameTypeName; -} - -bool CNEORules::CanChangeTeamClassLoadoutWhenAlive() -{ - return NEO_GAME_TYPE_SETTINGS[GetGameType()].changeTeamClassLoadoutWhenAlive; -} - -bool CNEORules::CanRespawnAnyTime() -{ - return NEO_GAME_TYPE_SETTINGS[GetGameType()].respawns; -} - float CNEORules::GetRemainingPreRoundFreezeTime(const bool clampToZero) const { // If there's no time left, return 0 instead of a negative value. @@ -4705,6 +3918,16 @@ bool CNEORules::IsJuggernautLocked() const return false; } +int CNEORules::GetAttackingTeam() const +{ + return roundNumberIsEven() ? TEAM_JINRAI : TEAM_NSF; +} + +int CNEORules::GetDefendingTeam() const +{ + return roundNumberIsEven() ? TEAM_NSF : TEAM_JINRAI; +} + const char *CNEORules::GetTeamClantag(const int iTeamNum) const { switch (iTeamNum) @@ -4719,8 +3942,8 @@ const char *CNEORules::GetTeamClantag(const int iTeamNum) const void CNEORules::OnNavMeshLoad(void) { // We need to access the game config directly because the game type might not be set at this stage - auto cfg = GetActiveGameConfig(); - if (!cfg || cfg->m_GameType != NEO_GAME_TYPE_DM) + if (auto pEntGameCfg = NEOGameConfig(); + !pEntGameCfg || pEntGameCfg->m_GameType != NEO_GAME_TYPE_DM) { TheNavMesh->SetPlayerSpawnName("info_player_defender"); } diff --git a/src/game/shared/neo/neo_gamerules.h b/src/game/shared/neo/gamerules/neo_gamerules.h similarity index 72% rename from src/game/shared/neo/neo_gamerules.h rename to src/game/shared/neo/gamerules/neo_gamerules.h index 5602e0b267..cfb81e05ec 100644 --- a/src/game/shared/neo/neo_gamerules.h +++ b/src/game/shared/neo/gamerules/neo_gamerules.h @@ -1,8 +1,4 @@ -#ifndef NEO_GAMERULES_H -#define NEO_GAMERULES_H -#ifdef _WIN32 #pragma once -#endif #include "gamerules.h" #include "teamplay_gamerules.h" @@ -24,8 +20,6 @@ #include "utlhashtable.h" #endif -#include "neo_player_shared.h" - #ifdef CLIENT_DLL #define CNEORules C_NEORules #define CNEOGameRulesProxy C_NEOGameRulesProxy @@ -85,8 +79,6 @@ class CNEO_Player; class CWeaponGhost; class CNEOBotSeekAndDestroy; -extern ConVar sv_neo_mirror_teamdamage_multiplier; -extern ConVar sv_neo_mirror_teamdamage_duration; extern ConVar sv_neo_mirror_teamdamage_immunity; extern ConVar sv_neo_teamdamage_kick; @@ -104,11 +96,12 @@ enum NeoGameType { NEO_GAME_TYPE_EMT, NEO_GAME_TYPE_TUT, NEO_GAME_TYPE_JGR, + NEO_GAME_TYPE_ATK, NEO_GAME_TYPE__TOTAL // Number of game types }; -struct NeoGameTypeSettings; +extern const char* NEO_GAME_TYPE_CLASS_NAMES[NEO_GAME_TYPE__TOTAL]; extern const SZWSZTexts NEO_GAME_TYPE_DESC_STRS[NEO_GAME_TYPE__TOTAL]; @@ -131,6 +124,7 @@ enum NeoWinReason { NEO_VICTORY_VIP_ELIMINATION, NEO_VICTORY_TEAM_ELIMINATION, NEO_VICTORY_TIMEOUT_WIN_BY_NUMBERS, + NEO_VICTORY_ATK_TIMEOUT, NEO_VICTORY_POINTS, NEO_VICTORY_FORFEIT, NEO_VICTORY_STALEMATE, // Not actually a victory @@ -169,195 +163,151 @@ enum NeoSpectateEvent { NEO_SPECTATE_EVENT_LAST_GHOSTER, }; -class CNEORules : public CHL2MPRules, public CGameEventListener +abstract_class CNEORules : public CHL2MPRules, public CGameEventListener { -public: - DECLARE_CLASS( CNEORules, CHL2MPRules ); -// This makes datatables able to access our private vars. -#ifdef CLIENT_DLL - DECLARE_CLIENTCLASS_NOBASE(); -#else - DECLARE_SERVERCLASS_NOBASE(); -#endif +friend class CNEORulesTDM; +friend class CNEORulesCTG; +friend class CNEORulesVIP; +friend class CNEORulesDM; +friend class CNEORulesEMT; +friend class CNEORulesTUT; +friend class CNEORulesJGR; +friend class CNEORulesATK; + +friend class CNEOGameConfig; // this only exists because we can't add a BEGIN_DATADESC to neogamerules +public: + DECLARE_CLASS( CNEORules, CHL2MPRules ); + // This makes datatables able to access our private vars. + DECLARE_NETWORKCLASS_NOBASE(); + CNEORules(); virtual ~CNEORules(); + + // IGameEventListener interface: + virtual void FireGameEvent(IGameEvent *event) OVERRIDE; + + virtual void Think() OVERRIDE; + bool CHL2MPRulesThink(); -#ifdef GAME_DLL - virtual void Precache() OVERRIDE; - - virtual bool ClientConnected(edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen) OVERRIDE; - - virtual bool ClientCommand(CBaseEntity* pEdict, const CCommand& args) OVERRIDE; - - virtual void SetWinningTeam(int team, int iWinReason, bool bForceMapReset = true, bool bSwitchTeams = false, bool bDontAddScore = false, bool bFinal = false) OVERRIDE; - void SetWinningDMPlayer(CNEO_Player *pWinner); - - virtual void ChangeLevel(void) OVERRIDE; - - virtual void ClientDisconnected(edict_t* pClient) OVERRIDE; + // This is the supposed encrypt key on NT, although it has its issues. + // See https://steamcommunity.com/groups/ANPA/discussions/0/1482109512299590948/ + // (and NT Discord) for discussions. + virtual const unsigned char* GetEncryptionKey(void) OVERRIDE { return (unsigned char*)"tBA%-ygc"; } - CBaseEntity *GetPlayerSpawnSpot(CBasePlayer *pPlayer) override; +#ifdef GAME_DLL +public: + virtual bool PlayerCanChangeLoadout(CNEO_Player* pPlayer); + bool PlayerCanChangeSkin(CNEO_Player* pPlayer); + + virtual bool FPlayerCanRespawn(CBasePlayer* pPlayer) override; + virtual float FlPlayerFallDamage(CBasePlayer* pPlayer) OVERRIDE; + float MirrorDamageMultiplier() const; + + void CheckChatCommand(CNEO_Player *pNeoPlayer, const char *pSzChat); virtual bool IsOfficialMap(void) override; - virtual void MarkAchievement ( IRecipientFilter& filter, char const *pchAchievementName ) override; + +private: + virtual void Precache() override; + virtual void InitDefaultAIRelationships(void) override; + void OnNavMeshLoad() override; + + bool RoundStartFromIdleOrPausedThink(); + virtual void PlayerRespawnThink(); + void CheckClantagsThink(); + virtual void CheckOvertime() {} + bool GameOverThink(); + void TeamDamageThink(); + bool RoundOverThink(); + void RoundStatusThink(); + void CheckWinByElimination(); + + virtual bool ClientConnected(edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen) override; + virtual bool ClientCommand(CBaseEntity* pEdict, const CCommand& args) override; + virtual void ClientDisconnected(edict_t* pClient) override; + virtual const char* GetChatFormat(bool bTeamOnly, CBasePlayer* pPlayer) OVERRIDE; + virtual const char* GetChatPrefix(bool bTeamOnly, CBasePlayer* pPlayer) OVERRIDE { return ""; } // handled by GetChatFormat + virtual const char* GetChatLocation(bool bTeamOnly, CBasePlayer* pPlayer) OVERRIDE { return NULL; } // unimplemented + + virtual void SetWinningTeam(int team, int iWinReason, bool bForceMapReset = true, bool bSwitchTeams = false, bool bDontAddScore = false, bool bFinal = false) OVERRIDE; + void AwardRankUp(CNEO_Player *pClient); + void SetRoundStatus(NeoRoundStatus status); - virtual void InitDefaultAIRelationships(void); -#endif - virtual bool ShouldCollide( int collisionGroup0, int collisionGroup1 ) OVERRIDE; - - virtual const char* GetGameName() { return NEO_GAME_NAME; } - -#ifdef NEO - bool GetTeamPlayEnabled() const override; -#endif - + // NEOGameConfig is a logic entity, which is a server only entity. To access config values client side, we need to copy values to a networked entity + void UpdateFromGameConfig(); + void StartNextRound(); + virtual void RoundTimeout() {}; -#ifdef GAME_DLL - virtual bool FPlayerCanRespawn(CBasePlayer* pPlayer) OVERRIDE; -#endif + struct ReadyPlayers + { + int array[TEAM__TOTAL]; + }; + ReadyPlayers FetchReadyPlayers() const; // NEO TODO (Adam) This needs to be shown in the ui somewhere instead, ready up button + CUtlHashtable m_readyAccIDs; // NEO NOTE (Adam) What's wrong with player ent index + bool m_bIgnoreOverThreshold = false; + bool ReadyUpPlayerIsReady(CNEO_Player *pNeoPlayer) const; - virtual int GetGameType(void) OVERRIDE; - int GetHiddenHudElements(); - int GetForcedTeam(); - int GetForcedClass(); - int GetForcedSkin(); - int GetForcedWeapon(); - bool IsCyberspace(); - virtual const char* GetGameTypeName(void) OVERRIDE; - bool CanChangeTeamClassLoadoutWhenAlive(); - bool CanRespawnAnyTime(); - - void GetDMHighestScorers( -#ifdef GAME_DLL - CNEO_Player *(*pHighestPlayers)[MAX_PLAYERS + 1], -#endif - int *iHighestPlayersTotal, - int *iHighestXP) const; + virtual void CleanUpMap() OVERRIDE; + virtual void RestartGame() OVERRIDE; + virtual void ChangeLevel(void) OVERRIDE; - virtual void Think( void ) OVERRIDE; - virtual void CreateStandardEntities( void ) OVERRIDE; + virtual void SetGameRelatedVars() {}; + void ResetTeamScores(); +#else // #ifdef CLIENT_DLL +public: +private: +#endif // GAME_DLL - virtual const char *GetGameDescription( void ) OVERRIDE; +public: virtual const CViewVectors* GetViewVectors() const OVERRIDE; - const NEOViewVectors* GetNEOViewVectors() const; - virtual void ClientSettingsChanged(CBasePlayer *pPlayer) OVERRIDE; - - virtual void ClientSpawned(edict_t* pPlayer) OVERRIDE; - - virtual void DeathNotice(CBasePlayer* pVictim, const CTakeDamageInfo& info) OVERRIDE -#ifdef CLIENT_DLL - { } -#else - ; -#endif - + virtual const char* GetGameName() { return NEO_GAME_NAME; } + virtual const char* GetGameTypeName(void) override = 0; + virtual int GetGameType(void) override = 0; + virtual const char* GetGameDescription(void) override = 0; + virtual bool GetTeamPlayEnabled() const override = 0; + virtual bool GetCompEnabled() const { return false; } + virtual bool GetCapPreventEnabled() const { return false; } + virtual bool CanChangeTeamClassLoadoutWhenAlive() const { return false; } + virtual bool RespawnsEnabled() const { return false; } + inline int GetHiddenHudElements() const { return m_iHiddenHudElements; } + inline int GetForcedTeam() const { return m_iForcedTeam; } + inline int GetForcedClass() const { return m_iForcedClass; } + inline int GetForcedSkin() const { return m_iForcedSkin; } + inline int GetForcedWeapon() const { return m_iForcedWeapon; } + inline bool IsCyberspace() const { return m_bCyberspaceLevel; } + virtual int DefaultFOV(void) override; + + NeoRoundStatus GetRoundStatus() const; bool RoundIsInSuddenDeath() const; bool RoundIsMatchPoint() const; bool RoundIsDoOrDie() const; - - virtual int DefaultFOV(void) override; - - float GetRemainingPreRoundFreezeTime(const bool clampToZero) const; - - float GetMapRemainingTime(); - - void PurgeGhostCapPoints(); - - void ResetGhostCapPoints(); - - void SetGameRelatedVars(); - void ResetTDM(); - void ResetGhost(); - void ResetVIP(); - void ResetJGR(); - - void CheckRestartGame(); - - void AwardRankUp(int client); -#ifdef CLIENT_DLL - void AwardRankUp(C_NEO_Player *pClient); -#else - void AwardRankUp(CNEO_Player *pClient); -#endif - - virtual bool CheckGameOver(void) OVERRIDE; - - float GetRoundRemainingTime() const; - float GetCTGOverTime() const; - float GetRoundAccumulatedTime() const; -#ifdef GAME_DLL - float MirrorDamageMultiplier() const; -#endif - -#ifdef GAME_DLL - void CheckIfCapPrevent(CNEO_Player *capPreventerPlayer); -#endif - virtual void PlayerKilled(CBasePlayer *pVictim, const CTakeDamageInfo &info) OVERRIDE; - - // IGameEventListener interface: - virtual void FireGameEvent(IGameEvent *event) OVERRIDE; - -#ifdef CLIENT_DLL - void CleanUpMap(); - void RestartGame(); -#else - virtual void CleanUpMap() OVERRIDE; - virtual void RestartGame() OVERRIDE; - - virtual float FlPlayerFallDamage(CBasePlayer* pPlayer) OVERRIDE; -#endif bool IsRoundPreRoundFreeze() const; + // Round winner not determined yet bool IsRoundLive() const; + // Can score points + bool IsRoundActive() const; + // Round timer is ticking down bool IsRoundOn() const; bool IsRoundOver() const; -#ifdef GAME_DLL - void GatherGameTypeVotes(); - - struct ReadyPlayers - { - int array[TEAM__TOTAL]; - }; - void CheckChatCommand(CNEO_Player *pNeoPlayer, const char *pSzChat); - ReadyPlayers FetchReadyPlayers() const; - CUtlHashtable m_readyAccIDs; - bool m_bIgnoreOverThreshold = false; - bool ReadyUpPlayerIsReady(CNEO_Player *pNeoPlayer) const; - - void CheckGameType(); - void CheckGameConfig(); - void StartNextRound(); - - virtual const char* GetChatFormat(bool bTeamOnly, CBasePlayer* pPlayer) OVERRIDE; - virtual const char* GetChatPrefix(bool bTeamOnly, CBasePlayer* pPlayer) OVERRIDE { return ""; } // handled by GetChatFormat - virtual const char* GetChatLocation(bool bTeamOnly, CBasePlayer* pPlayer) OVERRIDE { return NULL; } // unimplemented -#endif - bool CheckShouldNotThink(); - - void SetRoundStatus(NeoRoundStatus status); - NeoRoundStatus GetRoundStatus() const; - - // This is the supposed encrypt key on NT, although it has its issues. - // See https://steamcommunity.com/groups/ANPA/discussions/0/1482109512299590948/ - // (and NT Discord) for discussions. - virtual const unsigned char* GetEncryptionKey(void) OVERRIDE { return (unsigned char*)"tBA%-ygc"; } - - int GetGhosterTeam() const { return m_iGhosterTeam; } - int GetGhosterPlayer() const { return m_iGhosterPlayer; } - bool GhostExists() const { return m_bGhostExists; } - const Vector& GetGhostPos() const; - Vector GetGhostMarkerPos() const; - - int GetJuggernautPlayer() const { return m_iJuggernautPlayerIndex; } - bool JuggernautItemExists() const; - const Vector& GetJuggernautMarkerPos() const; - bool IsJuggernautLocked() const; - - + bool IsRoundIdle() const; + inline bool IsRoundPaused() const; + inline int roundNumber() const { return m_iRoundNumber; } + inline bool roundNumberIsEven() const { return (roundNumber() % 2 == 0); } + float GetRemainingPreRoundFreezeTime(const bool clampToZero) const; + float GetMapRemainingTime(); + virtual float GetRoundRemainingTime() const; + float GetRoundRemainingTime(float flGameTypeRoundTimeLimit) const; + float GetOverTime(float flRoundTimeLimit, float flOvertimeBaseAmount, float flOvertimeGrace, float flGraceDecay) const; + virtual bool CheckGameOver(void) OVERRIDE; + float GetRoundAccumulatedTime() const; + + int GetAttackingTeam() const; + int GetDefendingTeam() const; int GetOpposingTeam(const int team) const { if (team == TEAM_JINRAI) { return TEAM_NSF; } @@ -365,7 +315,6 @@ class CNEORules : public CHL2MPRules, public CGameEventListener Assert(false); return TEAM_SPECTATOR; } - int GetOpposingTeam(const CBaseCombatCharacter* player) const { if (!player) @@ -376,43 +325,45 @@ class CNEORules : public CHL2MPRules, public CGameEventListener return GetOpposingTeam(player->GetTeamNumber()); } - - inline int roundNumber() const { return m_iRoundNumber; } - inline bool roundNumberIsEven() const { return (roundNumber() % 2 == 0); } - #ifdef GLOWS_ENABLE void GetTeamGlowColor(int teamNumber, float &r, float &g, float &b) +{ + if (teamNumber == TEAM_JINRAI) { - if (teamNumber == TEAM_JINRAI) - { - r = static_cast(COLOR_JINRAI.r()/255.f); - g = static_cast(COLOR_JINRAI.g()/255.f); - b = static_cast(COLOR_JINRAI.b()/255.f); - } - else if (teamNumber == TEAM_NSF) - { - r = static_cast(COLOR_NSF.r()/255.f); - g = static_cast(COLOR_NSF.g()/255.f); - b = static_cast(COLOR_NSF.b()/255.f); - } - else - { - r = 0.76f; - g = 0.76f; - b = 0.76f; - } + r = static_cast(COLOR_JINRAI.r()/255.f); + g = static_cast(COLOR_JINRAI.g()/255.f); + b = static_cast(COLOR_JINRAI.b()/255.f); } + else if (teamNumber == TEAM_NSF) + { + r = static_cast(COLOR_NSF.r()/255.f); + g = static_cast(COLOR_NSF.g()/255.f); + b = static_cast(COLOR_NSF.b()/255.f); + } + else + { + r = 0.76f; + g = 0.76f; + b = 0.76f; + } +} #endif - const char *GetTeamClantag(const int iTeamNum) const; -#ifdef GAME_DLL - void OnNavMeshLoad() override; -#endif // GAME_DL: +private: + void ResetMapSessionCommon(); + virtual void CreateStandardEntities( void ) OVERRIDE; + + virtual void ClientSettingsChanged(CBasePlayer *pPlayer) OVERRIDE; + virtual void ClientSpawned(edict_t* pPlayer) OVERRIDE; + + virtual void PlayerKilled(CBasePlayer *pVictim, const CTakeDamageInfo &info) override; + virtual void EnemyPlayerKilled(CNEO_Player* pVictim, CNEO_Player* pAttacker, const CTakeDamageInfo& info) {} + virtual void DeathNotice(CBasePlayer* pVictim, const CTakeDamageInfo& info) OVERRIDE; + + virtual bool ShouldCollide( int collisionGroup0, int collisionGroup1 ) OVERRIDE; public: #ifdef GAME_DLL - // Workaround for bot spawning. See Bot_f() for details. - bool m_bNextClientIsFakeClient; struct RestoreInfo { int xp; @@ -431,23 +382,12 @@ class CNEORules : public CHL2MPRules, public CGameEventListener bool m_bThinkCheckClantags = false; bool m_bRotatingMapRightNow = false; #endif - CNetworkVar(float, m_flPauseEnd); -private: - void ResetMapSessionCommon(); + virtual const int GetScoreLimit() const { return 0; }; + virtual const int GetRoundLimit() const { return 0; }; #ifdef GAME_DLL - const int GetScoreLimit() const; - const int GetRoundLimit() const; - - void SpawnTheGhost(const Vector *origin = nullptr); - void SpawnTheJuggernaut(const Vector *origin = nullptr); - void SelectTheVIP(); public: - void JuggernautActivated(CNEO_Player *pPlayer); - void JuggernautDeactivated(CNEO_Juggernaut *pJuggernaut); - void JuggernautTotalRemoval(CNEO_Juggernaut *pJuggernaut); - void SetLastHurt(const int index) { m_iLastHurt = index; } void SetLastShooter(const int index) { m_iLastShooter = index; } void SetLastAttacker(const int index) { m_iLastAttacker = m_iLastEvent = index; } @@ -463,88 +403,141 @@ class CNEORules : public CHL2MPRules, public CGameEventListener const int GetLastGhoster() const { return m_iLastGhoster; } #ifdef GAME_DLL private: - CNEO_Juggernaut *m_pJuggernautItem = nullptr; - CNEO_Player *m_pJuggernautPlayer = nullptr; - float m_flJuggernautDeathTime = 0.0f; - int m_iLastJuggernautTeam = TEAM_INVALID; - - // For looking up capture zone locations - friend class CNEOBotCtgCarrier; - friend class CNEOBotCtgEscort; - friend class CNEOBotCtgLoneWolf; - friend class CNEOBotSeekAndDestroy; - CUtlVector m_pGhostCaps; - CWeaponGhost *m_pGhost = nullptr; - CNEO_Player *m_pVIP = nullptr; - int m_iVIPPreviousClass = 0; + friend class CMultiplayRules; float m_flPrevThinkKick = 0.0f; float m_flPrevThinkMirrorDmg = 0.0f; bool m_bTeamBeenAwardedDueToCapPrevent = false; int m_arrayiEntPrevCap[MAX_PLAYERS + 1]; // This is to check for cap-prevention workaround attempts int m_iEntPrevCapSize = 0; - int m_iPrintHelpCounter = 0; - bool m_bGamemodeTypeBeenInitialized = false; bool m_bServerIsCurrentlyAutoRecording = 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; int m_iMatchWinner = TEAM_UNASSIGNED; #endif - CNetworkVar(int, m_nRoundStatus); + + // neo_game_config variables CNetworkVar(int, m_iHiddenHudElements); CNetworkVar(int, m_iForcedTeam); CNetworkVar(int, m_iForcedClass); CNetworkVar(int, m_iForcedSkin); CNetworkVar(int, m_iForcedWeapon); CNetworkVar(bool, m_bCyberspaceLevel); - CNetworkVar(int, m_nGameTypeSelected); + + CNetworkVar(int, m_nRoundStatus); CNetworkVar(int, m_iRoundNumber); CNetworkVar(bool, m_bIsMatchPoint); CNetworkVar(bool, m_bIsDoOrDie); CNetworkVar(bool, m_bIsInSuddenDeath); + CNetworkVar(float, m_flNeoRoundStartTime); + CNetworkVar(float, m_flNeoNextRoundStartTime); + CNetworkVar(float, m_flPauseEnd); CNetworkString(m_szNeoJinraiClantag, NEO_MAX_CLANTAG_LENGTH); CNetworkString(m_szNeoNSFClantag, NEO_MAX_CLANTAG_LENGTH); - // Ghost networked variables + // For spectator commands. Networked so can be saved in demos for hltv + CNetworkVar(int, m_iLastHurt); + CNetworkVar(int, m_iLastShooter); + CNetworkVar(int, m_iLastEvent); + CNetworkVar(int, m_iLastAttacker); + CNetworkVar(int, m_iLastKiller); + CNetworkVar(int, m_iLastGhoster); + + ////////////////////// + // Ghost game logic // + ////////////////////// + +private: + friend class CNEO_GhostBoundary; + friend class CNEOGhostSpawnPoint; + // For looking up capture zone locations + friend class CNEOBotCtgCarrier; + friend class CNEOBotCtgEscort; + friend class CNEOBotCtgLoneWolf; + + CNetworkVar(bool, m_bGhostExists); CNetworkVar(int, m_iGhosterTeam); CNetworkVar(int, m_iGhosterPlayer); - CNetworkVector(m_vecGhostMarkerPos); - CNetworkVar(bool, m_bGhostExists); CNetworkVar(float, m_flGhostLastHeld); + CNetworkVector(m_vecGhostMarkerPos); CNetworkHandle( CWeaponGhost, m_hGhost ); - // Juggernaut networked variables +#ifdef GAME_DLL + CWeaponGhost *m_pGhost = nullptr; + CUtlVector m_pGhostCaps; + CUtlVector> m_ghostSpawns; + Vector m_vecPreviousGhostSpawn = vec3_origin; + + void SpawnTheGhost(const Vector *origin = nullptr); + void ResetGhostCapPoints(); + void CheckIfCapPrevent(CNEO_Player *capPreventerPlayer); + bool GhostTeamUpdateCheckWinCondition(); +#endif // GAME_DLL +public: + void ResetGhost(); + inline bool GhostExists() const { return m_bGhostExists; } + inline int GetGhosterTeam() const { return m_iGhosterTeam; } + inline int GetGhosterPlayer() const { return m_iGhosterPlayer; } + const Vector& GetGhostPos() const; + Vector GetGhostMarkerPos() const; + + /////////////////////////// + // Juggernaut game logic // + /////////////////////////// + +private: + friend class CNEOJuggernautSpawnPoint; + CNetworkVar(int, m_iJuggernautPlayerIndex); CNetworkVar(bool, m_bJuggernautItemExists); CNetworkHandle( CBaseEntity, m_hJuggernaut ); - CNetworkVar(float, m_flNeoRoundStartTime); - CNetworkVar(float, m_flNeoNextRoundStartTime); - - // For spectator commands. Networked so can be saved in demos for hltv - CNetworkVar(int, m_iLastHurt); - CNetworkVar(int, m_iLastShooter); - CNetworkVar(int, m_iLastEvent); - CNetworkVar(int, m_iLastAttacker); - CNetworkVar(int, m_iLastKiller); - CNetworkVar(int, m_iLastGhoster); +#ifdef GAME_DLL + CUtlVector> m_jgrSpawns; + Vector m_vecPreviousJuggernautSpawn = vec3_origin; + CNEO_Juggernaut *m_pJuggernautItem = nullptr; + CNEO_Player *m_pJuggernautPlayer = nullptr; + float m_flJuggernautDeathTime = 0.0f; + int m_iLastJuggernautTeam = TEAM_INVALID; + void SpawnTheJuggernaut(const Vector *origin = nullptr); + void ResetJGR(); +#endif // GAME_DLL public: - // VIP networked variables + void JuggernautTotalRemoval(CNEO_Juggernaut *pJuggernaut); + void JuggernautActivated(CNEO_Player *pPlayer); + void JuggernautDeactivated(CNEO_Juggernaut *pJuggernaut); + bool JuggernautItemExists() const; + bool IsJuggernautLocked() const; + int GetJuggernautPlayer() const { return m_iJuggernautPlayerIndex; } + const Vector& GetJuggernautMarkerPos() const; + + //////////////////// + // VIP game logic // + //////////////////// + +private: CNetworkVar(int, m_iEscortingTeam); + +#ifdef GAME_DLL + CNEO_Player *m_pVIP = nullptr; + int m_iVIPPreviousClass = NEO_CLASS_RECON; + + void SelectTheVIP(); + void ResetVIP(); +#endif // GAME_DLL +public: + inline int GetEscortingTeam() const { return m_iEscortingTeam; } + + ///////////////////// + // KOTH game logic // + ///////////////////// + +private: + }; inline CNEORules *NEORules() { return static_cast(g_pGameRules); } - -#endif // NEO_GAMERULES_H diff --git a/src/game/shared/neo/gamerules/neo_gamerules_atk.cpp b/src/game/shared/neo/gamerules/neo_gamerules_atk.cpp new file mode 100644 index 0000000000..16e8c367e7 --- /dev/null +++ b/src/game/shared/neo/gamerules/neo_gamerules_atk.cpp @@ -0,0 +1,151 @@ +#include "cbase.h" +#include "neo_gamerules_atk.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +REGISTER_GAMERULES_CLASS( CNEORulesATK ); + +BEGIN_NETWORK_TABLE_NOBASE( CNEORulesATK, DT_NEORulesATK ) +END_NETWORK_TABLE() + +LINK_ENTITY_TO_CLASS( neo_gamerules_atk, CNEOGameRulesATKProxy ); +IMPLEMENT_NETWORKCLASS_ALIASED( NEOGameRulesATKProxy, DT_NEOGameRulesATKProxy ); + +#ifdef CLIENT_DLL + void RecvProxy_NEORulesATK( const RecvProp *pProp, void **pOut, + void *pData, int objectID ) + { + CNEORulesATK *pRules = NEORulesATK(); + Assert( pRules ); + *pOut = pRules; + } + + BEGIN_RECV_TABLE( CNEOGameRulesATKProxy, DT_NEOGameRulesATKProxy ) + RecvPropDataTable( "neo_gamerules_atk_data", 0, 0, + &REFERENCE_RECV_TABLE( DT_NEORulesATK ), + RecvProxy_NEORulesATK ) + END_RECV_TABLE() +#else + void *SendProxy_NEORulesATK( const SendProp *pProp, + const void *pStructBase, const void *pData, + CSendProxyRecipients *pRecipients, int objectID ) + { + CNEORulesATK *pRules = NEORulesATK(); + Assert( pRules ); + return pRules; + } + + BEGIN_SEND_TABLE(CNEOGameRulesATKProxy, DT_NEOGameRulesATKProxy) + SendPropDataTable("neo_gamerules_atk_data", 0, + &REFERENCE_SEND_TABLE(DT_NEORulesATK), + SendProxy_NEORulesATK) + END_SEND_TABLE() +#endif + +ConVar sv_neo_atk_round_limit("sv_neo_atk_round_limit", "0", FCVAR_REPLICATED, "ATK max amount of rounds, 0 for no limit.", true, 0.0f, false, 0.0f); +ConVar sv_neo_atk_round_timelimit("sv_neo_atk_round_timelimit", "3.25", FCVAR_REPLICATED, "ATK round timelimit, in minutes.", true, 0.0f, false, 600.0f); +ConVar sv_neo_atk_score_limit("sv_neo_atk_score_limit", "7", FCVAR_REPLICATED, "ATK score limit", true, 0.0f, true, 99.0f); + +ConVar sv_neo_atk_ghost_overtime_enabled("sv_neo_atk_ghost_overtime_enabled", "0", FCVAR_REPLICATED, "Enable ghost overtime in the ATK mode.", true, 0, true, 1); +ConVar sv_neo_atk_ghost_overtime("sv_neo_atk_ghost_overtime", "45", FCVAR_REPLICATED, "Adds up to this many seconds to the round while the ghost is held.", true, 0, true, 120); +ConVar sv_neo_atk_ghost_overtime_grace("sv_neo_atk_ghost_overtime_grace", "10", FCVAR_REPLICATED, "Number of seconds left in the round when the ghost is dropped in overtime.", true, 0, true, 30); +ConVar sv_neo_atk_ghost_overtime_grace_decay("sv_neo_atk_ghost_overtime_grace_decay", "0", FCVAR_REPLICATED, "Slowly reduce the grace time as overtime goes on.", true, 0, true, 1); + +void CNEORulesATK::FireGameEvent(IGameEvent* event) +{ + BaseClass::FireGameEvent(event); +} + +float CNEORulesATK::GetRoundRemainingTime() const +{ + if (NeoRoundStatus::Overtime == m_nRoundStatus) + { + return GetOverTime(sv_neo_atk_round_timelimit.GetFloat() * 60.f, + sv_neo_atk_ghost_overtime.GetFloat(), + sv_neo_atk_ghost_overtime_grace.GetFloat(), + sv_neo_atk_ghost_overtime_grace_decay.GetBool()); + } + return BaseClass::GetRoundRemainingTime(sv_neo_atk_round_timelimit.GetFloat()); +} + +#ifdef GAME_DLL +void CNEORulesATK::SetGameRelatedVars() +{ + ResetGhost(); + SpawnTheGhost(); +} + +const int CNEORulesATK::GetScoreLimit() const +{ + return sv_neo_atk_score_limit.GetInt(); +} + +const int CNEORulesATK::GetRoundLimit() const +{ + return sv_neo_atk_round_limit.GetInt(); +} + +void CNEORulesATK::RoundTimeout() +{ + SetWinningTeam(GetDefendingTeam(), NEO_VICTORY_ATK_TIMEOUT, false, true, false, false); +} + +void CNEORulesATK::CheckOvertime() +{ + if (!sv_neo_atk_ghost_overtime_enabled.GetBool()) + return; + + float overtimeGrace = sv_neo_atk_round_timelimit.GetFloat() * 60; + float roundTimeLimit = sv_neo_atk_ghost_overtime_grace.GetFloat(); + + if (NeoRoundStatus::RoundLive == m_nRoundStatus && m_iGhosterPlayer && GetAttackingTeam() == m_iGhosterTeam && + (m_flNeoRoundStartTime + roundTimeLimit - overtimeGrace) < gpGlobals->curtime) + { + m_nRoundStatus = NeoRoundStatus::Overtime; + } + + if (NeoRoundStatus::Overtime == m_nRoundStatus && m_iGhosterPlayer) + { + m_flGhostLastHeld = gpGlobals->curtime; + } +} +#endif // GAME_DLL + +void CNEORulesATK::Think() +{ +#ifdef GAME_DLL + CGameRules::Think(); + + UpdateFromGameConfig(); + + if (RoundStartFromIdleOrPausedThink()) + return; + + PlayerRespawnThink(); + + CheckClantagsThink(); + + if (IsRoundPaused()) + return; + + GameOverThink(); + + CheckOvertime(); + + if (CHL2MPRulesThink()) + return; + + TeamDamageThink(); + + if (RoundOverThink()) + return; + + RoundStatusThink(); + + if (GhostTeamUpdateCheckWinCondition()) + return; + + CheckWinByElimination(); +#endif // GAME_DLL +} \ No newline at end of file diff --git a/src/game/shared/neo/gamerules/neo_gamerules_atk.h b/src/game/shared/neo/gamerules/neo_gamerules_atk.h new file mode 100644 index 0000000000..98abd99190 --- /dev/null +++ b/src/game/shared/neo/gamerules/neo_gamerules_atk.h @@ -0,0 +1,50 @@ +#pragma once + +#include "neo_gamerules.h" +#include "GameEventListener.h" + +#ifdef CLIENT_DLL + #define CNEORulesATK C_NEORulesATK + #define CNEOGameRulesATKProxy C_NEOGameRulesATKProxy +#endif + +class CNEOGameRulesATKProxy : public CNEOGameRulesProxy +{ +public: + DECLARE_CLASS( CNEOGameRulesATKProxy, CNEOGameRulesProxy ); + DECLARE_NETWORKCLASS(); +}; + +class CNEORulesATK : public CNEORules, public CGameEventListener +{ +public: + DECLARE_CLASS(CNEORulesATK, CNEORules); + DECLARE_NETWORKCLASS_NOBASE(); + + // IGameEventListener interface: + virtual void FireGameEvent(IGameEvent *event) override; + + virtual int GetGameType() override final { return NEO_GAME_TYPE_ATK; } + virtual const char* GetGameTypeName() override final { return "ATK"; } + const char* GetGameDescription() override final { return "Attack / Defend the ghost"; } + virtual bool GetTeamPlayEnabled() const override final { return true; } + virtual bool GetCompEnabled() const override final { return true; } + virtual bool GetCapPreventEnabled() const override final { return false; } + virtual bool CanChangeTeamClassLoadoutWhenAlive() const override final { return false; } + virtual bool RespawnsEnabled() const override final { return false; } + + virtual float GetRoundRemainingTime() const override final; +#ifdef GAME_DLL + virtual void SetGameRelatedVars() override final; + virtual const int GetScoreLimit() const override final; + virtual const int GetRoundLimit() const override final; + virtual void CheckOvertime() override final; + virtual void RoundTimeout() override final; +#endif // GAME_DLL + virtual void Think() override final; +}; + +inline CNEORulesATK *NEORulesATK() +{ + return static_cast(g_pGameRules); +} \ No newline at end of file diff --git a/src/game/shared/neo/gamerules/neo_gamerules_ctg.cpp b/src/game/shared/neo/gamerules/neo_gamerules_ctg.cpp new file mode 100644 index 0000000000..b6b4e4de32 --- /dev/null +++ b/src/game/shared/neo/gamerules/neo_gamerules_ctg.cpp @@ -0,0 +1,157 @@ +#include "cbase.h" +#include "neo_gamerules_ctg.h" + +#ifdef GAME_DLL + #include "neo_ghost_spawn_point.h" + #include "neo_ghost_cap_point.h" +#else +#endif // GAME_DLL + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +REGISTER_GAMERULES_CLASS( CNEORulesCTG ); + +BEGIN_NETWORK_TABLE_NOBASE( CNEORulesCTG, DT_NEORulesCTG ) +END_NETWORK_TABLE() + +LINK_ENTITY_TO_CLASS( neo_gamerules_ctg, CNEOGameRulesCTGProxy ); +IMPLEMENT_NETWORKCLASS_ALIASED( NEOGameRulesCTGProxy, DT_NEOGameRulesCTGProxy ); + +#ifdef CLIENT_DLL + void RecvProxy_NEORulesCTG( const RecvProp *pProp, void **pOut, + void *pData, int objectID ) + { + CNEORulesCTG *pRules = NEORulesCTG(); + Assert( pRules ); + *pOut = pRules; + } + + BEGIN_RECV_TABLE( CNEOGameRulesCTGProxy, DT_NEOGameRulesCTGProxy ) + RecvPropDataTable( "neo_gamerules_ctg_data", 0, 0, + &REFERENCE_RECV_TABLE( DT_NEORulesCTG ), + RecvProxy_NEORulesCTG ) + END_RECV_TABLE() +#else + void *SendProxy_NEORulesCTG( const SendProp *pProp, + const void *pStructBase, const void *pData, + CSendProxyRecipients *pRecipients, int objectID ) + { + CNEORulesCTG *pRules = NEORulesCTG(); + Assert( pRules ); + return pRules; + } + + BEGIN_SEND_TABLE(CNEOGameRulesCTGProxy, DT_NEOGameRulesCTGProxy) + SendPropDataTable("neo_gamerules_ctg_data", 0, + &REFERENCE_SEND_TABLE(DT_NEORulesCTG), + SendProxy_NEORulesCTG) + END_SEND_TABLE() +#endif + +ConVar sv_neo_ctg_round_limit("sv_neo_ctg_round_limit", "0", FCVAR_REPLICATED, "CTG max amount of rounds, 0 for no limit.", true, 0.0f, false, 0.0f); +ConVar sv_neo_ctg_round_timelimit("sv_neo_ctg_round_timelimit", "3.25", FCVAR_REPLICATED, "CTG round timelimit, in minutes.", true, 0.0f, false, 0.0f); +ConVar sv_neo_ctg_score_limit("sv_neo_ctg_score_limit", "7", FCVAR_REPLICATED, "CTG score limit", true, 0.0f, true, 99.0f); + +ConVar sv_neo_ctg_ghost_overtime_enabled("sv_neo_ctg_ghost_overtime_enabled", "0", FCVAR_REPLICATED, "Enable ghost overtime.", true, 0, true, 1); +ConVar sv_neo_ctg_ghost_overtime("sv_neo_ctg_ghost_overtime", "45", FCVAR_REPLICATED, "Adds up to this many seconds to the round while the ghost is held.", true, 0, true, 120); +ConVar sv_neo_ctg_ghost_overtime_grace("sv_neo_ctg_ghost_overtime_grace", "10", FCVAR_REPLICATED, "Number of seconds left in the round when the ghost is dropped in overtime.", true, 0, true, 30); +ConVar sv_neo_ctg_ghost_overtime_grace_decay("sv_neo_ctg_ghost_overtime_grace_decay", "0", FCVAR_REPLICATED, "Slowly reduce the grace time as overtime goes on.", true, 0, true, 1); + +void CNEORulesCTG::FireGameEvent(IGameEvent* event) +{ + BaseClass::FireGameEvent(event); +} + +float CNEORulesCTG::GetRoundRemainingTime() const +{ + if (NeoRoundStatus::Overtime == m_nRoundStatus) + { + return GetOverTime(sv_neo_ctg_round_timelimit.GetFloat() * 60.f, + sv_neo_ctg_ghost_overtime.GetFloat(), + sv_neo_ctg_ghost_overtime_grace.GetFloat(), + sv_neo_ctg_ghost_overtime_grace_decay.GetBool()); + } + return BaseClass::GetRoundRemainingTime(sv_neo_ctg_round_timelimit.GetFloat()); +} + +#ifdef GAME_DLL +void CNEORulesCTG::SetGameRelatedVars() +{ + ResetGhost(); + SpawnTheGhost(); +} + +const int CNEORulesCTG::GetScoreLimit() const +{ + return sv_neo_ctg_score_limit.GetInt(); +} + +const int CNEORulesCTG::GetRoundLimit() const +{ + return sv_neo_ctg_round_limit.GetInt(); +} + +void CNEORulesCTG::CheckOvertime() +{ + if (!sv_neo_ctg_ghost_overtime_enabled.GetBool()) + return; + + float overtimeGrace = sv_neo_ctg_round_timelimit.GetFloat() * 60; + float roundTimeLimit = sv_neo_ctg_ghost_overtime_grace.GetFloat(); + + if (NeoRoundStatus::RoundLive == m_nRoundStatus && m_iGhosterPlayer && + (m_flNeoRoundStartTime + roundTimeLimit - overtimeGrace) < gpGlobals->curtime) + { + m_nRoundStatus = NeoRoundStatus::Overtime; + } + + if (m_nRoundStatus == NeoRoundStatus::Overtime && m_iGhosterPlayer) + { + m_flGhostLastHeld = gpGlobals->curtime; + } +} + +void CNEORulesCTG::RoundTimeout() +{ + SetWinningTeam(TEAM_SPECTATOR, NEO_VICTORY_STALEMATE, false, false, true, false); +} +#endif // GAME_DLL + +void CNEORulesCTG::Think() +{ +#ifdef GAME_DLL + CGameRules::Think(); + + UpdateFromGameConfig(); + + if (RoundStartFromIdleOrPausedThink()) + return; + + PlayerRespawnThink(); + + CheckClantagsThink(); + + if (IsRoundPaused()) + return; + + GameOverThink(); + + CheckOvertime(); + + if (CHL2MPRulesThink()) + return; + + TeamDamageThink(); + + if (RoundOverThink()) + return; + + RoundStatusThink(); + + if (GhostTeamUpdateCheckWinCondition()) + return; + + CheckWinByElimination(); +#endif // GAME_DLL +} \ No newline at end of file diff --git a/src/game/shared/neo/gamerules/neo_gamerules_ctg.h b/src/game/shared/neo/gamerules/neo_gamerules_ctg.h new file mode 100644 index 0000000000..8fadefd2e1 --- /dev/null +++ b/src/game/shared/neo/gamerules/neo_gamerules_ctg.h @@ -0,0 +1,50 @@ +#pragma once + +#include "neo_gamerules.h" +#include "GameEventListener.h" + +#ifdef CLIENT_DLL + #define CNEORulesCTG C_NEORulesCTG + #define CNEOGameRulesCTGProxy C_NEOGameRulesCTGProxy +#endif + +class CNEOGameRulesCTGProxy : public CNEOGameRulesProxy +{ +public: + DECLARE_CLASS( CNEOGameRulesCTGProxy, CNEOGameRulesProxy ); + DECLARE_NETWORKCLASS(); +}; + +class CNEORulesCTG : public CNEORules, public CGameEventListener +{ +public: + DECLARE_CLASS(CNEORulesCTG, CNEORules); + DECLARE_NETWORKCLASS_NOBASE(); + + // IGameEventListener interface: + virtual void FireGameEvent(IGameEvent *event) override; + + virtual int GetGameType() override final { return NEO_GAME_TYPE_CTG; } + virtual const char* GetGameTypeName() override final { return "CTG"; } + const char* GetGameDescription() override final { return "Capture the Ghost"; } + virtual bool GetTeamPlayEnabled() const override final { return true; } + virtual bool GetCompEnabled() const override final { return true; } + virtual bool GetCapPreventEnabled() const override final { return true; } + virtual bool CanChangeTeamClassLoadoutWhenAlive() const override final { return false; } + virtual bool RespawnsEnabled() const override final { return false; } + + virtual float GetRoundRemainingTime() const override final; +#ifdef GAME_DLL + virtual void SetGameRelatedVars() override final; + virtual const int GetScoreLimit() const override final; + virtual const int GetRoundLimit() const override final; + virtual void CheckOvertime() override final; + virtual void RoundTimeout() override final; +#endif // GAME_DLL + virtual void Think() override final; +}; + +inline CNEORulesCTG *NEORulesCTG() +{ + return static_cast(g_pGameRules); +} \ No newline at end of file diff --git a/src/game/shared/neo/gamerules/neo_gamerules_dm.cpp b/src/game/shared/neo/gamerules/neo_gamerules_dm.cpp new file mode 100644 index 0000000000..5d0b3b13bc --- /dev/null +++ b/src/game/shared/neo/gamerules/neo_gamerules_dm.cpp @@ -0,0 +1,320 @@ +#include "cbase.h" +#include "neo_gamerules_dm.h" +#ifdef GAME_DLL +#include "neo_game_config.h" +#else +#include "c_playerresource.h" +#endif // GAME_DLL + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +REGISTER_GAMERULES_CLASS( CNEORulesDM ); + +BEGIN_NETWORK_TABLE_NOBASE( CNEORulesDM, DT_NEORulesDM ) +END_NETWORK_TABLE() + +LINK_ENTITY_TO_CLASS( neo_gamerules_dm, CNEOGameRulesDMProxy ); +IMPLEMENT_NETWORKCLASS_ALIASED( NEOGameRulesDMProxy, DT_NEOGameRulesDMProxy ); + +#ifdef CLIENT_DLL + void RecvProxy_NEORulesDM( const RecvProp *pProp, void **pOut, + void *pData, int objectID ) + { + CNEORulesDM *pRules = NEORulesDM(); + Assert( pRules ); + *pOut = pRules; + } + + BEGIN_RECV_TABLE( CNEOGameRulesDMProxy, DT_NEOGameRulesDMProxy ) + RecvPropDataTable( "neo_gamerules_dm_data", 0, 0, + &REFERENCE_RECV_TABLE( DT_NEORulesDM ), + RecvProxy_NEORulesDM ) + END_RECV_TABLE() +#else + void *SendProxy_NEORulesDM( const SendProp *pProp, + const void *pStructBase, const void *pData, + CSendProxyRecipients *pRecipients, int objectID ) + { + CNEORulesDM *pRules = NEORulesDM(); + Assert( pRules ); + return pRules; + } + + BEGIN_SEND_TABLE(CNEOGameRulesDMProxy, DT_NEOGameRulesDMProxy) + SendPropDataTable("neo_gamerules_dm_data", 0, + &REFERENCE_SEND_TABLE(DT_NEORulesDM), + SendProxy_NEORulesDM) + END_SEND_TABLE() +#endif + +ConVar sv_neo_dm_round_limit("sv_neo_dm_round_limit", "0", FCVAR_REPLICATED, "DM max amount of rounds, 0 for no limit.", true, 0.0f, false, 0.0f); +ConVar sv_neo_dm_round_timelimit("sv_neo_dm_round_timelimit", "10.25", FCVAR_REPLICATED, "DM round timelimit, in minutes.", true, 0.0f, false, 0.0f); +ConVar sv_neo_dm_score_limit("sv_neo_dm_score_limit", "7", FCVAR_REPLICATED, "DM score limit", true, 0.0f, true, 99.0f); + +ConVar sv_neo_dm_win_xp("sv_neo_dm_win_xp", "50", FCVAR_REPLICATED, "The XP limit to win the match.", true, 0.0f, true, 1000.0f); + +extern bool RespawnWithRet(CBaseEntity *pEdict, bool fCopyCorpse); + +void CNEORulesDM::FireGameEvent(IGameEvent* event) +{ + BaseClass::FireGameEvent(event); +} + +#ifdef GAME_DLL +void CNEORulesDM::PlayerRespawnThink() +{ + for (int i = 1; i <= gpGlobals->maxClients; i++) + { + auto player = static_cast(UTIL_PlayerByIndex(i)); + if (player && player->IsDead() && (IsRoundPaused() || player->DeathCount() > 0)) + { + const int playerTeam = player->GetTeamNumber(); + if ((playerTeam == TEAM_JINRAI || playerTeam == TEAM_NSF) && RespawnWithRet(player, false)) + { + player->m_bInAim = false; + player->m_bCarryingGhost = false; + player->m_bInThermOpticCamo = false; + player->m_bInVision = false; + player->m_bIneligibleForLoadoutPick = false; + player->SetTestMessageVisible(false); + + engine->ClientCommand(player->edict(), "loadoutmenu"); + } + } + } +} +#endif // GAME_DLL + +float CNEORulesDM::GetRoundRemainingTime() const +{ + return BaseClass::GetRoundRemainingTime(sv_neo_dm_round_timelimit.GetFloat()); +} + +#ifdef GAME_DLL +bool CNEORulesDM::FPlayerCanRespawn(CBasePlayer* pPlayer) +{ + if (NeoRoundStatus::PostRound == m_nRoundStatus) + return false; + + return true; +} + +extern ConVar sv_neo_dm_max_class_dur; +bool CNEORulesDM::PlayerCanChangeLoadout(CNEO_Player* pPlayer) +{ + if (!pPlayer->m_bIneligibleForLoadoutPick && pPlayer->GetAliveDuration() < sv_neo_dm_max_class_dur.GetFloat()) + return true; + + return BaseClass::PlayerCanChangeLoadout(pPlayer); +} + +void CNEORulesDM::SetGameRelatedVars() +{ + for (int i = 1; i <= gpGlobals->maxClients; ++i) + { + if (auto pPlayer = static_cast(UTIL_PlayerByIndex(i))) + { + pPlayer->m_iXP.GetForModify() = 0; + } + } +} + +const int CNEORulesDM::GetScoreLimit() const +{ + return sv_neo_dm_score_limit.GetInt(); +} + +const int CNEORulesDM::GetRoundLimit() const +{ + return sv_neo_dm_round_limit.GetInt(); +} + +void CNEORulesDM::RoundTimeout() +{ + // Winning player + CNEO_Player* pWinners[MAX_PLAYERS + 1] = {}; + int iWinnersTotal = 0; + int iWinnerXP = 0; + GetDMHighestScorers(&pWinners, &iWinnersTotal, &iWinnerXP); + if (iWinnersTotal == 1) + { + SetWinningDMPlayer(pWinners[0]); + } + + // Otherwise go into overtime +} +#endif // GAME_DLL + +void CNEORulesDM::Think() +{ +#ifdef GAME_DLL + CGameRules::Think(); + + UpdateFromGameConfig(); + + if (RoundStartFromIdleOrPausedThink()) + return; + + PlayerRespawnThink(); + + CheckClantagsThink(); + + if (IsRoundPaused()) + return; + + GameOverThink(); + + CheckOvertime(); + + if (CHL2MPRulesThink()) + return; + + TeamDamageThink(); + + if (RoundOverThink()) + return; + + RoundStatusThink(); + + // Win by XP + if (IsRoundLive() && sv_neo_dm_win_xp.GetInt() > 0) + { + // End game early if there's already a player past the winning XP + CNEO_Player *pHighestPlayers[MAX_PLAYERS + 1] = {}; + int iWinningTotal = 0; + int iWinningXP = 0; + GetDMHighestScorers(&pHighestPlayers, &iWinningTotal, &iWinningXP); + if (iWinningXP >= sv_neo_dm_win_xp.GetInt() && iWinningTotal == 1) + { + SetWinningDMPlayer(pHighestPlayers[0]); + } + } +#endif // GAME_DLL +} + + +void CNEORulesDM::GetDMHighestScorers( +#ifdef GAME_DLL + CNEO_Player *(*pHighestPlayers)[MAX_PLAYERS + 1], +#endif + int *iHighestPlayersTotal, + int *iHighestXP) const +{ + *iHighestPlayersTotal = 0; + *iHighestXP = 0; +#ifdef GAME_DLL + for (int i = 1; i <= gpGlobals->maxClients; ++i) +#else + if (!g_PR) + { + return; + } + + for (int i = 0; i < (MAX_PLAYERS + 1); ++i) +#endif + { + int iXP = 0; + +#ifdef GAME_DLL + auto pCmpPlayer = static_cast(UTIL_PlayerByIndex(i)); + if (!pCmpPlayer) + { + continue; + } + iXP = pCmpPlayer->m_iXP; +#else + if (!g_PR->IsConnected(i)) + { + continue; + } + iXP = g_PR->GetXP(i); +#endif + + if (iXP == *iHighestXP) + { +#ifdef GAME_DLL + (*pHighestPlayers)[(*iHighestPlayersTotal)++] = pCmpPlayer; +#else + (*iHighestPlayersTotal)++; +#endif + } + else if (iXP > *iHighestXP) + { + *iHighestPlayersTotal = 0; + *iHighestXP = iXP; +#ifdef GAME_DLL + (*pHighestPlayers)[(*iHighestPlayersTotal)++] = pCmpPlayer; +#else + (*iHighestPlayersTotal)++; +#endif + } + } +} + +#ifdef GAME_DLL +void CNEORulesDM::SetWinningDMPlayer(CNEO_Player *pWinner) +{ + if (IsRoundOver()) + { + return; + } + + if (auto pEntGameCfg = NEOGameConfig()) + { + pEntGameCfg->m_OnDMRoundEnd.FireOutput(pWinner, pEntGameCfg); + } + + SetRoundStatus(NeoRoundStatus::PostRound); + char victoryMsg[128]; + // TODO: Per client since client has neo_name settings + V_sprintf_safe(victoryMsg, "%s is the winner of the deathmatch!\n", pWinner->GetNeoPlayerName()); + + CRecipientFilter filter; + filter.AddAllPlayers(); + UserMessageBegin(filter, "RoundResult"); + WRITE_STRING("tie"); + WRITE_FLOAT(gpGlobals->curtime); + WRITE_STRING(victoryMsg); + MessageEnd(); + + EmitSound_t soundParams; + soundParams.m_nChannel = CHAN_AUTO; + soundParams.m_SoundLevel = SNDLVL_NONE; + soundParams.m_flVolume = 0.33f; + // Differing between Jinrai/NSF only as a sound cosmetic (no affect on DM) + const int team = pWinner->GetTeamNumber(); + soundParams.m_pSoundName = (team == TEAM_JINRAI) ? "gameplay/jinrai.mp3" : (team == TEAM_NSF) ? "gameplay/nsf.mp3" : "gameplay/draw.mp3"; + soundParams.m_bWarnOnDirectWaveReference = false; + soundParams.m_bEmitCloseCaption = false; + + for (int i = 1; i <= gpGlobals->maxClients; ++i) + { + CBasePlayer* basePlayer = UTIL_PlayerByIndex(i); + auto player = static_cast(basePlayer); + if (player) + { + if (!player->IsBot() || player->IsHLTV()) + { + const char* volStr = engine->GetClientConVarValue(i, "snd_victory_volume"); + const float jingleVolume = volStr ? atof(volStr) : 0.33f; + soundParams.m_flVolume = jingleVolume; + + CRecipientFilter soundFilter; + soundFilter.AddRecipient(basePlayer); + soundFilter.MakeReliable(); + player->EmitSound(soundFilter, i, soundParams); + } + } + } + + GoToIntermission(); + + IGameEvent *event = gameeventmanager->CreateEvent("game_end"); + if (event) + { + event->SetInt("winner", pWinner->GetUserID()); + gameeventmanager->FireEvent(event); + } +} +#endif // GAME_DLL \ No newline at end of file diff --git a/src/game/shared/neo/gamerules/neo_gamerules_dm.h b/src/game/shared/neo/gamerules/neo_gamerules_dm.h new file mode 100644 index 0000000000..738ecee87f --- /dev/null +++ b/src/game/shared/neo/gamerules/neo_gamerules_dm.h @@ -0,0 +1,64 @@ +#pragma once + +#include "neo_gamerules.h" +#include "GameEventListener.h" + +#ifdef CLIENT_DLL + #define CNEORulesDM C_NEORulesDM + #define CNEOGameRulesDMProxy C_NEOGameRulesDMProxy +#endif + +class CNEOGameRulesDMProxy : public CNEOGameRulesProxy +{ +public: + DECLARE_CLASS( CNEOGameRulesDMProxy, CNEOGameRulesProxy ); + DECLARE_NETWORKCLASS(); +}; + +class CNEORulesDM : public CNEORules, public CGameEventListener +{ +public: + DECLARE_CLASS(CNEORulesDM, CNEORules); + DECLARE_NETWORKCLASS_NOBASE(); + + // IGameEventListener interface: + virtual void FireGameEvent(IGameEvent *event) override; + + virtual int GetGameType() override final { return NEO_GAME_TYPE_DM; } + virtual const char* GetGameTypeName() override final { return "DM"; } + const char* GetGameDescription() override final { return "Deathmatch"; } + virtual bool GetTeamPlayEnabled() const override final { return false; } + virtual bool GetCompEnabled() const override final { return false; } + virtual bool GetCapPreventEnabled() const override final { return false; } + virtual bool CanChangeTeamClassLoadoutWhenAlive() const override final { return false; } + virtual bool RespawnsEnabled() const override final { return true; } + + virtual float GetRoundRemainingTime() const override final; +#ifdef GAME_DLL + virtual bool FPlayerCanRespawn(CBasePlayer* pPlayer) override final; + virtual bool PlayerCanChangeLoadout(CNEO_Player* pPlayer) override final; + + virtual void SetGameRelatedVars() override final; + virtual const int GetScoreLimit() const override final; + virtual const int GetRoundLimit() const override final; + virtual void RoundTimeout() override final; +#endif // GAME_DLL + virtual void Think() override final; +#ifdef GAME_DLL + virtual void PlayerRespawnThink() override final; +#endif // GAME_DLL + + void GetDMHighestScorers( +#ifdef GAME_DLL + CNEO_Player *(*pHighestPlayers)[MAX_PLAYERS + 1], +#endif + int *iHighestPlayersTotal, int *iHighestXP) const; +#ifdef GAME_DLL + void SetWinningDMPlayer(CNEO_Player *pWinner); +#endif // GAME_DLL +}; + +inline CNEORulesDM *NEORulesDM() +{ + return static_cast(g_pGameRules); +} \ No newline at end of file diff --git a/src/game/shared/neo/gamerules/neo_gamerules_emt.cpp b/src/game/shared/neo/gamerules/neo_gamerules_emt.cpp new file mode 100644 index 0000000000..ce8db14284 --- /dev/null +++ b/src/game/shared/neo/gamerules/neo_gamerules_emt.cpp @@ -0,0 +1,90 @@ +#include "cbase.h" +#include "neo_gamerules_emt.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +REGISTER_GAMERULES_CLASS( CNEORulesEMT ); + +BEGIN_NETWORK_TABLE_NOBASE( CNEORulesEMT, DT_NEORulesEMT ) +END_NETWORK_TABLE() + +LINK_ENTITY_TO_CLASS( neo_gamerules_emt, CNEOGameRulesEMTProxy ); +IMPLEMENT_NETWORKCLASS_ALIASED( NEOGameRulesEMTProxy, DT_NEOGameRulesEMTProxy ); + +#ifdef CLIENT_DLL + void RecvProxy_NEORulesEMT( const RecvProp *pProp, void **pOut, + void *pData, int objectID ) + { + CNEORulesEMT *pRules = NEORulesEMT(); + Assert( pRules ); + *pOut = pRules; + } + + BEGIN_RECV_TABLE( CNEOGameRulesEMTProxy, DT_NEOGameRulesEMTProxy ) + RecvPropDataTable( "neo_gamerules_emt_data", 0, 0, + &REFERENCE_RECV_TABLE( DT_NEORulesEMT ), + RecvProxy_NEORulesEMT ) + END_RECV_TABLE() +#else + void *SendProxy_NEORulesEMT( const SendProp *pProp, + const void *pStructBase, const void *pData, + CSendProxyRecipients *pRecipients, int objectID ) + { + CNEORulesEMT *pRules = NEORulesEMT(); + Assert( pRules ); + return pRules; + } + + BEGIN_SEND_TABLE(CNEOGameRulesEMTProxy, DT_NEOGameRulesEMTProxy) + SendPropDataTable("neo_gamerules_emt_data", 0, + &REFERENCE_SEND_TABLE(DT_NEORulesEMT), + SendProxy_NEORulesEMT) + END_SEND_TABLE() +#endif + +void CNEORulesEMT::FireGameEvent(IGameEvent* event) +{ + BaseClass::FireGameEvent(event); +} + +float CNEORulesEMT::GetRoundRemainingTime() const +{ +#ifdef GAME_DLL + Assert(false); // Shouldn't be calling this server side +#endif // GAME_DLL + return 0.f; +} + +#ifdef GAME_DLL +bool CNEORulesEMT::FPlayerCanRespawn(CBasePlayer* pPlayer) +{ + return true; +} + +const int CNEORulesEMT::GetScoreLimit() const +{ + Assert(false); + return 1; +} + +const int CNEORulesEMT::GetRoundLimit() const +{ + Assert(false); + return 1; +} + +void CNEORulesEMT::RoundTimeout() +{ + Assert(false); +} +#endif // GAME_DLL + +void CNEORulesEMT::Think() +{ +#ifdef GAME_DLL + CGameRules::Think(); + + UpdateFromGameConfig(); +#endif // GAME_DLL +} \ No newline at end of file diff --git a/src/game/shared/neo/gamerules/neo_gamerules_emt.h b/src/game/shared/neo/gamerules/neo_gamerules_emt.h new file mode 100644 index 0000000000..3851b3c3fb --- /dev/null +++ b/src/game/shared/neo/gamerules/neo_gamerules_emt.h @@ -0,0 +1,51 @@ +#pragma once + +#include "neo_gamerules.h" +#include "GameEventListener.h" + +#ifdef CLIENT_DLL + #define CNEORulesEMT C_NEORulesEMT + #define CNEOGameRulesEMTProxy C_NEOGameRulesEMTProxy +#endif + +class CNEOGameRulesEMTProxy : public CNEOGameRulesProxy +{ +public: + DECLARE_CLASS( CNEOGameRulesEMTProxy, CNEOGameRulesProxy ); + DECLARE_NETWORKCLASS(); +}; + +class CNEORulesEMT : public CNEORules, public CGameEventListener +{ +public: + DECLARE_CLASS(CNEORulesEMT, CNEORules); + DECLARE_NETWORKCLASS_NOBASE(); + + // IGameEventListener interface: + virtual void FireGameEvent(IGameEvent *event) override; + + virtual int GetGameType() override final { return NEO_GAME_TYPE_EMT; } + virtual const char* GetGameTypeName() override final { return "EMT"; } + const char* GetGameDescription() override final { return "Empty Gamemode"; } + virtual float GetRoundRemainingTime() const override final; + virtual bool GetTeamPlayEnabled() const override final { return true; } + virtual bool GetCompEnabled() const override final { return false; } + virtual bool GetCapPreventEnabled() const override final { return false; } + virtual bool CanChangeTeamClassLoadoutWhenAlive() const override final { return true; } + virtual bool RespawnsEnabled() const override final { return true; } + +#ifdef GAME_DLL + virtual bool FPlayerCanRespawn(CBasePlayer* pPlayer) override final; + + virtual void SetGameRelatedVars() override final {}; + virtual const int GetScoreLimit() const override final; + virtual const int GetRoundLimit() const override final; + virtual void RoundTimeout() override final; +#endif // GAME_DLL + virtual void Think() override final; +}; + +inline CNEORulesEMT *NEORulesEMT() +{ + return static_cast(g_pGameRules); +} \ No newline at end of file diff --git a/src/game/shared/neo/gamerules/neo_gamerules_jgr.cpp b/src/game/shared/neo/gamerules/neo_gamerules_jgr.cpp new file mode 100644 index 0000000000..9aca87f60e --- /dev/null +++ b/src/game/shared/neo/gamerules/neo_gamerules_jgr.cpp @@ -0,0 +1,281 @@ +#include "cbase.h" +#include "neo_gamerules_jgr.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +REGISTER_GAMERULES_CLASS( CNEORulesJGR ); + +BEGIN_NETWORK_TABLE_NOBASE( CNEORulesJGR, DT_NEORulesJGR ) +END_NETWORK_TABLE() + +LINK_ENTITY_TO_CLASS( neo_gamerules_jgr, CNEOGameRulesJGRProxy ); +IMPLEMENT_NETWORKCLASS_ALIASED( NEOGameRulesJGRProxy, DT_NEOGameRulesJGRProxy ); + +#ifdef CLIENT_DLL + void RecvProxy_NEORulesJGR( const RecvProp *pProp, void **pOut, + void *pData, int objectID ) + { + CNEORulesJGR *pRules = NEORulesJGR(); + Assert( pRules ); + *pOut = pRules; + } + + BEGIN_RECV_TABLE( CNEOGameRulesJGRProxy, DT_NEOGameRulesJGRProxy ) + RecvPropDataTable( "neo_gamerules_jgr_data", 0, 0, + &REFERENCE_RECV_TABLE( DT_NEORulesJGR ), + RecvProxy_NEORulesJGR ) + END_RECV_TABLE() +#else + void *SendProxy_NEORulesJGR( const SendProp *pProp, + const void *pStructBase, const void *pData, + CSendProxyRecipients *pRecipients, int objectID ) + { + CNEORulesJGR *pRules = NEORulesJGR(); + Assert( pRules ); + return pRules; + } + + BEGIN_SEND_TABLE(CNEOGameRulesJGRProxy, DT_NEOGameRulesJGRProxy) + SendPropDataTable("neo_gamerules_jgr_data", 0, + &REFERENCE_SEND_TABLE(DT_NEORulesJGR), + SendProxy_NEORulesJGR) + END_SEND_TABLE() +#endif + +ConVar sv_neo_jgr_round_limit("sv_neo_jgr_round_limit", "5", FCVAR_REPLICATED, "JGR max amount of rounds, 0 for no limit.", true, 0.0f, false, 0.0f); +ConVar sv_neo_jgr_round_timelimit("sv_neo_jgr_round_timelimit", "4.25", FCVAR_REPLICATED, "JGR round timelimit, in minutes.", true, 0.0f, false, 0.0f); +ConVar sv_neo_jgr_score_limit("sv_neo_jgr_score_limit", "0", FCVAR_REPLICATED, "JGR score limit", true, 0.0f, true, 99.0f); +ConVar sv_neo_jgr_max_points("sv_neo_jgr_max_points", "20", FCVAR_GAMEDLL, "Maximum points required for a team to win in JGR", true, 1, false, 0); + +extern bool RespawnWithRet(CBaseEntity *pEdict, bool fCopyCorpse); + +void CNEORulesJGR::FireGameEvent(IGameEvent* event) +{ + BaseClass::FireGameEvent(event); +} + +float CNEORulesJGR::GetRoundRemainingTime() const +{ + return BaseClass::GetRoundRemainingTime(sv_neo_jgr_round_timelimit.GetFloat()); +} + +#ifdef GAME_DLL +bool CNEORulesJGR::FPlayerCanRespawn(CBasePlayer* pPlayer) +{ + CNEO_Player* pNeoPlayer = ToNEOPlayer(pPlayer); + if (!pNeoPlayer) + { + Assert(false); + return false; + } + + // Special case for spectator player takeover + if (pNeoPlayer->GetSpectatorTakeoverPlayerPending()) + return true; + + if (pPlayer->GetTeamNumber() == m_iLastJuggernautTeam && + (m_pJuggernautPlayer || (gpGlobals->curtime - m_flJuggernautDeathTime) <= 8.0f)) + { + return false; + } + + if (NeoRoundStatus::PostRound == m_nRoundStatus) + return false; + + return true; +} + +extern ConVar sv_neo_dm_max_class_dur; +bool CNEORulesJGR::PlayerCanChangeLoadout(CNEO_Player* pPlayer) +{ + if (!pPlayer->m_bIneligibleForLoadoutPick && pPlayer->GetAliveDuration() < sv_neo_dm_max_class_dur.GetFloat()) + return true; + + return BaseClass::PlayerCanChangeLoadout(pPlayer); +} + +void CNEORulesJGR::EnemyPlayerKilled(CNEO_Player* pVictim, CNEO_Player* pAttacker, const CTakeDamageInfo& info) +{ + if (!IsRoundLive()) + return; + + if (pAttacker->GetClass() == NEO_CLASS_JUGGERNAUT) + { + auto jgrTeam = pAttacker->GetTeam(); + jgrTeam->SetScore(Min(jgrTeam->GetScore() + 2, sv_neo_jgr_max_points.GetInt())); + } + else if (m_pJuggernautPlayer) + { + const int attackerTeam = pAttacker->GetTeamNumber(); + const int jgrTeam = m_pJuggernautPlayer->GetTeamNumber(); + + if (attackerTeam == jgrTeam) + { + pAttacker->GetTeam()->AddScore(1); + } + } +} + +void CNEORulesJGR::SetGameRelatedVars() +{ + ResetTeamScores(); + ResetJGR(); + SpawnTheJuggernaut(); +} + +const int CNEORulesJGR::GetScoreLimit() const +{ + return sv_neo_jgr_score_limit.GetInt(); +} + +const int CNEORulesJGR::GetRoundLimit() const +{ + return sv_neo_jgr_round_limit.GetInt(); +} + +void CNEORulesJGR::RoundTimeout() +{ + if ((!m_pJuggernautPlayer && m_pJuggernautItem && !m_pJuggernautItem->IsBeingActivatedByLosingTeam()) || + (!m_pJuggernautPlayer && !m_pJuggernautItem)) // Juggernaut is absent entirely + { + if (GetGlobalTeam(TEAM_JINRAI)->GetScore() > GetGlobalTeam(TEAM_NSF)->GetScore()) + { + SetWinningTeam(TEAM_JINRAI, NEO_VICTORY_POINTS, false, true, false, false); + return; + } + + if (GetGlobalTeam(TEAM_NSF)->GetScore() > GetGlobalTeam(TEAM_JINRAI)->GetScore()) + { + SetWinningTeam(TEAM_NSF, NEO_VICTORY_POINTS, false, true, false, false); + return; + } + } + else + { + if (m_nRoundStatus == NeoRoundStatus::RoundLive) + { + m_nRoundStatus = NeoRoundStatus::Overtime; + } + + if (m_pJuggernautPlayer) + { + const int jgrTeam = m_pJuggernautPlayer->GetTeamNumber(); + const int oppositeTeam = (m_pJuggernautPlayer->GetTeamNumber() == TEAM_JINRAI ? TEAM_NSF : TEAM_JINRAI); + if (GetGlobalTeam(jgrTeam)->GetScore() > GetGlobalTeam(oppositeTeam)->GetScore()) + { + SetWinningTeam(jgrTeam, NEO_VICTORY_POINTS, false, true, false, false); + return; + } + } + + return; + } + + SetWinningTeam(TEAM_SPECTATOR, NEO_VICTORY_STALEMATE, false, false, true, false); +} +#endif // GAME_DLL + +extern ConVar sv_neo_preround_freeze_time; +void CNEORulesJGR::Think() +{ +#ifdef GAME_DLL + CGameRules::Think(); + + UpdateFromGameConfig(); + + if (RoundStartFromIdleOrPausedThink()) + return; + + PlayerRespawnThink(); + + CheckClantagsThink(); + + if (IsRoundPaused()) + return; + + GameOverThink(); + + if (CHL2MPRulesThink()) + return; + + TeamDamageThink(); + + if (RoundOverThink()) + return; + + RoundStatusThink(); + + JuggernautUnlockCheckWinCondition(); + +#endif // GAME_DLL +} + +#ifdef GAME_DLL +void CNEORulesJGR::PlayerRespawnThink() +{ + for (int i = 1; i <= gpGlobals->maxClients; i++) + { + auto player = static_cast(UTIL_PlayerByIndex(i)); + if (player && player->IsDead() && (IsRoundPaused() || player->DeathCount() > 0)) + { + const int playerTeam = player->GetTeamNumber(); + if ((playerTeam == TEAM_JINRAI || playerTeam == TEAM_NSF) && RespawnWithRet(player, false)) + { + player->m_bInAim = false; + player->m_bCarryingGhost = false; + player->m_bInThermOpticCamo = false; + player->m_bInVision = false; + player->m_bIneligibleForLoadoutPick = false; + player->SetTestMessageVisible(false); + + engine->ClientCommand(player->edict(), "loadoutmenu"); + } + } + } +} + +bool CNEORulesJGR::JuggernautUnlockCheckWinCondition() +{ + if (!IsRoundLive()) + return false; + + // Unlock the Juggernaut + if (m_pJuggernautItem && (gpGlobals->curtime > (m_flNeoRoundStartTime + sv_neo_preround_freeze_time.GetFloat()) + 20.0f) && IsJuggernautLocked()) + { + UTIL_CenterPrintAll("- JUGGERNAUT ENABLED -\n"); + + EmitSound_t soundParams; + soundParams.m_pSoundName = "HUD.GhostPickUp"; + soundParams.m_nChannel = CHAN_USER_BASE; + soundParams.m_bWarnOnDirectWaveReference = false; + soundParams.m_bEmitCloseCaption = false; + soundParams.m_SoundLevel = ATTN_TO_SNDLVL(ATTN_NONE); + + CRecipientFilter soundFilter; + soundFilter.AddAllPlayers(); + soundFilter.MakeReliable(); + m_pJuggernautItem->EmitSound(soundFilter, m_pJuggernautItem->entindex(), soundParams); + + m_pJuggernautItem->m_bLocked = false; + } + + // Check win condition + { + const int maxPoints = sv_neo_jgr_max_points.GetInt(); + if (GetGlobalTeam(TEAM_JINRAI)->GetScore() >= maxPoints) + { + SetWinningTeam(TEAM_JINRAI, NEO_VICTORY_POINTS, false, true, false, false); + return true; + } + + if (GetGlobalTeam(TEAM_NSF)->GetScore() >= maxPoints) + { + SetWinningTeam(TEAM_NSF, NEO_VICTORY_POINTS, false, true, false, false); + return true; + } + } + return false; +} + +#endif // GAME_DLL \ No newline at end of file diff --git a/src/game/shared/neo/gamerules/neo_gamerules_jgr.h b/src/game/shared/neo/gamerules/neo_gamerules_jgr.h new file mode 100644 index 0000000000..40640b6a0a --- /dev/null +++ b/src/game/shared/neo/gamerules/neo_gamerules_jgr.h @@ -0,0 +1,58 @@ +#pragma once + +#include "neo_gamerules.h" +#include "GameEventListener.h" + +#ifdef CLIENT_DLL + #define CNEORulesJGR C_NEORulesJGR + #define CNEOGameRulesJGRProxy C_NEOGameRulesJGRProxy +#endif + +class CNEOGameRulesJGRProxy : public CNEOGameRulesProxy +{ +public: + DECLARE_CLASS( CNEOGameRulesJGRProxy, CNEOGameRulesProxy ); + DECLARE_NETWORKCLASS(); +}; + +class CNEORulesJGR : public CNEORules, public CGameEventListener +{ +public: + DECLARE_CLASS(CNEORulesJGR, CNEORules); + DECLARE_NETWORKCLASS_NOBASE(); + + // IGameEventListener interface: + virtual void FireGameEvent(IGameEvent *event) override; + + virtual int GetGameType() override final { return NEO_GAME_TYPE_JGR; } + virtual const char* GetGameTypeName() override final { return "JGR"; } + const char* GetGameDescription() override final { return "Juggernaut"; } + virtual bool GetTeamPlayEnabled() const override final { return true; } + virtual bool GetCompEnabled() const override final { return true; } + virtual bool GetCapPreventEnabled() const override final { return false; } + virtual bool CanChangeTeamClassLoadoutWhenAlive() const override final { return false; } + virtual bool RespawnsEnabled() const override final { return true; } + + virtual float GetRoundRemainingTime() const override final; +#ifdef GAME_DLL + virtual bool FPlayerCanRespawn(CBasePlayer* pPlayer) override final; + virtual bool PlayerCanChangeLoadout(CNEO_Player* pPlayer) override final; + + virtual void EnemyPlayerKilled(CNEO_Player* pVictim, CNEO_Player* pAttacker, const CTakeDamageInfo& info) override final; + + virtual void SetGameRelatedVars() override final; + virtual const int GetScoreLimit() const override final; + virtual const int GetRoundLimit() const override final; + virtual void RoundTimeout() override final; +#endif // GAME_DLL + virtual void Think() override final; +#ifdef GAME_DLL + virtual void PlayerRespawnThink() override final; + bool JuggernautUnlockCheckWinCondition(); +#endif // GAME_DLL +}; + +inline CNEORulesJGR *NEORulesJGR() +{ + return static_cast(g_pGameRules); +} \ No newline at end of file diff --git a/src/game/shared/neo/gamerules/neo_gamerules_tdm.cpp b/src/game/shared/neo/gamerules/neo_gamerules_tdm.cpp new file mode 100644 index 0000000000..1175869456 --- /dev/null +++ b/src/game/shared/neo/gamerules/neo_gamerules_tdm.cpp @@ -0,0 +1,166 @@ +#include "cbase.h" +#include "neo_gamerules_tdm.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +REGISTER_GAMERULES_CLASS( CNEORulesTDM ); + +BEGIN_NETWORK_TABLE_NOBASE( CNEORulesTDM, DT_NEORulesTDM ) +END_NETWORK_TABLE() + +LINK_ENTITY_TO_CLASS( neo_gamerules_tdm, CNEOGameRulesTDMProxy ); +IMPLEMENT_NETWORKCLASS_ALIASED( NEOGameRulesTDMProxy, DT_NEOGameRulesTDMProxy ); + +#ifdef CLIENT_DLL + void RecvProxy_NEORulesTDM( const RecvProp *pProp, void **pOut, + void *pData, int objectID ) + { + CNEORulesTDM *pRules = NEORulesTDM(); + Assert( pRules ); + *pOut = pRules; + } + + BEGIN_RECV_TABLE( CNEOGameRulesTDMProxy, DT_NEOGameRulesTDMProxy ) + RecvPropDataTable( "neo_gamerules_tdm_data", 0, 0, + &REFERENCE_RECV_TABLE( DT_NEORulesTDM ), + RecvProxy_NEORulesTDM ) + END_RECV_TABLE() +#else + void *SendProxy_NEORulesTDM( const SendProp *pProp, + const void *pStructBase, const void *pData, + CSendProxyRecipients *pRecipients, int objectID ) + { + CNEORulesTDM *pRules = NEORulesTDM(); + Assert( pRules ); + return pRules; + } + + BEGIN_SEND_TABLE(CNEOGameRulesTDMProxy, DT_NEOGameRulesTDMProxy) + SendPropDataTable("neo_gamerules_tdm_data", 0, + &REFERENCE_SEND_TABLE(DT_NEORulesTDM), + SendProxy_NEORulesTDM) + END_SEND_TABLE() +#endif + +ConVar sv_neo_tdm_score_limit("sv_neo_tdm_score_limit", "1", FCVAR_REPLICATED, "TDM score limit", true, 0.0f, true, 99.0f); +ConVar sv_neo_tdm_round_limit("sv_neo_tdm_round_limit", "0", FCVAR_REPLICATED, "TDM max amount of rounds, 0 for no limit.", true, 0.0f, false, 0.0f); +ConVar sv_neo_tdm_round_timelimit("sv_neo_tdm_round_timelimit", "10.25", FCVAR_REPLICATED, "TDM round timelimit, in minutes.", true, 0.0f, false, 0.0f); + +extern bool RespawnWithRet(CBaseEntity *pEdict, bool fCopyCorpse); + +void CNEORulesTDM::FireGameEvent(IGameEvent* event) +{ + BaseClass::FireGameEvent(event); +} + +#ifdef GAME_DLL +void CNEORulesTDM::PlayerRespawnThink() +{ + for (int i = 1; i <= gpGlobals->maxClients; i++) + { + auto player = static_cast(UTIL_PlayerByIndex(i)); + if (player && player->IsDead() && (IsRoundPaused() || player->DeathCount() > 0)) + { + const int playerTeam = player->GetTeamNumber(); + if ((playerTeam == TEAM_JINRAI || playerTeam == TEAM_NSF) && RespawnWithRet(player, false)) + { + player->m_bInAim = false; + player->m_bCarryingGhost = false; + player->m_bInThermOpticCamo = false; + player->m_bInVision = false; + player->m_bIneligibleForLoadoutPick = false; + player->SetTestMessageVisible(false); + + engine->ClientCommand(player->edict(), "loadoutmenu"); + } + } + } +} +#endif // GAME_DLL + +float CNEORulesTDM::GetRoundRemainingTime() const +{ + return BaseClass::GetRoundRemainingTime(sv_neo_tdm_round_timelimit.GetFloat()); +} + +#ifdef GAME_DLL +bool CNEORulesTDM::FPlayerCanRespawn(CBasePlayer* pPlayer) +{ + if (NeoRoundStatus::PostRound == m_nRoundStatus) + return false; + + return true; +} + +extern ConVar sv_neo_dm_max_class_dur; +bool CNEORulesTDM::PlayerCanChangeLoadout(CNEO_Player* pPlayer) +{ + if (!pPlayer->m_bIneligibleForLoadoutPick && pPlayer->GetAliveDuration() < sv_neo_dm_max_class_dur.GetFloat()) + return true; + + return BaseClass::PlayerCanChangeLoadout(pPlayer); +} + +void CNEORulesTDM::SetGameRelatedVars() +{ + ResetTeamScores(); +} + +const int CNEORulesTDM::GetScoreLimit() const +{ + return sv_neo_tdm_score_limit.GetInt(); +} + +const int CNEORulesTDM::GetRoundLimit() const +{ + return sv_neo_tdm_round_limit.GetInt(); +} + +void CNEORulesTDM::RoundTimeout() +{ + if (GetGlobalTeam(TEAM_JINRAI)->GetScore() > GetGlobalTeam(TEAM_NSF)->GetScore()) + { + SetWinningTeam(TEAM_JINRAI, NEO_VICTORY_POINTS, false, true, false, false); + return; + } + if (GetGlobalTeam(TEAM_NSF)->GetScore() > GetGlobalTeam(TEAM_JINRAI)->GetScore()) + { + SetWinningTeam(TEAM_NSF, NEO_VICTORY_POINTS, false, true, false, false); + return; + } + + SetWinningTeam(TEAM_SPECTATOR, NEO_VICTORY_STALEMATE, false, false, true, false); +} +#endif // GAME_DLL + +void CNEORulesTDM::Think() +{ +#ifdef GAME_DLL + CGameRules::Think(); + + UpdateFromGameConfig(); + + if (RoundStartFromIdleOrPausedThink()) + return; + + PlayerRespawnThink(); + + CheckClantagsThink(); + + if (IsRoundPaused()) + return; + + GameOverThink(); + + if (CHL2MPRulesThink()) + return; + + TeamDamageThink(); + + if (RoundOverThink()) + return; + + RoundStatusThink(); +#endif // GAME_DLL +} \ No newline at end of file diff --git a/src/game/shared/neo/gamerules/neo_gamerules_tdm.h b/src/game/shared/neo/gamerules/neo_gamerules_tdm.h new file mode 100644 index 0000000000..ea9882610c --- /dev/null +++ b/src/game/shared/neo/gamerules/neo_gamerules_tdm.h @@ -0,0 +1,55 @@ +#pragma once + +#include "neo_gamerules.h" +#include "GameEventListener.h" + +#ifdef CLIENT_DLL + #define CNEORulesTDM C_NEORulesTDM + #define CNEOGameRulesTDMProxy C_NEOGameRulesTDMProxy +#endif + +class CNEOGameRulesTDMProxy : public CNEOGameRulesProxy +{ +public: + DECLARE_CLASS( CNEOGameRulesTDMProxy, CNEOGameRulesProxy ); + DECLARE_NETWORKCLASS(); +}; + +class CNEORulesTDM : public CNEORules, public CGameEventListener +{ +public: + DECLARE_CLASS(CNEORulesTDM, CNEORules); + DECLARE_NETWORKCLASS_NOBASE(); + + // IGameEventListener interface: + virtual void FireGameEvent(IGameEvent *event) override; + + virtual int GetGameType() override final { return NEO_GAME_TYPE_TDM; } + virtual const char* GetGameTypeName() override final { return "TDM"; } + const char* GetGameDescription() override final { return "Team Deathmatch"; } + virtual bool GetTeamPlayEnabled() const override final { return true; } + virtual bool GetCompEnabled() const override final { return false; } + virtual bool GetCapPreventEnabled() const override final { return false; } + virtual bool CanChangeTeamClassLoadoutWhenAlive() const override final { return false; } + virtual bool RespawnsEnabled() const override final { return true; } + + virtual float GetRoundRemainingTime() const override final; +#ifdef GAME_DLL + virtual bool FPlayerCanRespawn(CBasePlayer* pPlayer) override final; + virtual bool PlayerCanChangeLoadout(CNEO_Player* pPlayer) override final; + + virtual void SetGameRelatedVars() override final; + virtual const int GetScoreLimit() const override final; + virtual const int GetRoundLimit() const override final; + virtual void RoundTimeout() override final; +#endif // GAME_DLL + virtual void Think() override final; +#ifdef GAME_DLL + virtual void PlayerRespawnThink() override final; +#endif // GAME_DLL +}; + +inline CNEORulesTDM *NEORulesTDM() +{ + return static_cast(g_pGameRules); +} \ No newline at end of file diff --git a/src/game/shared/neo/gamerules/neo_gamerules_tut.cpp b/src/game/shared/neo/gamerules/neo_gamerules_tut.cpp new file mode 100644 index 0000000000..e66b35f4d6 --- /dev/null +++ b/src/game/shared/neo/gamerules/neo_gamerules_tut.cpp @@ -0,0 +1,112 @@ +#include "cbase.h" +#include "neo_gamerules_tut.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +REGISTER_GAMERULES_CLASS( CNEORulesTUT ); + +BEGIN_NETWORK_TABLE_NOBASE( CNEORulesTUT, DT_NEORulesTUT ) +END_NETWORK_TABLE() + +LINK_ENTITY_TO_CLASS( neo_gamerules_tut, CNEOGameRulesTUTProxy ); +IMPLEMENT_NETWORKCLASS_ALIASED( NEOGameRulesTUTProxy, DT_NEOGameRulesTUTProxy ); + +#ifdef CLIENT_DLL + void RecvProxy_NEORulesTUT( const RecvProp *pProp, void **pOut, + void *pData, int objectID ) + { + CNEORulesTUT *pRules = NEORulesTUT(); + Assert( pRules ); + *pOut = pRules; + } + + BEGIN_RECV_TABLE( CNEOGameRulesTUTProxy, DT_NEOGameRulesTUTProxy ) + RecvPropDataTable( "neo_gamerules_tut_data", 0, 0, + &REFERENCE_RECV_TABLE( DT_NEORulesTUT ), + RecvProxy_NEORulesTUT ) + END_RECV_TABLE() +#else + void *SendProxy_NEORulesTUT( const SendProp *pProp, + const void *pStructBase, const void *pData, + CSendProxyRecipients *pRecipients, int objectID ) + { + CNEORulesTUT *pRules = NEORulesTUT(); + Assert( pRules ); + return pRules; + } + + BEGIN_SEND_TABLE(CNEOGameRulesTUTProxy, DT_NEOGameRulesTUTProxy) + SendPropDataTable("neo_gamerules_tut_data", 0, + &REFERENCE_SEND_TABLE(DT_NEORulesTUT), + SendProxy_NEORulesTUT) + END_SEND_TABLE() +#endif + +void CNEORulesTUT::FireGameEvent(IGameEvent* event) +{ + BaseClass::FireGameEvent(event); +} + +float CNEORulesTUT::GetRoundRemainingTime() const +{ +#ifdef GAME_DLL + Assert(false); // Shouldn't be calling this server side +#endif // GAME_DLL + return 0.f; +} + +#ifdef GAME_DLL +bool CNEORulesTUT::FPlayerCanRespawn(CBasePlayer* pPlayer) +{ + if (pPlayer->IsAlive()) + return false; + + return true; +} + +const int CNEORulesTUT::GetScoreLimit() const +{ + Assert(false); + return 1; +} + +const int CNEORulesTUT::GetRoundLimit() const +{ + Assert(false); + return 1; +} + +void CNEORulesTUT::RoundTimeout() +{ + Assert(false); +} +#endif // GAME_DLL + +void CNEORulesTUT::Think() +{ +#ifdef GAME_DLL + CGameRules::Think(); + + UpdateFromGameConfig(); + + { + // This is kind of wonky, but we only need it for the tutorial, in order to play the dummy beacon sounds... + if (!m_pGhost) + { + auto pEnt = gEntList.FirstEnt(); + while (pEnt) + { + if (dynamic_cast(pEnt)) + { + m_pGhost = static_cast(pEnt); + m_hGhost = m_pGhost; + return; + } + pEnt = gEntList.NextEnt(pEnt); + } + } + if (m_pGhost) m_pGhost->UpdateNearestGhostBeaconDist(); + } +#endif // GAME_DLL +} \ No newline at end of file diff --git a/src/game/shared/neo/gamerules/neo_gamerules_tut.h b/src/game/shared/neo/gamerules/neo_gamerules_tut.h new file mode 100644 index 0000000000..32712f2f65 --- /dev/null +++ b/src/game/shared/neo/gamerules/neo_gamerules_tut.h @@ -0,0 +1,51 @@ +#pragma once + +#include "neo_gamerules.h" +#include "GameEventListener.h" + +#ifdef CLIENT_DLL + #define CNEORulesTUT C_NEORulesTUT + #define CNEOGameRulesTUTProxy C_NEOGameRulesTUTProxy +#endif + +class CNEOGameRulesTUTProxy : public CNEOGameRulesProxy +{ +public: + DECLARE_CLASS( CNEOGameRulesTUTProxy, CNEOGameRulesProxy ); + DECLARE_NETWORKCLASS(); +}; + +class CNEORulesTUT : public CNEORules, public CGameEventListener +{ +public: + DECLARE_CLASS(CNEORulesTUT, CNEORules); + DECLARE_NETWORKCLASS_NOBASE(); + + // IGameEventListener interface: + virtual void FireGameEvent(IGameEvent *event) override; + + virtual int GetGameType() override final { return NEO_GAME_TYPE_TUT; } + virtual const char* GetGameTypeName() override final { return "TUT"; } + const char* GetGameDescription() override final { return "Training"; } + virtual bool GetTeamPlayEnabled() const override final { return true; } + virtual bool GetCompEnabled() const override final { return false; } + virtual bool GetCapPreventEnabled() const override final { return false; } + virtual bool CanChangeTeamClassLoadoutWhenAlive() const override final { return false; } + virtual bool RespawnsEnabled() const override final { return true; } + + virtual float GetRoundRemainingTime() const override final; +#ifdef GAME_DLL + virtual bool FPlayerCanRespawn(CBasePlayer* pPlayer) override final; + + virtual void SetGameRelatedVars() override final {}; + virtual const int GetScoreLimit() const override final; + virtual const int GetRoundLimit() const override final; + virtual void RoundTimeout() override final; +#endif // GAME_DLL + virtual void Think() override final; +}; + +inline CNEORulesTUT *NEORulesTUT() +{ + return static_cast(g_pGameRules); +} \ No newline at end of file diff --git a/src/game/shared/neo/gamerules/neo_gamerules_vip.cpp b/src/game/shared/neo/gamerules/neo_gamerules_vip.cpp new file mode 100644 index 0000000000..bf19babca0 --- /dev/null +++ b/src/game/shared/neo/gamerules/neo_gamerules_vip.cpp @@ -0,0 +1,210 @@ +#include "cbase.h" +#include "neo_gamerules_vip.h" + +#ifdef GAME_DLL + #include "neo_ghost_cap_point.h" +#else +#endif // GAME_DLL + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +REGISTER_GAMERULES_CLASS( CNEORulesVIP ); + +BEGIN_NETWORK_TABLE_NOBASE( CNEORulesVIP, DT_NEORulesVIP ) +END_NETWORK_TABLE() + +LINK_ENTITY_TO_CLASS( neo_gamerules_vip, CNEOGameRulesVIPProxy ); +IMPLEMENT_NETWORKCLASS_ALIASED( NEOGameRulesVIPProxy, DT_NEOGameRulesVIPProxy ); + +#ifdef CLIENT_DLL + void RecvProxy_NEORulesVIP( const RecvProp *pProp, void **pOut, + void *pData, int objectID ) + { + CNEORulesVIP *pRules = NEORulesVIP(); + Assert( pRules ); + *pOut = pRules; + } + + BEGIN_RECV_TABLE( CNEOGameRulesVIPProxy, DT_NEOGameRulesVIPProxy ) + RecvPropDataTable( "neo_gamerules_vip_data", 0, 0, + &REFERENCE_RECV_TABLE( DT_NEORulesVIP ), + RecvProxy_NEORulesVIP ) + END_RECV_TABLE() +#else + void *SendProxy_NEORulesVIP( const SendProp *pProp, + const void *pStructBase, const void *pData, + CSendProxyRecipients *pRecipients, int objectID ) + { + CNEORulesVIP *pRules = NEORulesVIP(); + Assert( pRules ); + return pRules; + } + + BEGIN_SEND_TABLE(CNEOGameRulesVIPProxy, DT_NEOGameRulesVIPProxy) + SendPropDataTable("neo_gamerules_vip_data", 0, + &REFERENCE_SEND_TABLE(DT_NEORulesVIP), + SendProxy_NEORulesVIP) + END_SEND_TABLE() +#endif + +ConVar sv_neo_vip_round_limit("sv_neo_vip_round_limit", "0", FCVAR_REPLICATED, "VIP max amount of rounds, 0 for no limit.", true, 0.0f, false, 0.0f); +ConVar sv_neo_vip_round_timelimit("sv_neo_vip_round_timelimit", "3.25", FCVAR_REPLICATED, "VIP round timelimit, in minutes.", true, 0.0f, false, 0.0f); +ConVar sv_neo_vip_score_limit("sv_neo_vip_score_limit", "7", FCVAR_REPLICATED, "VIP score limit", true, 0.0f, true, 99.0f); + +ConVar sv_neo_vip_ctg_on_death("sv_neo_vip_ctg_on_death", "0", FCVAR_ARCHIVE, "Spawn Ghost when VIP dies, continue the game", true, 0, true, 1); + +void CNEORulesVIP::FireGameEvent(IGameEvent* event) +{ + BaseClass::FireGameEvent(event); +} + +float CNEORulesVIP::GetRoundRemainingTime() const +{ + return BaseClass::GetRoundRemainingTime(sv_neo_vip_round_timelimit.GetFloat()); +} + +#ifdef GAME_DLL +void CNEORulesVIP::SetGameRelatedVars() +{ + ResetVIP(); + + if (!m_iEscortingTeam) + { + m_iEscortingTeam.Set(RandomInt(TEAM_JINRAI, TEAM_NSF)); + } + else + { + m_iEscortingTeam.Set(m_iEscortingTeam.Get() == TEAM_JINRAI ? TEAM_NSF : TEAM_JINRAI); + } + + SelectTheVIP(); +} + +const int CNEORulesVIP::GetScoreLimit() const +{ + return sv_neo_vip_score_limit.GetInt(); +} + +const int CNEORulesVIP::GetRoundLimit() const +{ + return sv_neo_vip_round_limit.GetInt(); +} +#endif // GAME_DLL + +void CNEORulesVIP::Think() +{ +#ifdef GAME_DLL + CGameRules::Think(); + + UpdateFromGameConfig(); + + if (RoundStartFromIdleOrPausedThink()) + return; + + PlayerRespawnThink(); + + CheckClantagsThink(); + + if (IsRoundPaused()) + return; + + GameOverThink(); + + CheckOvertime(); + + if (CHL2MPRulesThink()) + return; + + TeamDamageThink(); + + if (RoundOverThink()) + return; + + RoundStatusThink(); + + // Check win condition + if (!m_pGhost) + { + if (!m_pVIP) + { + if (sv_neo_vip_ctg_on_death.GetBool()) + { + UTIL_CenterPrintAll("- HVT DOWN - RECOVER THE GHOST -\n"); + SpawnTheGhost(); + } + else + { + // Assume vip player disconnected, forfeit round + SetWinningTeam(GetOpposingTeam(m_iEscortingTeam), NEO_VICTORY_FORFEIT, false, true, false, false); + } + + if (IGameEvent* event = gameeventmanager->CreateEvent("vip_death")) + { + gameeventmanager->FireEvent(event); + } + } + else if (!m_pVIP->IsAlive()) + { + if (sv_neo_vip_ctg_on_death.GetBool()) + { + UTIL_CenterPrintAll("- HVT DOWN - RECOVER THE GHOST -\n"); + SpawnTheGhost(&m_pVIP->GetAbsOrigin()); + } + else + { + // VIP was killed, end round + SetWinningTeam(GetOpposingTeam(m_iEscortingTeam), NEO_VICTORY_VIP_ELIMINATION, false, true, false, false); + } + + if (IGameEvent* event = gameeventmanager->CreateEvent("vip_death")) + { + event->SetInt("userid", m_pVIP->GetUserID()); + gameeventmanager->FireEvent(event); + } + } + + // Check if the vip was escorted during this Think + int captorTeam, captorClient; + for (int i = 0; i < m_pGhostCaps.Count(); i++) + { + auto pGhostCap = dynamic_cast(UTIL_EntityByIndex(m_pGhostCaps[i])); + if (!pGhostCap) + { + Assert(false); + continue; + } + + // If vip was escorted + if (pGhostCap->IsGhostCaptured(captorTeam, captorClient)) + { + // Turn off all capzones + for (int i = 0; i < m_pGhostCaps.Count(); i++) + { + auto pGhostCap = dynamic_cast(UTIL_EntityByIndex(m_pGhostCaps[i])); + if (!pGhostCap) + { + Assert(false); + continue; + } + pGhostCap->SetActive(false); + } + + // And then announce team victory + SetWinningTeam(captorTeam, NEO_VICTORY_VIP_ESCORT, false, true, false, false); + + if (IGameEvent* event = gameeventmanager->CreateEvent("vip_extract")) + { + CBasePlayer* pCaptorClient = UTIL_PlayerByIndex(captorClient); + event->SetInt("userid", pCaptorClient ? pCaptorClient->GetUserID() : INVALID_USER_ID); + gameeventmanager->FireEvent(event); + } + + break; + } + } + } + + CheckWinByElimination(); +#endif // GAME_DLL +} \ No newline at end of file diff --git a/src/game/shared/neo/gamerules/neo_gamerules_vip.h b/src/game/shared/neo/gamerules/neo_gamerules_vip.h new file mode 100644 index 0000000000..79ab3b3529 --- /dev/null +++ b/src/game/shared/neo/gamerules/neo_gamerules_vip.h @@ -0,0 +1,48 @@ +#pragma once + +#include "neo_gamerules.h" +#include "GameEventListener.h" + +#ifdef CLIENT_DLL + #define CNEORulesVIP C_NEORulesVIP + #define CNEOGameRulesVIPProxy C_NEOGameRulesVIPProxy +#endif + +class CNEOGameRulesVIPProxy : public CNEOGameRulesProxy +{ +public: + DECLARE_CLASS( CNEOGameRulesVIPProxy, CNEOGameRulesProxy ); + DECLARE_NETWORKCLASS(); +}; + +class CNEORulesVIP : public CNEORules, public CGameEventListener +{ +public: + DECLARE_CLASS(CNEORulesVIP, CNEORules); + DECLARE_NETWORKCLASS_NOBASE(); + + // IGameEventListener interface: + virtual void FireGameEvent(IGameEvent *event) override; + + virtual int GetGameType() override final { return NEO_GAME_TYPE_VIP; } + virtual const char* GetGameTypeName() override final { return "VIP"; } + const char* GetGameDescription() override final { return "Protect or Eliminate the VIP"; } + virtual float GetRoundRemainingTime() const override final; + virtual bool GetTeamPlayEnabled() const override final { return true; } + virtual bool GetCompEnabled() const override final { return true; } + virtual bool GetCapPreventEnabled() const override final { return true; } + virtual bool CanChangeTeamClassLoadoutWhenAlive() const override final { return false; } + virtual bool RespawnsEnabled() const override final { return false; } + +#ifdef GAME_DLL + virtual void SetGameRelatedVars() override final; + virtual const int GetScoreLimit() const override final; + virtual const int GetRoundLimit() const override final; +#endif // GAME_DLL + virtual void Think() override final; +}; + +inline CNEORulesVIP *NEORulesVIP() +{ + return static_cast(g_pGameRules); +} \ No newline at end of file diff --git a/src/game/shared/neo/neo_player_spawnpoint.cpp b/src/game/shared/neo/neo_player_spawnpoint.cpp index f3acb0f902..3085af1dca 100644 --- a/src/game/shared/neo/neo_player_spawnpoint.cpp +++ b/src/game/shared/neo/neo_player_spawnpoint.cpp @@ -10,25 +10,25 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" -class CNEOSpawnPoint_Jinrai : public CNEOSpawnPoint +class CNEOSpawnPoint_Attacker : public CNEOSpawnPoint { public: - CNEOSpawnPoint_Jinrai() : CNEOSpawnPoint() + CNEOSpawnPoint_Attacker() : CNEOSpawnPoint() { - m_iOwningTeam = TEAM_JINRAI; + m_eSide = E_TeamSide::Attacker; } }; -LINK_ENTITY_TO_CLASS(info_player_attacker, CNEOSpawnPoint_Jinrai); +LINK_ENTITY_TO_CLASS(info_player_attacker, CNEOSpawnPoint_Attacker); -class CNEOSpawnPoint_NSF : public CNEOSpawnPoint +class CNEOSpawnPoint_Defender : public CNEOSpawnPoint { public: - CNEOSpawnPoint_NSF() : CNEOSpawnPoint() + CNEOSpawnPoint_Defender() : CNEOSpawnPoint() { - m_iOwningTeam = TEAM_NSF; + m_eSide = E_TeamSide::Defender; } }; -LINK_ENTITY_TO_CLASS(info_player_defender, CNEOSpawnPoint_NSF); +LINK_ENTITY_TO_CLASS(info_player_defender, CNEOSpawnPoint_Defender); #ifdef GAME_DLL IMPLEMENT_SERVERCLASS_ST(CNEOSpawnPoint, DT_NEOSpawnPoint) @@ -55,7 +55,7 @@ END_DATADESC() CNEOSpawnPoint::CNEOSpawnPoint() { - m_iOwningTeam = TEAM_UNASSIGNED; + m_eSide = E_TeamSide::Unspecified; } CNEOSpawnPoint::~CNEOSpawnPoint() @@ -69,7 +69,7 @@ void CNEOSpawnPoint::Spawn() { BaseClass::Spawn(); - AssertMsg(m_iOwningTeam == TEAM_JINRAI || m_iOwningTeam == TEAM_NSF || m_iOwningTeam == TEAM_ANY, + AssertMsg(m_eSide == E_TeamSide::Unspecified || m_eSide == E_TeamSide::Attacker || m_eSide == E_TeamSide::Defender, "CNEOSpawnPoint shouldn't be instantiated directly; use info_player_attacker/defender instead!\n"); #if(0) @@ -85,13 +85,20 @@ void CNEOSpawnPoint::Spawn() int CNEOSpawnPoint::GetOwningTeam() const { - const bool alternate = NEORules()->roundNumberIsEven(); - int owningTeam = m_iOwningTeam; - if (!alternate && owningTeam != TEAM_ANY) + switch (m_eSide) { - owningTeam = (owningTeam == TEAM_JINRAI) ? TEAM_NSF : (owningTeam == TEAM_NSF) ? TEAM_JINRAI : owningTeam; + case E_TeamSide::Attacker: + return NEORules()->GetAttackingTeam(); + case E_TeamSide::Defender: + return NEORules()->GetDefendingTeam(); + default: + return TEAM_ANY; } - return owningTeam; +} + +E_TeamSide CNEOSpawnPoint::GetSide() const +{ + return m_eSide; } #ifdef GAME_DLL diff --git a/src/game/shared/neo/neo_player_spawnpoint.h b/src/game/shared/neo/neo_player_spawnpoint.h index 9095a603b3..90598453e8 100644 --- a/src/game/shared/neo/neo_player_spawnpoint.h +++ b/src/game/shared/neo/neo_player_spawnpoint.h @@ -12,6 +12,13 @@ #define CNEOSpawnPoint C_NEOSpawnPoint #endif +enum class E_TeamSide +{ + Unspecified, + Attacker, + Defender +}; + class CNEOSpawnPoint : public CBaseEntity { DECLARE_CLASS(CNEOSpawnPoint, CBaseEntity); @@ -29,6 +36,7 @@ class CNEOSpawnPoint : public CBaseEntity virtual void Spawn() override; int GetOwningTeam() const; + E_TeamSide GetSide() const; #ifdef GAME_DLL bool m_bDisabled; @@ -40,7 +48,7 @@ class CNEOSpawnPoint : public CBaseEntity #endif protected: - int m_iOwningTeam; + E_TeamSide m_eSide; private: CNEOSpawnPoint(const CNEOSpawnPoint &other); diff --git a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp index d9783b1b2c..37ce211b5a 100644 --- a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp +++ b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp @@ -1356,7 +1356,7 @@ void CNEOBaseCombatWeapon::SetPickupTouch(void) return; } - if (!weaponstay.GetBool() || NEORules()->CanRespawnAnyTime()) + if (!weaponstay.GetBool() || NEORules()->RespawnsEnabled()) { // regardless of the value of mp_weaponstay, disappear weapons in game modes with respawns enabled. Otherwise things can get too chaotic BaseClass::SetPickupTouch(); return;