diff --git a/Couch/gamedata/Couch.txt b/Couch/gamedata/Couch.txt index c491b07..45f8821 100644 --- a/Couch/gamedata/Couch.txt +++ b/Couch/gamedata/Couch.txt @@ -10,6 +10,13 @@ "linux" "160" "windows" "159" } + + // CBasePlayer::PlayerUse(). Same offsets as PropSpawner. + "PlayerUse" + { + "linux" "414" + "windows" "413" + } } "Signatures" diff --git a/Couch/scripting/Couch.sp b/Couch/scripting/Couch.sp index 3d67929..5abe32a 100644 --- a/Couch/scripting/Couch.sp +++ b/Couch/scripting/Couch.sp @@ -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(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")) diff --git a/Couch/scripting/include/Couch.inc b/Couch/scripting/include/Couch.inc index 6d63fc8..143dd7a 100644 --- a/Couch/scripting/include/Couch.inc +++ b/Couch/scripting/include/Couch.inc @@ -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);