diff --git a/cfg/cs2fixes/cs2fixes.cfg b/cfg/cs2fixes/cs2fixes.cfg index 579b05d1..a623a612 100644 --- a/cfg/cs2fixes/cs2fixes.cfg +++ b/cfg/cs2fixes/cs2fixes.cfg @@ -33,4 +33,9 @@ cs2f_rtv_endround 0 // Whether to immediately end the round when RTV succeed zr_enable 0 // Whether to enable ZR features zr_knockback_scale 5.0 // Global knockback scale zr_ztele_max_distance 150.0 // Maximum distance players are allowed to move after starting ztele -zr_ztele_allow_humans 0 // Whether to allow humans to use ztele \ No newline at end of file +zr_ztele_allow_humans 0 // Whether to allow humans to use ztele +zr_infect_spawn_type 1 //Type of Mother Zombies Spawn [0 = MZ spawn where they stand, 1 = MZ get teleported back to spawn on being picked] +zr_infect_spawn_time_min 15 //Minimum time in which Mother Zombies should be picked, after round start +zr_infect_spawn_time_max 15 //Maximum time in which Mother Zombies should be picked, after round start +zr_infect_spawn_mz_ratio 7 //Ratio of all Players to Mother Zombies to be spawned at round start +zr_infect_spawn_mz_min_count 2 //Minimum amount of Mother Zombies to be spawned at round start \ No newline at end of file diff --git a/gamedata/cs2fixes.games.txt b/gamedata/cs2fixes.games.txt index dc93f641..d1a7ea08 100644 --- a/gamedata/cs2fixes.games.txt +++ b/gamedata/cs2fixes.games.txt @@ -262,6 +262,19 @@ "windows" "\x40\x53\x48\x83\xEC\x20\x8B\x91\x38\x0B\x00\x00\x48\x8B\xD9" "linux" "\x8B\x8F\x40\x0E\x00\x00\x83\xF9\xFF\x0F\x84\xD9\x01" } + // Search "Changes's player's model", look for a function containing 'models/%s.vmdl'. Below V_snprintf is the one + "CBaseModelEntity_SetModel" + { + "library" "server" + "windows" "\x48\x89\x5C\x24\x2A\x48\x89\x7C\x24\x2A\x55\x48\x8B\xEC\x48\x83\xEC\x50\x48\x8B\xF9" + "linux" "\x55\x48\x89\xF2\x48\x89\xE5\x41\x54\x49\x89\xFC\x48\x8D\x7D\xE0\x48\x83\xEC\x18\x48\x8D\x05\x3D\xD7\xBF\x00" + } + "CGameRules_TerminateRound" + { + "library" "server" + "windows" "\x48\x8B\xC4\x4C\x89\x48\x20\x55\x56" + "linux" "\x55\x48\x89\xE5\x41\x57\x41\x56\x41\x55\x49\x89\xFD\x41\x54\x53\x48\x81\xEC\xE8\x01\x00\x00" + } } "Offsets" { diff --git a/src/addresses.cpp b/src/addresses.cpp index a20a226e..a1e2cd68 100644 --- a/src/addresses.cpp +++ b/src/addresses.cpp @@ -56,11 +56,13 @@ bool addresses::Initialize(CGameConfig *g_GameConfig) RESOLVE_SIG(g_GameConfig, "SetGroundEntity", addresses::SetGroundEntity); RESOLVE_SIG(g_GameConfig, "CCSPlayerController_SwitchTeam", addresses::CCSPlayerController_SwitchTeam); RESOLVE_SIG(g_GameConfig, "CCSPlayerPawn_Respawn", addresses::CCSPlayerPawn_Respawn); + RESOLVE_SIG(g_GameConfig, "CBaseModelEntity_SetModel", addresses::CBaseModelEntity_SetModel); RESOLVE_SIG(g_GameConfig, "UTIL_Remove", addresses::UTIL_Remove); RESOLVE_SIG(g_GameConfig, "CEntitySystem_AddEntityIOEvent", addresses::CEntitySystem_AddEntityIOEvent); RESOLVE_SIG(g_GameConfig, "CEntityInstance_AcceptInput", addresses::CEntityInstance_AcceptInput); RESOLVE_SIG(g_GameConfig, "CGameEntitySystem_FindEntityByClassName", addresses::CGameEntitySystem_FindEntityByClassName); RESOLVE_SIG(g_GameConfig, "CGameEntitySystem_FindEntityByName", addresses::CGameEntitySystem_FindEntityByName); + RESOLVE_SIG(g_GameConfig, "CGameRules_TerminateRound", addresses::CGameRules_TerminateRound); return true; } diff --git a/src/addresses.h b/src/addresses.h index 6fe4e744..748e39a5 100644 --- a/src/addresses.h +++ b/src/addresses.h @@ -40,11 +40,13 @@ class CEntityInstance; class CBasePlayerController; class CCSPlayerController; class CCSPlayerPawn; +class CBaseModelEntity; class Z_CBaseEntity; class CGameConfig; class CEntitySystem; class IEntityFindFilter; struct variant_string_t; +class CGameRules; namespace addresses { @@ -57,6 +59,7 @@ namespace addresses inline void(FASTCALL *SetGroundEntity)(Z_CBaseEntity *ent, Z_CBaseEntity *ground); inline void(FASTCALL *CCSPlayerController_SwitchTeam)(CCSPlayerController *pController, uint32 team); inline void(FASTCALL *CCSPlayerPawn_Respawn)(CCSPlayerPawn *pPawn); + inline void(FASTCALL *CBaseModelEntity_SetModel)(CBaseModelEntity *pModel, const char *szModel); inline void(FASTCALL *UTIL_Remove)(CEntityInstance*); inline void(FASTCALL *CEntitySystem_AddEntityIOEvent)(CEntitySystem *pEntitySystem, CEntityInstance *pTarget, const char *pszInput, @@ -70,4 +73,5 @@ namespace addresses inline Z_CBaseEntity *(FASTCALL *CGameEntitySystem_FindEntityByName)(CEntitySystem *pEntitySystem, CEntityInstance *pStartEntity, const char *szName, CEntityInstance *pSearchingEntity, CEntityInstance *pActivator, CEntityInstance *pCaller, IEntityFindFilter *pFilter); + inline void(FASTCALL *CGameRules_TerminateRound)(CGameRules* pGameRules, float delay, unsigned int reason, int64 a4, unsigned int a5); } diff --git a/src/cs2_sdk/entity/cbasemodelentity.h b/src/cs2_sdk/entity/cbasemodelentity.h index 28a34e8a..ad95f788 100644 --- a/src/cs2_sdk/entity/cbasemodelentity.h +++ b/src/cs2_sdk/entity/cbasemodelentity.h @@ -29,4 +29,9 @@ class CBaseModelEntity : public Z_CBaseEntity SCHEMA_FIELD(CCollisionProperty , m_Collision) SCHEMA_FIELD(CGlowProperty, m_Glow) + + void SetModel(const char *szModel) + { + addresses::CBaseModelEntity_SetModel(this, szModel); + } }; \ No newline at end of file diff --git a/src/cs2_sdk/entity/cgamerules.h b/src/cs2_sdk/entity/cgamerules.h index aedbc4ab..e949405e 100644 --- a/src/cs2_sdk/entity/cgamerules.h +++ b/src/cs2_sdk/entity/cgamerules.h @@ -26,6 +26,7 @@ class CGameRules { public: DECLARE_SCHEMA_CLASS(CGameRules) + }; class CCSGameRules : public CGameRules @@ -38,6 +39,11 @@ class CCSGameRules : public CGameRules SCHEMA_FIELD(int, m_totalRoundsPlayed) SCHEMA_FIELD(GameTime_t, m_fRoundStartTime) SCHEMA_FIELD(GameTime_t, m_flRestartRoundTime) + + void TerminateRound(float delay, unsigned int reason) + { + addresses::CGameRules_TerminateRound(this, delay, reason, 0, 0); + } }; class CCSGameRulesProxy : public Z_CBaseEntity @@ -45,5 +51,33 @@ class CCSGameRulesProxy : public Z_CBaseEntity public: DECLARE_SCHEMA_CLASS(CCSGameRulesProxy) - SCHEMA_FIELD(CCSGameRules*, m_pGameRules) + SCHEMA_FIELD(CCSGameRules *, m_pGameRules) +}; + +enum CSRoundEndReason +{ + TargetBombed = 1, /**< Target Successfully Bombed! */ + + VIPEscaped, /**< The VIP has escaped! - Doesn't exist on CS:GO */ + VIPKilled, /**< VIP has been assassinated! - Doesn't exist on CS:GO */ + + TerroristsEscaped, /**< The terrorists have escaped! */ + CTStoppedEscape, /**< The CTs have prevented most of the terrorists from escaping! */ + TerroristsStopped, /**< Escaping terrorists have all been neutralized! */ + BombDefused, /**< The bomb has been defused! */ + CTWin, /**< Counter-Terrorists Win! */ + TerroristWin, /**< Terrorists Win! */ + Draw, /**< Round Draw! */ + HostagesRescued, /**< All Hostages have been rescued! */ + TargetSaved, /**< Target has been saved! */ + HostagesNotRescued, /**< Hostages have not been rescued! */ + TerroristsNotEscaped, /**< Terrorists have not escaped! */ + VIPNotEscaped, /**< VIP has not escaped! - Doesn't exist on CS:GO */ + GameStart, /**< Game Commencing! */ + TerroristsSurrender, /**< Terrorists Surrender */ + CTSurrender, /**< CTs Surrender */ + TerroristsPlanted, /**< Terrorists Planted the bomb */ + CTsReachedHostage, /**< CTs Reached the hostage */ + SurvivalWin, + SurvivalDraw }; \ No newline at end of file diff --git a/src/events.cpp b/src/events.cpp index b0feceb6..8bb21f16 100644 --- a/src/events.cpp +++ b/src/events.cpp @@ -261,4 +261,16 @@ GAME_EVENT_F(round_end) pPlayer->SetTotalDamage(0); } +} + +GAME_EVENT_F(round_freeze_end) +{ + if (g_bEnableZR) + ZR_OnRoundFreezeEnd(pEvent); +} + +GAME_EVENT_F(player_death) +{ + if (g_bEnableZR) + ZR_OnPlayerDeath(pEvent); } \ No newline at end of file diff --git a/src/zombiereborn.cpp b/src/zombiereborn.cpp index 4f552d01..dfd49c2c 100644 --- a/src/zombiereborn.cpp +++ b/src/zombiereborn.cpp @@ -18,6 +18,7 @@ */ #include "commands.h" +#include "utils/entity.h" #include "playermanager.h" #include "ctimer.h" #include "eventlistener.h" @@ -34,35 +35,89 @@ extern CCSGameRules* g_pGameRules; extern IGameEventManager2* g_gameEventManager; void ZR_Infect(CCSPlayerController *pAttackerController, CCSPlayerController *pVictimController, bool bBroadcast); +void ZR_CheckWinConditions(bool bCheckCT); EZRRoundState g_ZRRoundState = EZRRoundState::ROUND_START; +static int g_iRoundNum = 0; +static int g_iInfectionCountDown = 0; // CONVAR_TODO bool g_bEnableZR = false; static float g_flMaxZteleDistance = 150.0f; static bool g_bZteleHuman = false; - -CON_COMMAND_F(zr_enable, "Whether to enable ZR features", FCVAR_LINKED_CONCOMMAND | FCVAR_SPONLY) +static float g_flKnockbackScale = 5.0f; +static int g_flInfectSpawnType = EZRSpawnType::RESPAWN; +static int g_flInfectSpawnTimeMin = 15; +static int g_flInfectSpawnTimeMax = 15; +static int g_flInfectSpawnMZRatio = 7; +static int g_flInfectSpawnMinCount = 2; + +CON_ZR_CVAR(zr_enable, "Whether to enable ZR features", g_bEnableZR, Bool, false) +CON_ZR_CVAR(zr_ztele_max_distance, "Maximum distance players are allowed to move after starting ztele", g_flMaxZteleDistance, Float32, 150.0f) +CON_ZR_CVAR(zr_ztele_allow_humans, "Whether to allow humans to use ztele", g_bZteleHuman, Bool, false) +CON_ZR_CVAR(zr_knockback_scale, "Global knockback scale", g_flKnockbackScale, Float32, 5.0f) +CON_ZR_CVAR(zr_infect_spawn_type, "Type of Mother Zombies Spawn [0 = MZ spawn where they stand, 1 = MZ get teleported back to spawn on being picked]", g_flInfectSpawnType, Int32, EZRSpawnType::RESPAWN) +CON_ZR_CVAR(zr_infect_spawn_time_min, "Minimum time in which Mother Zombies should be picked, after round start", g_flInfectSpawnTimeMin, Int32, 15) +CON_ZR_CVAR(zr_infect_spawn_time_max, "Maximum time in which Mother Zombies should be picked, after round start", g_flInfectSpawnTimeMax, Int32, 15) +CON_ZR_CVAR(zr_infect_spawn_mz_ratio, "Ratio of all Players to Mother Zombies to be spawned at round start", g_flInfectSpawnMZRatio, Int32, 7) +CON_ZR_CVAR(zr_infect_spawn_mz_min_count, "Minimum amount of Mother Zombies to be spawned at round start", g_flInfectSpawnMinCount, Int32, 2) + +CON_COMMAND_CHAT(zspawn, "respawn yourself") { - if (args.ArgC() < 2) - Msg("%s %i\n", args[0], g_bEnableZR); - else - g_bEnableZR = V_StringToBool(args[1], false); + // Silently return so the command is completely hidden + if (!g_bEnableZR) + return; + + if (!player) + { + ClientPrint(player, HUD_PRINTCONSOLE, CHAT_PREFIX "You cannot use this command from the server console."); + return; + } + + CCSPlayerPawn *pPawn = (CCSPlayerPawn*)player->GetPawn(); + if (!pPawn) + { + ClientPrint(player, HUD_PRINTCONSOLE, CHAT_PREFIX "Invalid Pawn."); + return; + } + ClientPrint(player, HUD_PRINTCONSOLE, CHAT_PREFIX "Classname: %s", pPawn->GetClassname()); + if (pPawn->IsAlive()) + { + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX"You must be dead to respawn!"); + return; + } + + player->Respawn(); + pPawn->Respawn(); } -CON_COMMAND_F(zr_ztele_max_distance, "Maximum distance players are allowed to move after starting ztele", FCVAR_LINKED_CONCOMMAND | FCVAR_SPONLY) +CON_COMMAND_CHAT(setmodel, "set your model") { - if (args.ArgC() < 2) - Msg("%s %f\n", args[0], g_flMaxZteleDistance); - else - g_flMaxZteleDistance = V_StringToFloat32(args[1], 150.0f); + if (!player) + { + ClientPrint(player, HUD_PRINTCONSOLE, CHAT_PREFIX "You cannot use this command from the server console."); + return; + } + + CCSPlayerPawn *pPawn = (CCSPlayerPawn*)player->GetPawn(); + if (!pPawn) + { + ClientPrint(player, HUD_PRINTCONSOLE, CHAT_PREFIX "Invalid Pawn."); + return; + } + ClientPrint(player, HUD_PRINTCONSOLE, CHAT_PREFIX "Classname: %s", pPawn->GetClassname()); + if (!pPawn->IsAlive()) + { + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX"You must be alive to change model!"); + return; + } + + pPawn->SetModel(args[1]); } -CON_COMMAND_F(zr_ztele_allow_humans, "Whether to allow humans to use ztele", FCVAR_LINKED_CONCOMMAND | FCVAR_SPONLY) + +CON_COMMAND_CHAT(endround, "end the round") { - if (args.ArgC() < 2) - Msg("%s %i\n", args[0], g_bZteleHuman); - else - g_bZteleHuman = V_StringToBool(args[1], false); + g_pGameRules->TerminateRound(V_StringToFloat32(args[1], 5.0f), V_StringToUint32(args[2], 10u)); } void SetUpAllHumanClasses() @@ -88,8 +143,8 @@ void ZR_OnStartupServer() g_pEngineServer2->ServerCommand("mp_give_player_c4 0"); g_pEngineServer2->ServerCommand("mp_friendlyfire 0"); // Legacy Lua respawn stuff, do not use these, we should handle respawning ourselves now that we can - //g_pEngineServer2->ServerCommand("mp_respawn_on_death_t 1"); - //g_pEngineServer2->ServerCommand("mp_respawn_on_death_ct 1"); + g_pEngineServer2->ServerCommand("mp_respawn_on_death_t 1"); + g_pEngineServer2->ServerCommand("mp_respawn_on_death_ct 1"); //g_pEngineServer2->ServerCommand("bot_quota_mode fill"); //g_pEngineServer2->ServerCommand("mp_ignore_round_win_conditions 1"); } @@ -97,18 +152,13 @@ void ZR_OnStartupServer() void ZR_OnRoundPrestart(IGameEvent* pEvent) { g_ZRRoundState = EZRRoundState::ROUND_START; + g_iRoundNum++; } void ZR_OnRoundStart(IGameEvent* pEvent) { ClientPrintAll(HUD_PRINTTALK, ZR_PREFIX "The game is \x05Humans vs. Zombies\x01, the goal for zombies is to infect all humans by knifing them."); - new CTimer(1.0f, false, []() - { - g_ZRRoundState = EZRRoundState::POST_INFECTION; - return 1.0f; - }); - // SetUpAllHumanClasses(); // SetupRespawnToggler(); // SetupAmmoReplenish(); @@ -120,18 +170,22 @@ void ZR_OnPlayerSpawn(IGameEvent* pEvent) { CCSPlayerController* pController = (CCSPlayerController*)pEvent->GetPlayerController("userid"); - if (pController && g_ZRRoundState == EZRRoundState::POST_INFECTION && pController->m_iTeamNum == CS_TEAM_CT) - ZR_Infect(pController, pController, false); //set health and probably model doesn't work here -} + if (pController && g_ZRRoundState == EZRRoundState::POST_INFECTION) + { + // delay infection a bit + int iRoundNum = g_iRoundNum; + CHandle handle = pController->GetHandle(); + new CTimer(0.05f, false, [iRoundNum, handle]() + { + CCSPlayerController* pController = (CCSPlayerController*)handle.Get(); + if (iRoundNum != g_iRoundNum || !pController) + return -1.0f; -// CONVAR_TODO -float g_flKnockbackScale = 5.0f; -CON_COMMAND_F(zr_knockback_scale, "Global knockback scale", FCVAR_LINKED_CONCOMMAND | FCVAR_SPONLY) -{ - if (args.ArgC() < 2) - Msg("%s %f\n", args[0], g_flKnockbackScale); - else - g_flKnockbackScale = V_StringToFloat32(args[1], 5.0f); + ZR_Infect(pController, pController, true); + + return -1.0f; + }); + } } // Still need to implement weapon config @@ -154,13 +208,13 @@ void ZR_ApplyKnockbackExplosion(Z_CBaseEntity *pProjectile, CCSPlayerPawn *pVict pVictim->m_vecBaseVelocity = pVictim->m_vecBaseVelocity() + vecKnockback; } -void ZR_FakePlayerDeath(CCSPlayerController *pAttackerController, CCSPlayerController *pVictimController, const char *szWeapon) +void ZR_FakePlayerDeath(CCSPlayerController *pAttackerController, CCSPlayerController *pVictimController, const char *szWeapon, bool bDontBroadcast) { IGameEvent *pEvent = g_gameEventManager->CreateEvent("player_death"); if (!pEvent) return; - //SetPlayer functions are swapped, need to remove the cast once fixed + // SetPlayer functions are swapped, need to remove the cast once fixed pEvent->SetPlayer("userid", (CEntityInstance*)pVictimController->GetPlayerSlot()); pEvent->SetPlayer("attacker", (CEntityInstance*)pAttackerController->GetPlayerSlot()); pEvent->SetString("weapon", szWeapon); @@ -178,34 +232,128 @@ void ZR_FakePlayerDeath(CCSPlayerController *pAttackerController, CCSPlayerContr pEvent->GetInt("attacker_pawn"), pEvent->GetString("weapon")); - g_gameEventManager->FireEvent(pEvent, false); + g_gameEventManager->FireEvent(pEvent, bDontBroadcast); } -void ZR_Infect(CCSPlayerController *pAttackerController, CCSPlayerController *pVictimController, bool bBroadcast) +void ZR_Infect(CCSPlayerController *pAttackerController, CCSPlayerController *pVictimController, bool bDontBroadcast) { - if (bBroadcast) - ZR_FakePlayerDeath(pAttackerController, pVictimController, "knife"); // or any other killicon - pVictimController->SwitchTeam(CS_TEAM_T); + if (pVictimController->m_iTeamNum() == CS_TEAM_CT) + pVictimController->SwitchTeam(CS_TEAM_T); + + ZR_FakePlayerDeath(pAttackerController, pVictimController, "knife", bDontBroadcast); // or any other killicon + + ZR_CheckWinConditions(true); CCSPlayerPawn *pVictimPawn = (CCSPlayerPawn*)pVictimController->GetPawn(); if (!pVictimPawn) return; - //set model/health/etc here + // set model/health/etc here + //hardcode everything for now pVictimPawn->m_iMaxHealth = 10000; pVictimPawn->m_iHealth = 10000; + + pVictimPawn->SetModel("characters/models/tm_phoenix/tm_phoenix.vmdl"); } void ZR_InfectMotherZombie(CCSPlayerController *pVictimController) { - ZR_FakePlayerDeath(pVictimController, pVictimController, "prop_exploding_barrel"); // or any other killicon pVictimController->SwitchTeam(CS_TEAM_T); - //set model/health/etc here + CCSPlayerPawn *pVictimPawn = (CCSPlayerPawn*)pVictimController->GetPawn(); + if (!pVictimPawn) + return; + // set model/health/etc here + + //hardcode everything for now + pVictimPawn->m_iMaxHealth = 10000; + pVictimPawn->m_iHealth = 10000; + + pVictimPawn->SetModel("characters/models/tm_phoenix/tm_phoenix.vmdl"); } +void ZR_InitialInfection() +{ + // grab player count and mz infection candidates + CUtlVector pCandidateControllers; + for (int i = 0; i < gpGlobals->maxClients; i++) + { + CCSPlayerController* pController = CCSPlayerController::FromSlot(i); + if (!pController || pController->m_iTeamNum() != CS_TEAM_CT) + continue; + + CCSPlayerController* pPawn = (CCSPlayerController*)pController->GetPawn(); + if (!pPawn || !pPawn->IsAlive()) + continue; + + pCandidateControllers.AddToTail(pController); + } + + // calculate the num of mz to infect + int iMZToInfect = pCandidateControllers.Count() / g_flInfectSpawnMZRatio; + iMZToInfect = g_flInfectSpawnMinCount > iMZToInfect ? g_flInfectSpawnMinCount : iMZToInfect; + + // get spawn points + CUtlVector spawns; + if (g_flInfectSpawnType == EZRSpawnType::RESPAWN) + { + SpawnPoint* spawn = nullptr; + while (nullptr != (spawn = (SpawnPoint*)UTIL_FindEntityByClassname(spawn, "info_player_*"))) + { + if (spawn->m_bEnabled()) + spawns.AddToTail(spawn); + } + } + + // infect + while (pCandidateControllers.Count() > 0 && iMZToInfect > 0) + { + int randomindex = rand() % pCandidateControllers.Count(); + ZR_InfectMotherZombie(pCandidateControllers[randomindex]); + + CCSPlayerController* pPawn = (CCSPlayerController*)pCandidateControllers[randomindex]->GetPawn(); + if (pPawn && spawns.Count()) + { + int randomindex = rand() % spawns.Count(); + pPawn->SetAbsOrigin(spawns[randomindex]->GetAbsOrigin()); + pPawn->SetAbsRotation(spawns[randomindex]->GetAbsRotation()); + } + + pCandidateControllers.FastRemove(randomindex); + iMZToInfect--; + } + ClientPrintAll(HUD_PRINTCENTER, "First infection has started!"); + ClientPrintAll(HUD_PRINTTALK, ZR_PREFIX "First infection has started! Good luck, survivors!"); + g_ZRRoundState = EZRRoundState::POST_INFECTION; +} + +void ZR_StartInitialCountdown() +{ + int iRoundNum = g_iRoundNum; + g_iInfectionCountDown = g_flInfectSpawnTimeMin + (rand() % (g_flInfectSpawnTimeMax - g_flInfectSpawnTimeMin + 1)); + new CTimer(1.0f, false, [iRoundNum]() + { + if (iRoundNum != g_iRoundNum) + return -1.0f; + if (g_iInfectionCountDown <= 0) + { + ZR_InitialInfection(); + return -1.0f; + } + + if (g_iInfectionCountDown <= 60) + { + ClientPrintAll(HUD_PRINTCENTER, "First infection in \7%i second(s)\1!", g_iInfectionCountDown); + if (g_iInfectionCountDown % 5 == 0) + ClientPrintAll(HUD_PRINTTALK, ZR_PREFIX "First infection in \7%i second\1!", g_iInfectionCountDown); + } + g_iInfectionCountDown--; + + return 1.0f; + }); +} bool ZR_Detour_TakeDamageOld(CCSPlayerPawn *pVictimPawn, CTakeDamageInfo *pInfo) { @@ -216,19 +364,19 @@ bool ZR_Detour_TakeDamageOld(CCSPlayerPawn *pVictimPawn, CTakeDamageInfo *pInfo) CCSPlayerController *pAttackerController = CCSPlayerController::FromPawn(pAttackerPawn); CCSPlayerController *pVictimController = CCSPlayerController::FromPawn(pVictimPawn); - - if (pAttackerPawn->m_iTeamNum() == CS_TEAM_T && pVictimPawn->m_iTeamNum() == CS_TEAM_CT) + const char *pszAbilityClass = pInfo->m_hAbility.Get() ? pInfo->m_hAbility.Get()->GetClassname() : ""; + if (pAttackerPawn->m_iTeamNum() == CS_TEAM_T && pVictimPawn->m_iTeamNum() == CS_TEAM_CT && !V_strncmp(pszAbilityClass, "weapon_knife", 12)) { - ZR_Infect(pAttackerController, pVictimController, true); + ZR_Infect(pAttackerController, pVictimController, false); return true; // nullify the damage } - //grenade and molotov knockback + // grenade and molotov knockback if (pAttackerPawn->m_iTeamNum() == CS_TEAM_CT && pVictimPawn->m_iTeamNum() == CS_TEAM_T) { CBaseEntity *pInflictor = pInfo->m_hInflictor.Get(); const char *pszInflictorClass = pInflictor ? pInflictor->GetClassname() : ""; - if (V_strncmp(pszInflictorClass, "hegrenade", 9) || V_strncmp(pszInflictorClass, "inferno", 7)) + if (!V_strncmp(pszInflictorClass, "hegrenade_projectile", 9) || !V_strncmp(pszInflictorClass, "inferno", 7)) ZR_ApplyKnockbackExplosion((Z_CBaseEntity*)pInflictor, (CCSPlayerPawn*)pVictimPawn, (int)pInfo->m_flDamage); } return false; @@ -242,32 +390,57 @@ void ZR_OnPlayerHurt(IGameEvent* pEvent) int iDmgHealth = pEvent->GetInt("dmg_health"); - //grenade and molotov knockbacks are handled by TakeDamage detours - if (!pAttackerController || !pVictimController || strcmp(szWeapon, "") == 0 || strcmp(szWeapon, "inferno") == 0 || strcmp(szWeapon, "hegrenade") == 0) + // grenade and molotov knockbacks are handled by TakeDamage detours + if (!pAttackerController || !pVictimController || !V_strncmp(szWeapon, "inferno", 7) || !V_strncmp(szWeapon, "hegrenade", 9)) return; if (pAttackerController->m_iTeamNum() == CS_TEAM_CT && pVictimController->m_iTeamNum() == CS_TEAM_T) ZR_ApplyKnockback((CCSPlayerPawn*)pAttackerController->GetPawn(), (CCSPlayerPawn*)pVictimController->GetPawn(), iDmgHealth, szWeapon); - - //if (pAttacker->m_iTeamNum == CS_TEAM_CT && pVictim->m_iTeamNum == CS_TEAM_T) - // Message("lol"); - // //Knockback_Apply(pAttacker, pVictim, iDmgHealth, szWeapon); - //else if (szWeapon == "knife" && pAttacker->m_iTeamNum == CS_TEAM_T && pVictim->m_iTeamNum == CS_TEAM_CT) - // Message("lol"); - // //Infect(pAttacker, pVictim, true, iHealth == 0); } void ZR_OnPlayerDeath(IGameEvent* pEvent) { - // To T TeamSwitch happening in ZR_OnPlayerSpawn + if (g_ZRRoundState == EZRRoundState::ROUND_START) + return; - /*ZEPlayer* pPlayer = g_playerManager->GetPlayerFromUserId(pEvent->GetInt("userid")); + CCSPlayerController *pVictimController = (CCSPlayerController*)pEvent->GetPlayerController("userid"); + if (!pVictimController) + return; + CCSPlayerPawn *pVictimPawn = (CCSPlayerPawn*)pVictimController->GetPawn(); + if (!pVictimPawn) + return; + + ZR_CheckWinConditions(pVictimPawn->m_iTeamNum() == CS_TEAM_CT); +} - if (pPlayer && !pPlayer->IsInfected()) +void ZR_OnRoundFreezeEnd(IGameEvent* pEvent) +{ + ZR_StartInitialCountdown(); +} + +// check whether players on a team are all dead +void ZR_CheckWinConditions(bool bCheckCT) +{ + if (g_ZRRoundState == EZRRoundState::ROUND_END) + return; + + int iTeamNum = bCheckCT ? CS_TEAM_CT : CS_TEAM_T; + + // loop through each player, return early if both team has alive player + CCSPlayerPawn* pPawn = nullptr; + while (nullptr != (pPawn = (CCSPlayerPawn*)UTIL_FindEntityByClassname(pPawn, "player"))) { - CCSPlayerController* pController = (CCSPlayerController*)pEvent->GetPlayerController("userid"); - pController->SwitchTeam(CS_TEAM_T); - }*/ + if (!pPawn->IsAlive()) + continue; + + if (pPawn->m_iTeamNum() == iTeamNum) + return; + } + + // didn't return early, all players on one or both teams are dead. + // 8: CT win; 9: T wins; 10: draw + g_pGameRules->TerminateRound(5.0, bCheckCT ? CSRoundEndReason::TerroristWin : CSRoundEndReason::CTWin); + g_ZRRoundState = EZRRoundState::ROUND_END; } CON_COMMAND_CHAT(ztele, "teleport to spawn") diff --git a/src/zombiereborn.h b/src/zombiereborn.h index 96c0a5f1..a83337a0 100644 --- a/src/zombiereborn.h +++ b/src/zombiereborn.h @@ -29,9 +29,14 @@ enum class EZRRoundState ROUND_END, }; +enum EZRSpawnType +{ + IN_PLACE, + RESPAWN, +}; + extern bool g_bEnableZR; extern EZRRoundState g_ZRRoundState; -extern float g_flKnockbackScale; void ZR_OnStartupServer(); void ZR_OnRoundPrestart(IGameEvent* pEvent); @@ -39,4 +44,15 @@ void ZR_OnRoundStart(IGameEvent* pEvent); void ZR_OnPlayerSpawn(IGameEvent* pEvent); void ZR_OnPlayerHurt(IGameEvent* pEvent); void ZR_OnPlayerDeath(IGameEvent* pEvent); -bool ZR_Detour_TakeDamageOld(CCSPlayerPawn *pVictimPawn, CTakeDamageInfo *pInfo); \ No newline at end of file +void ZR_OnRoundFreezeEnd(IGameEvent* pEvent); +bool ZR_Detour_TakeDamageOld(CCSPlayerPawn *pVictimPawn, CTakeDamageInfo *pInfo); + +// need to replace with actual cvar someday +#define CON_ZR_CVAR(name, description, variable_name, variable_type, variable_default) \ + CON_COMMAND_F(name, description, FCVAR_LINKED_CONCOMMAND | FCVAR_SPONLY) \ + { \ + if (args.ArgC() < 2) \ + Msg("%s %i\n", args[0], variable_name); \ + else \ + variable_name = V_StringTo##variable_type(args[1], variable_default); \ + }