Skip to content

Commit

Permalink
LocalPlayer and MaxClients override stacking (#162)
Browse files Browse the repository at this point in the history
Implements LocalPlayer + MaxClients override stacking first mentioned in #158 (comment) .
  • Loading branch information
Alienmario authored Oct 24, 2024
1 parent 0547610 commit 033cb00
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 61 deletions.
170 changes: 114 additions & 56 deletions scripting/include/srccoop/entitypatch.inc
Original file line number Diff line number Diff line change
Expand Up @@ -8,84 +8,144 @@

// Patches common to multiple source games

/**
* Sets an index of a player or entity for which a player will be searched on demand.
*/
void SetLocalPlayerEntity(int iEntIndex)
{
SetLocalPlayerEntityEx(CBaseEntity(iEntIndex));
}
//------------------------------------------------------
// Local Player override
//------------------------------------------------------

/**
* Sets a player or entity for which a player will be searched on demand.
* Pushes a player or an entity, for which a player will be searched on demand, to the stack.
* Must be followed by PopLocalPlayerOverride - use AddLocalPlayerPlaceholder for pairing without adding an override.
*/
void SetLocalPlayerEntityEx(CBaseEntity pEntity)
void AddLocalPlayerOverride(CBaseEntity pEntity)
{
if (g_pLocalPlayerEntity != NULL_CBASEENTITY)
if (pEntity == NULL_CBASEENTITY)
{
ThrowError("WARN: SetLocalPlayerEntity, but one has already been set (%d).", g_pLocalPlayerEntity.entindex);
g_iLocalPlayerStackSkips[g_iLocalPlayerStackPointer]++;
return;
}
if (++g_iLocalPlayerStackPointer >= sizeof(g_pLocalPlayerEntity))
{
g_iLocalPlayerStackPointer--;
ThrowError("LocalPlayer override stack overflow (max: %d)", sizeof(g_pLocalPlayerEntity) - 1);
}
g_pLocalPlayerEntity[g_iLocalPlayerStackPointer] = pEntity;
}

g_pLocalPlayerEntity = pEntity;
void AddLocalPlayerOverrideEx(int iEntIndex)
{
AddLocalPlayerOverride(CBaseEntity(iEntIndex));
}

void ClearLocalPlayerEntity()
void AddLocalPlayerPlaceholder()
{
if (g_pLocalPlayerEntity == NULL_CBASEENTITY)
AddLocalPlayerOverride(NULL_CBASEENTITY);
}

/*
* Undo's current local player override entity by popping the stack.
* Must be preceded by a call to AddLocalPlayerOverride or AddLocalPlayerPlaceholder.
*/
void PopLocalPlayerOverride()
{
if (g_iLocalPlayerStackSkips[g_iLocalPlayerStackPointer] > 0)
{
ThrowError("WARN: ClearLocalPlayerEntity, but none was set.");
g_iLocalPlayerStackSkips[g_iLocalPlayerStackPointer]--;
return;
}

g_pLocalPlayerEntity = NULL_CBASEENTITY;
if (g_iLocalPlayerStackPointer <= 0)
{
ThrowError("Tried to pop LocalPlayer override, but none was set.");
}
g_iLocalPlayerStackPointer--;
}

void SetMaxClientsOverride()
//------------------------------------------------------
// MaxClients override
//------------------------------------------------------

/**
* Overrides MaxClients to 1 and increments internal override counter.
* Must be followed by PopMaxClientsOverride - use AddMaxClientsPlaceholder for pairing without adding an override.
*/
void AddMaxClientsOverride()
{
if (g_iMaxClientsOverride != -1)
#define MAX_MAXPLAYERS_OVERRIDES 100
if (g_iMaxClientsOverrides >= MAX_MAXPLAYERS_OVERRIDES)
{
ThrowError("WARN: SetMaxClientsOverride, but one has already been set (%i).", g_iMaxClientsOverride);
ThrowError("MaxClients override overflow (max: %d)", MAX_MAXPLAYERS_OVERRIDES);
}

g_iMaxClientsOverride = gpGlobals.GetMaxPlayers();
gpGlobals.SetMaxPlayers(1);
g_iMaxClientsOverrides++;
}

void ClearMaxClientsOverride()
void AddMaxClientsPlaceholder()
{
if (g_iMaxClientsOverride == -1)
g_iMaxClientsOverrides++;
}

/*
* Decrements internal MaxClients override counter and undo's the override when reaching 0.
* Must be preceded by a call to AddMaxClientsOverride or AddMaxClientsPlaceholder.
*/
void PopMaxClientsOverride()
{
if (g_iMaxClientsOverrides <= 0)
{
ThrowError("Tried to pop MaxClients override, but none was set.");
}
if (!--g_iMaxClientsOverrides)
{
ThrowError("WARN: ClearMaxClientsOverride, but none was set.");
gpGlobals.SetMaxPlayers(MaxClients);
}
}

//------------------------------------------------------
// Convenience methods
//------------------------------------------------------

gpGlobals.SetMaxPlayers(g_iMaxClientsOverride);
g_iMaxClientsOverride = -1;
/**
* Convenience method that adds both LocalPlayer and MaxClients overrides.
*/
void AddSinglePlayerOverride(CBaseEntity pLocalPlayerEntity)
{
AddLocalPlayerOverride(pLocalPlayerEntity);
AddMaxClientsOverride();
}

bool IsOverridingMaxClients()
/**
* Convenience method that adds placeholder entries to both LocalPlayer and MaxClients overrides.
*/
void AddSinglePlayerPlaceholder()
{
return g_iMaxClientsOverride != -1;
AddLocalPlayerPlaceholder();
AddMaxClientsPlaceholder();
}

bool IsOverridingLocalPlayerEntity()
/**
* Convenience method that pops both LocalPlayer and MaxClients overrides.
*/
void PopSinglePlayerOverride()
{
return g_pLocalPlayerEntity != NULL_CBASEENTITY;
PopLocalPlayerOverride();
PopMaxClientsOverride();
}

//------------------------------------------------------
// UTIL_GetLocalPlayer
//------------------------------------------------------
public MRESReturn Hook_UTIL_GetLocalPlayer(Handle hReturn)
{
if (g_pLocalPlayerEntity.IsValid())
CBaseEntity pOverrideEntity = g_pLocalPlayerEntity[g_iLocalPlayerStackPointer];
if (pOverrideEntity.IsValid())
{
CBasePlayer pPlayer;
if (g_pLocalPlayerEntity.IsClassPlayer())
if (pOverrideEntity.IsClassPlayer())
{
pPlayer = view_as<CBasePlayer>(g_pLocalPlayerEntity);
pPlayer = view_as<CBasePlayer>(pOverrideEntity);
}
else
{
pPlayer = GetNearestPlayerPreferAlive(g_pLocalPlayerEntity);
pPlayer = GetNearestPlayerPreferAlive(pOverrideEntity);
if (pPlayer == NULL_CBASEENTITY)
{
return MRES_Ignored;
Expand Down Expand Up @@ -145,13 +205,13 @@ public MRESReturn BaseNPCSetModelBlock(int _this, Handle hParams)
public MRESReturn Hook_BaseNPCThink(int _this)
{
// Set this entity into context
SetLocalPlayerEntity(_this);
AddLocalPlayerOverrideEx(_this);
return MRES_Ignored;
}

public MRESReturn Hook_BaseNPCThinkPost(int _this)
{
ClearLocalPlayerEntity();
PopLocalPlayerOverride();
return MRES_Ignored;
}

Expand Down Expand Up @@ -622,19 +682,19 @@ public MRESReturn Hook_EnvZoomAcceptInput(int _this, Handle hReturn, Handle hPar
bHookSkip = true;
if (iNewClient > 0)
{
SetLocalPlayerEntity(iNewClient);
AddLocalPlayerOverrideEx(iNewClient);
AcceptEntityInput(_this, szNewInput);
ClearLocalPlayerEntity();
PopLocalPlayerOverride();
}
else
{
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientInGame(i))
{
SetLocalPlayerEntity(i);
AddLocalPlayerOverrideEx(i);
AcceptEntityInput(_this, szNewInput);
ClearLocalPlayerEntity();
PopLocalPlayerOverride();
}
}
}
Expand Down Expand Up @@ -892,6 +952,8 @@ public MRESReturn Hook_AIConditionsThink(int _this)
{
LogDebug("Hook_AIConditionsThink");
CAI_ScriptConditions pThis = CAI_ScriptConditions(_this);
CBasePlayer pLocalPlayerOverride = NULL_CBASEENTITY;

if (pThis.IsEnabled())
{
int count;
Expand Down Expand Up @@ -933,20 +995,20 @@ public MRESReturn Hook_AIConditionsThink(int _this)
}
}

CBasePlayer pPlayer = GetNearestPlayerPreferAliveEx(vecMidpoint);
if (pPlayer == NULL_CBASEENTITY)
pLocalPlayerOverride = GetNearestPlayerPreferAliveEx(vecMidpoint);
if (pLocalPlayerOverride == NULL_CBASEENTITY)
{
AddLocalPlayerPlaceholder();
return MRES_Supercede;
}

SetLocalPlayerEntityEx(pPlayer);
}
AddLocalPlayerOverride(pLocalPlayerOverride);
return MRES_Ignored;
}

public MRESReturn Hook_AIConditionsThinkPost(int _this)
{
ClearLocalPlayerEntity();
PopLocalPlayerOverride();
return MRES_Ignored;
}

Expand Down Expand Up @@ -1161,22 +1223,18 @@ public MRESReturn Hook_BaseNPCRunTask(int _this, Handle hParams)
(eTask == TASK_FACE_PLAYER) ||
(eTask == view_as<sharedtasks_e>(100000))) // TASK_FEAR_GET_PATH_TO_SAFETY_HINT
{
if (!IsOverridingLocalPlayerEntity())
{
// This should never happen as `CAI_BaseNPC::RunTask` is called from the hooked function `CAI_BaseNPC::Think`.
ThrowError("`CAI_BaseNPC::RunTask` called without overriding local player.");
}
SetMaxClientsOverride();
AddSinglePlayerOverride(pEntity);
}
else
{
AddSinglePlayerPlaceholder();
}
return MRES_Ignored;
}

public MRESReturn Hook_BaseNPCRunTaskPost(int _this, Handle hParams)
{
if (IsOverridingMaxClients())
{
ClearMaxClientsOverride();
}
PopSinglePlayerOverride();
return MRES_Ignored;
}

Expand Down
13 changes: 8 additions & 5 deletions scripting/include/srccoop/globals.inc
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,14 @@ ArrayList g_pPostponedSpawns;
// Represents the return value of the IsMultiplayer hook.
bool g_bIsMultiplayerOverride = true;

// Overrides `gpGlobals->maxclients`. The value is defaulted to `-1` to represent no override.
int g_iMaxClientsOverride = -1;

// the entity which UTIL_GetLocalPlayer hook will return the closest player to, alternatively containing the player index itself
CBaseEntity g_pLocalPlayerEntity = NULL_CBASEENTITY;
// Counts the # of overrides to `gpGlobals->maxclients`.
int g_iMaxClientsOverrides = 0;

// The entity which UTIL_GetLocalPlayer hook will return the closest player to, alternatively containing the player index itself.
// Note: Real stack size is 1 less, because first entry starts at index 1.
CBaseEntity g_pLocalPlayerEntity[32] = {NULL_CBASEENTITY, ...};
int g_iLocalPlayerStackSkips[sizeof(g_pLocalPlayerEntity)];
int g_iLocalPlayerStackPointer;

// ----------------------------
// Plugin API
Expand Down

0 comments on commit 033cb00

Please sign in to comment.