Add new humoristic railgunarena-like multiplayer game mode "GhostBusters" from HL 25th anniversary update. (#433)

This commit is contained in:
Andrey Akhmichin 2024-02-02 13:02:25 +00:00 committed by GitHub
parent 6787caaf9e
commit 9c247c5bcf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 337 additions and 18 deletions

View File

@ -291,7 +291,8 @@ void CEgon::Fire( const Vector &vecOrigSrc, const Vector &vecDir )
// multiplayer uses 1 ammo every 1/10th second
if( gpGlobals->time >= m_flAmmoUseTime )
{
UseAmmo( 1 );
if( !g_pGameRules->IsBustingGame())
UseAmmo( 1 );
m_flAmmoUseTime = gpGlobals->time + 0.1f;
}
}
@ -336,7 +337,8 @@ void CEgon::Fire( const Vector &vecOrigSrc, const Vector &vecDir )
//multiplayer uses 5 ammo/second
if( gpGlobals->time >= m_flAmmoUseTime )
{
UseAmmo( 1 );
if( !g_pGameRules->IsBustingGame())
UseAmmo( 1 );
m_flAmmoUseTime = gpGlobals->time + 0.2f;
}
}
@ -497,6 +499,15 @@ void CEgon::WeaponIdle( void )
m_deployed = TRUE;
}
BOOL CEgon::CanHolster( void )
{
#if CLIENT_DLL
return TRUE;
#else
return !g_pGameRules->IsBustingGame();
#endif
}
void CEgon::EndAttack( void )
{
bool bMakeNoise = false;

View File

@ -458,6 +458,7 @@ cvar_t sk_player_leg3 = { "sk_player_leg3","1" };
// END Cvars for Skill Level settings
cvar_t sv_pushable_fixed_tick_fudge = { "sv_pushable_fixed_tick_fudge", "15" };
cvar_t sv_busters = { "sv_busters", "0" };
// Register your console variables here
// This gets called one time when the game is initialied
@ -507,7 +508,7 @@ void GameDLLInit( void )
CVAR_REGISTER( &multibyte_only );
CVAR_REGISTER( &mp_chattime );
CVAR_REGISTER( &sv_busters );
// REGISTER CVARS FOR SKILL LEVEL STUFF

View File

@ -44,6 +44,7 @@ extern cvar_t defaultteam;
extern cvar_t allowmonsters;
extern cvar_t bhopcap;
extern cvar_t sv_pushable_fixed_tick_fudge;
extern cvar_t sv_busters;
// Engine Cvars
extern cvar_t *g_psv_gravity;

View File

@ -331,6 +331,11 @@ CGameRules *InstallGameRules( void )
g_teamplay = 1;
return new CHalfLifeTeamplay;
}
if( sv_busters.value > 0 )
{
g_teamplay = 0;
return new CMultiplayBusters;
}
if( (int)gpGlobals->deathmatch == 1 )
{
// vanilla deathmatch

View File

@ -162,6 +162,7 @@ public:
// Immediately end a multiplayer game
virtual void EndMultiplayerGame( void ) {}
virtual BOOL IsBustingGame( void ){ return FALSE; };
};
extern CGameRules *InstallGameRules( void );
@ -362,5 +363,30 @@ protected:
void SendMOTDToClient( edict_t *client );
};
bool IsPlayerBusting( CBaseEntity *pPlayer );
BOOL BustingCanHaveItem( CBasePlayer *pPlayer, CBaseEntity *pItem );
class CMultiplayBusters : public CHalfLifeMultiplay
{
public:
CMultiplayBusters();
void Think();
void PlayerSpawn( CBasePlayer *pPlayer );
void ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer );
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 *pevInflictor );
BOOL CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pItem );
void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon );
int WeaponShouldRespawn( CBasePlayerItem *pWeapon );
BOOL CanHaveItem( CBasePlayer *pPlayer, CItem *pItem );
void CheckForEgons();
void SetPlayerModel( CBasePlayer *pPlayer, BOOL bKnownBuster );
BOOL IsBustingGame( void ){ return TRUE; };
protected:
float m_flEgonBustingCheckTime;
};
extern DLL_GLOBAL CGameRules *g_pGameRules;
#endif // GAMERULES_H

View File

@ -1723,3 +1723,219 @@ void CHalfLifeMultiplay::SendMOTDToClient( edict_t *client )
FREE_FILE( (void*)aFileList );
}
int CMultiplayBusters::WeaponShouldRespawn( CBasePlayerItem *pWeapon )
{
if( pWeapon->m_iId == WEAPON_EGON )
return GR_WEAPON_RESPAWN_NO;
return CHalfLifeMultiplay::WeaponShouldRespawn( pWeapon );
}
BOOL CMultiplayBusters::CanHaveItem( CBasePlayer *pPlayer, CItem *pItem )
{
return BustingCanHaveItem( pPlayer, pItem );
}
BOOL CMultiplayBusters::CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pItem )
{
if( !BustingCanHaveItem( pPlayer, pItem ))
return FALSE;
return CHalfLifeMultiplay::CanHavePlayerItem( pPlayer, pItem );
}
int CMultiplayBusters::IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled )
{
if( IsPlayerBusting( pAttacker ))
return 1;
if( IsPlayerBusting( pKilled ))
return 2;
return 0;
}
void CMultiplayBusters::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor )
{
if( IsPlayerBusting( pVictim )
|| IsPlayerBusting( CBaseEntity::Instance( pKiller )))
CHalfLifeMultiplay::DeathNotice( pVictim, pKiller, pevInflictor );
}
void CMultiplayBusters::PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor )
{
if( IsPlayerBusting( pVictim ))
{
UTIL_ClientPrintAll( HUD_PRINTCENTER, "The Buster is dead!!" );
m_flEgonBustingCheckTime = -1.0f;
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 = (CBasePlayer *)( (CFuncVehicle *)ktmp )->m_pDriver;
if( pDriver != NULL )
{
pKiller = pDriver->pev;
peKiller = (CBasePlayer *)pDriver;
}
}
if( peKiller && peKiller->IsPlayer() )
{
UTIL_ClientPrintAll( HUD_PRINTTALK, UTIL_VarArgs( "%s has killed the Buster!", STRING( peKiller->pev->netname )));
}
pVictim->pev->renderfx = 0;
pVictim->pev->rendercolor = g_vecZero;
}
CHalfLifeMultiplay::PlayerKilled( pVictim, pKiller, pInflictor );
}
void CMultiplayBusters::ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer )
{
SetPlayerModel( pPlayer, FALSE );
CHalfLifeMultiplay::ClientUserInfoChanged( pPlayer, infobuffer );
}
void CMultiplayBusters::PlayerSpawn( CBasePlayer *pPlayer )
{
CHalfLifeMultiplay::PlayerSpawn( pPlayer );
SetPlayerModel( pPlayer, FALSE );
}
bool IsPlayerBusting( CBaseEntity *pPlayer )
{
if( g_pGameRules->IsBustingGame()
&& pPlayer && pPlayer->IsPlayer()
&& ((CBasePlayer*)pPlayer)->HasPlayerItemFromID( WEAPON_EGON ))
return true;
return false;
}
BOOL BustingCanHaveItem( CBasePlayer *pPlayer, CBaseEntity *pItem )
{
if( IsPlayerBusting( pPlayer )
&& !( strncmp( STRING( pItem->pev->classname ), "weapon_", 7 )
&& strncmp( STRING( pItem->pev->classname ), "ammo_", 5 )))
return FALSE;
return TRUE;
}
CMultiplayBusters::CMultiplayBusters()
{
CHalfLifeMultiplay();
m_flEgonBustingCheckTime = -1.0;
}
void CMultiplayBusters::CheckForEgons( void )
{
CBaseEntity *pPlayer;
CWeaponBox *pWeaponBox = NULL;
CBasePlayerItem *pWeapon;
CBasePlayer *pNewBuster = NULL;
int i, bestfrags = 9999;
if( m_flEgonBustingCheckTime <= 0.0f )
{
m_flEgonBustingCheckTime = gpGlobals->time + 10.0f;
return;
}
if( gpGlobals->time < m_flEgonBustingCheckTime )
return;
m_flEgonBustingCheckTime = -1.0f;
for( i = 1; i <= gpGlobals->maxClients; i++ )
{
pPlayer = UTIL_PlayerByIndex( i );
if( IsPlayerBusting( pPlayer ))
return;
}
for( i = 1; i <= gpGlobals->maxClients; i++ )
{
pPlayer = UTIL_PlayerByIndex( i );
if( pPlayer && pPlayer->pev->frags < bestfrags )
{
pNewBuster = (CBasePlayer*)pPlayer;
bestfrags = pPlayer->pev->frags;
}
}
if( !pNewBuster )
return;
pNewBuster->GiveNamedItem( "weapon_egon" );
while( ( pWeaponBox = (CWeaponBox*)UTIL_FindEntityByClassname( pWeaponBox, "weaponbox" )))
{
// destroy weaponboxes with egons
for( i = 0; i < MAX_ITEM_TYPES; i++ )
{
pWeapon = pWeaponBox->m_rgpPlayerItems[i];
while( pWeapon )
{
if( pWeapon->m_iId != WEAPON_EGON )
{
pWeapon = pWeapon->m_pNext;
continue;
}
pWeaponBox->Kill();
pWeapon = 0;
i = MAX_ITEM_TYPES;
}
}
}
}
void CMultiplayBusters::Think( void )
{
CheckForEgons();
CHalfLifeMultiplay::Think();
}
void CMultiplayBusters::SetPlayerModel( CBasePlayer *pPlayer, BOOL bKnownBuster )
{
const char *pszModel = NULL;
if( bKnownBuster || IsPlayerBusting( pPlayer ))
{
pszModel = "ivan";
}
else
{
pszModel = "skeleton";
}
g_engfuncs.pfnSetClientKeyValue( ENTINDEX( pPlayer->edict()), g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict()), "model", pszModel );
}
void CMultiplayBusters::PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon )
{
if( pWeapon->m_iId != WEAPON_EGON )
return;
pPlayer->RemoveAllItems( FALSE );
UTIL_ClientPrintAll( HUD_PRINTCENTER, "Long live the new Buster!" );
UTIL_ClientPrintAll( HUD_PRINTTALK, UTIL_VarArgs( "%s is busting!\n", STRING( pPlayer->pev->netname )));
SetPlayerModel( pPlayer, TRUE );
pPlayer->pev->health = pPlayer->pev->max_health;
pPlayer->pev->armorvalue = MAX_NORMAL_BATTERY;
pPlayer->pev->renderfx = kRenderFxGlowShell;
pPlayer->pev->renderamt = 25;
pPlayer->pev->rendercolor = Vector( 0, 75, 250 );
pPlayer->m_rgAmmo[pWeapon->PrimaryAmmoIndex()] = pPlayer->ammo_uranium = 100;
}

View File

@ -755,24 +755,44 @@ void CBasePlayer::PackDeadPlayerItems( void )
iPA = 0;
iPW = 0;
// pack the ammo
while( iPackAmmo[iPA] != -1 )
if( g_pGameRules->IsBustingGame())
{
pWeaponBox->PackAmmo( MAKE_STRING( CBasePlayerItem::AmmoInfoArray[iPackAmmo[iPA]].pszName ), m_rgAmmo[iPackAmmo[iPA]] );
iPA++;
while( rgpPackWeapons[iPW] )
{
// weapon unhooked from the player. Pack it into der box.
if( FClassnameIs( rgpPackWeapons[iPW]->pev, "weapon_egon" ))
{
pWeaponBox->PackWeapon( rgpPackWeapons[iPW] );
SET_MODEL( pWeaponBox->edict(), "models/w_egon.mdl" );
pWeaponBox->pev->velocity = g_vecZero;
pWeaponBox->pev->renderfx = kRenderFxGlowShell;
pWeaponBox->pev->renderamt = 25;
pWeaponBox->pev->rendercolor = Vector( 0, 75, 250 );
break;
}
iPW++;
}
}
// now pack all of the items in the lists
while( rgpPackWeapons[iPW] )
else
{
// weapon unhooked from the player. Pack it into der box.
pWeaponBox->PackWeapon( rgpPackWeapons[iPW] );
// pack the ammo
while( iPackAmmo[iPA] != -1 )
{
pWeaponBox->PackAmmo( MAKE_STRING( CBasePlayerItem::AmmoInfoArray[iPackAmmo[iPA]].pszName ), m_rgAmmo[iPackAmmo[iPA]] );
iPA++;
}
iPW++;
// 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.
}
@ -4635,6 +4655,31 @@ BOOL CBasePlayer::HasNamedPlayerItem( const char *pszItemName )
return FALSE;
}
//=========================================================
// HasPlayerItemFromID
//=========================================================
BOOL CBasePlayer::HasPlayerItemFromID( int nID )
{
CBasePlayerItem *pItem;
int i;
for( i = 0; i < MAX_ITEM_TYPES; i++ )
{
pItem = m_rgpPlayerItems[i];
while( pItem )
{
if( nID == pItem->m_iId )
{
return TRUE;
}
pItem = pItem->m_pNext;
}
}
return FALSE;
}
//=========================================================
//
//=========================================================

View File

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

View File

@ -471,6 +471,19 @@ void CBasePlayerItem::FallThink( void )
Materialize();
}
else if( m_pPlayer )
{
SetThink( NULL );
}
if( g_pGameRules->IsBustingGame())
{
if( !FNullEnt( pev->owner ))
return;
if( FClassnameIs( pev, "weapon_egon" ))
UTIL_Remove( this );
}
}
//=========================================================
@ -1076,7 +1089,7 @@ void CBasePlayerAmmo::Materialize( void )
void CBasePlayerAmmo::DefaultTouch( CBaseEntity *pOther )
{
if( !pOther->IsPlayer() )
if( !pOther->IsPlayer() || IsPlayerBusting( pOther ))
{
return;
}

View File

@ -793,13 +793,13 @@ public:
int AddToPlayer( CBasePlayer *pPlayer );
BOOL Deploy( void );
BOOL CanHolster( void );
void Holster( int skiplocal = 0 );
void UpdateEffect( const Vector &startPoint, const Vector &endPoint, float timeBlend );
void CreateEffect ( void );
void DestroyEffect ( void );
void EndAttack( void );
void Attack( void );
void PrimaryAttack( void );