GameRules
The GameRules object defines the abstract rules of a game. It conventionally handles all event processing that is not innate to players, NPCs, the world, or other such tangible entities.
In a mod that supported deathmatch and capture the flag game modes, the modes would each have their own GameRules class containing the appropriate logic for when to award points, where to spawn players, and so on.
Minimum implementation
In shared\<mod>\<mod>_gamerules.cpp
:
#include "cbase.h"
#include "multiplay_gamerules.h"
#ifdef CLIENT_DLL
#define CSkeletonGameRules C_SkeletonGameRules
#endif
// Valve's base classes: CSingleplayRules, CMultiplayRules, CTeamplayRules, CTeamplayRoundBasedRules
class CSkeletonGameRules : public CMultiplayRules
{
DECLARE_CLASS( CSkeletonGameRules, CMultiplayRules );
};
REGISTER_GAMERULES_CLASS( CSkeletonGameRules );
In server\<mod>\<mod>_client.cpp
:
void InstallGameRules()
{
CreateGameRulesObject( "CSkeletonGameRules" );
}
Basic Multiplay Game Rule Class (Beta)
Header file:src\game\shared\czgo\czgo_base_mode.h
#ifndef CZGO_BASE_MODE_H
#define CZGO_BASE_MODE_H
#pragma once
#include "multiplay_gamerules.h"
#ifdef CLIENT_DLL
#define CCZGOBaseMode C_CZGOBaseMode
#define CCZGOBaseModeProxy C_CZGOBaseModeProxy
#endif
class CCZGOBaseModeProxy : public CGameRulesProxy
{
public:
DECLARE_CLASS(CCZGOBaseModeProxy, CGameRulesProxy);
DECLARE_NETWORKCLASS();
};
class CCZGOBaseMode : public CMultiplayRules
{
public:
DECLARE_CLASS(CCZGOBaseMode, CMultiplayRules);
#ifdef CLIENT_DLL
DECLARE_CLIENTCLASS_NOBASE(); // This makes datatables able to access our private vars.
#else
DECLARE_SERVERCLASS_NOBASE(); // This makes datatables able to access our private vars.
#endif
CCZGOBaseMode();
virtual ~CCZGOBaseMode();
virtual void Precache(void);
virtual bool ShouldCollide(int collisionGroup0, int collisionGroup1);
virtual bool ClientCommand(CBaseEntity *pEdict, const CCommand &args);
virtual void Think(void);
virtual void CreateStandardEntities(void);
virtual const char *GetGameDescription(void);
virtual void ClientDisconnected(edict_t *pClient);
#ifndef CLIENT_DLL
virtual bool CanHavePlayerItem(CBasePlayer *pPlayer, CBaseCombatWeapon *pItem);
virtual bool FShouldSwitchWeapon(CBasePlayer *pPlayer, CBaseCombatWeapon *pWeapon);
void AddLevelDesignerPlacedObject(CBaseEntity *pEntity);
void RemoveLevelDesignerPlacedObject(CBaseEntity *pEntity);
const char *GetChatFormat(bool bTeamOnly, CBasePlayer *pPlayer);
#endif
};
inline CCZGOBaseMode* CZGOBaseMode()
{
return static_cast<CCZGOBaseMode*>(g_pGameRules);
}
#endif
Source Code:src\game\shared\czgo\czgo_base_mode.cpp
#include "cbase.h"
#include "czgo_base_mode.h"
#ifdef CLIENT_DLL
#include "../client/hl2mp/c_hl2mp_player.h"
#else
#include "../server/hl2mp/hl2mp_player.h"
#endif
REGISTER_GAMERULES_CLASS(CCZGOBaseMode);
BEGIN_NETWORK_TABLE_NOBASE(CCZGOBaseMode, DT_CZGOBaseMode)
END_NETWORK_TABLE()
LINK_ENTITY_TO_CLASS(czgo_basemode, CCZGOBaseModeProxy);
IMPLEMENT_NETWORKCLASS_ALIASED(CZGOBaseModeProxy, DT_CZGOBaseModeProxy)
#ifdef CLIENT_DLL
void RecvProxy_CZGOBaseMode(const RecvProp *pProp, void **pOut, void *pData, int objectID)
{
CCZGOBaseMode *pRules = CZGOBaseMode();
Assert(pRules);
*pOut = pRules;
}
BEGIN_RECV_TABLE(CCZGOBaseModeProxy, DT_CZGOBaseModeProxy)
RecvPropDataTable("czgo_basemode_data", 0, 0, &REFERENCE_RECV_TABLE(DT_CZGOBaseMode), RecvProxy_CZGOBaseMode)
END_RECV_TABLE()
#else
void* SendProxy_CZGOBaseMode(const SendProp *pProp, const void *pStructBase, const void *pData, CSendProxyRecipients *pRecipients, int objectID)
{
CCZGOBaseMode *pRules = CZGOBaseMode();
Assert(pRules);
return pRules;
}
BEGIN_SEND_TABLE(CCZGOBaseModeProxy, DT_CZGOBaseModeProxy)
SendPropDataTable("czgo_basemode_data", 0, &REFERENCE_SEND_TABLE(DT_CZGOBaseMode), SendProxy_CZGOBaseMode)
END_SEND_TABLE()
#endif
CCZGOBaseMode::CCZGOBaseMode()
{
}
CCZGOBaseMode::~CCZGOBaseMode()
{
}
bool CCZGOBaseMode::ClientCommand(CBaseEntity *pEdict, const CCommand &args)
{
#ifndef CLIENT_DLL
if (BaseClass::ClientCommand(pEdict, args))
return true;
CHL2MP_Player *pPlayer = (CHL2MP_Player *)pEdict;
if (pPlayer->ClientCommand(args))
return true;
#endif
return false;
}
void CCZGOBaseMode::CreateStandardEntities(void)
{
#ifndef CLIENT_DLL
// Create the entity that will send our data to the client.
BaseClass::CreateStandardEntities();
#ifdef DBGFLAG_ASSERT
CBaseEntity *pEnt =
#endif
CBaseEntity::Create("czgo_basemode", vec3_origin, vec3_angle);
Assert(pEnt);
#endif
}
void CCZGOBaseMode::Think(void)
{
#ifndef CLIENT_DLL
CGameRules::Think();
#endif
}
#ifndef CLIENT_DLL
//=========================================================
// CanHaveWeapon - returns false if the player is not allowed
// to pick up this weapon
//=========================================================
bool CCZGOBaseMode::CanHavePlayerItem(CBasePlayer *pPlayer, CBaseCombatWeapon *pItem)
{
return true;
}
#endif
void CCZGOBaseMode::ClientDisconnected(edict_t *pClient)
{
}
const char *CCZGOBaseMode::GetGameDescription(void)
{
return "Condition Zero: Global Operation";
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CCZGOBaseMode::Precache(void)
{
}
bool CCZGOBaseMode::ShouldCollide(int collisionGroup0, int collisionGroup1)
{
if (collisionGroup0 > collisionGroup1)
{
// swap so that lowest is always first
V_swap(collisionGroup0, collisionGroup1);
}
if ((collisionGroup0 == COLLISION_GROUP_PLAYER || collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT) &&
collisionGroup1 == COLLISION_GROUP_WEAPON)
{
return false;
}
return BaseClass::ShouldCollide(collisionGroup0, collisionGroup1);
}
#ifdef CLIENT_DLL
#else
bool CCZGOBaseMode::FShouldSwitchWeapon(CBasePlayer *pPlayer, CBaseCombatWeapon *pWeapon)
{
if (pPlayer->GetActiveWeapon() && pPlayer->IsNetClient())
{
// Player has an active item, so let's check cl_autowepswitch.
const char *cl_autowepswitch = engine->GetClientConVarValue(engine->IndexOfEdict(pPlayer->edict()), "cl_autowepswitch");
if (cl_autowepswitch && atoi(cl_autowepswitch) <= 0)
{
return false;
}
}
return BaseClass::FShouldSwitchWeapon(pPlayer, pWeapon);
}
#endif
#ifndef CLIENT_DLL
const char *CCZGOBaseMode::GetChatFormat(bool bTeamOnly, CBasePlayer *pPlayer)
{
if (!pPlayer) // dedicated server output
{
return NULL;
}
const char *pszFormat = NULL;
// team only
if (bTeamOnly == TRUE)
{
if (pPlayer->GetTeamNumber() == TEAM_SPECTATOR)
{
pszFormat = "HL2MP_Chat_Spec";
}
else
{
const char *chatLocation = GetChatLocation(bTeamOnly, pPlayer);
if (chatLocation && *chatLocation)
{
pszFormat = "HL2MP_Chat_Team_Loc";
}
else
{
pszFormat = "HL2MP_Chat_Team";
}
}
}
// everyone
else
{
if (pPlayer->GetTeamNumber() != TEAM_SPECTATOR)
{
pszFormat = "HL2MP_Chat_All";
}
else
{
pszFormat = "HL2MP_Chat_AllSpec";
}
}
return pszFormat;
}
#endif
IMPORTANT: One last step, navigate to line 1257 in hl2mp_player.cpp, find and comment out:
GetGlobalTeam( pAttacker->GetTeamNumber() )->AddScore( iScoreToAdd );
This is because class "CMultiplay" doesn't define "Team".
GameRules are mostly written in shared code so that the client can predict the effects of its actions. However, there are some functions that are only present on the client or on the server.
Networking
GameRules are not themselves networked. Instead, a proxy object is created by inheriting from the CGameRulesProxy
class.
Notable functions
int Damage_GetNoPhysicsForce()
int Damage_GetShouldGibCorpse()
- etc.
- Various functions that return a integer bitstring containing the damage types that fall into the given category.
bool ShouldCollide()
- Whether entities in the given collision groups should collide with each other. Since the arguments are collision groups and not entities, this function isn't as useful as it might at first seem.
bool SwitchToNextBestWeapon()
CBaseCombatWeapon* GetNextBestWeapon()
- Two functions that can be used to choose a weapon for the player if, for instance, their current one runs out of ammo.
SwitchToNextBestWeapon()
returns true if the switch was successful. float GetAmmoDamage()
- Decides how much damage to apply when an entity (any entity!) fires bullets. Normally forwards calls to
GetAmmoDef()
. unsigned char* GetEncryptionKey()
- Your mod's ICE key for script encryption.
CViewVectors* GetViewVectors()
- Accessor for
g_DefaultViewVectors
.
Server
void LevelShutdown()
- Called when the GameRules are destroyed by the world.
void FrameUpdatePostEntityThink()
- Called each frame. By default forwards the call to
Think()
.Todo: What is the difference between this "PostEntityThink" func andEndGameFrame()
? void EndGameFrame()
- Called at the end of
CServerGameDLL::GameFrame()
(i.e. after all game logic has run). CreateStandardEntities()
- Creates the external entities needed for the GameRules to function. Warning:An untraceable crash will occur if you override this function without calling
BaseClass::CreateStandardEntities()
.
There are too many other important functions to list here individually. See game\shared\gamerules.h
line 179 onwards for the complete list, which covers:
- Difficulty levels
- The single/multiplayer status of a game
- Client connection & disconnection
- Client damage rules
- Client kills and scoring
- Spawning and respawning control
- Weapon Damage
- Weapon/item/ammo retrieval
- Weapon/item spawn and respawn control
- AI Definitions
- Healthcharger respawn control
- What happens to a dead player's weapons and ammo
- Teamplay rules
- Footstep sounds
- NPCs
- TraceLines
- Chat team/location prefixes
- Achievements