Couch fixes

This commit is contained in:
2026-09-29 17:54:40 +01:00
parent 63c218a0e9
commit aa7b60343f
3 changed files with 88 additions and 29 deletions
+7
View File
@@ -10,6 +10,13 @@
"linux" "160"
"windows" "159"
}
// CBasePlayer::PlayerUse(). Same offsets as PropSpawner.
"PlayerUse"
{
"linux" "414"
"windows" "413"
}
}
"Signatures"
+79 -27
View File
@@ -93,7 +93,10 @@ int g_iBeaconTick;
Handle g_hForceVPhysicsCollide;
// Couch bans: hits and +use are refused in OnCouchTakeDamage/OnCouchUse, and the vphysics collision filter is
Handle g_hPlayerUse;
ConVar g_cvTurboPhysics;
// Couch bans: hits and +use are refused in OnCouchTakeDamage/Hook_PlayerUse, and the vphysics collision filter is
// detoured so a blocked player can't push it. The detour is only enabled while someone is blocked.
Handle g_hShouldCollideDetour;
Handle g_hCollisionRulesChanged;
@@ -148,7 +151,7 @@ public void OnPluginStart()
g_cvModel = CreateConVar("sm_couch_model", COUCH_DEFAULT_MODEL, "Model used for the couch. Applies to the next spawn.", FCVAR_NOTIFY);
g_cvPushScale = CreateConVar("sm_couch_push_scale", "1.0", "Multiplier on the force bullets apply to the couch. 1.0 is the engine's own push.", FCVAR_NOTIFY, true, 0.0, true, 100.0);
g_cvPistolOnly = CreateConVar("sm_couch_pistol_only", "0", "Only pistols and knives move the couch; other guns and grenades are ignored.", FCVAR_NOTIFY, true, 0.0, true, 1.0);
g_cvAllowUse = CreateConVar("sm_allow_couch_use", "1", "Players may +use the couch to pick it up. 0 means it can only be moved by shooting it.", FCVAR_NOTIFY, true, 0.0, true, 1.0);
g_cvAllowUse = CreateConVar("sm_allow_couch_use", "1", "Players may push the couch by holding +use on it. 0 means it can only be moved by shooting it.", FCVAR_NOTIFY, true, 0.0, true, 1.0);
g_cvEffect = CreateConVar("sm_couch_effect", "0", "Colour cycle and pulsing light on the couch.", FCVAR_NOTIFY, true, 0.0, true, 1.0);
g_cvEffectIntensity = CreateConVar("sm_couch_effect_intensity", "3", "Brightness of the couch effect light. 0 is off, 3 is the default, 8 is very bright. Applies immediately.", FCVAR_NOTIFY, true, 0.0, true, 10.0);
g_cvIgnoreClip = CreateConVar("sm_couch_ignore_clip", "1", "Let the couch pass through func_clip_vphysics brushes.", FCVAR_NOTIFY, true, 0.0, true, 1.0);
@@ -164,9 +167,17 @@ public void OnPluginStart()
HookEvent("round_start", Event_RoundStart, EventHookMode_Post);
g_cvTurboPhysics = FindConVar("sv_turbophysics");
LoadGameData();
// Late load: the clip brushes and buttons already exist, so hook them now.
// Late load: the players, clip brushes and buttons already exist, so hook them now.
for (int client = 1; client <= MaxClients; client++)
{
if (IsClientInGame(client))
OnClientPutInServer(client);
}
int entity = -1;
if (g_hForceVPhysicsCollide != null)
{
@@ -223,6 +234,13 @@ public void OnEntityDestroyed(int entity)
Call_Finish();
}
// DHooks drops the hook with the entity.
public void OnClientPutInServer(int client)
{
if (g_hPlayerUse != null)
DHookEntity(g_hPlayerUse, false, client);
}
// Slot flag only; persistence across reconnects is the ban plugin's job.
public void OnClientDisconnect(int client)
{
@@ -431,7 +449,6 @@ int SpawnCouchAt(const float vecOrigin[3], const float vecAngles[3])
// SDKHooks drops the hooks with the entity.
SDKHook(couch, SDKHook_OnTakeDamage, OnCouchTakeDamage);
SDKHook(couch, SDKHook_Use, OnCouchUse);
// Takes the beacon's colour modulation at full opacity.
SetEntityRenderMode(couch, RENDER_NORMAL);
@@ -506,25 +523,49 @@ public Action OnCouchTakeDamage(int victim, int &attacker, int &inflictor, float
return result;
}
// +use on the couch: same ban and tier rules as shooting it, and off entirely with sm_allow_couch_use 0.
// Plugin_Handled stops the pickup.
public Action OnCouchUse(int entity, int activator, int caller, UseType type, float value)
// Reach of the engine's +use push.
#define USE_PUSH_DISTANCE 96.0
// Holding +use pushes the physics object in the crosshair (sv_turbophysics). The couch never gets a Use
// input, so the push is stopped by skipping PlayerUse for the tick.
public MRESReturn Hook_PlayerUse(int client)
{
if (activator < 1 || activator > MaxClients || !IsClientInGame(activator))
return Plugin_Continue;
if (!(GetClientButtons(client) & IN_USE))
return MRES_Ignored;
// Ban and tier first; their message is the more useful one.
if (IsClientRefused(entity, activator))
return Plugin_Handled;
if (g_cvTurboPhysics != null && !g_cvTurboPhysics.BoolValue)
return MRES_Ignored;
if (!g_cvAllowUse.BoolValue)
{
PrintHintText(activator, "The couch can't be picked up. Shoot it to move it.");
return Plugin_Handled;
}
int couch = GetCouchIndex();
if (couch == -1 || GetUsePushTarget(client) != couch)
return MRES_Ignored;
ReportInteraction(entity, activator, CouchMethod_Use, "");
return Plugin_Continue;
// Runs every tick +use is held; only the press is told why.
bool bPressed = view_as<bool>(GetEntProp(client, Prop_Data, "m_afButtonPressed") & IN_USE);
if (IsClientRefused(couch, client, bPressed) || !g_cvAllowUse.BoolValue)
return MRES_Supercede;
ReportInteraction(couch, client, CouchMethod_Use, "");
return MRES_Ignored;
}
// Entities only, like the engine's trace.
int GetUsePushTarget(int client)
{
float vecEye[3];
float vecAngles[3];
float vecForward[3];
GetClientEyePosition(client, vecEye);
GetClientEyeAngles(client, vecAngles);
GetAngleVectors(vecAngles, vecForward, NULL_VECTOR, NULL_VECTOR);
float vecEnd[3];
ScaleVector(vecForward, USE_PUSH_DISTANCE);
AddVectors(vecEye, vecForward, vecEnd);
TR_TraceRayFilter(vecEye, vecEnd, MASK_SOLID, RayType_EndPoint, TraceFilter_NotSelf, client, TRACE_ENTITIES_ONLY);
return TR_DidHit() ? TR_GetEntityIndex() : -1;
}
// Works out how an accepted hit moved the couch. Skipped when nobody listens; it runs per pellet.
@@ -571,22 +612,27 @@ void ReportInteraction(int couch, int client, CouchMethod method, const char[] s
Call_Finish();
}
// Couch ban or tier requirement. Tells the client why, or for a ban lets the ban plugin do so.
bool IsClientRefused(int couch, int client)
// Couch ban or tier requirement. With notify, tells the client why, or for a ban lets the ban plugin do so.
bool IsClientRefused(int couch, int client, bool notify = true)
{
if (g_bBlocked[client])
{
Call_StartForward(g_fwdOnClientBlocked);
Call_PushCell(couch);
Call_PushCell(client);
Call_Finish();
if (notify)
{
Call_StartForward(g_fwdOnClientBlocked);
Call_PushCell(couch);
Call_PushCell(client);
Call_Finish();
}
return true;
}
if (!MeetsTier(client, g_cvMoveTier.IntValue))
{
PrintHintText(client, "You need to be tier %d or higher to move the couch.", g_cvMoveTier.IntValue);
if (notify)
PrintHintText(client, "You need to be tier %d or higher to move the couch.", g_cvMoveTier.IntValue);
return true;
}
@@ -702,7 +748,7 @@ void LoadGameData()
GameData gd = new GameData("Couch");
if (gd == null)
{
LogError("Missing gamedata/Couch.txt; the couch will collide with func_clip_vphysics and couch-banned players can still push it.");
LogError("Missing gamedata/Couch.txt; the couch will collide with func_clip_vphysics, couch-banned players can still push it, and +use pushing can't be restricted.");
return;
}
@@ -717,6 +763,12 @@ void LoadGameData()
DHookAddParam(g_hForceVPhysicsCollide, HookParamType_CBaseEntity);
}
offset = gd.GetOffset("PlayerUse");
if (offset == -1)
LogError("Missing \"PlayerUse\" offset in gamedata/Couch.txt; sm_allow_couch_use, tiers and couch bans won't stop +use pushing.");
else
g_hPlayerUse = DHookCreate(offset, HookType_Entity, ReturnType_Void, ThisPointer_CBaseEntity, Hook_PlayerUse);
// Collision filter detour for couch bans; enabled only while someone is blocked.
g_hShouldCollideDetour = DHookCreateDetour(Address_Null, CallConv_THISCALL, ReturnType_Int, ThisPointer_Address);
if (!DHookSetFromConf(g_hShouldCollideDetour, gd, SDKConf_Signature, "CCollisionEvent::ShouldCollide"))
+2 -2
View File
@@ -125,12 +125,12 @@ native bool Couch_IsPistolOnly();
native void Couch_SetPistolOnly(bool enabled);
/**
* Whether players may +use the couch to pick it up. Mirrors sm_allow_couch_use.
* Whether players may push the couch by holding +use on it. Mirrors sm_allow_couch_use.
*/
native bool Couch_IsUseAllowed();
/**
* Allows or forbids picking the couch up with +use; when forbidden it can only be shot. Writes sm_allow_couch_use.
* Allows or forbids pushing the couch with +use; when forbidden it can only be shot. Writes sm_allow_couch_use.
*/
native void Couch_SetUseAllowed(bool allowed);