Hey all. I have this Sword system I made with various security actions, but I am having some trouble with making a certain action happen on the server, like a critical hit; based on the current animation that is playing.
The issue is, can’t exploiters play an animation that triggers this critical hit, Since I am using animator:GetPlayingAnimationTracks()?
Server Script part:
-- ValidateSwordHit function
validateSwordHit.OnServerInvoke = function(player, eHums, currentCombo)
print(currentCombo)
if typeof(player) ~= "Instance" then
return false
end
local char = player.Character or player.CharacterAdded:Wait()
local hum = char:WaitForChild("Humanoid")
local animator = hum:WaitForChild("Animator") :: Animator
local currentTime = tick()
playerHitTimes[player] = playerHitTimes[player] or 0
local timeSinceLastHit = currentTime - playerHitTimes[player]
playerHitTimes[player] = currentTime
if timeSinceLastHit < 0.25 then
warn("Time since last hit is too soon.")
return false
end
-- TODO: Track combo on the server separately to reject any impossible combos
local aliveHums = {}
for _, eHum in ipairs(eHums) do
if typeof(eHum) ~= "Instance" or not eHum.Parent:IsDescendantOf(workspace) then
return false
end
-- If everything above passes; add a distance check
local eChar = eHum.Parent
local distance = (eChar.PrimaryPart.Position - char.PrimaryPart.Position).Magnitude
if distance >= serverProperties.MAX_LEGAL_HIT_RANGE then
warn("Distance is invalid/impossible")
return false
end
if eHum.Health > 0 then
table.insert(aliveHums, eHum)
end
end
-- All security checks pass; apply damage and server only logic
local appliedStun
for _, aliveHum in ipairs(aliveHums) do
if aliveHum.Health > 0 and aliveHum.Parent ~= char and not char:GetAttribute("Stunned") then
-- First apply a stun to the player that IS attacking
-- TODO: apply a half stun to the player attacking
local dmg = serverProperties.BASE_DAMAGE
-- For normal attacks
local isCritical = rng:NextNumber(0, 1) <= serverProperties.CRITICAL_HIT_CHANCE
if isCritical then
dmg = setCriticalHitDmg(dmg)
end
-- TODO: Replace with a more secure critical hit method
for _, track in pairs(animator:GetPlayingAnimationTracks()) do
if track.Animation.AnimationId == sharedProperties.Tracks[4] then
isCritical = true
dmg = setCriticalHitDmg(dmg)
break
end
end
-- Deduct damage if attacking multiple enemies at once
local damageDeduction
if #aliveHums >= 2 then
damageDeduction = 1 - (#aliveHums * 0.1)
damageDeduction = math.max(damageDeduction, 0.5)
else
damageDeduction = 1
end
dmg *= damageDeduction
-- Final Damage
local finalDmg = math.round(dmg)
local didAttack, msg = utilityModule.attack(aliveHum.Parent, char, finalDmg, serverProperties.ENEMY_STUN_DURATION, stunModule)
faceAttacker(char, aliveHum.Parent)
if didAttack then
-- Apply attack logic
local isFinalBlow = aliveHum.Health <= 0
-- Apply a hit reaction to the enemy
local preloadedTracks = preloadedAnimations[aliveHum.Parent] -- stored on the character of the player/npc
if not preloadedTracks then
warn("[DEBUG]: Did not find a preloaded track for ", aliveHum.Parent, " possibly R15?")
return false
end
local hitReactionTrack = preloadedTracks[currentCombo]
if not hitReactionTrack then
warn("[DEBUG]: Did not find a hitReactionTrack for ", aliveHum.Parent)
return false
end
local playedHitTrack = fxManager.playOrStopTrack(hitReactionTrack, true)
playedHitTrack.Ended:Connect(function()
playedHitTrack:Destroy()
end)
swordEffectEvent:FireAllClients(player, aliveHum, isCritical, isFinalBlow)
else
warn(msg)
end
else
continue
end
end
return true
end
Here is the client script:
local function onSwordActivate()
if swordCooldown then
return
end
-- return if the player is stunned
if char:GetAttribute("Stunned") then
return
end
swordCooldown = true
-- Update combo
local newCombo = updateCombo()
lastSwingTime = tick()
-- Create and play the specified track
local currentTrack = animations[currentCombo]
local track = fxManager.playOrStopTrack(currentTrack, true) :: AnimationTrack
if not track then
warn("Did not successfully play track")
swordCooldown = false
return
end
local validateSuccess = false
track.KeyframeReached:Connect(function(keyName)
if keyName ~= "Slash" then
return
end
-- Play a swing sound
swordSwingSound:FireServer(sword.Handle) -- fire an event to play a sword swing sound
local newHitbox = createHitbox()
newHitbox.OnTouch = function(eHums)
for _, eHum in ipairs(eHums) do
if eHum == hum or eHum.Health <= 0 then
continue
end
end
validateSuccess = validateSwordHit:InvokeServer(eHums, currentCombo)
if not validateSuccess then
warn("Did not successfully validate hit!")
end
end
newHitbox:Start()
task.wait(0.05)
newHitbox:Stop()
newHitbox:Destroy()
end)
track.Stopped:Connect(function()
swordCooldown = false
track:Destroy()
end)
end
The issue is these parts on the server:
-- TODO: Replace with a more secure critical hit method
for _, track in pairs(animator:GetPlayingAnimationTracks()) do
if track.Animation.AnimationId == sharedProperties.Tracks[4] then
isCritical = true
dmg = setCriticalHitDmg(dmg)
break
end
end
Any help would be appreciated.