From e0ba8cebc7a3554fc71b8550096881c164e4b42e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl=20Troch?= Date: Wed, 28 Aug 2024 15:33:25 +0200 Subject: [PATCH] [HL25] Backport busters game mode Also backport a define being moved at the top in "multiplay_gamerules.cpp". And two changes related to weapons in general in "weapons.cpp". --- dlls/egon.cpp | 17 +++ dlls/game.cpp | 4 + dlls/game.h | 2 + dlls/gamerules.cpp | 7 +- dlls/gamerules.h | 27 ++++ dlls/multiplay_gamerules.cpp | 289 ++++++++++++++++++++++++++++++++++- dlls/player.cpp | 96 ++++++++++-- dlls/player.h | 1 + dlls/weapons.cpp | 20 ++- dlls/weapons.h | 1 + 10 files changed, 447 insertions(+), 17 deletions(-) diff --git a/dlls/egon.cpp b/dlls/egon.cpp index 0458376..2931edf 100644 --- a/dlls/egon.cpp +++ b/dlls/egon.cpp @@ -27,6 +27,8 @@ #ifdef CLIENT_DLL #include "hud.h" #include "com_weapons.h" +#else +extern bool IsBustingGame(); #endif #define EGON_SWITCH_NARROW_TIME 0.75 // Time it takes to switch fire modes @@ -124,6 +126,11 @@ bool CEgon::HasAmmo() void CEgon::UseAmmo(int count) { +#ifndef CLIENT_DLL + if (IsBustingGame()) + return; +#endif + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] >= count) m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= count; else @@ -491,7 +498,17 @@ void CEgon::WeaponIdle() m_deployed = true; } +bool CEgon::CanHolster() +{ +#ifndef CLIENT_DLL + if (IsBustingGame()) + { + return false; + } +#endif + return true; +} void CEgon::EndAttack() { diff --git a/dlls/game.cpp b/dlls/game.cpp index d71e59e..562e887 100644 --- a/dlls/game.cpp +++ b/dlls/game.cpp @@ -454,6 +454,8 @@ cvar_t sk_player_leg3 = {"sk_player_leg3", "1"}; // END Cvars for Skill Level settings +cvar_t sv_busters = {"sv_busters", "0", FCVAR_SERVER}; + static bool SV_InitServer() { if (!FileSystem_LoadFileSystem()) @@ -515,6 +517,8 @@ void GameDLLInit() CVAR_REGISTER(&mp_chattime); + CVAR_REGISTER(&sv_busters); + CVAR_REGISTER(&sv_allowbunnyhopping); // REGISTER CVARS FOR SKILL LEVEL STUFF diff --git a/dlls/game.h b/dlls/game.h index 46fca46..ce74d84 100644 --- a/dlls/game.h +++ b/dlls/game.h @@ -43,6 +43,8 @@ extern cvar_t mp_chattime; extern cvar_t sv_allowbunnyhopping; +extern cvar_t sv_busters; + // Engine Cvars inline cvar_t* g_psv_gravity; inline cvar_t* g_psv_aim; diff --git a/dlls/gamerules.cpp b/dlls/gamerules.cpp index 8867880..3c7f0ae 100644 --- a/dlls/gamerules.cpp +++ b/dlls/gamerules.cpp @@ -373,7 +373,12 @@ CGameRules* InstallGameRules() SERVER_COMMAND("exec game.cfg\n"); SERVER_EXECUTE(); - if (0 == gpGlobals->deathmatch) + if (1 == sv_busters.value) + { + g_teamplay = false; + return new CMultiplayBusters; + } + else if (0 == gpGlobals->deathmatch) { // generic half-life g_teamplay = false; diff --git a/dlls/gamerules.h b/dlls/gamerules.h index cb21218..0bef721 100644 --- a/dlls/gamerules.h +++ b/dlls/gamerules.h @@ -363,6 +363,33 @@ protected: void SendMOTDToClient(edict_t* client); }; +//========================================================= +// CMultiplayBusters +// Rules for a multiplayer mode that makes you feel good +//========================================================= +class CMultiplayBusters : public CHalfLifeMultiplay +{ +public: + CMultiplayBusters(); + + void Think(); + int IPointsForKill(CBasePlayer* pAttacker, CBasePlayer* pKilled); + void PlayerKilled(CBasePlayer* pVictim, entvars_t* pKiller, entvars_t* pInflictor); + void DeathNotice(CBasePlayer* pVictim, entvars_t* pKiller, entvars_t* pInflictor); + int WeaponShouldRespawn(CBasePlayerItem* pWeapon); + bool CanHavePlayerItem(CBasePlayer* pPlayer, CBasePlayerItem* pWeapon); + bool CanHaveItem(CBasePlayer* pPlayer, CItem* pItem); + void PlayerGotWeapon(CBasePlayer* pPlayer, CBasePlayerItem* pWeapon); + void ClientUserInfoChanged(CBasePlayer* pPlayer, char* infobuffer); + void PlayerSpawn(CBasePlayer* pPlayer); + + void SetPlayerModel(CBasePlayer* pPlayer); + +protected: + float m_flEgonBustingCheckTime = -1.0f; + void CheckForEgons(); +}; + inline DLL_GLOBAL CGameRules* g_pGameRules = nullptr; inline DLL_GLOBAL bool g_fGameOver; inline bool g_teamplay = false; diff --git a/dlls/multiplay_gamerules.cpp b/dlls/multiplay_gamerules.cpp index 7674a9a..7a3413b 100644 --- a/dlls/multiplay_gamerules.cpp +++ b/dlls/multiplay_gamerules.cpp @@ -34,6 +34,9 @@ #define WEAPON_RESPAWN_TIME 20 #define AMMO_RESPAWN_TIME 20 +// longest the intermission can last, in seconds +#define MAX_INTERMISSION_TIME 120 + CVoiceGameMgr g_VoiceGameMgr; class CMultiplayGameMgrHelper : public IVoiceGameMgrHelper @@ -159,9 +162,6 @@ void CHalfLifeMultiplay::RefreshSkillData() gSkillData.plrDmgHornet = 10; } -// longest the intermission can last, in seconds -#define MAX_INTERMISSION_TIME 120 - //========================================================= //========================================================= void CHalfLifeMultiplay::Think() @@ -1631,3 +1631,286 @@ void CHalfLifeMultiplay::SendMOTDToClient(edict_t* client) FREE_FILE(aFileList); } + +//========================================================= +//========================================================= +// Busters Gamerules +//========================================================= +//========================================================= + +#define EGON_BUSTING_TIME 10 + +bool IsBustingGame() +{ + return sv_busters.value == 1; +} + +bool IsPlayerBusting(CBaseEntity* pPlayer) +{ + if (!pPlayer || !pPlayer->IsPlayer() || !IsBustingGame()) + return false; + + return ((CBasePlayer*)pPlayer)->HasPlayerItemFromID(WEAPON_EGON); +} + +bool BustingCanHaveItem(CBasePlayer* pPlayer, CBaseEntity* pItem) +{ + bool bIsWeaponOrAmmo = false; + + if (strstr(STRING(pItem->pev->classname), "weapon_") || strstr(STRING(pItem->pev->classname), "ammo_")) + { + bIsWeaponOrAmmo = true; + } + + // Busting players can't have ammo nor weapons + if (IsPlayerBusting(pPlayer) && bIsWeaponOrAmmo) + return false; + + return true; +} + +//========================================================= +CMultiplayBusters::CMultiplayBusters() +{ + m_flEgonBustingCheckTime = -1; +} + +//========================================================= +void CMultiplayBusters::Think() +{ + CheckForEgons(); + + CHalfLifeMultiplay::Think(); +} + +//========================================================= +int CMultiplayBusters::IPointsForKill(CBasePlayer* pAttacker, CBasePlayer* pKilled) +{ + // If the attacker is busting, they get a point per kill + if (IsPlayerBusting(pAttacker)) + return 1; + + // If the victim is busting, then the attacker gets a point + if (IsPlayerBusting(pKilled)) + return 2; + + return 0; +} + +//========================================================= +void CMultiplayBusters::PlayerKilled(CBasePlayer* pVictim, entvars_t* pKiller, entvars_t* pInflictor) +{ + if (IsPlayerBusting(pVictim)) + { + UTIL_ClientPrintAll(HUD_PRINTCENTER, "The Buster is dead!!"); + + // Reset egon check time + m_flEgonBustingCheckTime = -1; + + CBasePlayer* peKiller = NULL; + CBaseEntity* ktmp = CBaseEntity::Instance(pKiller); + + if (ktmp && (ktmp->Classify() == CLASS_PLAYER)) + { + peKiller = (CBasePlayer*)ktmp; + } + else if (ktmp && (ktmp->Classify() == CLASS_VEHICLE)) + { + CBasePlayer* pDriver = ((CFuncVehicle*)ktmp)->m_pDriver; + + if (pDriver != NULL) + { + peKiller = pDriver; + ktmp = pDriver; + pKiller = pDriver->pev; + } + } + + if (peKiller) + { + UTIL_ClientPrintAll(HUD_PRINTTALK, UTIL_VarArgs("%s has has killed the Buster!\n", STRING((CBasePlayer*)peKiller->pev->netname))); + } + + pVictim->pev->renderfx = kRenderFxNone; + pVictim->pev->rendercolor = g_vecZero; + // pVictim->pev->effects &= ~EF_BRIGHTFIELD; + } + + CHalfLifeMultiplay::PlayerKilled(pVictim, pKiller, pInflictor); +} + +//========================================================= +void CMultiplayBusters::DeathNotice(CBasePlayer* pVictim, entvars_t* pKiller, entvars_t* pevInflictor) +{ + // Only death notices that the Buster was involved in in Busting game mode + if (!IsPlayerBusting(pVictim) && !IsPlayerBusting(CBaseEntity::Instance(pKiller))) + return; + + CHalfLifeMultiplay::DeathNotice(pVictim, pKiller, pevInflictor); +} + +//========================================================= +int CMultiplayBusters::WeaponShouldRespawn(CBasePlayerItem* pWeapon) +{ + if (pWeapon->m_iId == WEAPON_EGON) + return GR_WEAPON_RESPAWN_NO; + + return CHalfLifeMultiplay::WeaponShouldRespawn(pWeapon); +} + + +//========================================================= +// CheckForEgons: +// Check to see if any player has an egon +// If they don't then get the lowest player on the scoreboard and give them one +// Then check to see if any weapon boxes out there has an egon, and delete it +//========================================================= +void CMultiplayBusters::CheckForEgons() +{ + if (m_flEgonBustingCheckTime <= 0.0f) + { + m_flEgonBustingCheckTime = gpGlobals->time + EGON_BUSTING_TIME; + return; + } + + if (m_flEgonBustingCheckTime <= gpGlobals->time) + { + m_flEgonBustingCheckTime = -1.0f; + + for (int i = 1; i <= gpGlobals->maxClients; i++) + { + CBasePlayer* pPlayer = (CBasePlayer*)UTIL_PlayerByIndex(i); + + // Someone is busting, no need to continue + if (IsPlayerBusting(pPlayer)) + return; + } + + int bBestFrags = 9999; + CBasePlayer* pBestPlayer = NULL; + + for (int i = 1; i <= gpGlobals->maxClients; i++) + { + CBasePlayer* pPlayer = (CBasePlayer*)UTIL_PlayerByIndex(i); + + if (pPlayer && pPlayer->pev->frags <= bBestFrags) + { + bBestFrags = pPlayer->pev->frags; + pBestPlayer = pPlayer; + } + } + + if (pBestPlayer) + { + pBestPlayer->GiveNamedItem("weapon_egon"); + + CBaseEntity* pEntity = NULL; + + // Find a weaponbox that includes an Egon, then destroy it + while ((pEntity = UTIL_FindEntityByClassname(pEntity, "weaponbox")) != NULL) + { + CWeaponBox* pWeaponBox = (CWeaponBox*)pEntity; + + if (pWeaponBox) + { + CBasePlayerItem* pWeapon; + + for (int i = 0; i < MAX_ITEM_TYPES; i++) + { + pWeapon = pWeaponBox->m_rgpPlayerItems[i]; + + while (pWeapon) + { + // There you are, bye box + if (pWeapon->m_iId == WEAPON_EGON) + { + pWeaponBox->Kill(); + break; + } + + pWeapon = pWeapon->m_pNext; + } + } + } + } + } + } +} + +//========================================================= +bool CMultiplayBusters::CanHavePlayerItem(CBasePlayer* pPlayer, CBasePlayerItem* pItem) +{ + // Buster cannot have more weapons nor ammo + if (BustingCanHaveItem(pPlayer, pItem) == false) + { + return false; + } + + return CHalfLifeMultiplay::CanHavePlayerItem(pPlayer, pItem); +} + +//========================================================= +bool CMultiplayBusters::CanHaveItem(CBasePlayer* pPlayer, CItem* pItem) +{ + // Buster cannot have more weapons nor ammo + if (BustingCanHaveItem(pPlayer, pItem) == false) + { + return false; + } + + return CHalfLifeMultiplay::CanHaveItem(pPlayer, pItem); +} + +//========================================================= +void CMultiplayBusters::PlayerGotWeapon(CBasePlayer* pPlayer, CBasePlayerItem* pWeapon) +{ + if (pWeapon->m_iId == WEAPON_EGON) + { + pPlayer->RemoveAllItems(false); + + UTIL_ClientPrintAll(HUD_PRINTCENTER, "Long live the new Buster!"); + UTIL_ClientPrintAll(HUD_PRINTTALK, UTIL_VarArgs("%s is busting!\n", STRING((CBasePlayer*)pPlayer->pev->netname))); + + SetPlayerModel(pPlayer); + + pPlayer->pev->health = pPlayer->pev->max_health; + pPlayer->pev->armorvalue = 100; + + pPlayer->pev->renderfx = kRenderFxGlowShell; + pPlayer->pev->renderamt = 25; + pPlayer->pev->rendercolor = Vector(0, 75, 250); + + CBasePlayerWeapon* pEgon = (CBasePlayerWeapon*)pWeapon; + + pEgon->m_iDefaultAmmo = 100; + pPlayer->m_rgAmmo[pEgon->m_iPrimaryAmmoType] = pEgon->m_iDefaultAmmo; + + g_engfuncs.pfnSetClientKeyValue(pPlayer->entindex(), g_engfuncs.pfnGetInfoKeyBuffer(pPlayer->edict()), "model", "ivan"); + } +} + +void CMultiplayBusters::ClientUserInfoChanged(CBasePlayer* pPlayer, char* infobuffer) +{ + SetPlayerModel(pPlayer); + + // Set preferences + pPlayer->SetPrefsFromUserinfo(infobuffer); +} + +void CMultiplayBusters::PlayerSpawn(CBasePlayer* pPlayer) +{ + CHalfLifeMultiplay::PlayerSpawn(pPlayer); + SetPlayerModel(pPlayer); +} + +void CMultiplayBusters::SetPlayerModel(CBasePlayer* pPlayer) +{ + if (IsPlayerBusting(pPlayer)) + { + g_engfuncs.pfnSetClientKeyValue(pPlayer->entindex(), g_engfuncs.pfnGetInfoKeyBuffer(pPlayer->edict()), "model", "ivan"); + } + else + { + g_engfuncs.pfnSetClientKeyValue(pPlayer->entindex(), g_engfuncs.pfnGetInfoKeyBuffer(pPlayer->edict()), "model", "skeleton"); + } +} diff --git a/dlls/player.cpp b/dlls/player.cpp index f3a8a88..6a1cb84 100644 --- a/dlls/player.cpp +++ b/dlls/player.cpp @@ -47,6 +47,8 @@ extern void CopyToBodyQue(entvars_t* pev); extern edict_t* EntSelectSpawnPoint(CBaseEntity* pPlayer); +extern bool IsBustingGame(); + #define TRAIN_ACTIVE 0x80 #define TRAIN_NEW 0xc0 #define TRAIN_OFF 0x00 @@ -688,24 +690,67 @@ void CBasePlayer::PackDeadPlayerItems() iPA = 0; iPW = 0; - // pack the ammo - while (iPackAmmo[iPA] != -1) + if (IsBustingGame()) { - pWeaponBox->PackAmmo(MAKE_STRING(CBasePlayerItem::AmmoInfoArray[iPackAmmo[iPA]].pszName), m_rgAmmo[iPackAmmo[iPA]]); - iPA++; - } + if (HasNamedPlayerItem("weapon_egon")) + { + for (i = 0; i < MAX_ITEM_TYPES; i++) + { + CBasePlayerItem* pItem = m_rgpPlayerItems[i]; - // now pack all of the items in the lists - while (rgpPackWeapons[iPW]) + if (pItem) + { + if (!strcmp("weapon_egon", STRING(pItem->pev->classname))) + { + pWeaponBox->PackWeapon(pItem); + + SET_MODEL(ENT(pWeaponBox->pev), "models/w_egon.mdl"); + + pWeaponBox->pev->velocity = Vector(0, 0, 0); + pWeaponBox->pev->renderfx = kRenderFxGlowShell; + pWeaponBox->pev->renderamt = 25; + pWeaponBox->pev->rendercolor = Vector(0, 75, 250); + + break; + } + } + } + } + } + else { - // weapon unhooked from the player. Pack it into der box. - pWeaponBox->PackWeapon(rgpPackWeapons[iPW]); + bool bPackItems = true; - iPW++; + if (iAmmoRules == GR_PLR_DROP_AMMO_ACTIVE && iWeaponRules == GR_PLR_DROP_GUN_ACTIVE) + { + if (FClassnameIs(rgpPackWeapons[0]->pev, "weapon_satchel") && (iPackAmmo[0] == -1 || (m_rgAmmo[iPackAmmo[0]] == 0))) + { + bPackItems = false; + } + } + + if (bPackItems) + { + // pack the ammo + while (iPackAmmo[iPA] != -1) + { + pWeaponBox->PackAmmo(MAKE_STRING(CBasePlayerItem::AmmoInfoArray[iPackAmmo[iPA]].pszName), m_rgAmmo[iPackAmmo[iPA]]); + iPA++; + } + + // now pack all of the items in the lists + while (rgpPackWeapons[iPW]) + { + // weapon unhooked from the player. Pack it into der box. + pWeaponBox->PackWeapon(rgpPackWeapons[iPW]); + + iPW++; + } + } + + pWeaponBox->pev->velocity = pev->velocity * 1.2; // weaponbox has player's velocity, then some. } - pWeaponBox->pev->velocity = pev->velocity * 1.2; // weaponbox has player's velocity, then some. - RemoveAllItems(true); // now strip off everything that wasn't handled by the code above. } @@ -4746,6 +4791,33 @@ bool CBasePlayer::HasNamedPlayerItem(const char* pszItemName) return false; } +//========================================================= +// HasPlayerItemFromID +// Just compare IDs, rather than classnames +//========================================================= +bool CBasePlayer::HasPlayerItemFromID(int nID) +{ + CBasePlayerItem* pItem; + int i; + + for (i = 0; i < MAX_ITEM_TYPES; i++) + { + pItem = m_rgpPlayerItems[i]; + + while (pItem) + { + if (pItem->m_iId == nID) + { + return true; + } + + pItem = pItem->m_pNext; + } + } + + return false; +} + //========================================================= // //========================================================= diff --git a/dlls/player.h b/dlls/player.h index 0485cea..b337d1d 100644 --- a/dlls/player.h +++ b/dlls/player.h @@ -283,6 +283,7 @@ public: void DropPlayerItem(char* pszItemName); bool HasPlayerItem(CBasePlayerItem* pCheckItem); bool HasNamedPlayerItem(const char* pszItemName); + bool HasPlayerItemFromID(int nID); bool HasWeapons(); // do I have ANY weapons? void SelectPrevItem(int iItem); void SelectNextItem(int iItem); diff --git a/dlls/weapons.cpp b/dlls/weapons.cpp index 737c1d8..c83025a 100644 --- a/dlls/weapons.cpp +++ b/dlls/weapons.cpp @@ -35,6 +35,8 @@ #define TRACER_FREQ 4 // Tracers fire every fourth bullet +extern bool IsBustingGame(); +extern bool IsPlayerBusting(CBaseEntity* pPlayer); //========================================================= // MaxAmmoCarry - pass in a name and this function will tell @@ -444,6 +446,19 @@ void CBasePlayerItem::FallThink() Materialize(); } + else if (m_pPlayer != NULL) + { + SetThink(NULL); + } + + // This weapon is an egon, it has no owner and we're in busting mode, so just remove it when it hits the ground + if (IsBustingGame() && FNullEnt(pev->owner)) + { + if (!strcmp("weapon_egon", STRING(pev->classname))) + { + UTIL_Remove(this); + } + } } //========================================================= @@ -536,6 +551,9 @@ void CBasePlayerItem::DefaultTouch(CBaseEntity* pOther) if (!pOther->IsPlayer()) return; + if (IsPlayerBusting(pOther)) + return; + CBasePlayer* pPlayer = (CBasePlayer*)pOther; // can I have this? @@ -811,7 +829,7 @@ bool CBasePlayerWeapon::IsUseable() } // clip is empty (or nonexistant) and the player has no more ammo of this type. - return false; + return CanDeploy(); } bool CBasePlayerWeapon::DefaultDeploy(const char* szViewModel, const char* szWeaponModel, int iAnim, const char* szAnimExt, int body) diff --git a/dlls/weapons.h b/dlls/weapons.h index 65c02d4..350be43 100644 --- a/dlls/weapons.h +++ b/dlls/weapons.h @@ -974,6 +974,7 @@ public: void Fire(const Vector& vecOrigSrc, const Vector& vecDir); bool HasAmmo(); + bool CanHolster(); void UseAmmo(int count);