Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to TTT2 will be documented here. Inspired by [keep a changel

## Unreleased

### Added

- Added `plymeta:HasExternalGestureActive()` to check if an external gesture is active (by @mexikoedi)

### Changed

- Made improvements to door destruction (by @TW1STaL1CKY)
Expand All @@ -25,6 +29,7 @@ All notable changes to TTT2 will be documented here. Inspired by [keep a changel
- Fixed vFire explosions sometimes not damaging entities in the way they should, like explosive barrels (by @TW1STaL1CKY)
- Fixed an issue where overhead icons from `TTT2ModifyOverheadIcon` were ignored when `shouldDrawDefault` was false (by @mexikoedi)
- Fixed `SetIronsights` nil value error (by @mexikoedi)
- Fixed thirdperson animations from third-party addons (by @mexikoedi)

### Removed / Breaking Changes

Expand Down
101 changes: 99 additions & 2 deletions gamemodes/terrortown/gamemode/client/cl_player_ext.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
local net = net
local CurTime = CurTime

local plymeta = FindMetaTable("Player")
if not plymeta then
Expand All @@ -7,6 +8,94 @@ if not plymeta then
return
end

-- use a dedicated slot if available to reduce collisions with third-party gesture addons
local ttt2GestureSlot = GESTURE_SLOT_VCD or GESTURE_SLOT_CUSTOM
Comment on lines +11 to +12
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot really understand the reasoning here. What is the intention of using the VCD slot?
GMOD and sandbox gamemode both default to the GESTUR_SLOT_CUSTOM.

Besides, those two slot variables are always defined, so i'd assume this or statement to not be effective at all.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was from my first iteration where I just tried a different slot and noticed that it began working again but not ideal because of the voice chat for example but I never changed it afterwards.
So yes, that can be changed again.


-- safety padding to avoid one frame desync when external gestures end
local extGestureDurationPadding = 0.1

-- marks the end time of an externally started gesture on the player
local function MarkExternalGesture(ply, sequence)
if not isnumber(sequence) or sequence < 0 then
return
end

local sequenceDuration = ply:SequenceDuration(sequence)

if not isnumber(sequenceDuration) or sequenceDuration <= 0 then
return
end

local endTime = CurTime() + sequenceDuration + extGestureDurationPadding

if endTime > (ply.ttt2ExternalGestureEndTime or 0) then
ply.ttt2ExternalGestureEndTime = endTime
end
end

-- wrap this once to track externally started gestures on all slots while ignoring TTT2 internal writes
if
not plymeta._ttt2WrappedAddVCDSequenceToGestureSlot
and isfunction(plymeta.AddVCDSequenceToGestureSlot)
then
plymeta._ttt2WrappedAddVCDSequenceToGestureSlot = true

local plymeta_old_AddVCDSequenceToGestureSlot = plymeta.AddVCDSequenceToGestureSlot

---
-- Wrapped version of @{Player:AddVCDSequenceToGestureSlot} to track external gesture activity.
-- @param number slot The gesture slot.
-- @param number sequence The sequence id.
-- @param number cycle Cycle to start the animation. Value must be ranging from 0 to 1.
-- @param boolean autokill Whether the gesture should stop automatically.
-- @realm client
-- @see https://wiki.facepunch.com/gmod/Player:AddVCDSequenceToGestureSlot
function plymeta:AddVCDSequenceToGestureSlot(slot, sequence, cycle, autokill)
-- depth > 0 means this call originates from TTT2's AnimApplyGesture and should not count as external
if (self._ttt2InternalGestureWriteDepth or 0) <= 0 then
Comment on lines +54 to +55
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is never called from the AnimApplyGesture function, so we do not need to check this here afaik

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to make it similar to AnimRestartGesture which is called from AnimApplyGesture.
But yes, I can remove that from here.

MarkExternalGesture(self, sequence)
end

return plymeta_old_AddVCDSequenceToGestureSlot(self, slot, sequence, cycle, autokill)
end
end

if not plymeta._ttt2WrappedAnimRestartGesture and isfunction(plymeta.AnimRestartGesture) then
plymeta._ttt2WrappedAnimRestartGesture = true

local plymeta_old_AnimRestartGesture = plymeta.AnimRestartGesture

---
-- Wrapped version of @{Player:AnimRestartGesture} to track external gesture activity.
-- @param number slot The gesture slot.
-- @param ACT activity The @{ACT} that should be played.
-- @param boolean autokill Whether the gesture should stop automatically.
-- @realm client
-- @see https://wiki.facepunch.com/gmod/Player:AnimRestartGesture
function plymeta:AnimRestartGesture(slot, activity, autokill)
-- some addons use AnimRestartGesture -> map activity to sequence to track duration
if
(self._ttt2InternalGestureWriteDepth or 0) <= 0
and isnumber(activity)
and activity >= 0
then
local sequence = self:SelectWeightedSequence(activity)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could not find how this is handled internally within GMOD, but this can be wrong, when the sequence here is different from the one that GMOD chooses when playing ACT gestures that hold multiple sequences.

This is because you select one sequence at random here, you would need to pass this exact sequence to the AnimRestartGesture call, as far as i can see.

Copy link
Copy Markdown
Contributor Author

@mexikoedi mexikoedi Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense.
Since Custom Taunt uses AddVCDSequenceToGestureSlot directly, the whole AnimRestartGesture part is not required for the fix so I could even remove that part entirely if you want? But I don't know if maybe other gesture/... addons might use it and if/how they are affected.


MarkExternalGesture(self, sequence)
end

return plymeta_old_AnimRestartGesture(self, slot, activity, autokill)
end
end

---
-- Returns whether an external gesture is currently active on this player.
-- @return boolean
-- @realm client
function plymeta:HasExternalGestureActive()
return (self.ttt2ExternalGestureEndTime or 0) > CurTime()
end

---
-- Applies a animation gesture
-- @param ACT act The @{ACT} or sequence that should be played
Expand All @@ -15,8 +104,11 @@ end
-- @see https://wiki.facepunch.com/gmod/Player:AnimRestartGesture
-- @see https://wiki.facepunch.com/gmod/Player:AnimSetGestureWeight
function plymeta:AnimApplyGesture(act, weight)
self:AnimRestartGesture(GESTURE_SLOT_CUSTOM, act, true) -- true = autokill
self:AnimSetGestureWeight(GESTURE_SLOT_CUSTOM, weight)
-- prevent our own gesture writes from being treated as external
self._ttt2InternalGestureWriteDepth = (self._ttt2InternalGestureWriteDepth or 0) + 1
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not have to be a counter and can be a simple boolean flag.
This code is executed instantly and sequentially and should never reach any concurrency situations afaik how gmod works.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, that can be simplified.

self:AnimRestartGesture(ttt2GestureSlot, act, true) -- true = autokill
self:AnimSetGestureWeight(ttt2GestureSlot, weight)
self._ttt2InternalGestureWriteDepth = math.max(0, self._ttt2InternalGestureWriteDepth - 1)
end

local function MakeSimpleRunner(act)
Expand All @@ -37,6 +129,11 @@ local act_runner = {
-- ear grab needs weight control
-- sadly it's currently the only one
[ACT_GMOD_IN_CHAT] = function(ply, w)
-- prevent voice chat ear grab gesture from interfering with active third-party gestures
if ply:HasExternalGestureActive() then
return 0
end

Comment on lines +132 to +136
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about all the other native gestures listed below in the gestTbl?
Are they non-problematic and is this only an issue because of the automatic activation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot about them first and only did it for the voicechat because it was enabled by default and caused visual interference with the Custom Taunt gestures.
It also could be activated/deactivated all the time via a button click.
But then I also noticed the other gestures and tested it again.
They do not affect the dancing when I use them while using Custom Taunt gestures.
So there is no change needed for them.

local dest = ply:IsSpeaking() and 1 or 0

w = math.Approach(w, dest, FrameTime() * 10)
Expand Down