Skip to content

Commit 2cb83f3

Browse files
authored
Input: Refactor +use handling to fix continuous use (#1821)
1 parent 5d9a353 commit 2cb83f3

File tree

9 files changed

+122
-199
lines changed

9 files changed

+122
-199
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ All notable changes to TTT2 will be documented here. Inspired by [keep a changel
2020
- Fixed loading screen tips translation (by @mexikoedi)
2121
- Fixed missing `limit_left` translation (by @mexikoedi)
2222
- Fixed `GetHeadshotMultiplier` nil value error (by @mexikoedi)
23+
- Fixed continuous use not working correctly with entities like health stations (by @saibotk)
24+
25+
### Removed / Breaking Changes
26+
27+
- Removed `IsSpecialUsableEntity` & `IsUsableEntity` functions on entities (by @saibotk)
28+
- Changed `ENT:ClientUse()`: Removed return value, now always blocks the default use handling (by @saibotk)
2329

2430
## [v0.14.5b](https://github.com/TTT-2/TTT2/tree/v0.14.5b) (2025-08-18)
2531

gamemodes/terrortown/entities/entities/ttt_base_placeable.lua

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ ENT.isDestructible = true
1414

1515
ENT.pickupWeaponClass = nil
1616

17-
ENT.CanUseKey = true
18-
1917
local soundDeny = Sound("HL2Player.UseDeny")
2018

2119
---
@@ -55,29 +53,6 @@ function ENT:PlayerCanPickupWeapon(activator)
5553
return true
5654
end
5755

58-
if CLIENT then
59-
---
60-
-- Hook that is called if a player uses their use key while focusing on the entity.
61-
-- Implement this to predict early if entity can be picked up
62-
-- @return bool True to prevent pickup
63-
-- @realm client
64-
function ENT:ClientUse()
65-
local client = LocalPlayer()
66-
67-
if not IsValid(client) or not client:IsTerror() or not self.pickupWeaponClass then
68-
return true
69-
end
70-
71-
if not self:PlayerCanPickupWeapon(client) then
72-
LANG.Msg("pickup_fail", nil, MSG_MSTACK_WARN)
73-
74-
self:EmitSound(soundDeny)
75-
76-
return true
77-
end
78-
end
79-
end -- CLIENT
80-
8156
if SERVER then
8257
local soundRumble = {
8358
Sound("physics/concrete/concrete_break2.wav"),

gamemodes/terrortown/entities/entities/ttt_c4/shared.lua

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -721,9 +721,7 @@ else -- CLIENT
721721
})
722722

723723
---
724-
-- Hook that is called if a player uses their use key while focusing on the entity.
725-
-- Shows C4 UI
726-
-- @return bool True to prevent pickup
724+
-- This is called if a player uses their use key while in proximity of the entity.
727725
-- @realm client
728726
function ENT:ClientUse()
729727
if IsValid(self) then
@@ -733,8 +731,6 @@ else -- CLIENT
733731
ShowC4Disarm(self)
734732
end
735733
end
736-
737-
return true
738734
end
739735

740736
---

gamemodes/terrortown/entities/entities/ttt_hat_deerstalker.lua

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ ENT.PrintName = "hat_deerstalker_name"
1111
ENT.Model = Model("models/ttt/deerstalker.mdl")
1212
ENT.CanHavePrints = false
1313

14-
ENT.CanUseKey = true
15-
1614
---
1715
-- @realm shared
1816
function ENT:SetupDataTables()

gamemodes/terrortown/entities/entities/ttt_health_station.lua

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -198,19 +198,6 @@ else
198198
walkkey = Key("+walk", "WALK"),
199199
}
200200

201-
---
202-
-- Hook that is called if a player uses their use key while focusing on the entity.
203-
-- Early check if client can use the health station
204-
-- @return bool True to prevent pickup
205-
-- @realm client
206-
function ENT:ClientUse()
207-
local client = LocalPlayer()
208-
209-
if not IsValid(client) or not client:IsPlayer() or not client:IsActive() then
210-
return true
211-
end
212-
end
213-
214201
-- handle looking at healthstation
215202
hook.Add("TTTRenderEntityInfo", "HUDDrawTargetIDHealthStation", function(tData)
216203
local client = LocalPlayer()

gamemodes/terrortown/gamemode/client/cl_keys.lua

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ function GM:OnSpawnMenuOpen()
2626
end
2727

2828
---
29-
-- Runs when a bind has been pressed. Allows to block commands.
29+
-- Runs when a bind has been pressed. Allows blocking commands.
30+
-- @note This does not block continuous use activation on the server when running into the entity with +use already being pressed.
3031
-- @note By using the "alias" console command, this hook can be effectively circumvented
3132
-- @note To stop the user from using +attack, +left and any other movement commands
3233
-- of the sort, please look into using @{GM:StartCommand} instead
@@ -64,6 +65,17 @@ function GM:PlayerBindPress(ply, bindName, pressed)
6465
return true
6566
end
6667
elseif bindName == "+use" and pressed then
68+
-- Handle special spectator use override
69+
if ply:IsSpec() then
70+
net.Start("TTT2PlayerUseEntity")
71+
net.WriteEntity(nil)
72+
net.WriteString("spectator_use")
73+
net.SendToServer()
74+
75+
-- Suppress the +use bind
76+
return true
77+
end
78+
6779
-- Do old traitor button check
6880
if TBHUD:PlayerIsFocused() then
6981
if ply:KeyDown(IN_WALK) then
@@ -76,47 +88,56 @@ function GM:PlayerBindPress(ply, bindName, pressed)
7688
end
7789

7890
-- Find out if a marker is focused otherwise check normal use
79-
local isClientOnly = false
80-
local useEnt = markerVision.GetFocusedEntity()
81-
local isRemote = IsValid(useEnt)
82-
if not isRemote then
83-
local shootPos = ply:GetShootPos()
84-
85-
local tr = util.TraceLine({
86-
start = shootPos,
87-
endpos = shootPos + ply:GetAimVector() * 100,
88-
filter = ply,
89-
mask = bit.bor(MASK_SOLID, CONTENTS_DEBRIS),
90-
})
91-
92-
useEnt = tr.Entity
93-
94-
if not tr.Hit or not IsValid(useEnt) then
95-
useEnt = nil
96-
end
91+
local markerVisionFocusedEnt = markerVision.GetFocusedEntity()
9792

98-
if useEnt and isfunction(useEnt.ClientUse) then
99-
isClientOnly = useEnt:ClientUse()
100-
end
101-
elseif isfunction(useEnt.RemoteUse) then
93+
if IsValid(markerVisionFocusedEnt) and isfunction(markerVisionFocusedEnt.RemoteUse) then
10294
sound.ConditionalPlay(soundUse, SOUND_TYPE_INTERACT)
10395

104-
isClientOnly = useEnt:RemoteUse(ply)
96+
local clientSideOnly = markerVisionFocusedEnt:RemoteUse()
97+
98+
if not clientSideOnly then
99+
-- Call this on the server too.
100+
-- This cannot be done on the server's Entity:Use hook etc., because we suppress the +use bind here to not trigger it
101+
-- on other close by entities.
102+
103+
net.Start("TTT2PlayerUseEntity")
104+
net.WriteEntity(markerVisionFocusedEnt)
105+
net.WriteString("remote_use")
106+
net.SendToServer()
107+
end
108+
109+
-- Suppress the +use bind here, so it does not trigger the Entity:Use hook on other close by entities.
110+
-- Suppressing means, that the server will not see this key as being pressed anymore, even if the client is still pressing it.
111+
return true
105112
end
106113

107-
-- If returned true by ClientUse or RemoteUse, then dont call Use and UseOverride or RemoteUse serverside
108-
if isClientOnly then
114+
-- This can sometimes deviate from the entity that the server actually uses.
115+
-- But this is fine for now.
116+
-- See https://github.com/Facepunch/garrysmod-issues/issues/5027
117+
local clientUseEnt = ply:GetUseEntity()
118+
119+
-- Handle client side use methods
120+
-- E.g. for showing client side UIs on interaction
121+
if IsValid(clientUseEnt) and isfunction(clientUseEnt.ClientUse) then
122+
clientUseEnt:ClientUse()
123+
109124
return true
110125
end
111126

112-
if IsValid(useEnt) and (ply:IsSpec() or useEnt:IsSpecialUsableEntity()) then
127+
-- Handle special weapon pickup use override
128+
local trace = util.QuickTrace(ply:GetShootPos(), ply:GetAimVector() * 100, ply)
129+
130+
if IsValid(trace.Entity) and trace.Entity:IsWeapon() then
113131
net.Start("TTT2PlayerUseEntity")
114-
net.WriteEntity(useEnt)
115-
net.WriteBool(isRemote)
132+
net.WriteEntity(trace.Entity)
133+
net.WriteString("weapon_pickup")
116134
net.SendToServer()
117135

136+
-- Suppress the +use bind
118137
return true
119138
end
139+
140+
-- Otherwise the +use is executed as usual, triggering the server-side Entity:Use function.
120141
elseif string.sub(bindName, 1, 4) == "slot" and pressed then
121142
local idx = tonumber(string.sub(bindName, 5, -1)) or 1
122143

gamemodes/terrortown/gamemode/server/sv_entity.lua

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,6 @@
22
-- @ref https://wiki.facepunch.com/gmod/Entity
33
-- @class Entity
44

5-
-- Caps taken from here: https://github.com/ValveSoftware/source-sdk-2013/blob/55ed12f8d1eb6887d348be03aee5573d44177ffb/mp/src/game/shared/baseentity_shared.h#L21-L38
6-
FCAP_IMPULSE_USE = 16
7-
FCAP_CONTINUOUS_USE = 32
8-
FCAP_ONOFF_USE = 64
9-
FCAP_DIRECTIONAL_USE = 128
10-
FCAP_USE_ONGROUND = 256
11-
FCAP_USE_IN_RADIUS = 512
12-
135
local safeCollisionGroups = {
146
[COLLISION_GROUP_WEAPON] = true,
157
}
@@ -78,38 +70,6 @@ function entmeta:Spawn()
7870
oldSpawn(self)
7971
end
8072

81-
---
82-
-- Checks if the entity has any use functionality attached. This can be attached in the engine/via hammer or by
83-
-- setting `.CanUseKey` to true. Player ragdolls and weapons always have use functionality attached.
84-
-- @param[default=0] number requiredCaps Use caps that are required for this entity
85-
-- @return boolean Returns true if the entity is usable by the player
86-
-- @ref https://github.com/ValveSoftware/source-sdk-2013/blob/0d8dceea4310fde5706b3ce1c70609d72a38efdf/sp/src/game/server/player.cpp#L2766C71-L2781
87-
-- @realm server
88-
function entmeta:IsUsableEntity(requiredCaps)
89-
requiredCaps = requiredCaps or 0
90-
91-
-- special case: TTT specific lua based use interactions
92-
-- when we're looking for specifically the lua use
93-
if self:IsSpecialUsableEntity() then
94-
return true
95-
end
96-
97-
local caps = self:ObjectCaps()
98-
99-
if
100-
bit.band(
101-
caps,
102-
bit.bor(FCAP_IMPULSE_USE, FCAP_CONTINUOUS_USE, FCAP_ONOFF_USE, FCAP_DIRECTIONAL_USE)
103-
)
104-
> 0
105-
and bit.band(caps, requiredCaps) == requiredCaps
106-
then
107-
return true
108-
end
109-
110-
return false
111-
end
112-
11373
---
11474
-- Some sounds are important enough that they shouldn't be affected by CPASAttenuationFilter
11575
-- @param string snd The name of the sound to be played

0 commit comments

Comments
 (0)