[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".
This commit is contained in:
Joël Troch 2024-08-28 15:33:25 +02:00
parent 33b2b3acd4
commit e0ba8cebc7
10 changed files with 447 additions and 17 deletions

View file

@ -27,6 +27,8 @@
#ifdef CLIENT_DLL #ifdef CLIENT_DLL
#include "hud.h" #include "hud.h"
#include "com_weapons.h" #include "com_weapons.h"
#else
extern bool IsBustingGame();
#endif #endif
#define EGON_SWITCH_NARROW_TIME 0.75 // Time it takes to switch fire modes #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) void CEgon::UseAmmo(int count)
{ {
#ifndef CLIENT_DLL
if (IsBustingGame())
return;
#endif
if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] >= count) if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] >= count)
m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= count; m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= count;
else else
@ -491,7 +498,17 @@ void CEgon::WeaponIdle()
m_deployed = true; m_deployed = true;
} }
bool CEgon::CanHolster()
{
#ifndef CLIENT_DLL
if (IsBustingGame())
{
return false;
}
#endif
return true;
}
void CEgon::EndAttack() void CEgon::EndAttack()
{ {

View file

@ -454,6 +454,8 @@ cvar_t sk_player_leg3 = {"sk_player_leg3", "1"};
// END Cvars for Skill Level settings // END Cvars for Skill Level settings
cvar_t sv_busters = {"sv_busters", "0", FCVAR_SERVER};
static bool SV_InitServer() static bool SV_InitServer()
{ {
if (!FileSystem_LoadFileSystem()) if (!FileSystem_LoadFileSystem())
@ -515,6 +517,8 @@ void GameDLLInit()
CVAR_REGISTER(&mp_chattime); CVAR_REGISTER(&mp_chattime);
CVAR_REGISTER(&sv_busters);
CVAR_REGISTER(&sv_allowbunnyhopping); CVAR_REGISTER(&sv_allowbunnyhopping);
// REGISTER CVARS FOR SKILL LEVEL STUFF // REGISTER CVARS FOR SKILL LEVEL STUFF

View file

@ -43,6 +43,8 @@ extern cvar_t mp_chattime;
extern cvar_t sv_allowbunnyhopping; extern cvar_t sv_allowbunnyhopping;
extern cvar_t sv_busters;
// Engine Cvars // Engine Cvars
inline cvar_t* g_psv_gravity; inline cvar_t* g_psv_gravity;
inline cvar_t* g_psv_aim; inline cvar_t* g_psv_aim;

View file

@ -373,7 +373,12 @@ CGameRules* InstallGameRules()
SERVER_COMMAND("exec game.cfg\n"); SERVER_COMMAND("exec game.cfg\n");
SERVER_EXECUTE(); 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 // generic half-life
g_teamplay = false; g_teamplay = false;

View file

@ -363,6 +363,33 @@ protected:
void SendMOTDToClient(edict_t* client); 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 CGameRules* g_pGameRules = nullptr;
inline DLL_GLOBAL bool g_fGameOver; inline DLL_GLOBAL bool g_fGameOver;
inline bool g_teamplay = false; inline bool g_teamplay = false;

View file

@ -34,6 +34,9 @@
#define WEAPON_RESPAWN_TIME 20 #define WEAPON_RESPAWN_TIME 20
#define AMMO_RESPAWN_TIME 20 #define AMMO_RESPAWN_TIME 20
// longest the intermission can last, in seconds
#define MAX_INTERMISSION_TIME 120
CVoiceGameMgr g_VoiceGameMgr; CVoiceGameMgr g_VoiceGameMgr;
class CMultiplayGameMgrHelper : public IVoiceGameMgrHelper class CMultiplayGameMgrHelper : public IVoiceGameMgrHelper
@ -159,9 +162,6 @@ void CHalfLifeMultiplay::RefreshSkillData()
gSkillData.plrDmgHornet = 10; gSkillData.plrDmgHornet = 10;
} }
// longest the intermission can last, in seconds
#define MAX_INTERMISSION_TIME 120
//========================================================= //=========================================================
//========================================================= //=========================================================
void CHalfLifeMultiplay::Think() void CHalfLifeMultiplay::Think()
@ -1631,3 +1631,286 @@ void CHalfLifeMultiplay::SendMOTDToClient(edict_t* client)
FREE_FILE(aFileList); 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");
}
}

View file

@ -47,6 +47,8 @@
extern void CopyToBodyQue(entvars_t* pev); extern void CopyToBodyQue(entvars_t* pev);
extern edict_t* EntSelectSpawnPoint(CBaseEntity* pPlayer); extern edict_t* EntSelectSpawnPoint(CBaseEntity* pPlayer);
extern bool IsBustingGame();
#define TRAIN_ACTIVE 0x80 #define TRAIN_ACTIVE 0x80
#define TRAIN_NEW 0xc0 #define TRAIN_NEW 0xc0
#define TRAIN_OFF 0x00 #define TRAIN_OFF 0x00
@ -688,24 +690,67 @@ void CBasePlayer::PackDeadPlayerItems()
iPA = 0; iPA = 0;
iPW = 0; iPW = 0;
// pack the ammo if (IsBustingGame())
while (iPackAmmo[iPA] != -1)
{ {
pWeaponBox->PackAmmo(MAKE_STRING(CBasePlayerItem::AmmoInfoArray[iPackAmmo[iPA]].pszName), m_rgAmmo[iPackAmmo[iPA]]); if (HasNamedPlayerItem("weapon_egon"))
iPA++; {
} for (i = 0; i < MAX_ITEM_TYPES; i++)
{
CBasePlayerItem* pItem = m_rgpPlayerItems[i];
// now pack all of the items in the lists if (pItem)
while (rgpPackWeapons[iPW]) {
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. bool bPackItems = true;
pWeaponBox->PackWeapon(rgpPackWeapons[iPW]);
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. 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; 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;
}
//========================================================= //=========================================================
// //
//========================================================= //=========================================================

View file

@ -283,6 +283,7 @@ public:
void DropPlayerItem(char* pszItemName); void DropPlayerItem(char* pszItemName);
bool HasPlayerItem(CBasePlayerItem* pCheckItem); bool HasPlayerItem(CBasePlayerItem* pCheckItem);
bool HasNamedPlayerItem(const char* pszItemName); bool HasNamedPlayerItem(const char* pszItemName);
bool HasPlayerItemFromID(int nID);
bool HasWeapons(); // do I have ANY weapons? bool HasWeapons(); // do I have ANY weapons?
void SelectPrevItem(int iItem); void SelectPrevItem(int iItem);
void SelectNextItem(int iItem); void SelectNextItem(int iItem);

View file

@ -35,6 +35,8 @@
#define TRACER_FREQ 4 // Tracers fire every fourth bullet #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 // MaxAmmoCarry - pass in a name and this function will tell
@ -444,6 +446,19 @@ void CBasePlayerItem::FallThink()
Materialize(); 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()) if (!pOther->IsPlayer())
return; return;
if (IsPlayerBusting(pOther))
return;
CBasePlayer* pPlayer = (CBasePlayer*)pOther; CBasePlayer* pPlayer = (CBasePlayer*)pOther;
// can I have this? // 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. // 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) bool CBasePlayerWeapon::DefaultDeploy(const char* szViewModel, const char* szWeaponModel, int iAnim, const char* szAnimExt, int body)

View file

@ -974,6 +974,7 @@ public:
void Fire(const Vector& vecOrigSrc, const Vector& vecDir); void Fire(const Vector& vecOrigSrc, const Vector& vecDir);
bool HasAmmo(); bool HasAmmo();
bool CanHolster();
void UseAmmo(int count); void UseAmmo(int count);