-- Global variables for SERVER context
local lastPainSoundTime = {}
local lastFreeFallSoundTime = {}
-- Rapid hit tracking for shotgun knockdown system
local playerHitTracking = {}
local RAPID_HIT_WINDOW = 0.5 -- Time window in seconds to track rapid hits
if SERVER then
-- Function to clean up old hit data to prevent memory leaks
local function CleanupOldHitData()
local currentTime = CurTime()
for steamID, hitData in pairs(playerHitTracking) do
-- Remove hits older than the tracking window
for i = #hitData.hits, 1, -1 do
if currentTime - hitData.hits[i] > RAPID_HIT_WINDOW then
table.remove(hitData.hits, i)
end
end
-- Remove player entry if no recent hits
if #hitData.hits == 0 then
playerHitTracking[steamID] = nil
end
end
end
-- Clean up old hit data every 2 seconds
timer.Create("RADS_CleanupHitData", 2, 0, CleanupOldHitData)
-- vFire integration variables
local vFireSupported = false
-- Check for vFire support
timer.Simple(0.5, function()
if vFireInstalled then
vFireSupported = true
if GetConVar("developer"):GetInt() > 0 then
print("[RADS] vFire integration enabled in ragdoll system")
end
end
if SERVER then
-- Automatic shock ragdoll system
hook.Add("Think", "RADS_ShockRagdollCheck", function()
if not GetConVar("rads_shock_enable"):GetBool() then return end
local ragdollThreshold = GetConVar("rads_shock_ragdoll_threshold"):GetFloat()
for _, ply in ipairs(player.GetAll()) do
if IsValid(ply) and ply:Alive() and not ply:GetNWBool("radsfa", false) then
local shock = ply:GetNWFloat("RADS_Shock", 0)
if shock >= ragdollThreshold then
-- Force ragdoll due to shock
if not ply.lastShockRagdollTime or CurTime() - ply.lastShockRagdollTime > 2 then
rads(ply, false) -- Not manual ragdoll
ply.lastShockRagdollTime = CurTime()
end
end
end
end
end)
-- Store shock responsiveness factor for ragdolls
hook.Add("Think", "RADS_ShockRagdollSlowMovement", function()
if not GetConVar("rads_shock_enable"):GetBool() then return end
for _, rag in ipairs(ents.FindByClass("prop_ragdoll")) do
if IsValid(rag) and rag.isShockRagdoll then
local owner = rag:GetNWEntity("owner")
if IsValid(owner) and owner:IsPlayer() then
local shock = owner:GetNWFloat("RADS_Shock", 0)
-- Calculate control responsiveness factor based on shock level
-- Higher shock = slower/less responsive controls (not physics)
local responsivenessFactor = math.Clamp(1 - (shock / 150), 0.2, 1) -- Max 80% control slowdown
-- Store the responsiveness factor on the ragdoll for use in control systems
rag.shockResponsiveness = responsivenessFactor
-- Update shock ragdoll status
local ragdollThreshold = GetConVar("rads_shock_ragdoll_threshold"):GetFloat()
if shock < ragdollThreshold then
rag.isShockRagdoll = false
rag.shockLevel = nil
rag.shockResponsiveness = 1.0 -- Reset to normal responsiveness
end
else
-- Clean up if owner is invalid
rag.isShockRagdoll = false
rag.shockLevel = nil
rag.shockResponsiveness = 1.0
end
end
end
end)
end
end)
-- Transfer vFire from player to ragdoll
function RADS_TransferVFire(ply, rag)
if not vFireInstalled or not IsValid(ply) or not IsValid(rag) then return end
-- Check if player is on fire
if ply:IsOnFire() then
-- Get fires on player
local fires = vFireGetFires(ply)
if fires and #fires > 0 then
-- Extinguish player
ply:Extinguish()
-- Create fires on ragdoll
local fireCount = math.min(#fires, 8) -- Limit fire count
CreateVFireEntFires(rag, fireCount)
-- Set fire owner for kill tracking
timer.Simple(0.1, function()
if IsValid(rag) then
local ragFires = vFireGetFires(rag)
if ragFires then
for _, fire in pairs(ragFires) do
if IsValid(fire) and fire.SetOwner then
fire:SetOwner(ply.LastAttacker or ply)
end
end
end
end
end)
end
end
end
-- Transfer vFire from ragdoll back to player
function RADS_TransferVFireToPlayer(rag, ply)
if not vFireInstalled or not IsValid(rag) or not IsValid(ply) then return end
-- Check if ragdoll is on fire
if rag:IsOnFire() then
-- Get fires on ragdoll
local fires = vFireGetFires(rag)
if fires and #fires > 0 then
-- Store fire owner for tracking
local fireOwner = nil
if fires[1] and IsValid(fires[1]) and fires[1].GetOwner then
fireOwner = fires[1]:GetOwner()
end
-- Extinguish ragdoll
rag:Extinguish()
-- Ignite player
local fireCount = math.min(#fires, 6)
CreateVFireEntFires(ply, fireCount)
-- Restore fire owner
if IsValid(fireOwner) then
timer.Simple(0.1, function()
if IsValid(ply) then
local playerFires = vFireGetFires(ply)
if playerFires then
for _, fire in pairs(playerFires) do
if IsValid(fire) and fire.SetOwner then
fire:SetOwner(fireOwner)
end
end
end
end
end)
end
end
end
end
-- Gore System: Head Explosion Function
-- Old RADS_TriggerHeadExplosion function removed - using newer version with proper positioning
-- Gore System: Blood Explosion Effect
function RADS_CreateBloodExplosion(pos, target)
if not IsValid(target) then return end
-- Create multiple blood spurts in different directions
for i = 1, 12 do
local bloodDir = VectorRand():GetNormalized()
bloodDir.z = math.abs(bloodDir.z) * 0.5 -- Bias upward
local effectdata = EffectData()
effectdata:SetOrigin(pos + VectorRand() * 2)
effectdata:SetNormal(bloodDir)
effectdata:SetMagnitude(math.random(50, 100))
effectdata:SetScale(math.random(1, 3))
util.Effect("BloodImpact", effectdata)
end
-- Create blood decals on nearby surfaces
for i = 1, 8 do
local traceDir = VectorRand():GetNormalized()
local trace = util.TraceLine({
start = pos,
endpos = pos + traceDir * 200,
filter = target
})
if trace.Hit then
util.Decal("Blood", trace.HitPos + trace.HitNormal, trace.HitPos - trace.HitNormal)
end
end
end
-- Gore System: Continuous Blood Stream
function RADS_StartBloodStream(target, pos)
if not IsValid(target) then return end
local bloodDuration = GetConVar("rads_gore_blood_duration"):GetFloat()
local timerName = "RADS_BloodStream_" .. target:EntIndex()
-- Remove existing timer if any
if timer.Exists(timerName) then
timer.Remove(timerName)
end
-- Start blood stream timer
timer.Create(timerName, 0.1, bloodDuration * 10, function()
if not IsValid(target) or not target.hasGoreExplosion then
timer.Remove(timerName)
return
end
-- Get current neck position
local currentPos = target:GetBonePosition(target:LookupBone("ValveBiped.Bip01_Neck1") or 0)
if currentPos == Vector(0,0,0) then
currentPos = pos
end
-- Create blood drip effect
local effectdata = EffectData()
effectdata:SetOrigin(currentPos)
effectdata:SetNormal(Vector(0, 0, -1))
effectdata:SetMagnitude(math.random(10, 30))
effectdata:SetScale(1)
util.Effect("BloodImpact", effectdata)
-- Occasionally create blood decals below
if math.random(1, 3) == 1 then
local trace = util.TraceLine({
start = currentPos,
endpos = currentPos + Vector(0, 0, -100),
filter = target
})
if trace.Hit then
util.Decal("Blood", trace.HitPos + trace.HitNormal, trace.HitPos - trace.HitNormal)
end
end
end)
end
function RADS.PainSound(ply)
if not IsValid(ply) then return end
local model = ply:GetModel()
if not model or type(model) ~= "string" then return end
local g = "man"
if string.find(model, "alyx.mdl") or string.find(model, "mossman.mdl") or string.find(model, "mossman_arctic.mdl") or string.find(model, "p2_chell.mdl") or string.find(model, "female_") then g = "woman" end
if string.find(model, "combine") or string.find(model, "police.mdl") then g = "combine" end
if GetConVar("rads_painsounds"):GetBool() then
local curTime = CurTime()
local cooldown = 0.3
if g == "man" then
if not lastPainSoundTime[ply] or curTime - lastPainSoundTime[ply] >= cooldown then
local rndmsnd = math.random(1, #RADS.MalePain)
local randomSound = RADS.MalePain[rndmsnd]
ply:EmitSound(randomSound)
lastPainSoundTime[ply] = curTime
end
elseif g == "woman" then
if not lastPainSoundTime[ply] or curTime - lastPainSoundTime[ply] >= cooldown then
local rndmsnd = math.random(1, #RADS.FemalePain)
local randomSound = RADS.FemalePain[rndmsnd]
ply:EmitSound(randomSound)
lastPainSoundTime[ply] = curTime
end
elseif g == "combine" then
if not lastPainSoundTime[ply] or curTime - lastPainSoundTime[ply] >= cooldown then
local rndmsnd = math.random(1, #RADS.CombinePain)
local randomSound = RADS.CombinePain[rndmsnd]
ply:EmitSound(randomSound)
lastPainSoundTime[ply] = curTime
end
end
end
end
function RADS.FreeFall(ply)
if not IsValid(ply) then return end
local model = ply:GetModel()
if not model or type(model) ~= "string" then return end
local g = "man"
if string.find(model, "alyx.mdl") or string.find(model, "mossman.mdl") or string.find(model, "female_") then g = "woman" end
if string.find(model, "combine") or string.find(model, "police.mdl") then g = "combine" end
if GetConVar("rads_painsounds"):GetBool() then
local curTime = CurTime()
local cooldown = 0.3
if g == "man" then
if not lastFreeFallSoundTime[ply] or curTime - lastFreeFallSoundTime[ply] >= cooldown then
local rndmsnd = math.random(1, #RADS.MaleFall)
local randomSound = RADS.MaleFall[rndmsnd]
ply:EmitSound(randomSound)
lastFreeFallSoundTime[ply] = curTime
end
elseif g == "woman" then
if not lastFreeFallSoundTime[ply] or curTime - lastFreeFallSoundTime[ply] >= cooldown then
local rndmsnd = math.random(1, #RADS.FemaleFall)
local randomSound = RADS.FemaleFall[rndmsnd]
ply:EmitSound(randomSound)
lastFreeFallSoundTime[ply] = curTime
end
elseif g == "combine" then
if not lastFreeFallSoundTime[ply] or curTime - lastFreeFallSoundTime[ply] >= cooldown then
local rndmsnd = math.random(1, #RADS.CombineFall)
local randomSound = RADS.CombineFall[rndmsnd]
ply:EmitSound(randomSound)
lastFreeFallSoundTime[ply] = curTime
end
end
end
end
_P = FindMetaTable("Player")
_ENT = FindMetaTable("Entity")
function _P:IsRag()
return self:GetNWBool("radsfa")
end
function _P:GetRads()
return self:GetNWEntity("player_ragdoll")
end
function RADS.IsTTT()
if engine.ActiveGamemode() == "terrortown" then return true end
return false
end
function _ENT:GetOwnerrr()
if self:GetNWEntity("owner") ~= nil then return self:GetNWEntity("owner") end
return nil
end
function _ENT:IsRads()
if self:GetNWEntity("owner") ~= nil then return true end
return false
end
function RADS.IsJmodAct()
return type(JMod) == "table"
end
hook.Add("PhysgunDrop", "RADS.Drop", function(ply, ent)
if ply:IsSuperAdmin() and ent:IsRagdoll() and ent:IsRads() then ent.physgunned = false end
if ply:IsSuperAdmin() and ent:IsPlayer() then ent.physgunned = false end
end)
hook.Add("PhysgunPickup", "RADS.Pickup", function(ply, ent)
if ply:IsSuperAdmin() and ent:IsPlayer() and not ent.fake then
rads(ent)
ent.physgunned = true
return false
end
if ent:IsRagdoll() and ent:IsRads() then ent.physgunned = true end
end)
hook.Add("CanPlayerSuicide", "RADS.SuicideAllow", function(ply)
if not GetConVar("rads_enablekill"):GetBool() and ply.Otrub then
ply:ChatPrint("No easy way out.")
return false
end
return true
end)
local CurTime = CurTime
local time
local player_GetAll = player.GetAll
local tbl
hook.Add("PlayerSpawn", "RADS.SpawnReset", function(ply)
ply:SetParent(nil)
while not ply:IsInWorld() and not timer.Exists("respawntimer" .. ply:EntIndex()) do
ply:Spawn()
end
if timer.Exists("respawntimer" .. ply:EntIndex()) then return end
if timer.Exists("radstimer" .. ply:EntIndex()) then timer.Remove("radstimer" .. ply:EntIndex()) end
-- Always remove calcview on spawn to ensure clean state
net.Start('REMOVECALC')
net.Send(ply)
-- Clear death first-person flag
ply:SetNWBool('rads_dead_firstperson', false)
local exr = ply:GetNWEntity("player_ragdoll")
if IsValid(exr) then
ply:SetNWBool('radsfa', false)
exr:RemoveEFlags(EFL_KEEP_ON_RECREATE_ENTITIES)
ply:SetNWEntity('deadbody', exr)
exr:SetNWEntity('deadbodyowner', ply)
ply:SetNWEntity("player_ragdoll", nil)
exr:SetNWEntity("RagdollController", nil)
exr:SetNWEntity("owner", nil)
end
ply.fake = false
ply.physgunned = false
ply.brokenspine = false
ply.gettingUp = false
ply.upValue = 0
ply.lastGetUpAttempt = 0
ply.lastGetUpTime = nil -- Reset the get up timer on spawn
-- NEW: Reset lung oxygen system states on spawn
ply.lungOxygenActive = false
ply.lungStaminaDrain = false
if IsValid(ply.wheezeEntity) then
ply.wheezeEntity:Remove()
ply.wheezeEntity = nil
end
end)
util.AddNetworkString("ragscale")
util.AddNetworkString("ragplayercolor")
util.AddNetworkString("showiconleft")
util.AddNetworkString("hideiconleft")
util.AddNetworkString("showiconright")
util.AddNetworkString("hideiconright")
-- Improved color transfer function
function _ENT:BetterSetPlayerColor(col)
if not (col or self) then return end
timer.Simple(
.1,
function()
if not IsValid(self) then return end
net.Start("ragplayercolor")
net.WriteEntity(self)
net.WriteVector(col)
net.Broadcast()
end
)
end
hook.Add("Think", "RADS.PlayerThink", function(ply)
tbl = player_GetAll()
time = CurTime()
for i = 1, #tbl do
hook.Run("Player Think", tbl[i], time)
end
end)
-- NEW: Hook to ensure wheeze sounds follow ragdolls
hook.Add("Think", "RADS_UpdateWheezePosition", function()
for _, ply in ipairs(player.GetAll()) do
if IsValid(ply) and ply:IsPlayer() and IsValid(ply.wheezeEntity) then
local targetPos
-- If player is ragdolled, follow the ragdoll
if ply:GetNWBool("radsfa") then
local ragdoll = ply:GetNWEntity("player_ragdoll")
if IsValid(ragdoll) then
-- Try to get head position from ragdoll
local headBone = ragdoll:LookupBone("ValveBiped.Bip01_Head1")
if headBone then
targetPos = ragdoll:GetBonePosition(headBone)
else
targetPos = ragdoll:GetPos() + Vector(0, 0, 64)
end
else
targetPos = ply:GetPos() + Vector(0, 0, 64)
end
else
-- Follow the player
local headBone = ply:LookupBone("ValveBiped.Bip01_Head1")
if headBone then
targetPos = ply:GetBonePosition(headBone)
else
targetPos = ply:GetPos() + Vector(0, 0, 64)
end
end
-- Update wheeze entity position
if targetPos then
ply.wheezeEntity:SetPos(targetPos)
end
end
end
end)
-- NEW: Clean up wheeze entities when player disconnects
hook.Add("PlayerDisconnected", "RADS_CleanupWheezeOnDisconnect", function(ply)
if IsValid(ply) and IsValid(ply.wheezeEntity) then
ply.wheezeEntity:Remove()
ply.wheezeEntity = nil
end
end)
util.AddNetworkString('REMOVECALC')
util.AddNetworkString("ADDCALC")
fallChanceTable = {
[HITGROUP_HEAD] = 0.45, -- Reduced from 0.75 to 0.45 (45% chance)
[HITGROUP_CHEST] = 0.35, -- Reduced from 0.65 to 0.35 (35% chance)
[HITGROUP_STOMACH] = 0.40, -- Reduced from 0.50 to 0.40 (40% chance)
[HITGROUP_LEFTARM] = 0.15, -- Reduced from 0.25 to 0.15 (15% chance)
[HITGROUP_RIGHTARM] = 0.15, -- Reduced from 0.25 to 0.15 (15% chance)
[HITGROUP_LEFTLEG] = 0.25, -- Reduced from 0.35 to 0.25 (25% chance)
[HITGROUP_RIGHTLEG] = 0.25, -- Reduced from 0.35 to 0.25 (25% chance)
[HITGROUP_GENERIC] = 0.55 -- NEW: Pelvis hitgroup - high fall chance due to balance importance
}
-- Improved shouldFall function that works with damage and body part
function shouldFall(bodyPart, damage, damageType, ply)
local baseChance = fallChanceTable[bodyPart] or 0.20 -- Default 20% for unknown body parts
-- NEW: Special pelvis handling - check for existing pelvis damage
if bodyPart == HITGROUP_GENERIC and IsValid(ply) then
-- If pelvis is broken, extremely high fall chance
if ply.brokenpelvis then
baseChance = 0.95 -- 95% chance if pelvis is already broken
elseif ply.Organs and ply.Organs['pelvis'] then
-- Increase fall chance based on pelvis damage level
local pelvisHealth = ply.Organs['pelvis']
local pelvisDamagePercent = 1 - (pelvisHealth / 25) -- 25 is max pelvis health
baseChance = baseChance + (pelvisDamagePercent * 0.3) -- Up to 30% additional fall chance
end
end
-- General pelvis damage modifier for all body parts
local pelvisModifier = 1.0
if IsValid(ply) and ply.brokenpelvis then
pelvisModifier = 1.4 -- 40% higher fall chance for any damage when pelvis is broken
elseif IsValid(ply) and ply.Organs and ply.Organs['pelvis'] then
local pelvisHealth = ply.Organs['pelvis']
if pelvisHealth < 15 then -- Severely damaged pelvis
pelvisModifier = 1.2 -- 20% higher fall chance
elseif pelvisHealth < 20 then -- Moderately damaged pelvis
pelvisModifier = 1.1 -- 10% higher fall chance
end
end
-- Damage scaling: higher damage increases chance
local damageMultiplier = 1.0
if damage then
if damage >= 75 then
damageMultiplier = 1.8 -- Very high damage
elseif damage >= 50 then
damageMultiplier = 1.5 -- High damage
elseif damage >= 35 then
damageMultiplier = 1.2 -- Medium damage
elseif damage >= 25 then
damageMultiplier = 1.0 -- Normal damage
else
damageMultiplier = 0.7 -- Low damage
end
end
-- Damage type modifiers
local typeMultiplier = 1.0
if damageType then
if bit.band(damageType, DMG_BLAST) ~= 0 then
typeMultiplier = 1.4 -- Explosions more likely to knock down
elseif bit.band(damageType, DMG_BUCKSHOT) ~= 0 then
typeMultiplier = 1.2 -- Shotguns more likely
elseif bit.band(damageType, DMG_CLUB) ~= 0 then
typeMultiplier = 0.8 -- Club damage less likely
end
end
local finalChance = baseChance * damageMultiplier * typeMultiplier * pelvisModifier
finalChance = math.min(finalChance, 0.95) -- Cap at 95%
return math.random() < finalChance
end
concommand.Add("rads_viewrag", function(ply, cmd, args)
if not ply:IsSuperAdmin() then return end
local tr = ply:GetEyeTrace()
if not IsValid(tr.Entity) or not tr.Entity:IsPlayer() then return end
local tarpp = tr.Entity
rads(tarpp)
end)
-- Console command to remove handcuffs from ragdolls
concommand.Add("rads_remove_handcuffs", function(ply, cmd, args)
if not IsValid(ply) then return end
local tr = util.TraceLine({
start = ply:GetShootPos(),
endpos = ply:GetShootPos() + ply:GetAimVector() * 80,
filter = ply
})
if IsValid(tr.Entity) and tr.Entity:IsRagdoll() then
local ragdollOwner = tr.Entity:GetNWEntity("owner")
-- Check if it's a handcuffed player ragdoll
if IsValid(ragdollOwner) and ragdollOwner:IsPlayer() and tr.Entity:GetNWBool("RADS_Handcuffed", false) then
-- Remove handcuff status
tr.Entity:SetNWBool("RADS_Handcuffed", false)
ragdollOwner:SetNWBool("RADS_Handcuffed", false)
-- Remove handcuff model
if IsValid(tr.Entity.HandcuffModel) then
tr.Entity.HandcuffModel:Remove()
tr.Entity.HandcuffModel = nil
end
-- Play sound
tr.Entity:EmitSound("physics/metal/metal_solid_impact_soft1.wav", 60, 100)
-- Notify players
ply:PrintMessage(HUD_PRINTCENTER, "Removed handcuffs from " .. ragdollOwner:Nick() .. ".")
ragdollOwner:PrintMessage(HUD_PRINTCENTER, ply:Nick() .. " removed your handcuffs!")
-- Create handcuffs item on ground
local handcuffsEnt = ents.Create("ent_jack_hmcd_handcuffs")
handcuffsEnt:SetPos(tr.Entity:GetPos() + Vector(0, 0, 10))
handcuffsEnt:SetAngles(Angle(0, math.random(0, 360), 0))
handcuffsEnt:Spawn()
handcuffsEnt:Activate()
-- Add some velocity to the dropped handcuffs
local phys = handcuffsEnt:GetPhysicsObject()
if IsValid(phys) then
phys:SetVelocity(Vector(math.random(-50, 50), math.random(-50, 50), math.random(20, 50)))
end
else
ply:PrintMessage(HUD_PRINTCENTER, "No handcuffed ragdoll in range.")
end
else
ply:PrintMessage(HUD_PRINTCENTER, "No ragdoll in range.")
end
end)
-- Removed damage interruption hook as it was causing issues with getting up
-- hook.Add("EntityTakeDamage", "RADS.RagdollDamageInterrupt", function(target, dmginfo)
-- local owner = nil
-- if IsValid(target) and target:IsRagdoll() then
-- owner = target:GetNWEntity("owner") or target:GetNWEntity('deadbodyowner')
-- elseif IsValid(target) and target:IsPlayer() then
-- owner = target
-- end
--
-- if IsValid(owner) and owner:IsPlayer() and owner:GetNWBool("radsfa") then
-- owner.takingDamage = true
-- end
-- end)
end
local CustomWeight = {
["models/player/police_fem.mdl"] = 50,
["models/player/police.mdl"] = 60,
["models/player/combine_soldier.mdl"] = 70,
["models/player/combine_super_soldier.mdl"] = 80,
["models/player/combine_soldier_prisonguard.mdl"] = 70,
['models/player/charple.mdl'] = 5
}
if SERVER then
util.AddNetworkString("SendSavedPlayerWeaponsToActivator")
util.AddNetworkString("SavedPlayerWeapons")
-- ConVars removed - values are now hardcoded
-- rads_waketime = 2.5, rads_upspeed = 400, rads_maxupspeed = 400
CreateConVar("rads_namedisplay_server", "1", {FCVAR_ARCHIVE, "Enable server-side name display support"})
function SendSavedPlayerWeapons(ply)
net.Start("SavedPlayerWeapons")
net.WriteTable(ply.Info.Weapons3)
net.Send(ply)
end
savedPlayerState = {}
function RADS_EzArmorSaveInfo(ply)
local steamID = ply:SteamID()
savedPlayerState[steamID] = {
EZarmor = {},
EZhealth = ply.EZhealth or nil,
EZirradiated = ply.EZirradiated or nil,
o2 = ply.o2 or nil,
EZbleeding = ply.EZbleeding or nil,
EZvirus = ply.EZvirus or nil,
-- NEW: Save lung damage oxygen system states
lungOxygenActive = ply.lungOxygenActive or false,
lungStaminaDrain = ply.lungStaminaDrain or false,
wheezeEntity = ply.wheezeEntity or nil
}
if ply.EZarmor then
savedPlayerState[steamID].EZarmor = {
items = ply.EZarmor.items or nil,
speedFrac = ply.EZarmor.speedFrac or nil,
effects = ply.EZarmor.effects or nil,
mskmat = ply.EZarmor.mskmat or nil,
sndlop = ply.EZarmor.sndlop or nil,
suited = ply.EZarmor.suited or nil,
bodygroups = ply.EZarmor.bodygroups or nil,
totalWeight = ply.EZarmor.totalWeight or nil
}
end
end
function RADS_RestoreEzArmor(ply) -- SendSavedPlayerWeapons(ply)
local steamID = ply:SteamID()
if savedPlayerState[steamID] then
local state = savedPlayerState[steamID]
if RADS.IsJmodAct() and ply.EZarmor then
ply.EZarmor = {
items = state.EZarmor.items,
speedFrac = state.EZarmor.speedFrac,
effects = state.EZarmor.effects,
mskmat = state.EZarmor.mskmat,
sndlop = state.EZarmor.sndlop,
suited = state.EZarmor.suited,
bodygroups = state.EZarmor.bodygroups,
totalWeight = state.EZarmor.totalWeight
}
ply.EZhealth = state.EZhealth
ply.EZirradiated = state.EZirradiated
ply.o2 = state.o2
ply.EZbleeding = state.EZbleeding
ply.EZvirus = state.EZvirus
-- NEW: Restore lung damage oxygen system states
ply.lungOxygenActive = state.lungOxygenActive
ply.lungStaminaDrain = state.lungStaminaDrain
-- Clean up any existing wheeze entity before restoring
if IsValid(ply.wheezeEntity) then
ply.wheezeEntity:Remove()
ply.wheezeEntity = nil
end
end
savedPlayerState[steamID] = nil
end
end
function RADS_SavePlyInfo(ply)
ply.Info = {}
local info = ply.Info
info.HasSuit = ply:IsSuitEquipped()
info.SuitPower = ply:GetSuitPower()
info.Ammo = ply:GetAmmo()
info.ActiveWeapon = IsValid(ply:GetActiveWeapon()) and ply:GetActiveWeapon():GetClass() or nil
info.runspeed = ply:GetRunSpeed()
info.walkspeed = ply:GetWalkSpeed()
info.ActiveWeapon2 = ply:GetActiveWeapon()
GetFakeWeapon(ply)
info.Angles = ply:GetAngles()
info.Weapons = {}
for i, wep in pairs(ply:GetWeapons()) do
info.Weapons[wep:GetClass()] = {
Clip1 = wep:Clip1(),
Clip2 = wep:Clip2(),
AmmoType = wep:GetPrimaryAmmoType()
}
-- RADS Grappling Hook Compatibility: Save grappling hook state
if wep:GetClass() == "wep_jack_hmcd_grapl" and wep.SaveGrapplingState then
wep:SaveGrapplingState()
end
-- IED Compatibility: Save IED rigged state for proper restoration
if wep:GetClass() == "wep_jack_hmcd_ied" and wep.GetRigged then
info.Weapons[wep:GetClass()].IEDRigged = wep:GetRigged()
end
end
info.Weapons2 = {}
for i, wep in ipairs(ply:GetWeapons()) do
info.Weapons2[i - 1] = wep:GetClass()
end
info.Weapons3 = {}
for i, wep in ipairs(ply:GetWeapons()) do
end
SendSavedPlayerWeapons(ply) -- info.Weapons3[wep:GetClass()] = wep:GetPrintName()
info.eyeviewcvar = ply:GetInfoNum('eyeview_enabled', 1)
info.AllAmmo = {}
local i
for ammo, amt in pairs(ply:GetAmmo()) do
i = i or 0
i = i + 1
info.AllAmmo[ammo] = {i, amt}
end
return info
end
function RADS_ReturnPlyInfo(ply)
ClearFakeWeapon(ply)
ply:SetSuppressPickupNotices(true)
local info = ply.Info
if not info then return end
-- CRITICAL FIX: Enhanced weapon duplication prevention for guns1 compatibility
if ply.gettingUpFromRagdoll then
-- Store the flag on ALL existing weapons before stripping
for _, weapon in pairs(ply:GetWeapons()) do
if IsValid(weapon) then
weapon.ownerGettingUpFromRagdoll = true
-- Special handling for guns1 weapons
if string.find(weapon:GetClass(), "wep_jack_") or string.find(weapon:GetClass(), "wep_mann_") or string.find(weapon:GetClass(), "wep_viz_") then
weapon.preventDuplication = true
end
end
end
-- Clean up any existing dropped weapon entities from guns1 that might cause duplication
-- Only remove dropped weapon boxes, not planted explosives
for _, ent in pairs(ents.FindByClass("prop_physics")) do
if IsValid(ent) and ent.IEDAttacker == ply then
-- Check if this is a dropped weapon box (not a planted explosive)
-- Planted explosives should have ExplodeIED function, dropped weapons don't
if not ent.ExplodeIED then
ent:Remove()
end
end
end
end
ply:StripWeapons()
ply:StripAmmo()
ply.slots = {}
-- Enhanced weapon restoration with duplication prevention
for name, wepinfo in pairs(info.Weapons or {}) do
local weapon = ply:Give(name, true)
if IsValid(weapon) then
-- Set gettingUpFromRagdoll flag on newly given weapons
if ply.gettingUpFromRagdoll then
weapon.ownerGettingUpFromRagdoll = true
weapon.preventDuplication = true
end
-- Restore clip data
if wepinfo.Clip1 ~= nil and wepinfo.Clip2 ~= nil then
weapon:SetClip1(wepinfo.Clip1)
weapon:SetClip2(wepinfo.Clip2)
end
-- RADS Grappling Hook Compatibility: Restore grappling hook state
if weapon:GetClass() == "wep_jack_hmcd_grapl" and weapon.RestoreGrapplingState then
timer.Simple(0.1, function()
if IsValid(weapon) and IsValid(ply) then
weapon:RestoreGrapplingState()
end
end)
end
-- IED Compatibility: Restore IED state if needed
if weapon:GetClass() == "wep_jack_hmcd_ied" and wepinfo.IEDRigged then
timer.Simple(0.05, function()
if IsValid(weapon) and IsValid(ply) then
weapon:SetRigged(wepinfo.IEDRigged)
-- Reconnect to planted explosive if it exists
for _, ent in pairs(ents.FindByClass("prop_physics")) do
if IsValid(ent) and ent.IEDAttacker == ply and ent.ExplodeIED then
weapon.Explosive = ent
break
end
end
end
end)
end
end
end
for ammo, amt in pairs(info.Ammo or {}) do
ply:GiveAmmo(amt, ammo)
end
if info.ActiveWeapon then ply:SelectWeapon(info.ActiveWeapon) end
if info.HasSuit then
ply:EquipSuit()
ply:SetSuitPower(info.SuitPower or 0)
else
ply:RemoveSuit()
end
ply:SetRunSpeed(info.runspeed)
ply:SetWalkSpeed(info.walkspeed)
ply:SetHealth(info.Hp or 0)
ply:SetArmor(info.Armor or 0)
ply:SetEyeAngles(info.Angles)
info.Weapons3 = nil
-- CRITICAL: Clear gettingUpFromRagdoll flag after restoration to prevent lingering effects
if ply.gettingUpFromRagdoll then
timer.Simple(0.2, function()
if IsValid(ply) then
ply.gettingUpFromRagdoll = nil
-- Clear flags from all weapons as well
for _, weapon in pairs(ply:GetWeapons()) do
if IsValid(weapon) then
weapon.ownerGettingUpFromRagdoll = nil
weapon.preventDuplication = nil
end
end
end
end)
end
end
function GetFakeWeapon(ply)
ply.curweapon = ply.Info.ActiveWeapon
end
function ClearFakeWeapon(ply)
if ply.FakeShooting then
if _G.DespawnWeapon then
_G.DespawnWeapon(ply)
else
if GetConVar("developer"):GetInt() > 0 then
print("[RADS] ERROR: DespawnWeapon function not available globally at line 470!")
end
end
end
end
function RADS_RagBones(ent) -- util.AddNetworkString('RetrieveWeaponFromRagdoll') -- net.Receive("RetrieveWeaponFromRagdoll", function(len, ply) -- weaponClass = net.ReadTable() -- local tr = ply:GetEyeTrace() -- if IsValid(tr.Entity) and tr.Entity:GetClass() == "prop_ragdoll" then -- local owner = tr.Entity:GetNWEntity("owner") -- local deadbodyowner = tr.Entity:GetNWEntity('deadbodyowner') -- if IsValid(owner) and owner:IsPlayer() or IsValid(deadbodyowner) and deadbodyowner:IsPlayer() then -- if owner.Info and owner.Info.Weapons3 then -- owner.Info.Weapons3[weaponClass] = nil -- ply:Give(weaponClass) -- else -- ply:Give(weaponClass) -- end -- end -- end -- end)
local rag = ent:GetNWEntity("player_ragdoll")
local ragdollBones = rag:GetPhysicsObjectCount()
local vel = ent:GetVelocity() / 1
for i = 0, rag:GetPhysicsObjectCount() - 1 do
local physobj = rag:GetPhysicsObjectNum(i)
local ragbonename = rag:GetBoneName(rag:TranslatePhysBoneToBone(i))
local bone = ent:LookupBone(ragbonename)
if bone then
local bonemat = ent:GetBoneMatrix(bone)
if bonemat then
local bonepos = bonemat:GetTranslation()
local boneang = bonemat:GetAngles()
physobj:SetPos(bonepos, true)
physobj:SetAngles(boneang)
if ent:Alive() then vel = vel end
if not ent:Alive() then vel = vel / 2 end
physobj:AddVelocity(vel)
end
end
end
end
-- RADS_ValidPos function removed due to being buggy
-- function RADS_ValidPos(originalPos, ply)
-- This function is removed due to being buggy
-- end
-- Function to remove all armors from a ragdoll
local function RemoveRag(rag)
if not IsValid(rag) then return end
if rag.armors then
for id, ent in pairs(rag.armors) do
if IsValid(ent) then
ent.override = true
ent:Remove()
end
end
end
-- Clean up gore system when ragdoll is removed
if rag.hasGoreExplosion then
-- Remove gore stump
if IsValid(rag.goreStump) then
rag.goreStump:Remove()
rag.goreStump = nil
end
-- Stop blood stream timer
local timerName = "RADS_BloodStream_" .. rag:EntIndex()
if timer.Exists(timerName) then
timer.Remove(timerName)
end
rag.hasGoreExplosion = nil
end
end
hook.Add("RADS_Ready", "RADS.CustomApi", function(rag) if GetConVar("developer"):GetInt() > 0 then print("") end end)
-- RADS Grappling Hook Compatibility Hook
hook.Add("RADS_Ready", "RADS.GrapplingHookCompat", function(rag)
local ply = rag:GetNWEntity("owner")
if not IsValid(ply) then return end
-- Handle grappling hook physics interactions with ragdoll
if ply.GrapplingHookData and ply.GrapplingHookData.pos then
timer.Simple(0.1, function()
if IsValid(rag) and IsValid(ply) then
-- Find the grappling hook entity
local hookEnt = nil
for _, ent in pairs(ents.FindByClass("ent_jack_hmcd_grapl")) do
if ent.Owner == ply then
hookEnt = ent
break
end
end
if IsValid(hookEnt) then
-- Get the pelvis bone and physics object for proper attachment
local pelvisBone = rag:LookupBone("ValveBiped.Bip01_Pelvis")
local pelvisPhysBone = rag:TranslateBoneToPhysBone(pelvisBone)
local pelvisPhys = rag:GetPhysicsObjectNum(pelvisPhysBone)
if IsValid(pelvisPhys) then
-- Create a rope constraint between hook and ragdoll pelvis (invisible)
local ropeConstraint = constraint.Rope(
hookEnt, rag,
0, pelvisPhysBone,
Vector(0,0,0), Vector(0,0,0),
ply.GrapplingHookData.ropeLength or 1000,
0, 0, 0, "cable/rope", false
)
-- Store constraint for cleanup
rag.GrapplingRopeConstraint = ropeConstraint
-- Apply grappling forces to pelvis physics object
local hookPos = hookEnt:GetPos()
local pelvisPos = pelvisPhys:GetPos()
local dist = hookPos:Distance(pelvisPos)
local desiredDist = ply.GrapplingHookData.ropeLength or 1000
if dist > desiredDist then
local dir = (hookPos - pelvisPos):GetNormalized()
local force = dir * math.min((dist - desiredDist) * 100, 3000)
pelvisPhys:ApplyForceCenter(force)
-- Apply counter-force to hook for realistic physics
hookEnt:GetPhysicsObject():ApplyForceCenter(-dir * force * 0.3)
end
-- Create a think timer for continuous grappling physics
timer.Create("GrapplingPhysics_" .. rag:EntIndex(), 0.1, 0, function()
if not IsValid(rag) or not IsValid(hookEnt) or not IsValid(pelvisPhys) then
timer.Remove("GrapplingPhysics_" .. rag:EntIndex())
return
end
local hookPos = hookEnt:GetPos()
local pelvisPos = pelvisPhys:GetPos()
local dist = hookPos:Distance(pelvisPos)
local desiredDist = ply.GrapplingHookData.ropeLength or 1000
if dist > desiredDist then
local dir = (hookPos - pelvisPos):GetNormalized()
local force = dir * math.min((dist - desiredDist) * 80, 2500)
pelvisPhys:ApplyForceCenter(force)
-- Apply counter-force to hook
hookEnt:GetPhysicsObject():ApplyForceCenter(-dir * force * 0.2)
end
end)
end
end
end
end)
end
end)
-- RADS Grappling Hook Cleanup Hooks
hook.Add("PlayerDisconnected", "RADS.GrapplingHookCleanup", function(ply)
if not IsValid(ply) then return end
-- Clean up grappling physics timer
local rag = ply:GetNWEntity("player_ragdoll")
if IsValid(rag) then
timer.Remove("GrapplingPhysics_" .. rag:EntIndex())
-- Clean up rope constraint
if rag.GrapplingRopeConstraint and IsValid(rag.GrapplingRopeConstraint) then
rag.GrapplingRopeConstraint:Remove()
rag.GrapplingRopeConstraint = nil
end
end
-- Clean up any grappling hooks owned by disconnecting player
for _, ent in pairs(ents.FindByClass("ent_jack_hmcd_grapl")) do
if ent.Owner == ply then
ent:Remove()
end
end
-- Clean up stored grappling hook data
ply.GrapplingHookState = nil
ply.GrapplingHookData = nil
end)
hook.Add("PlayerDeath", "RADS.GrapplingHookCleanup", function(ply)
if not IsValid(ply) then return end
-- Clean up grappling physics timer
local rag = ply:GetNWEntity("player_ragdoll")
if IsValid(rag) then
timer.Remove("GrapplingPhysics_" .. rag:EntIndex())
-- Clean up rope constraint
if rag.GrapplingRopeConstraint and IsValid(rag.GrapplingRopeConstraint) then
rag.GrapplingRopeConstraint:Remove()
rag.GrapplingRopeConstraint = nil
end
end
-- Clean up grappling hooks on death
for _, ent in pairs(ents.FindByClass("ent_jack_hmcd_grapl")) do
if ent.Owner == ply then
ent:Remove()
end
end
-- Clean up grappling hook weapon state
local grapplingWeapon = ply:GetWeapon("wep_jack_hmcd_grapl")
if IsValid(grapplingWeapon) and grapplingWeapon.CleanupGrapplingHook then
grapplingWeapon:CleanupGrapplingHook()
end
-- Clean up stored data
ply.GrapplingHookState = nil
ply.GrapplingHookData = nil
end)
function rads(ply, isManual)
if not GetConVar("rads_status"):GetBool() then
if GetConVar("developer"):GetInt() > 0 then
print("Script is disabled. Not executing functionality.")
end
return
end
if not IsValid(ply) or not ply:IsPlayer() or not ply:Alive() then
if GetConVar("developer"):GetInt() > 0 then
print("[RADS] Failed validation - IsValid: " .. tostring(IsValid(ply)) .. ", IsPlayer: " .. tostring(ply:IsPlayer()) .. ", Alive: " .. tostring(ply:Alive()))
end
return
end
if timer.Exists("radstimer" .. ply:EntIndex()) then
if GetConVar("developer"):GetInt() > 0 then
print("[RADS] Timer exists for player: " .. ply:Nick())
end
return
end
if ply:GetNWBool("gh.Ghosted") then
if GetConVar("developer"):GetInt() > 0 then
print("[RADS] Player is ghosted: " .. ply:Nick())
end
return
end
if GetConVar("developer"):GetInt() > 0 then
print("[RADS] Attempting to ragdoll player: " .. ply:Nick() .. ", Manual: " .. tostring(isManual))
end
-- Only apply 2-second protection for manual ragdolling
if isManual and ply.lastGetUpTime and CurTime() - ply.lastGetUpTime < 1 then
ply:ChatPrint("You need to wait a moment before you can ragdoll again.")
return
end
local rag = ply:GetNWEntity("player_ragdoll")
if IsValid(rag) then
if ply.brokenspine then
ply:ChatPrint("You are Paralyzed.")
return
end
-- Prevent getting up from shock ragdoll
local shock = ply:GetNWFloat("RADS_Shock", 0)
local ragdollThreshold = GetConVar("rads_shock_ragdoll_threshold"):GetFloat()
if shock >= ragdollThreshold and rag.isShockRagdoll then
return
end
ply:SetNWBool("radsfa", false)
local health = ply:Health()
ragpos = rag:GetPos()
respawnmodel = ply:GetModel()
spawnpos = rag:GetPos() -- Direct position without room checking
-- Transfer vFire from ragdoll back to player
RADS_TransferVFireToPlayer(rag, ply)
-- Mark player as getting up from ragdoll to preserve adrenaline
ply.gettingUpFromRagdoll = true
-- Set the get up time to prevent immediate re-ragdolling
ply.lastGetUpTime = CurTime()
-- Restore normal NPC targeting when getting up from ragdoll
ply:SetNoTarget(false)
ply:Spawn()
ply:SetPos(spawnpos) -- RADS_PostRag(ply)
-- Clean up bullseye entity when ragdoll is removed
if IsValid(rag.bullseye) then
print("[BULLSEYE DEBUG] Cleaning up bullseye entity for player: " .. ply:Name())
rag.bullseye:Remove()
rag.bullseye = nil
end
rag:Remove()
if table.HasValue(BleedingEntities, rag) then table.insert(BleedingEntities, ply) end
ply.fake = false
ply:SetModel(respawnmodel)
ply.resetinv = true
hook.Run("RADSLoadout", ply)
ply.resetinv = false
ply:SetParent(nil)
ply:SetNoDraw(false)
ply:SetMoveType(MOVETYPE_WALK)
ply:SetCollisionGroup(COLLISION_GROUP_PLAYER)
ply:DrawViewModel(true)
ply:DrawWorldModel(true)
ply:SetSuppressPickupNotices(false)
ply:SetShouldPlayPickupSound(true)
ply.FakeShooting = false
ply:SetNWEntity("player_ragdoll", nil)
ply:SetViewEntity(ply)
ply:SetHealth(health)
net.Start("REMOVECALC")
net.Send(ply)
if IsValid(rag.target) then rag.target:Remove() end
timer.Remove("respawntimer" .. ply:EntIndex())
-- Clean up grappling physics timer when getting up
timer.Remove("GrapplingPhysics_" .. rag:EntIndex())
-- Clean up rope constraint
if rag.GrapplingRopeConstraint and IsValid(rag.GrapplingRopeConstraint) then
rag.GrapplingRopeConstraint:Remove()
rag.GrapplingRopeConstraint = nil
end
-- Clear the flag after a short delay
timer.Simple(0.2, function()
if IsValid(ply) then
ply.gettingUpFromRagdoll = nil
end
end)
else
local veh
if ply:InVehicle() then
veh = ply:GetVehicle()
ply:ExitVehicle()
end
ply:SetNoDraw(true)
timer.Create("respawntimer" .. ply:EntIndex(), 99999, 1, function() end)
ply.fake = true
if ply.IsBleeding or (ply.BloodLosing or 0) > 0 then
rag.IsBleeding = true
rag.bloodNext = CurTime()
rag.Blood = ply.Blood
RADS_Bleed(rag)
end
RADS_SavePlyInfo(ply)
RADS_EzArmorSaveInfo(ply)
if not ply:IsInWorld() then return end
net.Start("ADDCALC")
net.Send(ply)
local rag = ents.Create("prop_ragdoll")
rag:SetModel(ply:GetModel())
rag:SetSkin(ply:GetSkin())
for k, v in pairs(ply:GetBodyGroups()) do
rag:SetBodygroup(v.id, ply:GetBodygroup(v.id))
end
-- Get player color and transfer it
local playerColor = ply:GetPlayerColor()
if playerColor then
rag:BetterSetPlayerColor(playerColor)
else
-- Simple fallback
rag:BetterSetPlayerColor(Vector(1, 1, 1))
end
rag:SetAngles(ply:GetAngles())
rag:Spawn()
timer.Simple(0, function()
if IsValid(ply) and IsValid(rag) then
ply:SetNWBool("radsfa", true) -- RADS_PreRag(ply)
ply:SetParent(rag)
ply:SetMoveType(MOVETYPE_NONE)
ply:SetCollisionGroup(COLLISION_GROUP_IN_VEHICLE)
end
end)
rag:Activate()
local wep = ply:GetActiveWeapon()
if IsValid(wep) and table.HasValue(Guns, wep:GetClass()) then
if _G.SpawnWeapon then
_G.SpawnWeapon(ply)
ply.FakeShooting = true
else
if GetConVar("developer"):GetInt() > 0 then
print("[RADS] ERROR: SpawnWeapon function not available globally!")
end
end
end
rag:SetCollisionGroup(COLLISION_GROUP_WEAPON)
ply:SetNWEntity("player_ragdoll", rag)
rag:SetNWEntity("owner", ply)
rag:SetPos(ply:GetPos())
RADS_RagBones(ply)
rag:Activate()
-- Transfer vFire to ragdoll with proper timing
timer.Simple(0.1, function()
if IsValid(ply) and IsValid(rag) then
RADS_TransferVFire(ply, rag)
end
end)
-- Supersus feature removed - convar no longer exists
--[[
if GetConVar("rads_supersus"):GetBool() then
local light = ents.Create("light_dynamic")
light:SetPos(rag:GetPos() + Vector(0, 0, 20))
light:SetKeyValue("brightness", "5")
light:SetKeyValue("distance", "200")
light:SetKeyValue("style", "0")
light:Spawn()
light:Activate()
light:Fire("TurnOn", "", 0)
light:SetParent(rag)
end
--]]
local rpos = rag:GetPos()
timer.Simple(0, function()
if RADS.IsJmodAct() then
local armors = {}
for id, info in pairs(ply.EZarmor.items) do
local ent = CreateArmor(rag, info)
ent.armorID = id
ent.ragdoll = rag
ent.Owner = ply
armors[id] = ent
ent:CallOnRemove("Fake", function()
if ent.override then return end
rag.armors[ent.armorID] = nil
JMod.RemoveArmorByID(ply, ent.armorID, true)
end)
end
rag.armors = armors
rag:CallOnRemove("ArmorCleanup", function(ragdoll)
if IsValid(ragdoll) then
RemoveRag(ragdoll)
end
end)
end
end)
if IsValid(rag:GetPhysicsObject()) then rag:GetPhysicsObject():SetMass(CustomWeight[rag:GetModel()] or 20) end
rag:AddEFlags(EFL_KEEP_ON_RECREATE_ENTITIES)
ply:SetActiveWeapon(nil)
ply:DropObject()
ply:SetPos(rag:GetPos())
rag.pulse = ply.pulse or 0
RADS_RagBones(ply)
-- Transfer player velocity to ragdoll (FIX FOR EXCESSIVE INERTIA)
-- Mark as shock ragdoll if applicable
local shock = ply:GetNWFloat("RADS_Shock", 0)
local ragdollThreshold = GetConVar("rads_shock_ragdoll_threshold"):GetFloat()
if shock >= ragdollThreshold then
rag.isShockRagdoll = true
rag.shockLevel = shock
end
local playerVel = ply:GetVelocity()
if playerVel:Length() > 0 then
-- Apply velocity to main physics object with reduced multiplier to prevent excessive speed
local velocityMultiplier = 0.85 -- Reduce from default 1.0 to prevent speed doubling/tripling
-- GOALKEEPER DIVING MECHANICS
-- Check if player was jumping (vertical velocity > 100) and holding A or D
local isJumping = playerVel.z > 100
local holdingLeft = ply:KeyDown(IN_MOVELEFT)
local holdingRight = ply:KeyDown(IN_MOVERIGHT)
if isJumping and (holdingLeft or holdingRight) then
-- Player is diving like a goalkeeper
local diveForce = 130 -- Reduced horizontal dive force to prevent excessive flinging
local diveDirection = Vector(0, 0, 0)
local angularVelocity = Vector(0, 0, 0)
-- Use player's right vector for proper lateral diving
local playerRight = ply:GetRight()
local playerForward = ply:GetForward()
if holdingLeft then
diveDirection = playerRight * -diveForce -- Dive left (negative right)
-- Angular velocity to lean left (roll around forward axis) - increased for more dramatic lean
angularVelocity = playerForward * -25 -- Negative roll for left lean
elseif holdingRight then
diveDirection = playerRight * diveForce -- Dive right (positive right)
-- Angular velocity to lean right (roll around forward axis) - increased for more dramatic lean
angularVelocity = playerForward * 25 -- Positive roll for right lean
end
-- Apply diving velocity with original player velocity
local finalVelocity = (playerVel * velocityMultiplier) + diveDirection
rag:GetPhysicsObject():SetVelocity(finalVelocity)
-- Mark ragdoll as diving to prevent rolling until landing
rag.isDiving = true
rag.diveStartTime = CurTime()
-- Set timer to return to normal rolling after landing (3 seconds max)
timer.Simple(3, function()
if IsValid(rag) then
rag.isDiving = false
end
end)
-- Apply diving velocity and angular velocity to all physics objects for realistic goalkeeper dive
for i = 0, rag:GetPhysicsObjectCount() - 1 do
local physObj = rag:GetPhysicsObjectNum(i)
if IsValid(physObj) then
physObj:SetVelocity(finalVelocity)
-- Add angular velocity to make the ragdoll lean/rotate during dive
physObj:AddAngleVelocity(angularVelocity)
end
end
else
-- Normal ragdoll velocity transfer
rag:GetPhysicsObject():SetVelocity(playerVel * velocityMultiplier)
-- Also apply to other physics objects for more realistic momentum transfer
for i = 0, rag:GetPhysicsObjectCount() - 1 do
local physObj = rag:GetPhysicsObjectNum(i)
if IsValid(physObj) then
physObj:SetVelocity(playerVel * velocityMultiplier)
end
end
end
end
hook.Run("RADS_Ready", rag)
-- Create npc_bullseye for ragdolled player
if IsValid(rag) and IsValid(ply) then
local bullseye = ents.Create("npc_bullseye")
if IsValid(bullseye) then
-- Position bullseye slightly outside ragdoll body for NPC visibility
local ragPos = rag:GetPos()
local ragAngles = rag:GetAngles()
local offset = ragAngles:Forward() * 10 + Vector(0, 0, 15) -- 10 units forward, 15 units up
bullseye:SetPos(ragPos + offset)
bullseye:SetAngles(ragAngles)
bullseye:Spawn()
bullseye:Activate()
-- Make bullseye bigger
bullseye:SetModelScale(2.0, 0)
-- Make bullseye nodraw and remove physics
bullseye:SetNoDraw(true)
bullseye:SetSolid(SOLID_NONE)
bullseye:SetMoveType(MOVETYPE_NONE)
bullseye:SetCollisionGroup(COLLISION_GROUP_IN_VEHICLE)
-- Parent bullseye to ragdoll
bullseye:SetParent(rag)
-- Store references
rag:SetNWEntity("bullseye", bullseye)
bullseye:SetNWEntity("owner", ply)
bullseye:SetNWEntity("ragdoll", rag)
-- Set NPC relationships based on player's relationships
timer.Simple(0.1, function()
if IsValid(bullseye) and IsValid(ply) then
for _, npc in pairs(ents.FindByClass("npc_*")) do
if IsValid(npc) then
local playerDisposition = D_HT -- Default to hate disposition
if npc.Disposition then
playerDisposition = npc:Disposition(ply)
print("[RADS BULLSEYE] NPC " .. npc:GetClass() .. " disposition to player: " .. playerDisposition)
else
print("[RADS BULLSEYE] NPC " .. npc:GetClass() .. " has no Disposition method, using default: " .. playerDisposition)
end
npc:AddEntityRelationship(bullseye, playerDisposition, 99)
end
end
print("[RADS BULLSEYE] Created bullseye for player: " .. ply:Nick() .. " at position: " .. tostring(bullseye:GetPos()))
end
end)
-- Make NPCs ignore the ragdolled player and target the bullseye instead
ply:SetNoTarget(true)
end
end
end
if IsValid(veh) then rag:GetPhysicsObject():SetVelocity(veh:GetPhysicsObject():GetVelocity() * 5) end
end
function CC(ply, message)
if not IsValid(ply) then return end
local curTime = CurTime()
if ply.lastChatTime == nil or curTime - ply.lastChatTime >= 3 then
ply.lastChatTime = curTime
ply:ChatPrint(message)
end
end
hook.Add("PlayerFootstep", "RADS.BrokenBones", function(ply, pos, foot, sound, volume, filter)
if ply.LeftLeg <= 0.6 or ply.RightLeg <= 0.6 then
if ply:IsSprinting() then
ply.pain = ply.pain + 35
end
end
end)
hook.Add("Player Think", "RADS.SYNCPOS", function(ply)
local qw = ply:GetNWEntity('player_ragdoll')
if IsValid(qw) and ply:IsRag() then
local qe = qw:GetAttachment(qw:LookupAttachment("eyes")).Pos
ply:SetPos(qe)
end
end)
util.AddNetworkString("CheckPulseAndLink")
net.Receive("CheckPulseAndLink", function(len, ply)
local ragdoll = net.ReadEntity()
if IsValid(ragdoll) and ragdoll:IsRagdoll() then
-- Check if this is a dead body first
local deadOwner = ragdoll:GetNWEntity('deadbodyowner')
local livingOwner = ragdoll:GetNWEntity('owner')
-- Enhanced debug info
local debugMsg = "[STATUS DEBUG] "
if IsValid(deadOwner) then
debugMsg = debugMsg .. "DeadOwner: " .. deadOwner:Nick() .. " (HP: " .. deadOwner:Health() .. ") "
end
if IsValid(livingOwner) then
debugMsg = debugMsg .. "LivingOwner: " .. livingOwner:Nick() .. " (HP: " .. livingOwner:Health() .. ") "
end
if GetConVar("developer"):GetInt() > 0 then
print(debugMsg)
end
ply:ChatPrint(debugMsg)
if IsValid(deadOwner) and deadOwner:IsPlayer() then
-- This is a dead body - always show no vitals
ply:ChatPrint("No Pulse")
ply:ChatPrint("Not Breathing")
ply:ChatPrint("No Reaction")
return
end
if IsValid(livingOwner) and livingOwner:IsPlayer() then
local owner = livingOwner
-- FIXED: Get pulse from networked value or ragdoll
local pulse = owner:GetNWInt("PlayerPulse", owner.pulse or 70)
local hp = owner:Health()
local isDead = hp <= 0
-- FIXED: Check networked Otrub value instead of direct property
local isUnconscious = owner:GetNWBool("Otrub", false)
-- Enhanced debug info to chat and console
local statusDebug = "[LIVING] HP: " .. hp .. ", Pulse: " .. pulse .. ", Otrub: " .. tostring(isUnconscious) .. " (networked)"
if GetConVar("developer"):GetInt() > 0 then
print(statusDebug)
end
ply:ChatPrint(statusDebug)
-- If player is dead, show no vitals
if isDead then
ply:ChatPrint("No Pulse")
ply:ChatPrint("Not Breathing")
ply:ChatPrint("No Reaction")
return
end
-- Get organ health (default to healthy values if not set)
local leftLungHealth = (owner.Organs and owner.Organs['left_lung']) or 5
local rightLungHealth = (owner.Organs and owner.Organs['right_lung']) or 5
local heartHealth = (owner.Organs and owner.Organs['heart']) or 9
local organDebug = "[ORGANS] Heart: " .. heartHealth .. ", Left Lung: " .. leftLungHealth .. ", Right Lung: " .. rightLungHealth
if GetConVar("developer"):GetInt() > 0 then
print(organDebug)
end
ply:ChatPrint(organDebug)
-- Pulse status (based on heart health and pulse value)
if heartHealth <= 0 or pulse <= 0 then
ply:ChatPrint("No Pulse")
elseif pulse >= 130 then
ply:ChatPrint("Has High Pulse")
elseif pulse >= 60 then
ply:ChatPrint("Has Normal Pulse")
elseif pulse >= 30 and pulse < 60 then
ply:ChatPrint("Has Low Pulse")
else
ply:ChatPrint("No Pulse")
end
-- Breathing status (based on lung health)
if leftLungHealth <= 0 and rightLungHealth <= 0 then
ply:ChatPrint("Not Breathing")
else
ply:ChatPrint("Breathing")
end
-- Consciousness status (based on Otrub variable) - FIXED
local consciousnessDebug = "[CONSCIOUSNESS] Otrub value: " .. tostring(owner.Otrub) .. " (" .. type(owner.Otrub) .. "), isUnconscious: " .. tostring(isUnconscious)
if GetConVar("developer"):GetInt() > 0 then
print(consciousnessDebug)
end
ply:ChatPrint(consciousnessDebug)
-- FIXED: Use the isUnconscious variable we already calculated
if isUnconscious then
ply:ChatPrint("No Reaction")
else
ply:ChatPrint("Has Reaction")
end
else
-- No valid owner found - treat as dead body
ply:ChatPrint("[NO OWNER] No Pulse")
ply:ChatPrint("[NO OWNER] Not Breathing")
ply:ChatPrint("[NO OWNER] No Reaction")
end
end
end)
util.AddNetworkString("rads.bloodcheck")
net.Receive("rads.bloodcheck", function(len, ply)
local r = net.ReadEntity()
if IsValid(r) and r:IsRagdoll() then
net.Start("rads.bloodcheck")
local i = r.Blood or 0
net.WriteInt(i, 14)
net.Send(ply)
end
end)
function _P:PickupEnt()
local ply = self
local rag = ply:GetNWEntity("player_ragdoll")
local phys = rag:GetPhysicsObjectNum(7)
local offset = phys:GetAngles():Right() * 5
local traceinfo = {
start = phys:GetPos(),
endpos = phys:GetPos() + offset,
filter = rag,
output = trace,
}
local trace = util.TraceLine(traceinfo)
if trace.Entity == Entity(0) or trace.Entity == NULL or not trace.Entity.canpickup then return end
if trace.Entity:GetClass() == "wep" then
ply:Give(trace.Entity.curweapon, true):SetClip1(trace.Entity.Clip)
ply.wep.Clip = trace.Entity.Clip
trace.Entity:Remove()
end
end
function _P:DropWeapon1(wep)
local ply = self
wep = wep or ply:GetActiveWeapon()
if not IsValid(wep) then return end
ply:DropWeapon(wep)
wep.Spawned = true
ply:SetActiveWeapon(nil)
end
hook.Add("PlayerSay", "dropweaponhuy", function(ply, text)
if string.lower(text) == "#drop" or string.lower(text) == "*drop" or string.lower(text) == "!drop" then
if not ply.fake then
ply:DropWeapon1()
return ""
else
if IsValid(ply.wep) then
if IsValid(ply.WepCons) then
ply.WepCons:Remove()
ply.WepCons = nil
end
if IsValid(ply.WepCons2) then
ply.WepCons2:Remove()
ply.WepCons2 = nil
end
ply.wep.canpickup = true
ply.wep:SetOwner()
ply.wep.curweapon = ply.curweapon
-- Preserve weapon data instead of deleting it
if ply.Info.Weapons[ply.Info.ActiveWeapon] then
ply.Info.Weapons[ply.Info.ActiveWeapon].Clip1 = ply.wep.Clip
-- Don't delete the weapon data, just mark it as not active
ply.Info.Weapons[ply.Info.ActiveWeapon].IsActive = false
end
ply:StripWeapon(ply.Info.ActiveWeapon)
ply.wep = nil
ply.Info.ActiveWeapon = nil
ply.Info.ActiveWeapon2 = nil
ply:SetActiveWeapon(nil)
ply.FakeShooting = false
else
ply:PickupEnt()
end
return ""
end
end
end)
hook.Add("Think", "radsshoot", function()
for i, ply in pairs(player.GetAll()) do
if ply:Alive() then
if IsValid(ply:GetNWEntity("player_ragdoll")) and ply.FakeShooting then
if _G.SpawnWeapon then
_G.SpawnWeapon(ply)
else
if GetConVar("developer"):GetInt() > 0 then
print("[RADS] ERROR: SpawnWeapon function not available globally at line 1772!")
end
end
else
if IsValid(ply.wep) then
if _G.DespawnWeapon then
_G.DespawnWeapon(ply)
else
if GetConVar("developer"):GetInt() > 0 then
print("[RADS] ERROR: DespawnWeapon function not available globally!")
end
end
end
end
end
end
end)
function _P:PickupEnt()
local ply = self
local rag = ply:GetNWEntity("player_ragdoll")
local phys = rag:GetPhysicsObjectNum(7)
local offset = phys:GetAngles():Right() * 5
local traceinfo = {
start = phys:GetPos(),
endpos = phys:GetPos() + offset,
filter = rag,
output = trace,
}
local trace = util.TraceLine(traceinfo)
if trace.Entity == Entity(0) or trace.Entity == NULL or not trace.Entity.canpickup then return end
if trace.Entity:GetClass() == "wep" then
ply:Give(trace.Entity.curweapon, true):SetClip1(trace.Entity.Clip)
ply.wep.Clip = trace.Entity.Clip
trace.Entity:Remove()
end
end
util.AddNetworkString("Unload")
net.Receive("Unload", function(len, ply)
local wep = net.ReadEntity()
local oldclip = wep:Clip1()
local ammo = wep:GetPrimaryAmmoType()
wep:EmitSound("snd_jack_hmcd_ammotake.wav")
wep:SetClip1(0)
ply:GiveAmmo(oldclip, ammo)
end)
-- Network strings for inventory system
util.AddNetworkString("RequestRagdollInventory")
util.AddNetworkString("SendRagdollInventory")
util.AddNetworkString("TakeWeaponFromRagdoll")
util.AddNetworkString("TakeAllWeaponsFromRagdoll")
-- Handle inventory request
net.Receive("RequestRagdollInventory", function(len, ply)
local ragdoll = net.ReadEntity()
if not IsValid(ragdoll) or not ragdoll:IsRagdoll() then return end
local owner = ragdoll:GetNWEntity("owner") or ragdoll:GetNWEntity('deadbodyowner')
if not IsValid(owner) or not owner:IsPlayer() then return end
-- Check distance
local distance = ply:GetPos():Distance(ragdoll:GetPos())
if distance > 100 then return end
-- Get weapons from owner's saved info
local weaponsData = {}
if owner.Info and owner.Info.Weapons then
for weaponClass, weaponInfo in pairs(owner.Info.Weapons) do
local weaponTable = weapons.Get(weaponClass)
weaponsData[weaponClass] = {
name = weaponTable and weaponTable.PrintName or weaponClass,
clip1 = weaponInfo.Clip1 or 0,
clip2 = weaponInfo.Clip2 or 0,
ammoType = weaponInfo.AmmoType or -1
}
end
end
-- Send inventory to client
net.Start("SendRagdollInventory")
net.WriteTable(weaponsData)
net.Send(ply)
end)
-- Handle taking single weapon
net.Receive("TakeWeaponFromRagdoll", function(len, ply)
local ragdoll = net.ReadEntity()
local weaponClass = net.ReadString()
if not IsValid(ragdoll) or not ragdoll:IsRagdoll() then return end
local owner = ragdoll:GetNWEntity("owner") or ragdoll:GetNWEntity('deadbodyowner')
if not IsValid(owner) or not owner:IsPlayer() then return end
-- Check distance
local distance = ply:GetPos():Distance(ragdoll:GetPos())
if distance > 100 then return end
-- Check if weapon exists in ragdoll's inventory
if owner.Info and owner.Info.Weapons and owner.Info.Weapons[weaponClass] then
local weaponInfo = owner.Info.Weapons[weaponClass]
-- Give weapon to player
local weapon = ply:Give(weaponClass, true)
if IsValid(weapon) then
weapon:SetClip1(weaponInfo.Clip1 or 0)
weapon:SetClip2(weaponInfo.Clip2 or 0)
-- Remove from ragdoll's inventory
owner.Info.Weapons[weaponClass] = nil
ply:ChatPrint("Taken: " .. (weapons.Get(weaponClass) and weapons.Get(weaponClass).PrintName or weaponClass))
end
end
end)
-- Handle taking all weapons
net.Receive("TakeAllWeaponsFromRagdoll", function(len, ply)
local ragdoll = net.ReadEntity()
if not IsValid(ragdoll) or not ragdoll:IsRagdoll() then return end
local owner = ragdoll:GetNWEntity("owner") or ragdoll:GetNWEntity('deadbodyowner')
if not IsValid(owner) or not owner:IsPlayer() then return end
-- Check distance
local distance = ply:GetPos():Distance(ragdoll:GetPos())
if distance > 100 then return end
local takenCount = 0
-- Give all weapons to player
if owner.Info and owner.Info.Weapons then
for weaponClass, weaponInfo in pairs(owner.Info.Weapons) do
local weapon = ply:Give(weaponClass, true)
if IsValid(weapon) then
weapon:SetClip1(weaponInfo.Clip1 or 0)
weapon:SetClip2(weaponInfo.Clip2 or 0)
takenCount = takenCount + 1
end
end
-- Clear ragdoll's inventory
owner.Info.Weapons = {}
if takenCount > 0 then
ply:ChatPrint("Taken " .. takenCount .. " weapons from " .. owner:Name())
else
ply:ChatPrint("No weapons to take")
end
end
end)
hook.Add("KeyPress", "Shooting", function(ply, key)
if not ply:Alive() then return end
-- if key == IN_RELOAD then Reload(ply.wep) end -- Removed undefined Reload call
-- Jump pain for broken/dislocated bones
if key == IN_JUMP then
local hasLegInjury = false
local painAmount = 0
-- Check for broken legs
if ply:GetNWBool("RADS_LeftLegBroken") or ply:GetNWBool("RADS_RightLegBroken") then
hasLegInjury = true
painAmount = painAmount + math.random(15, 25) -- High pain for broken bones
end
-- Check for dislocated legs
if ply:GetNWBool("RADS_LeftLegDislocated") or ply:GetNWBool("RADS_RightLegDislocated") then
hasLegInjury = true
painAmount = painAmount + math.random(8, 15) -- Moderate pain for dislocated bones
end
-- Check for other broken bones that would affect jumping
if ply:GetNWBool("RADS_LeftArmBroken") or ply:GetNWBool("RADS_RightArmBroken") then
painAmount = painAmount + math.random(3, 8) -- Minor pain for arm injuries
end
if ply.brokenspine or ply.brokenupperspine then
painAmount = painAmount + math.random(20, 35) -- Severe pain for spine injuries
end
-- Apply realistic pain system for jumping with injuries
if hasLegInjury or painAmount > 0 then
local damageIntensity = painAmount
-- Check for severe bone injury pain (instant pain threshold)
if painAmount >= 20 or (ply.brokenspine or ply.brokenupperspine) then
-- Severe injuries: instant pain + pain debt
local instantPain = painAmount * 0.5 -- 50% instant for severe jumping pain
local painDebt = painAmount * 0.5 -- 50% debt
ply.pain = (ply.pain or 0) + instantPain
ply.painDebt = (ply.painDebt or 0) + painDebt
ply.lastDamageTime = CurTime()
ply.damageIntensity = (ply.damageIntensity or 0) + damageIntensity
ply:ChatPrint("Jumping with your injuries causes excruciating pain!")
elseif painAmount >= 8 then
-- Moderate injuries: mostly pain debt
local instantPain = painAmount * 0.2 -- 20% instant
local painDebt = painAmount * 0.8 -- 80% debt
ply.pain = (ply.pain or 0) + instantPain
ply.painDebt = (ply.painDebt or 0) + painDebt
ply.lastDamageTime = CurTime()
ply.damageIntensity = (ply.damageIntensity or 0) + damageIntensity
ply:ChatPrint("Jumping with your injuries hurts badly.")
else
-- Minor injuries: almost all pain debt
local instantPain = painAmount * 0.1 -- 10% instant
local painDebt = painAmount * 0.9 -- 90% debt
ply.pain = (ply.pain or 0) + instantPain
ply.painDebt = (ply.painDebt or 0) + painDebt
ply.lastDamageTime = CurTime()
ply.damageIntensity = (ply.damageIntensity or 0) + damageIntensity
ply:ChatPrint("Jumping with your injuries causes some pain.")
end
end
end
end)
local lbt = 0 -- util.AddNetworkString('RequestRagdollInfo') -- util.AddNetworkString('SendPlayerWeaponsAndAmmo') -- util.AddNetworkString('RequestRagdollInfo') -- net.Receive("RequestRagdollInfo", function(len, ply) -- local ragdoll = net.ReadEntity() -- if IsValid(ragdoll) and ragdoll:GetClass() == "prop_ragdoll" then -- owner = ragdoll:GetNWEntity("owner") -- deadbodyowner = ragdoll:GetNWEntity('deadbodyowner') -- if IsValid(owner) and owner:IsPlayer() or IsValid(deadbodyowner) and deadbodyowner:IsPlayer() then -- local weaponsData = owner.Info and owner.Info.Weapons3 or {} -- local ammoData = owner.Info and owner.Info.AllAmmo or {} -- if not weaponsData or not AmmoData then -- local weaponsData = deadbodyowner.Info and deadbodyowner.Info.AllAmmo or {} -- end -- net.Start("SendPlayerWeaponsAndAmmo") -- net.WriteTable({weapons = weaponsData, ammo = ammoData}) -- net.Send(ply) -- end -- end -- end
util.AddNetworkString("RADS.CHATSAY")
hook.Add('Think', "RADS.CHAT", function()
if CurTime() - lbt >= 600 then
net.Start('RADS.CHATSAY')
net.Broadcast()
lbt = CurTime()
end
end)
hook.Add("PlayerUse", "nouse", function(ply, ent) if ply.fake then return false end end)
function deathrem(victim)
local rag = victim:GetNWEntity("player_ragdoll")
-- If player dies while in ragdoll state, set to spectator mode to prevent entity appearance
if victim.fake then
victim:Spectate(OBS_MODE_ROAMING)
victim:SetMoveType(MOVETYPE_OBSERVER)
end
net.Start('ADDCALC')
net.Send(victim)
timer.Remove('respawntimer' .. victim:EntIndex())
if victim.IsBleeding or (victim.BloodLosing or 0) > 0 then
rag.IsBleeding = true
rag.bloodNext = CurTime()
rag.Blood = victim.Blood
RADS_Bleed(rag)
end
if IsValid(rag.ZacConsLH) then
rag.ZacConsLH:Remove()
rag.ZacConsLH = nil
end
if IsValid(rag.ZacConsRH) then
rag.ZacConsRH:Remove()
rag.ZacConsRH = nil
end
if not IsValid(rag) and not RADS.IsTTT() then
victim:SetNWBool("radsfa", false)
rag = ents.Create("prop_ragdoll")
rag:SetModel(victim:GetModel())
rag:SetPos(victim:GetPos())
rag:SetAngles(victim:GetAngles())
rag:Spawn()
rag:Activate()
rag:SetSkin(victim:GetSkin())
for key, value in pairs(victim:GetBodyGroups()) do
rag:SetBodygroup(value.id, victim:GetBodygroup(value.id))
end
if victim.IsBleeding or (victim.BloodLosing or 0) > 0 then
rag.IsBleeding = true
rag.bloodNext = CurTime()
rag.Blood = victim.Blood
RADS_Bleed(rag)
end
-- Get player color and transfer it
local playerColor = victim:GetPlayerColor()
if playerColor then
rag:BetterSetPlayerColor(playerColor)
else
-- Simple fallback
rag:BetterSetPlayerColor(Vector(1, 1, 1))
end
victim:SetNWEntity("player_ragdoll", rag)
rag:SetNWEntity("owner", victim)
RADS_RagBones(victim)
if IsValid(rag:GetPhysicsObject()) then
local CustomWeightt = CustomWeight[rag:GetModel()]
rag:GetPhysicsObject():SetMass(30)
end
victim:SetPos(rag:GetPos())
if RADS.IsJmodAct() then
local armors = {}
for id, info in pairs(victim.EZarmor.items) do
local ent = CreateArmor(rag, info)
ent.armorID = id
ent.ragdoll = rag
ent.Owner = victim
armors[id] = ent
ent:CallOnRemove("Fake", Remove, victim)
end
rag.armors = armors
rag:CallOnRemove("Armors", RemoveRag)
end
victim:SetParent(nil)
if not victim:IsBot() then
local steamID = victim:SteamID()
if victim.Info then
victim.Info.Hp = nil
victim.Info.Armor = nil
end
if victim.Info then
-- Don't clear weapons on death - preserve inventory for looting
-- victim.Info.Weapons2 = {}
-- victim.Info.Weapons = {}
-- victim.Info.AllAmmo = {}
end
savedPlayerState[steamID] = nil
if victim:HasGodMode() then victim:GodDisable() end
end
end
-- This is the correct place to apply rigor mortis, after 'rag' is guaranteed to be the active ragdoll.
if victim.rigorMortis and IsValid(rag) then
ApplyRigorMortis(rag)
victim.rigorMortis = nil -- Clear the flag after applying
end
end
hook.Add("DoPlayerDeath", "RADS.DeathH3", function(ply, att, dmg) deathrem(ply) end)
hook.Add("PlayerDeath", "RADS.DeathH1", function(v, i, a)
v:SetParent(nil)
v:SetNWBool('brokenspine', false)
v.pulse = 0
end)
hook.Add("PlayerDeath", "RADS.DeathH2", function(victim, inflictor, attacker)
local rag = victim:GetNWEntity('player_ragdoll')
victim:SetParent(nil)
victim:SetNWBool('brokenspine', false)
-- Check if death first-person is enabled, if not remove calcview
if not GetConVar('rads_death_firstperson'):GetBool() then
if GetConVar('rads_spectatorfix'):GetBool() then
net.Start('REMOVECALC')
net.Send(victim)
end
else
-- Mark player as dead for calcview system
victim:SetNWBool('rads_dead_firstperson', true)
end
victim.pulse = 0
-- Clean up delayed bone pain timers to prevent pain after death
local entIndex = victim:EntIndex()
local limbNames = {"head", "neck", "chest", "stomach", "leftarm", "rightarm", "leftleg", "rightleg"}
for _, limb in ipairs(limbNames) do
timer.Remove("RADS_DelayedBonePain_" .. entIndex .. "_" .. limb)
timer.Remove("RADS_DelayedBonePain_" .. entIndex .. "_" .. limb .. "_dislocated")
timer.Remove("RADS_DelayedBonePain_" .. entIndex .. "_" .. limb .. "_escalation")
end
-- Clean up gore system on death
if victim.hasGoreExplosion then
victim.hasGoreExplosion = nil
end
if IsValid(rag) and rag.hasGoreExplosion then
-- Stop blood stream timer
local timerName = "RADS_BloodStream_" .. rag:EntIndex()
if timer.Exists(timerName) then
timer.Remove(timerName)
end
end
-- Clean up fear system on death
if victim.fearLevel then
victim.fearLevel = 0
end
if victim.fearDecayTimer then
timer.Remove(victim.fearDecayTimer)
victim.fearDecayTimer = nil
end
end)
concommand.Add("fake", function(ply, cmd, args)
if ply:GetMoveType() == MOVETYPE_OBSERVER then return end
if ply.fake then -- If already in ragdoll, try to get up
if ply:IsRag() then if ply:GetRads().physgunned then return nil end end
if timer.Exists("radstimer" .. ply:EntIndex()) then return nil end
if timer.Exists("StunTime" .. ply:EntIndex()) then return nil end
if timer.Exists("Epilepsy" .. ply:EntIndex()) then return nil end
if ply.brokenspine or ply.brokenupperspine then return nil end
if ply.Blood < GetConVar("rads_bloodlimit"):GetInt() then return end
if IsValid(ply:GetNWEntity("player_ragdoll")) and ply:GetNWEntity("player_ragdoll"):GetVelocity():Length() > 300 then return nil end
if table.Count(constraint.FindConstraints(ply:GetNWEntity("player_ragdoll"), 'Rope')) > 0 then
ply:ChatPrint("You\'re tied up, Space to struggle.")
return nil
end
-- Check if player is handcuffed
if ply:GetNWBool("RADS_Handcuffed", false) or (IsValid(ply:GetNWEntity("player_ragdoll")) and ply:GetNWEntity("player_ragdoll"):GetNWBool("RADS_Handcuffed", false)) then
ply:ChatPrint("You are handcuffed and cannot get up.")
return nil
end
-- Check for conditions that prevent getting up
if ply.Otrub then
return nil
end
if ply.concussionActive then
ply:ChatPrint("You are too disoriented from the concussion to get up.")
return nil
end
-- Check for extreme shock preventing get up
local shockLevel = ply:GetNWFloat("RADS_Shock", 0)
if shockLevel >= 75 then
ply:ChatPrint("You are in too much shock to even attempt getting up.")
return nil
end
-- Check if player is fully submerged in water (prevent getting up)
local ragdoll = ply:GetNWEntity("player_ragdoll")
if IsValid(ragdoll) then
local headBone = ragdoll:LookupBone("ValveBiped.Bip01_Head1")
if headBone then
local headPos = ragdoll:GetBonePosition(headBone)
local waterLevel = util.PointContents(headPos)
local isUnderwater = bit.band(waterLevel, CONTENTS_WATER) ~= 0
if isUnderwater then
ply:ChatPrint("You cannot get up while fully submerged in water.")
return nil
end
end
end
-- Check if instant get up is enabled
if GetConVar("rads_instant_getup"):GetBool() then
-- Instant get up - bypass the get up process but respect all blocking conditions
rads(ply)
return
end
ply.gettingUp = true
ply.lastGetUpAttempt = CurTime()
ply:ChatPrint("Attempting to get up...")
else -- If not in ragdoll, ragdoll the player
rads(ply, true) -- Pass true to indicate this is manual
if not RADS.IsTTT() and not ply.fake then timer.Create("radstimer" .. ply:EntIndex(), 1.5, 1, function() end) end
end
end)
hook.Add("PlayerDisconnected", "removeallwhenleave", function(ply)
local steamID = ply:SteamID()
savedPlayerState[steamID] = nil
if ply.Info then
ply.Info.Hp = nil
ply.Info.Armor = nil
ply.Info = nil
end
if savedPlayerState[steamID] then savedPlayerState[steamID] = nil end
local exr = ply:GetNWEntity("player_ragdoll")
if IsValid(exr) then
-- Clean up bullseye entity when player disconnects
if IsValid(exr.bullseye) then
print("[BULLSEYE DEBUG] Cleaning up bullseye entity for disconnected player: " .. ply:Name())
exr.bullseye:Remove()
exr.bullseye = nil
end
-- Clean up gore system when player disconnects
if exr.hasGoreExplosion then
-- Remove gore stump
if IsValid(exr.goreStump) then
exr.goreStump:Remove()
exr.goreStump = nil
end
-- Stop blood stream timer
local timerName = "RADS_BloodStream_" .. exr:EntIndex()
if timer.Exists(timerName) then
timer.Remove(timerName)
end
exr.hasGoreExplosion = nil
end
ply:SetNWEntity("player_ragdoll", nil)
exr:SetNWEntity('owner', nil)
exr:SetNWEntity("RagdollController", nil)
end
-- Clean up player gore state
if ply.hasGoreExplosion then
ply.hasGoreExplosion = nil
end
end)
hook.Add("OnPlayerHitGround", "GovnoJopa", function(ply, a, b, speed)
if speed > 200 then
local tr = {}
tr.start = ply:GetPos()
tr.endpos = ply:GetPos() - Vector(0, 0, 10)
tr.mins = ply:OBBMins()
tr.maxs = ply:OBBMaxs()
tr.filter = ply
local traceResult = util.TraceHull(tr)
if traceResult.Entity:IsPlayer() and not traceResult.Entity.fake then rads(traceResult.Entity) end
end
end)
hook.Add("Think", "RemoveRagdoll", function()
for _, ply in ipairs(player.GetAll()) do -- end
local ragdoll_entity = ply:GetRagdollEntity()
if IsValid(ragdoll_entity) then ragdoll_entity:Remove() end
end
end)
function propknocked(ply)
if timer.Exists("propknocked" .. ply:UserID()) then
return true
else
timer.Create("propknocked" .. ply:UserID(), 1, 1, function() end)
return false
end
end
hook.Add("EntityTakeDamage", "fallfromclub", function(target, dmginfo)
local random = math.Rand(1, 2)
if target:IsPlayer() and GetConVar('rads_fallonclub'):GetBool() and dmginfo:IsDamageType(DMG_CLUB) and not target.fake then
-- Make club damage more reasonable - only 10% chance instead of guaranteed
if GetConVar('rads_randomfallfromclub'):GetBool() then
if random > 1.9 then rads(target) end
else
-- Instead of always ragdolling, add damage threshold and chance
if dmginfo:GetDamage() >= 25 and math.random() < 0.1 then -- 10% chance for significant club damage
rads(target)
end
end
end
end)
hook.Add("EntityTakeDamage", "fallondamage", function(target, dmginfo)
if target:IsPlayer() then -- ply.pain = ply.pain + 5
if GetConVar('rads_fallchance'):GetBool() and not target.fake then
-- Add damage threshold to prevent ragdolling from minor damage
local damage = dmginfo:GetDamage()
if damage >= 15 then -- Increased threshold from 10 to 15
local damagePosition = dmginfo:GetDamagePosition()
local bodyPart = target:LastHitGroup(damagePosition)
-- Initialize pain and consciousness if not set
target.pain = target.pain or 0
-- Check for knockdown protection (1 second after getting up)
if target.lastGetUpTime and CurTime() - target.lastGetUpTime < 1.0 then
return -- Player has knockdown protection
end
-- Use the improved shouldFall function for base calculation
local shouldRagdoll = shouldFall(bodyPart, damage, dmginfo:GetDamageType(), target)
if not shouldRagdoll then
-- Additional chance modifiers for special conditions
local extraChance = 0
-- Pain-based modifier: Higher pain increases knockdown chance
if target.pain and target.pain > 100 then
extraChance = extraChance + 0.20 -- 20% extra for very high pain
elseif target.pain and target.pain > 50 then
extraChance = extraChance + 0.10 -- 10% extra for high pain
end
-- Consciousness-based modifier: Unconscious players more likely to be knocked down
if target.Otrub then -- Player is unconscious
extraChance = extraChance + 0.30 -- 30% extra when unconscious
end
-- Rapid hit tracking for shotguns (preserved from original system)
if dmginfo:IsDamageType(DMG_BUCKSHOT) then
local steamID = target:SteamID()
local currentTime = CurTime()
-- Initialize hit tracking for this player if not exists
if not playerHitTracking[steamID] then
playerHitTracking[steamID] = {
hits = {},
lastCleanup = currentTime
}
end
local hitData = playerHitTracking[steamID]
-- Add current hit
table.insert(hitData.hits, currentTime)
-- Count hits within the rapid hit window
local recentHits = 0
for _, hitTime in ipairs(hitData.hits) do
if currentTime - hitTime <= RAPID_HIT_WINDOW then
recentHits = recentHits + 1
end
end
-- Apply rapid hit bonus based on hit count
if recentHits >= 4 then
extraChance = extraChance + 0.40 -- 40% extra for 4+ rapid hits
elseif recentHits >= 3 then
extraChance = extraChance + 0.25 -- 25% extra for 3+ rapid hits
elseif recentHits >= 2 then
extraChance = extraChance + 0.15 -- 15% extra for 2+ rapid hits
end
end
-- Apply extra chance if any modifiers are present
if extraChance > 0 then
shouldRagdoll = math.random() < extraChance
end
end
-- Execute ragdoll if shouldRagdoll is true
if shouldRagdoll then
rads(target)
end
end
end
end
end)
hook.Add("EntityTakeDamage", "stundmg", function(target, dmginfo)
if IsValid(target) and not target.fake then -- target.pain = target.pain + 7
if IsValid(dmginfo:GetAttacker()) then
local attacker = dmginfo:GetAttacker()
local inflictor = dmginfo:GetInflictor()
if IsValid(attacker) and IsValid(inflictor) then if attacker:IsPlayer() and attacker:GetActiveWeapon() and attacker:GetActiveWeapon():IsValid() then if attacker:GetActiveWeapon():GetClass() == "weapon_stunstick" then Stun(target) end end end
end
end
end)
-- Removed duplicate damage interruption hook
-- hook.Add("EntityTakeDamage", "RADS.RagdollDamageInterrupt", function(target, dmginfo)
-- local owner = nil
-- if IsValid(target) and target:IsRagdoll() then
-- owner = target:GetNWEntity("owner") or target:GetNWEntity('deadbodyowner')
-- elseif IsValid(target) and target:IsPlayer() then
-- owner = target
-- end
--
-- if IsValid(owner) and owner:IsPlayer() and owner:GetNWBool("radsfa") then
-- owner.takingDamage = true
-- end
-- end)
function Seizure(ent) -- target.pain = target.pain + 20
if ent:IsRagdoll() then
local seizuret = math.random(1, 6)
local iterrr = seizuret * 10
RagdollOwner(ent):ChatPrint("Cramps")
timer.Create("seizuret_" .. ent:EntIndex(), seizuret, 1, function() end)
timer.Create("seizuret_" .. ent:EntIndex(), 0.1, iterrr, function() ent:GetPhysicsObjectNum(1):SetVelocity(ent:GetPhysicsObjectNum(1):GetVelocity() + Vector(math.random(-45, 45), math.random(-45, 45), 0)) end)
end
end
function Stun(Entity)
if Entity:IsPlayer() then
rads(Entity)
local stuntime = math.random(1, 15)
local iter = stuntime * 10
timer.Create("StunTime" .. Entity:EntIndex(), stuntime, 1, function() end)
local radsrag = Entity:GetNWEntity("player_ragdoll")
if not IsValid(radsrag) then return end
timer.Create("StunEffect" .. Entity:EntIndex(), 0.1, iter, function()
local rand = math.random(1, 2)
if rand == 2 then end
radsrag:GetPhysicsObjectNum(1):SetVelocity(radsrag:GetPhysicsObjectNum(1):GetVelocity() + Vector(math.random(-85, 85), math.random(-85, 85), 0)) -- Entity:Say('#drop')
radsrag:EmitSound("ambient/energy/spark2.wav")
end)
end
end
function RADS_Epilepsy(Entity)
if Entity:IsPlayer() then
local rag = Entity:GetNWEntity('player_ragdoll')
if IsValid(rag) then return end
rads(Entity)
local mineptime, maxeptime = GetConVar('rads_mineptime'):GetInt(), GetConVar('rads_maxeptime'):GetInt()
local eptime = math.random(mineptime, maxeptime)
local iterr = eptime * 10
timer.Create("Epilepsy" .. Entity:EntIndex(), eptime, 1, function() end)
local radsrag = Entity:GetNWEntity("player_ragdoll")
if not IsValid(radsrag) then return end
timer.Create("EpilepsyM" .. Entity:EntIndex(), 0.1, iterr, function()
local rand = math.random(1, 2)
if rand == 2 then end
radsrag:GetPhysicsObjectNum(1):SetVelocity(radsrag:GetPhysicsObjectNum(1):GetVelocity() + Vector(math.random(-125, 125), math.random(-125, 125), 0)) -- Entity:Say('#drop')
end)
radsrag:EmitSound("lol.wav")
end
end
concommand.Add('rads_epil', function(ply) RADS_Epilepsy(ply) end)
function RADS_RagdollCollision(ragdoll, collisionData)
if GetConVar('rads_doorbreach'):GetBool() then
local collidedEntity = collisionData.HitEntity
if IsValid(collidedEntity) and (collidedEntity:GetClass() == "prop_door_rotating" or collidedEntity:GetClass() == "func_door") then
local ragdollVelocity = collisionData.OurOldVelocity:Length()
if ragdollVelocity >= 450 then BreachDoor(ragdoll, collidedEntity, collisionData) end
end
end
end
hook.Add("PlayerInitialSpawn", "rads-knocked-callback", function(ply)
ply:AddCallback("PhysicsCollide", function(phys, data) hook.Run("Player Collide", ply, data.HitEntity, data) end)
net.Start("RADS.CHATSAY")
net.Send(ply)
end)
hook.Add("Player Collide", "rads-knocked", function(ply, hitEnt, data)
if GetConVar('rads_propknock'):GetBool() and not ply:HasGodMode() and data.Speed > 8000 or not GetConVar('rads_propknock'):GetBool() and not ply:HasGodMode() and data.Speed >= math.max(5000, 8000 / hitEnt:GetPhysicsObject():GetMass() * 20) and not ply.fake and hitEnt:IsPlayerHolding() and hitEnt:GetVelocity():Length() > 1500 then
timer.Simple(0, function()
if not IsValid(ply) or ply.fake then return end
if hook.Run("Should Fake Collide", ply, hitEnt, data) == false then return end
rads(ply)
RADS.PainSound(ply)
end)
end
end)
-- Enhanced Player Collide hook based on reference addon
hook.Add("Player Collide", "homigrad-fake", function(ply, hitEnt, data)
if not ply:HasGodMode() and not ply.fake then
local speed = data.Speed
local threshold = 6000
-- Calculate speed threshold based on entity mass if it has physics
-- Use much higher base values and ensure minimum threshold
if IsValid(hitEnt) and IsValid(hitEnt:GetPhysicsObject()) then
threshold = math.max(5000, 8000 / hitEnt:GetPhysicsObject():GetMass() * 20)
end
-- Additional check: only ragdoll if the entity is moving fast or player is moving very fast
local entityVelocity = IsValid(hitEnt) and hitEnt:GetVelocity():Length() or 0
local shouldRagdoll = speed > threshold and (entityVelocity > 200 or speed > threshold * 1.5)
if shouldRagdoll then
timer.Simple(0, function()
if IsValid(ply) and not ply.fake then
rads(ply)
end
end)
end
end
end)
util.AddNetworkString('nodraw_helmet')
function CreateArmor(ragdoll, info)
local item = JMod.ArmorTable[info.name]
if not item then return end
local Index = ragdoll:LookupBone(item.bon)
if not Index then return end
local Pos, Ang = (ply or ragdoll):GetBonePosition(Index)
if not Pos then return end
local ent = ents.Create(item.ent)
local Right, Forward, Up = Ang:Right(), Ang:Forward(), Ang:Up()
Pos = Pos + Right * item.pos.x + Forward * item.pos.y + Up * item.pos.z
Ang:RotateAroundAxis(Right, item.ang.p)
Ang:RotateAroundAxis(Up, item.ang.y)
Ang:RotateAroundAxis(Forward, item.ang.r)
ent.IsArmor = true
ent:SetPos(Pos)
ent:SetAngles(Ang)
local color = info.col
ent:SetColor(Color(color.r, color.g, color.b, color.a))
ent:Spawn() -- timer.Simple(.1,function()
ent:SetCollisionGroup(COLLISION_GROUP_IN_VEHICLE) -- ent:SetCollisionGroup(COLLISION_GROUP_DEBRIS)
if IsValid(ent:GetPhysicsObject()) then
ent:GetPhysicsObject():SetMaterial("Armorflesh")
ent:GetPhysicsObject():SetMass(1)
ent:GetPhysicsObject():EnableCollisions(false)
end
timer.Simple(0.1, function()
local ply = RagdollOwner(ragdoll) -- end)
if item.bon == "ValveBiped.Bip01_Head1" and ply and IsValid(ply) and ply:IsPlayer() then
net.Start("nodraw_helmet")
net.WriteEntity(ent)
net.Send(ply)
end
end)
constraint.Weld(ent, ragdoll, 0, ragdoll:TranslateBoneToPhysBone(Index), 0, true, false)
ragdoll:DeleteOnRemove(ent)
return ent
end
local function Remove(self, ply)
if self.override then return end
self.ragdoll.armors[self.armorID] = nil
JMod.RemoveArmorByID(ply, self.armorID, true)
end
-- RemoveRag function moved earlier in the file
hook.Add("OnEntityCreated", "ragdoor", function(ent) if IsValid(ent) and ent:IsRagdoll() and GetConVar('rads_doorbreach'):GetBool() then ent:AddCallback("PhysicsCollide", function(ragdoll, collisionData) RADS_RagdollCollision(ragdoll, collisionData) end) end end)
-- Ragdoll tackling system based on reference addon
hook.Add('Think', 'RADS_RagdollTackleCheck', function()
for _, ragdoll in pairs(ents.FindByClass('prop_ragdoll')) do
if IsValid(ragdoll) then
local velocity = ragdoll:GetVelocity():Length()
if velocity > 200 then
ragdoll:SetCollisionGroup(COLLISION_GROUP_NONE)
-- Trace ahead of the ragdoll to detect potential player collisions
local traceData = {
start = ragdoll:GetPos(),
endpos = ragdoll:GetPos() + ragdoll:GetVelocity():GetNormalized() * 40,
filter = ragdoll
}
local trace = util.TraceLine(traceData)
if IsValid(trace.Entity) and trace.Entity:IsPlayer() then
local ragdollOwner = ragdoll:GetNWEntity("owner")
if IsValid(ragdollOwner) and ragdollOwner ~= trace.Entity and not trace.Entity.fake then
-- Trigger tackling on the hit player
rads(trace.Entity)
end
end
else
ragdoll:SetCollisionGroup(COLLISION_GROUP_WEAPON)
end
end
end
end)
function BreachDoor(ragdoll, door, collisionData)
if GetConVar('rads_doorbreach'):GetBool() then
local ragdollVelocity = collisionData.OurOldVelocity:GetNormalized()
local breachedDoor = ents.Create("prop_physics")
breachedDoor:SetModel(door:GetModel())
breachedDoor:SetSkin(door:GetSkin())
for key, value in pairs(door:GetBodyGroups()) do
breachedDoor:SetBodygroup(value.id, door:GetBodygroup(value.id))
end
breachedDoor:SetPos(door:GetPos())
breachedDoor:SetAngles(door:GetAngles())
door:Remove()
breachedDoor:Spawn()
local phys = breachedDoor:GetPhysicsObject()
if IsValid(phys) then
local force = ragdollVelocity * phys:GetMass()
phys:ApplyForceCenter(force, collisionData.HitPos)
end
end
end
concommand.Add("rads_god", function(ply, cmd, args)
if IsValid(ply) and ply:IsSuperAdmin() then
if ply:HasGodMode() then
ply:GodDisable()
ply:PrintMessage(HUD_PRINTTALK, "God disabled.")
else
ply:GodEnable()
ply:EmitSound("restains.wav", 150, 200, 1, CHAN_ITEM)
ply:PrintMessage(HUD_PRINTTALK, "God enabled.")
end
end
end)
-- Initialize cumulative damage tracking tables
if not RADS_CumulativeDamage then
RADS_CumulativeDamage = {}
end
-- Function to clean up old damage entries
local function RADS_CleanupOldDamage()
local currentTime = CurTime()
for targetID, data in pairs(RADS_CumulativeDamage) do
if currentTime - data.lastHit > 0.5 then
RADS_CumulativeDamage[targetID] = nil
end
end
end
-- Gore System: Head Explosion on Extreme Damage
hook.Add("EntityTakeDamage", "RADS_GoreSystem", function(target, dmginfo)
-- Check if gore system is enabled
if not GetConVar('rads_gore_enable'):GetBool() then return end
local damage = dmginfo:GetDamage()
local baseDamageThreshold = GetConVar('rads_gore_head_damage_threshold'):GetFloat()
-- Apply damage type multipliers
local damageThreshold = baseDamageThreshold
local damageType = dmginfo:GetDamageType()
-- Explicitly exclude fall and crush damage from triggering head explosions
if bit.band(damageType, DMG_FALL) > 0 or bit.band(damageType, DMG_CRUSH) > 0 or dmginfo:IsFallDamage() then
return -- Fall and crush damage should not cause head explosions
end
local isBuckshot = bit.band(damageType, DMG_BUCKSHOT) > 0
if bit.band(damageType, DMG_CLUB) > 0 then
damageThreshold = baseDamageThreshold * 2 -- DMG_CLUB needs 2x damage
elseif bit.band(damageType, DMG_BULLET) > 0 then
damageThreshold = baseDamageThreshold * 1.0 -- DMG_BULLET uses base threshold
elseif isBuckshot then
damageThreshold = baseDamageThreshold * 1.0 -- DMG_BUCKSHOT uses base threshold
else
-- Other damage types don't trigger head explosions (including DMG_BLAST)
return
end
-- Handle cumulative damage for buckshot and bullet (shotgun pellets and bullets)
local finalDamage = damage
local isBulletOrBuckshot = bit.band(damageType, DMG_BULLET) > 0 or isBuckshot
if isBulletOrBuckshot then
-- Clean up old damage entries periodically
RADS_CleanupOldDamage()
local targetID = target:EntIndex()
local currentTime = CurTime()
-- Initialize or update cumulative damage for this target
if not RADS_CumulativeDamage[targetID] then
RADS_CumulativeDamage[targetID] = {
totalDamage = 0,
lastHit = currentTime
}
end
local damageData = RADS_CumulativeDamage[targetID]
-- Check if this hit is within the 0.5 second window
if currentTime - damageData.lastHit <= 0.5 then
-- Add to cumulative damage
damageData.totalDamage = damageData.totalDamage + damage
damageData.lastHit = currentTime
finalDamage = damageData.totalDamage
else
-- Reset cumulative damage (new damage window)
damageData.totalDamage = damage
damageData.lastHit = currentTime
finalDamage = damage
end
end
-- Check if target is valid and damage is above threshold
if not IsValid(target) or finalDamage < damageThreshold then return end
local isPlayer = target:IsPlayer()
local isRagdoll = target:IsRagdoll()
-- Completely disable head explosions for ragdolls
if isRagdoll then return end
-- Only process players (ragdolls are now excluded)
if not isPlayer then return end
-- Check if head already exploded to prevent multiple explosions
if target.headExploded then return end
-- For players, check if damage is to the head
local damagePosition = dmginfo:GetDamagePosition()
local bodyPart = target:LastHitGroup(damagePosition)
if bodyPart == HITGROUP_HEAD and not target.fake then
-- Mark head as exploded to prevent multiple explosions
target.headExploded = true
-- Trigger head explosion
RADS_TriggerHeadExplosion(target, dmginfo)
-- Force instant death
target:Kill()
target:SetHealth(0)
end
end)
hook.Add("EntityTakeDamage", "falldamage", function(target, dmginfo)
if GetConVar('rads_ragonfall'):GetBool() then
if target:IsPlayer() and dmginfo:IsFallDamage() and not target.fake then
-- Increased threshold from 8 to 25 for more realistic ragdolling
if dmginfo:GetDamage() > 25 then
target:EmitSound("NPC_Barnacle.BreakNeck", 511, 200, 1, CHAN_ITEM)
rads(target)
-- NEW: Trigger adrenaline based on fall damage severity
if SERVER then
local fallDamage = dmginfo:GetDamage()
local adrenalineGain = 0
-- More reasonable thresholds
if fallDamage >= 50 then
adrenalineGain = 35 -- Severe fall (was 50)
RADS_TriggerSevereFallEffects(target, fallDamage)
-- Trigger internal bleeding for extreme fall damage
RADS_TriggerInternalBleeding(target, "fall", fallDamage)
elseif fallDamage >= 40 then
adrenalineGain = 15 -- Medium fall damage (was 15)
RADS_TriggerLightFallEffects(target, fallDamage)
elseif fallDamage >= 35 then
adrenalineGain = 25 -- High fall damage (was 30)
RADS_TriggerModerateFallEffects(target, fallDamage)
-- Trigger internal bleeding for severe fall damage
RADS_TriggerInternalBleeding(target, "fall", fallDamage)
elseif fallDamage >= 25 then
adrenalineGain = 10 -- Light fall damage (was 8)
end
if adrenalineGain > 0 then
UpdateAdrenaline(target, adrenalineGain)
if GetConVar("developer"):GetInt() > 0 then
print("[FALL DAMAGE] Triggered adrenaline for " .. target:Name() .. " with gain: " .. adrenalineGain)
end
end
end
end
end
end
end)
-- NEW: Fall damage effect functions
if SERVER then
-- Network strings for fall effects
function RADS_TriggerSevereFallEffects(ply, damage)
if not IsValid(ply) or not ply:IsPlayer() then return end
-- Add significant pain
ply.pain = (ply.pain or 0) + math.min(damage * 2, 80)
-- Severe speed reduction for 15-25 seconds
local duration = math.Rand(15, 25)
ply.fallSpeedDebuff = true
ply:SetWalkSpeed(20) -- Very slow
ply:SetRunSpeed(35)
-- Timer to restore speed
timer.Create("RADS_FallSpeedRestore_" .. ply:UserID(), duration, 1, function()
if IsValid(ply) and ply:IsPlayer() then
ply.fallSpeedDebuff = false
-- Restore normal speed (check if other debuffs are active)
if not (ply:GetNWBool("RADS_LeftLegDislocated") or ply:GetNWBool("RADS_RightLegDislocated") or
ply:GetNWBool("RADS_LeftLegBroken") or ply:GetNWBool("RADS_RightLegBroken")) then
ply:SetWalkSpeed(160)
ply:SetRunSpeed(250)
end
end
end)
end
function RADS_TriggerModerateFallEffects(ply, damage)
if not IsValid(ply) or not ply:IsPlayer() then return end
-- Add moderate pain
ply.pain = (ply.pain or 0) + math.min(damage * 1, 50)
-- Moderate speed reduction for 8-15 seconds
local duration = math.Rand(8, 15)
ply.fallSpeedDebuff = true
ply:SetWalkSpeed(40)
ply:SetRunSpeed(70)
-- Timer to restore speed
timer.Create("RADS_FallSpeedRestore_" .. ply:UserID(), duration, 1, function()
if IsValid(ply) and ply:IsPlayer() then
ply.fallSpeedDebuff = false
if not (ply:GetNWBool("RADS_LeftLegDislocated") or ply:GetNWBool("RADS_RightLegDislocated") or
ply:GetNWBool("RADS_LeftLegBroken") or ply:GetNWBool("RADS_RightLegBroken")) then
ply:SetWalkSpeed(160)
ply:SetRunSpeed(250)
end
end
end)
end
function RADS_TriggerLightFallEffects(ply, damage)
if not IsValid(ply) or not ply:IsPlayer() then return end
-- Add light pain
ply.pain = (ply.pain or 0) + math.min(damage * 0.6, 30)
-- Light speed reduction for 5-10 seconds
local duration = math.Rand(5, 10)
ply.fallSpeedDebuff = true
ply:SetWalkSpeed(80)
ply:SetRunSpeed(120)
-- Timer to restore speed
timer.Create("RADS_FallSpeedRestore_" .. ply:UserID(), duration, 1, function()
if IsValid(ply) and ply:IsPlayer() then
ply.fallSpeedDebuff = false
if not (ply:GetNWBool("RADS_LeftLegDislocated") or ply:GetNWBool("RADS_RightLegDislocated") or
ply:GetNWBool("RADS_LeftLegBroken") or ply:GetNWBool("RADS_RightLegBroken")) then
ply:SetWalkSpeed(160)
ply:SetRunSpeed(250)
end
end
end)
end
-- Gore System: Head Explosion Function
function RADS_TriggerHeadExplosion(target, dmginfo)
if not IsValid(target) then return end
local isPlayer = target:IsPlayer()
-- COMPLETELY DISABLE HEAD EXPLOSIONS FOR RAGDOLLS
if not isPlayer then
return -- Block all ragdoll head explosions
end
local ragdoll = nil
-- Ensure instant death for players
if isPlayer then
-- Destroy brain organ to cause instant death
target.Organs = target.Organs or {
["brain"] = 1,
["heart"] = 1,
["lungs"] = 1,
["liver"] = 1,
["stomach"] = 1,
["intestines"] = 1,
["spine"] = 1
}
target.Organs["brain"] = 0
-- Set consciousness to 0 for instant unconsciousness
if target.consciousness then
target.consciousness = 0
target:SetNWInt("PlayerConsciousness", 0)
target.Otrub = true
target:SetNWBool("Otrub", true)
target.consciousnessBasedUnconscious = false
end
-- Force instant death
target:Kill()
target:SetHealth(0)
end
-- Get or create ragdoll
if isPlayer then
-- If player, get their ragdoll (create one if needed)
ragdoll = target:GetNWEntity('player_ragdoll')
if not IsValid(ragdoll) then
-- Player doesn't have ragdoll yet, create one
rads(target)
timer.Simple(0.1, function()
if IsValid(target) then
ragdoll = target:GetNWEntity('player_ragdoll')
if IsValid(ragdoll) then
RADS_ProcessHeadExplosion(ragdoll, dmginfo)
end
end
end)
return
end
else
-- Target is already a ragdoll
ragdoll = target
end
if IsValid(ragdoll) then
RADS_ProcessHeadExplosion(ragdoll, dmginfo)
end
end
function RADS_ProcessHeadExplosion(ragdoll, dmginfo)
if not IsValid(ragdoll) then return end
-- Prevent multiple explosions on same ragdoll
if ragdoll.headExploded then return end
ragdoll.headExploded = true
-- Find head bone
local headBone = ragdoll:LookupBone("ValveBiped.Bip01_Head1")
if not headBone then return end
local headPos, headAng = ragdoll:GetBonePosition(headBone)
if not headPos then return end
-- Mark head as exploded for gore system (prevents conflicts with other head hiding code)
ragdoll.goreHeadExploded = true
-- Hide the head bone by scaling it to 0
ragdoll:ManipulateBoneScale(headBone, Vector(0, 0, 0))
-- Find neck bone for stump placement
local neckBone = ragdoll:LookupBone("ValveBiped.Bip01_Neck1")
local stumpPos = headPos
local stumpAng = headAng
-- ADJUSTABLE STUMP POSITIONING VARIABLES (modify these to adjust stump placement)
local STUMP_OFFSET_FORWARD = 7.45 -- Forward/backward offset from neck bone
local STUMP_OFFSET_RIGHT = 3 -- Left/right offset from neck bone
local STUMP_OFFSET_UP = -0.7 -- Up/down offset from neck bone (positive = up)
local STUMP_ANGLE_PITCH = -253 -- Pitch angle adjustment (positive = nose down)
local STUMP_ANGLE_YAW = 0 -- Yaw angle adjustment (positive = turn right)
local STUMP_ANGLE_ROLL = 75 -- Roll angle adjustment (positive = roll right)
-- Always use neck bone for positioning if available, with improved fallback
if neckBone then
stumpPos, stumpAng = ragdoll:GetBonePosition(neckBone)
-- Apply adjustable positioning offsets
local forward = stumpAng:Forward() * STUMP_OFFSET_FORWARD
local right = stumpAng:Right() * STUMP_OFFSET_RIGHT
local up = stumpAng:Up() * STUMP_OFFSET_UP
stumpPos = stumpPos + forward + right + up
-- Apply adjustable angle offsets
stumpAng = stumpAng + Angle(STUMP_ANGLE_PITCH, STUMP_ANGLE_YAW, STUMP_ANGLE_ROLL)
else
-- Fallback: Use head position but adjust for better stump placement
-- Move the stump slightly down from head position to simulate neck cut
stumpPos = headPos + Vector(0, 0, -8) -- Move down 8 units from head
-- Try to get a more appropriate angle for the stump
-- Use the ragdoll's root bone angle as reference if available
local spineBone = ragdoll:LookupBone("ValveBiped.Bip01_Spine4") or ragdoll:LookupBone("ValveBiped.Bip01_Spine3") or ragdoll:LookupBone("ValveBiped.Bip01_Spine2")
if spineBone then
local _, spineAng = ragdoll:GetBonePosition(spineBone)
if spineAng then
stumpAng = spineAng + Angle(STUMP_ANGLE_PITCH, STUMP_ANGLE_YAW, STUMP_ANGLE_ROLL)
end
else
-- Last resort: use head angle with adjustments
stumpAng = headAng + Angle(STUMP_ANGLE_PITCH, STUMP_ANGLE_YAW, STUMP_ANGLE_ROLL)
end
end
-- Create gore stump model
local stump = ents.Create("prop_physics")
if IsValid(stump) then
stump:SetModel("models/mosi/fnv/props/character/headcap.mdl")
stump:SetPos(stumpPos)
stump:SetAngles(stumpAng)
stump:Spawn()
-- Make stump non-collidable and attach to ragdoll
stump:SetCollisionGroup(COLLISION_GROUP_IN_VEHICLE)
if IsValid(stump:GetPhysicsObject()) then
stump:GetPhysicsObject():SetMass(1)
stump:GetPhysicsObject():EnableCollisions(false)
end
-- Weld stump to neck bone
if neckBone then
local neckPhysBone = ragdoll:TranslateBoneToPhysBone(neckBone)
if neckPhysBone >= 0 then
constraint.Weld(stump, ragdoll, 0, neckPhysBone, 0, true, false)
end
end
-- Clean up stump when ragdoll is removed
ragdoll:DeleteOnRemove(stump)
-- Store stump reference for blood effects
ragdoll.goreStump = stump
end
-- Play explosion sound with random pitch
local pitch = math.random(95, 115)
ragdoll:EmitSound("head_explodie_01.mp3", 75, pitch, 1, CHAN_AUTO)
-- Create initial blood explosion effect
RADS_CreateBloodExplosion(ragdoll, headPos)
-- Start continuous blood stream from stump
RADS_StartBloodStream(ragdoll, stumpPos)
end
function RADS_CreateBloodExplosion(ragdoll, position)
if not IsValid(ragdoll) or not position then return end
-- Create dramatic arterial blood spurts in all directions
for i = 1, 15 do
local direction = VectorRand():GetNormalized()
direction.z = math.abs(direction.z) * 0.8 -- Bias upward for arterial spurting
-- Create high-pressure arterial blood spurts
local effectdata = EffectData()
effectdata:SetOrigin(position + VectorRand() * 3)
effectdata:SetNormal(direction)
effectdata:SetMagnitude(math.random(80, 150)) -- Increased magnitude for arterial spurting
effectdata:SetScale(math.random(3, 6)) -- Larger scale for dramatic effect
util.Effect("BloodImpact", effectdata)
-- Add secondary smaller spurts for realism
if math.random(1, 3) == 1 then
local secondaryDir = (direction + VectorRand() * 0.3):GetNormalized()
local secondaryEffect = EffectData()
secondaryEffect:SetOrigin(position + direction * math.random(10, 25))
secondaryEffect:SetNormal(secondaryDir)
secondaryEffect:SetMagnitude(math.random(40, 80))
secondaryEffect:SetScale(math.random(1, 3))
util.Effect("BloodImpact", secondaryEffect)
end
end
-- Create extensive blood decals around the explosion
for i = 1, 12 do
local traceDir = VectorRand():GetNormalized()
local trace = util.TraceLine({
start = position,
endpos = position + traceDir * 200, -- Increased range for arterial spurting
filter = ragdoll
})
if trace.Hit then
util.Decal("Blood", trace.HitPos + trace.HitNormal, trace.HitPos - trace.HitNormal)
-- Add additional blood splatters nearby
for j = 1, 3 do
local nearbyPos = trace.HitPos + VectorRand() * 15
local nearbyTrace = util.TraceLine({
start = nearbyPos + Vector(0, 0, 10),
endpos = nearbyPos - Vector(0, 0, 10),
filter = ragdoll
})
if nearbyTrace.Hit then
util.Decal("Blood", nearbyTrace.HitPos + nearbyTrace.HitNormal, nearbyTrace.HitPos - nearbyTrace.HitNormal)
end
end
end
end
end
function RADS_StartBloodStream(ragdoll, stumpPos)
if not IsValid(ragdoll) or not stumpPos then return end
local bloodDuration = GetConVar('rads_gore_blood_duration'):GetFloat()
local timerName = "RADS_BloodStream_" .. ragdoll:EntIndex()
-- Create blood stream effect every 0.5 seconds
timer.Create(timerName, 0.5, bloodDuration * 2, function()
if not IsValid(ragdoll) then
timer.Remove(timerName)
return
end
-- Update stump position if it exists
local currentStumpPos = stumpPos
if IsValid(ragdoll.goreStump) then
currentStumpPos = ragdoll.goreStump:GetPos()
end
-- Create downward blood stream
local effectdata = EffectData()
effectdata:SetOrigin(currentStumpPos)
effectdata:SetNormal(Vector(0, 0, -1))
effectdata:SetMagnitude(30)
effectdata:SetScale(1.5)
util.Effect("BloodImpact", effectdata)
-- Create blood decal on ground
local trace = util.TraceLine({
start = currentStumpPos,
endpos = currentStumpPos + Vector(0, 0, -200),
filter = ragdoll
})
if trace.Hit then
util.Decal("Blood", trace.HitPos + trace.HitNormal, trace.HitPos - trace.HitNormal)
end
end)
-- Clean up timer when ragdoll is removed
ragdoll:CallOnRemove("CleanupBloodStream", function()
timer.Remove(timerName)
end)
end
-- Cleanup cumulative damage tracking when ragdoll is removed
if IsValid(ragdoll) then
ragdoll:CallOnRemove("CleanupCumulativeDamage", function()
local targetID = ragdoll:EntIndex()
if RADS_CumulativeDamage and RADS_CumulativeDamage[targetID] then
RADS_CumulativeDamage[targetID] = nil
end
end)
end
end
-- Cleanup cumulative damage tracking when player disconnects
hook.Add("PlayerDisconnected", "RADS_CleanupCumulativeDamage", function(ply)
if not RADS_CumulativeDamage then return end
local playerID = ply:EntIndex()
if RADS_CumulativeDamage[playerID] then
RADS_CumulativeDamage[playerID] = nil
end
-- Also cleanup any ragdoll associated with this player
local ragdoll = ply:GetNWEntity('player_ragdoll')
if IsValid(ragdoll) then
local ragdollID = ragdoll:EntIndex()
if RADS_CumulativeDamage[ragdollID] then
RADS_CumulativeDamage[ragdollID] = nil
end
end
end)
-- Blast damage ragdoll system with knockback
hook.Add("EntityTakeDamage", "RADS_BlastRagdoll", function(target, dmginfo)
if IsValid(target) and target:IsPlayer() and not target.fake and dmginfo:IsDamageType(DMG_BLAST) then
-- Check if player is already ragdolled to prevent double ragdolling
local existingRag = target:GetNWEntity('player_ragdoll')
if IsValid(existingRag) then
-- Player is already ragdolled, just apply knockback to existing ragdoll
local forceMagnitude = math.min(dmginfo:GetDamage() * 15, 35000)
local explosionPosition = dmginfo:GetDamagePosition()
for i = 0, existingRag:GetPhysicsObjectCount() - 1 do
local physobj = existingRag:GetPhysicsObjectNum(i)
if IsValid(physobj) then
local bonePosition = physobj:GetPos()
local forceDirection = (bonePosition - explosionPosition):GetNormalized()
-- Add some upward force for more realistic blast effect
forceDirection = forceDirection + Vector(0, 0, 0.6)
forceDirection:Normalize()
physobj:ApplyForceOffset(forceDirection * forceMagnitude, bonePosition)
end
end
return
end
-- Player is not ragdolled, ragdoll them first
rads(target)
-- Store damage info before timer since dmginfo becomes invalid
local damage = dmginfo:GetDamage()
local explosionPos = dmginfo:GetDamagePosition()
-- Apply knockback after a short delay to ensure ragdoll is created
timer.Simple(0.1, function()
if IsValid(target) then
local rag = target:GetNWEntity('player_ragdoll')
if IsValid(rag) then
local forceMagnitude = math.min(damage * 30, 2000)
for i = 0, rag:GetPhysicsObjectCount() - 1 do
local physobj = rag:GetPhysicsObjectNum(i)
if IsValid(physobj) then
local bonePosition = physobj:GetPos()
local forceDirection = (bonePosition - explosionPos):GetNormalized()
-- Add some upward force for more realistic blast effect
forceDirection = forceDirection + Vector(0, 0, 0.3)
forceDirection:Normalize()
physobj:ApplyForceOffset(forceDirection * forceMagnitude, bonePosition)
end
end
end
end
end)
end
end)
-- Club and slash damage knockback system (only applies to existing ragdolls)
-- Directional Club/Slash Knockback System - Applies force only to specific hit body parts
hook.Add("EntityTakeDamage", "RADS_ClubSlashKnockback", function(target, dmginfo)
if IsValid(target) and target:IsPlayer() and not target.fake and (dmginfo:IsDamageType(DMG_CLUB) or dmginfo:IsDamageType(DMG_SLASH)) then
-- Prevent multiple knockback applications with cooldown
target.lastKnockbackTime = target.lastKnockbackTime or 0
if CurTime() - target.lastKnockbackTime < 0.5 then
return -- Ignore if knockback was applied recently
end
target.lastKnockbackTime = CurTime()
-- Store damage info before timer since dmginfo becomes invalid
local hitGroup = target:LastHitGroup() or HITGROUP_GENERIC
local damage = dmginfo:GetDamage()
local attacker = dmginfo:GetAttacker()
local damagePos = dmginfo:GetDamagePosition()
-- Calculate precise directional force from attacker to hit location
local forceDirection = Vector(0, 0, 0)
if IsValid(attacker) and attacker:IsPlayer() then
-- Get attacker's aim direction for more realistic knockback
local attackerEyes = attacker:EyePos()
local targetHitPos = target:GetBonePosition(target:LookupBone("ValveBiped.Bip01_Spine2") or 0)
-- Adjust target position based on hit group for accuracy
if hitGroup == HITGROUP_HEAD then
targetHitPos = target:GetBonePosition(target:LookupBone("ValveBiped.Bip01_Head1") or 0)
elseif hitGroup == HITGROUP_LEFTARM then
targetHitPos = target:GetBonePosition(target:LookupBone("ValveBiped.Bip01_L_UpperArm") or 0)
elseif hitGroup == HITGROUP_RIGHTARM then
targetHitPos = target:GetBonePosition(target:LookupBone("ValveBiped.Bip01_R_UpperArm") or 0)
elseif hitGroup == HITGROUP_LEFTLEG then
targetHitPos = target:GetBonePosition(target:LookupBone("ValveBiped.Bip01_L_Thigh") or 0)
elseif hitGroup == HITGROUP_RIGHTLEG then
targetHitPos = target:GetBonePosition(target:LookupBone("ValveBiped.Bip01_R_Thigh") or 0)
end
forceDirection = (targetHitPos - attackerEyes):GetNormalized()
elseif damagePos and damagePos ~= Vector(0,0,0) then
forceDirection = (target:GetPos() - damagePos):GetNormalized()
else
-- Fallback to attacker's forward direction
if IsValid(attacker) then
forceDirection = attacker:GetAngles():Forward()
else
forceDirection = target:GetAngles():Forward()
end
end
-- Store knockback data for later application
target.pendingKnockback = {
hitGroup = hitGroup,
damage = damage,
forceDirection = forceDirection,
attacker = attacker,
timestamp = CurTime()
}
-- Apply knockback with delay to ensure ragdoll is fully created
timer.Simple(0.15, function()
if IsValid(target) and target.pendingKnockback and (CurTime() - target.pendingKnockback.timestamp) < 2.5 then
local rag = target:GetNWEntity('player_ragdoll')
if IsValid(rag) then
local knockbackData = target.pendingKnockback
-- Calculate force magnitude based on damage (further reduced to prevent neck breaking)
local baseForceMagnitude = math.min(knockbackData.damage * 120, 10000)
-- Apply force only to the specific hit body part for realistic effect
local targetBoneName = ""
local forceMultiplier = 1.0
local upwardComponent = 0.2 -- Reduced upward force
if knockbackData.hitGroup == HITGROUP_HEAD then
targetBoneName = "ValveBiped.Bip01_Head1"
forceMultiplier = 1.4 -- Further reduced from 2.5 to make neck breaking harder
upwardComponent = 0.3 -- Further reduced upward snap
elseif knockbackData.hitGroup == HITGROUP_CHEST then
targetBoneName = "ValveBiped.Bip01_Spine2"
forceMultiplier = 2.0 -- Reduced
upwardComponent = 0.3
elseif knockbackData.hitGroup == HITGROUP_STOMACH then
targetBoneName = "ValveBiped.Bip01_Spine1"
forceMultiplier = 1.8 -- Reduced
upwardComponent = 0.2
elseif knockbackData.hitGroup == HITGROUP_LEFTARM then
targetBoneName = "ValveBiped.Bip01_L_UpperArm"
forceMultiplier = 2.2 -- Reduced from 4.0
upwardComponent = 0.3
elseif knockbackData.hitGroup == HITGROUP_RIGHTARM then
targetBoneName = "ValveBiped.Bip01_R_UpperArm"
forceMultiplier = 2.2 -- Reduced from 4.0
upwardComponent = 0.3
elseif knockbackData.hitGroup == HITGROUP_LEFTLEG then
targetBoneName = "ValveBiped.Bip01_L_Thigh"
forceMultiplier = 2.0 -- Reduced
upwardComponent = 0.4
elseif knockbackData.hitGroup == HITGROUP_RIGHTLEG then
targetBoneName = "ValveBiped.Bip01_R_Thigh"
forceMultiplier = 2.0 -- Reduced
upwardComponent = 0.4
else
-- Generic hit - apply to torso
targetBoneName = "ValveBiped.Bip01_Spine2"
forceMultiplier = 1.5 -- Reduced
upwardComponent = 0.2
end
-- Find and apply force to the specific bone
local boneIndex = rag:LookupBone(targetBoneName)
if boneIndex then
local physBone = rag:TranslateBoneToPhysBone(boneIndex)
if physBone >= 0 then
local physobj = rag:GetPhysicsObjectNum(physBone)
if IsValid(physobj) then
-- Validate and fix force direction to prevent zero force
local finalForce = knockbackData.forceDirection
-- Check if force direction is invalid (zero vector or very small)
if not finalForce or finalForce:Length() < 0.1 then
-- Fallback: use attacker's forward direction or random horizontal direction
if IsValid(knockbackData.attacker) then
finalForce = knockbackData.attacker:GetAngles():Forward()
else
finalForce = Vector(math.random(-1, 1), math.random(-1, 1), 0):GetNormalized()
end
if GetConVar("developer"):GetInt() > 0 then
print("[DIRECTIONAL KNOCKBACK] Fixed invalid force direction for " .. target:Name())
end
end
-- Add upward component
finalForce = finalForce + Vector(0, 0, upwardComponent)
finalForce:Normalize()
local forceMagnitude = baseForceMagnitude * forceMultiplier
-- Ensure minimum force magnitude
if forceMagnitude < 500 then
forceMagnitude = 7500
if GetConVar("developer"):GetInt() > 0 then
print("[DIRECTIONAL KNOCKBACK] Applied minimum force to " .. target:Name())
end
end
-- Apply force using ApplyForceCenter for more controlled physics
physobj:ApplyForceCenter(finalForce * forceMagnitude)
-- For head hits, add even more minimal rotational effect to prevent neck breaking
if knockbackData.hitGroup == HITGROUP_HEAD then
timer.Simple(0.05, function()
if IsValid(physobj) then
-- Add extremely gentle rotational force
local rotForce = knockbackData.forceDirection:Cross(Vector(0, 0, 1)) * forceMagnitude * 0.05
physobj:ApplyForceCenter(rotForce)
end
end)
end
-- Debug print
if GetConVar("developer"):GetInt() > 0 then
print("[DIRECTIONAL KNOCKBACK] Applied " .. math.Round(forceMagnitude) .. " force to " .. target:Name() .. "'s " .. targetBoneName .. " (hitgroup: " .. knockbackData.hitGroup .. ")")
end
end
end
end
-- Clear the pending knockback
target.pendingKnockback = nil
else
-- Player didn't ragdoll, clear pending knockback
target.pendingKnockback = nil
end
end
end)
end
end)
hook.Add("PlayerDeathSound", "DeFlatline", function()
return true
end)
local noise = Sound("death.wav")
hook.Add("PlayerDeath", "NewSound", function(vic, unused1, unused2) vic:EmitSound(noise) end)
hook.Add("PlayerTick", "CheckPlayerSpeed", function(ply, mv)
if GetConVar('rads_fallonspeedlimit'):GetBool() and not ply.fake then
local speed = ply:GetVelocity():Length()
local rag = ply:GetNWEntity('player_ragdoll')
if not IsValid(rag) and ply:GetMoveType() ~= MOVETYPE_NOCLIP and not ply:HasGodMode() and ply:GetMoveType() ~= MOVETYPE_OBSERVER then
-- Use ConVar for velocity threshold
local velocityThreshold = GetConVar("rads_fallonspeedlimit_threshold"):GetInt()
if speed >= velocityThreshold then
rads(ply)
RADS.FreeFall(ply)
end
end
end
end)
-- Optimized ragdoll velocity adrenaline system with performance scaling
local lastRagdollVelocityCheck = 0
local ragdollVelocityInterval = 0.5
hook.Add("Think", "RADS_RagdollVelocityAdrenaline", function()
if not SERVER then return end
local cheapEffectsCvar = GetConVar("rads_cheapeffects")
local cheapEffects = cheapEffectsCvar and cheapEffectsCvar:GetInt() or 0
-- Performance scaling based on cheapeffects
if cheapEffects >= 2 then
ragdollVelocityInterval = 2.0 -- Very slow updates for max performance
elseif cheapEffects >= 1 then
ragdollVelocityInterval = 1.0 -- Moderate updates
else
ragdollVelocityInterval = 0.5 -- Normal updates
end
if CurTime() - lastRagdollVelocityCheck < ragdollVelocityInterval then return end
lastRagdollVelocityCheck = CurTime()
-- Skip entirely if cheapeffects is 2
if cheapEffects >= 2 then return end
for _, ply in pairs(player.GetAll()) do
if IsValid(ply) and ply:Alive() then
local rag = ply:GetNWEntity('player_ragdoll')
-- Check if non-ragdolled player is fully submerged and auto-ragdoll them
if not IsValid(rag) then
local headPos = ply:GetPos() + Vector(0, 0, 64) -- Approximate head position
local waterLevel = util.PointContents(headPos)
local isUnderwater = bit.band(waterLevel, CONTENTS_WATER) ~= 0
if isUnderwater then
-- Force ragdoll the player when fully submerged
rads(ply, false) -- Auto-ragdoll when submerged
end
return -- Skip drowning logic for non-ragdolled players
end
if IsValid(rag) then
-- Initialize cooldown tracking
ply.ragdollAdrenalineCooldown = ply.ragdollAdrenalineCooldown or 0
-- Check if cooldown has passed (15 seconds)
if CurTime() >= ply.ragdollAdrenalineCooldown then
-- Get main physics object (torso)
local mainPhys = rag:GetPhysicsObjectNum(1)
if IsValid(mainPhys) then
local velocity = mainPhys:GetVelocity():Length()
-- Trigger adrenaline based on velocity thresholds
if velocity >= 1100 then
UpdateAdrenaline(ply, 35) -- Extreme velocity
ply.ragdollAdrenalineCooldown = CurTime() + 15
if cheapEffects == 0 then
if GetConVar("developer"):GetInt() > 0 then
print("[ADRENALINE DEBUG] " .. ply:Name() .. " ragdoll extreme velocity: " .. math.Round(velocity) .. " units/s")
end
end
elseif velocity >= 850 then
UpdateAdrenaline(ply, 25) -- High velocity
ply.ragdollAdrenalineCooldown = CurTime() + 15
if cheapEffects == 0 then
if GetConVar("developer"):GetInt() > 0 then
print("[ADRENALINE DEBUG] " .. ply:Name() .. " ragdoll high velocity: " .. math.Round(velocity) .. " units/s")
end
end
elseif velocity >= 650 then
UpdateAdrenaline(ply, 15) -- Medium velocity
ply.ragdollAdrenalineCooldown = CurTime() + 15
if cheapEffects == 0 then
if GetConVar("developer"):GetInt() > 0 then
print("[ADRENALINE DEBUG] " .. ply:Name() .. " ragdoll medium velocity: " .. math.Round(velocity) .. " units/s")
end
end
end
end
end
end
end
end
end)
-- Drowning System for Ragdolls
local drowningPlayers = {}
local lastDrowningCheck = 0
local drowningCheckInterval = 0.5
local swimmingPlayers = {} -- Track swimming state
local lastSplashTime = {} -- Track splash sound cooldown
local lastSwimTime = {} -- Track individual swimming cooldown (0.5 seconds)
hook.Add("Think", "RADS_DrowningSystem", function()
if not SERVER then return end
if CurTime() - lastDrowningCheck < drowningCheckInterval then return end
lastDrowningCheck = CurTime()
for _, ply in pairs(player.GetAll()) do
if IsValid(ply) and ply:Alive() then
local rag = ply:GetNWEntity('player_ragdoll')
if IsValid(rag) then
-- Initialize drowning data
if not drowningPlayers[ply] then
drowningPlayers[ply] = {
submergedTime = 0,
isDrowning = false,
drowningStartTime = 0,
soundPlaying = false
}
end
local drowningData = drowningPlayers[ply]
-- Check if ragdoll head is underwater
local head = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_Head1")))
if IsValid(head) then
local headPos = head:GetPos()
local waterLevel = util.PointContents(headPos)
local isUnderwater = bit.band(waterLevel, CONTENTS_WATER) ~= 0
if isUnderwater then
drowningData.submergedTime = drowningData.submergedTime + drowningCheckInterval
-- Start drowning after 20 seconds underwater
if drowningData.submergedTime >= GetConVar("rads_drowning_threshold_time"):GetFloat() and not drowningData.isDrowning then
drowningData.isDrowning = true
drowningData.drowningStartTime = CurTime()
-- Set player drowning state for damage.lua
ply.isDrowning = true
ply.drowningStartTime = CurTime()
-- Start drowning sound with continuous looping (clientside only)
if not drowningData.soundPlaying then
if SERVER then
net.Start("PlayDrowningSound")
net.Send(ply)
end
drowningData.soundPlaying = true
-- Create looping timer for drowning sound (loop every 10 seconds)
timer.Create("DrowningLoop_" .. ply:EntIndex(), 10, 0, function()
if IsValid(ply) and drowningData and drowningData.soundPlaying and drowningData.isDrowning and ply:Alive() then
if SERVER then
net.Start("PlayDrowningSound")
net.Send(ply)
end
else
timer.Remove("DrowningLoop_" .. ply:EntIndex())
end
end)
end
end
-- Apply continuous sinking physics with gravity-like force
-- But reduce sinking significantly when player is actively swimming
local sinkForce = GetConVar("rads_drowning_sink_force"):GetFloat()
local isSwimming = swimmingPlayers[ply] and (CurTime() - swimmingPlayers[ply]) < 0.5
local sinkMultiplier = isSwimming and 0.05 or 1.0 -- Reduce sinking by 95% when swimming
for i = 0, rag:GetPhysicsObjectCount() - 1 do
local phys = rag:GetPhysicsObjectNum(i)
if IsValid(phys) then
local pos = phys:GetPos()
local waterCheck = util.PointContents(pos)
if bit.band(waterCheck, CONTENTS_WATER) ~= 0 then
local mass = phys:GetMass()
-- Apply continuous downward force proportional to mass (like gravity) - reduced intensity
local gravityForce = Vector(0, 0, -sinkForce * mass * 0.003 * sinkMultiplier) -- Reduced from 0.005 to 0.003 (40% additional reduction for even slower sinking)
phys:ApplyForceCenter(gravityForce)
-- Set buoyancy based on swimming state
phys:SetBuoyancyRatio(isSwimming and 0.8 or 0)
-- Add underwater drag for more realistic movement
local velocity = phys:GetVelocity()
local drag = velocity * -0.2
phys:ApplyForceCenter(drag)
end
end
end
else
-- Reset drowning when head is above water
if drowningData.isDrowning or drowningData.submergedTime > 0 then
-- Stop looping sound immediately (clientside)
if drowningData.soundPlaying then
timer.Remove("DrowningLoop_" .. ply:EntIndex())
timer.Remove("DrowningFadeOut_" .. ply:EntIndex())
if SERVER then
net.Start("StopDrowningSound")
net.Send(ply)
end
drowningData.soundPlaying = false
end
drowningData.submergedTime = 0
drowningData.isDrowning = false
drowningData.drowningStartTime = 0
-- Reset player drowning state for damage.lua
ply.isDrowning = false
ply.drowningStartTime = nil
end
end
-- Check for death after 1 minute of drowning
if drowningData.isDrowning then
local drowningTime = CurTime() - drowningData.drowningStartTime
if drowningTime >= GetConVar("rads_drowning_death_time"):GetFloat() then
-- Kill player after drowning time limit
ply:Kill()
-- Clean up drowning data
drowningPlayers[ply] = nil
timer.Remove("DrowningLoop_" .. ply:EntIndex())
timer.Remove("DrowningFadeOut_" .. ply:EntIndex())
if SERVER then
net.Start("StopDrowningSound")
net.Send(ply)
end
end
end
end
end
else
-- Clean up drowning data for dead/invalid players
if drowningPlayers[ply] then
if drowningPlayers[ply].soundPlaying then
if SERVER then
net.Start("StopDrowningSound")
net.Send(ply)
end
end
drowningPlayers[ply] = nil
timer.Remove("DrowningLoop_" .. ply:EntIndex())
timer.Remove("DrowningFadeOut_" .. ply:EntIndex())
end
end
end
end)
-- Swimming Controls for Ragdolls (Individual Arm Control)
hook.Add("PlayerButtonDown", "RADS_SwimmingControls", function(ply, button)
if not IsValid(ply) or not ply:Alive() then return end
local rag = ply:GetNWEntity('player_ragdoll')
if not IsValid(rag) then return end
-- Check if ragdoll is in water first
local head = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_Head1")))
if not IsValid(head) then return end
local headPos = head:GetPos()
local waterLevel = util.PointContents(headPos)
local isUnderwater = bit.band(waterLevel, CONTENTS_WATER) ~= 0
if not isUnderwater then return end
-- Check swimming cooldown (0.5 seconds between strokes)
if lastSwimTime[ply] and (CurTime() - lastSwimTime[ply]) < 0.5 then
return -- Prevent swimming spam
end
local swimForce = GetConVar("rads_drowning_swim_force"):GetFloat()
-- Update swimming cooldown
lastSwimTime[ply] = CurTime()
-- Track swimming state for sinking reduction
swimmingPlayers[ply] = CurTime()
-- Play splash sound with cooldown
if not lastSplashTime[ply] or (CurTime() - lastSplashTime[ply]) > 1.0 then
ply:EmitSound("physics/water/water_impact_soft" .. math.random(1,3) .. ".wav", 60, math.random(90, 110), 0.7)
lastSplashTime[ply] = CurTime()
end
-- LMB - Left Arm Swimming
if button == MOUSE_LEFT then
local leftArmBone = rag:LookupBone("ValveBiped.Bip01_L_UpperArm")
if leftArmBone then
local leftArmPhys = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(leftArmBone))
if IsValid(leftArmPhys) then
-- Get player's view direction for more intuitive swimming
local viewAngles = ply:EyeAngles()
local armDirection = viewAngles:Forward()
-- Add slight left bias for left arm swimming
local leftBias = viewAngles:Right() * -0.3
armDirection = (armDirection + leftBias):GetNormalized()
-- Apply stronger force to multiple body parts for better swimming
local torso = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_Spine")))
local pelvis = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_Pelvis")))
local chest = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_Spine2")))
-- Enhanced force multiplier for more dynamic swimming
local forceMultiplier = 3.2 -- Reduced from 4.0 to 3.2 for more balanced swimming strokes
-- Add upward component to help surface above water
local upwardForce = Vector(0, 0, swimForce * 2.8) -- Reduced from 3.5 to 2.8 for more balanced upward movement
if IsValid(torso) then
torso:ApplyForceCenter(armDirection * swimForce * forceMultiplier + upwardForce)
-- Add angular velocity for more natural swimming motion
local torqueForce = viewAngles:Right() * swimForce * 0.3
torso:ApplyTorqueCenter(torqueForce)
end
if IsValid(pelvis) then
pelvis:ApplyForceCenter(armDirection * swimForce * forceMultiplier * 0.8 + upwardForce * 0.7)
-- Add slight rotational force to pelvis
local pelvisTorque = viewAngles:Up() * swimForce * 0.2
pelvis:ApplyTorqueCenter(pelvisTorque)
end
if IsValid(chest) then
chest:ApplyForceCenter(armDirection * swimForce * forceMultiplier * 0.6 + upwardForce * 0.5)
-- Add chest rotation for swimming stroke
local chestTorque = viewAngles:Right() * swimForce * -0.2
chest:ApplyTorqueCenter(chestTorque)
end
-- Add leg paddling for left arm swimming
local leftThighBone = rag:LookupBone("ValveBiped.Bip01_L_Thigh")
local rightThighBone = rag:LookupBone("ValveBiped.Bip01_R_Thigh")
local leftCalfBone = rag:LookupBone("ValveBiped.Bip01_L_Calf")
local rightCalfBone = rag:LookupBone("ValveBiped.Bip01_R_Calf")
if leftThighBone then
local leftThighPhys = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(leftThighBone))
if IsValid(leftThighPhys) then
leftThighPhys:ApplyForceCenter(armDirection * swimForce * forceMultiplier * 0.4 + upwardForce * 0.3)
end
end
if rightThighBone then
local rightThighPhys = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rightThighBone))
if IsValid(rightThighPhys) then
rightThighPhys:ApplyForceCenter(armDirection * swimForce * forceMultiplier * 0.4 + upwardForce * 0.3)
end
end
if leftCalfBone then
local leftCalfPhys = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(leftCalfBone))
if IsValid(leftCalfPhys) then
leftCalfPhys:ApplyForceCenter(armDirection * swimForce * forceMultiplier * 0.3 + upwardForce * 0.2)
end
end
if rightCalfBone then
local rightCalfPhys = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rightCalfBone))
if IsValid(rightCalfPhys) then
rightCalfPhys:ApplyForceCenter(armDirection * swimForce * forceMultiplier * 0.3 + upwardForce * 0.2)
end
end
end
end
end
-- RMB - Right Arm Swimming
if button == MOUSE_RIGHT then
local rightArmBone = rag:LookupBone("ValveBiped.Bip01_R_UpperArm")
if rightArmBone then
local rightArmPhys = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rightArmBone))
if IsValid(rightArmPhys) then
-- Get player's view direction for more intuitive swimming
local viewAngles = ply:EyeAngles()
local armDirection = viewAngles:Forward()
-- Add slight right bias for right arm swimming
local rightBias = viewAngles:Right() * 0.3
armDirection = (armDirection + rightBias):GetNormalized()
-- Apply stronger force to multiple body parts for better swimming
local torso = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_Spine")))
local pelvis = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_Pelvis")))
local chest = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_Spine2")))
-- Enhanced force multiplier for more dynamic swimming
local forceMultiplier = 3.2 -- Reduced from 4.0 to 3.2 for more balanced swimming strokes
-- Add upward component to help surface above water
local upwardForce = Vector(0, 0, swimForce * 2.8) -- Reduced from 3.5 to 2.8 for more balanced upward movement
if IsValid(torso) then
torso:ApplyForceCenter(armDirection * swimForce * forceMultiplier + upwardForce)
-- Add angular velocity for more natural swimming motion
local torqueForce = viewAngles:Right() * swimForce * -0.3 -- Opposite direction for right arm
torso:ApplyTorqueCenter(torqueForce)
end
if IsValid(pelvis) then
pelvis:ApplyForceCenter(armDirection * swimForce * forceMultiplier * 0.8 + upwardForce * 0.7)
-- Add slight rotational force to pelvis
local pelvisTorque = viewAngles:Up() * swimForce * -0.2 -- Opposite direction for right arm
pelvis:ApplyTorqueCenter(pelvisTorque)
end
if IsValid(chest) then
chest:ApplyForceCenter(armDirection * swimForce * forceMultiplier * 0.6 + upwardForce * 0.5)
-- Add chest rotation for swimming stroke
local chestTorque = viewAngles:Right() * swimForce * 0.2 -- Opposite direction for right arm
chest:ApplyTorqueCenter(chestTorque)
end
-- Add leg paddling for right arm swimming
local leftThighBone = rag:LookupBone("ValveBiped.Bip01_L_Thigh")
local rightThighBone = rag:LookupBone("ValveBiped.Bip01_R_Thigh")
local leftCalfBone = rag:LookupBone("ValveBiped.Bip01_L_Calf")
local rightCalfBone = rag:LookupBone("ValveBiped.Bip01_R_Calf")
if leftThighBone then
local leftThighPhys = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(leftThighBone))
if IsValid(leftThighPhys) then
leftThighPhys:ApplyForceCenter(armDirection * swimForce * forceMultiplier * 0.4 + upwardForce * 0.3)
end
end
if rightThighBone then
local rightThighPhys = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rightThighBone))
if IsValid(rightThighPhys) then
rightThighPhys:ApplyForceCenter(armDirection * swimForce * forceMultiplier * 0.4 + upwardForce * 0.3)
end
end
if leftCalfBone then
local leftCalfPhys = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(leftCalfBone))
if IsValid(leftCalfPhys) then
leftCalfPhys:ApplyForceCenter(armDirection * swimForce * forceMultiplier * 0.3 + upwardForce * 0.2)
end
end
if rightCalfBone then
local rightCalfPhys = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rightCalfBone))
if IsValid(rightCalfPhys) then
rightCalfPhys:ApplyForceCenter(armDirection * swimForce * forceMultiplier * 0.3 + upwardForce * 0.2)
end
end
end
end
end
end)
-- Death state cleanup for drowning system
hook.Add("PlayerDeath", "RADS_DrowningDeathCleanup", function(victim, inflictor, attacker)
if not IsValid(victim) then return end
-- Clean up drowning data and sounds on death
if drowningPlayers[victim] then
if drowningPlayers[victim].soundPlaying then
victim:StopSound("drowning.ogg")
end
drowningPlayers[victim] = nil
end
-- Clean up swimming state tracking
swimmingPlayers[victim] = nil
lastSplashTime[victim] = nil
lastSwimTime[victim] = nil
-- Remove all drowning-related timers
timer.Remove("DrowningLoop_" .. victim:EntIndex())
timer.Remove("DrowningFadeOut_" .. victim:EntIndex())
-- Reset drowning state variables
victim.isDrowning = false
victim.drowningStartTime = nil
end)
-- Respawn cleanup for drowning system
hook.Add("PlayerSpawn", "RADS_DrowningRespawnCleanup", function(ply)
if not IsValid(ply) then return end
-- Ensure clean state on respawn
if drowningPlayers[ply] then
drowningPlayers[ply] = nil
end
-- Clean up swimming state tracking
swimmingPlayers[ply] = nil
lastSplashTime[ply] = nil
lastSwimTime[ply] = nil
-- Remove any lingering timers
timer.Remove("DrowningLoop_" .. ply:EntIndex())
timer.Remove("DrowningFadeOut_" .. ply:EntIndex())
-- Stop any drowning sounds
ply:StopSound("drowning.ogg")
-- Reset drowning state variables
ply.isDrowning = false
ply.drowningStartTime = nil
end)
hook.Add("RADSLoadout", "RADSLuaLoad", function(ply)
if not ply.resetinv and not GetConVar('rads_loadoutusinglua'):GetBool() then
return
else
RADS_ReturnPlyInfo(ply)
RADS_RestoreEzArmor(ply)
end
if RADS.IsTTT() then
ply:Give('weapon_zm_improvised')
ply:Give('weapon_zm_carry')
ply:Give('weapon_ttt_unarmed')
end
end)
hook.Add("PreCleanupMap", "getupnoobis", function()
for i, v in pairs(player.GetAll()) do
if v.brokenspine then v:Kill() end
if v.fake then rads(v) end
end
end)
util.AddNetworkString("ebal_chellele")
hook.Add("PlayerSwitchWeapon", "fakewep", function(ply, oldwep, newwep)
rag = ply:GetNWEntity('player_ragdoll')
if IsValid(rag) then
if ply.fake then
if IsValid(ply.Info.ActiveWeapon2) and IsValid(ply.wep) and ply.wep.Clip ~= nil and ply.wep.Amt ~= nil and ply.wep.AmmoType ~= nil then
ply.Info.ActiveWeapon2:SetClip1(ply.wep.Clip or 0)
ply:SetAmmo(ply.wep.Amt or 0, ply.wep.AmmoType or 0)
end
if table.HasValue(Guns, newwep:GetClass()) then
if IsValid(ply.wep) then
if _G.DespawnWeapon then
_G.DespawnWeapon(ply)
else
if GetConVar("developer"):GetInt() > 0 then
print("[RADS] ERROR: DespawnWeapon function not available globally at line 1770!")
end
end
end
ply:SetActiveWeapon(newwep)
ply.Info.ActiveWeapon = newwep
ply.curweapon = newwep:GetClass()
RADS_SavePlyInfo(ply)
ply:SetActiveWeapon(nil)
if _G.SpawnWeapon then
_G.SpawnWeapon(ply)
else
if GetConVar("developer"):GetInt() > 0 then
print("[RADS] ERROR: SpawnWeapon function not available globally at line 1776!")
end
end
ply.FakeShooting = true
else
if IsValid(ply.wep) then
if _G.DespawnWeapon then
_G.DespawnWeapon(ply)
else
if GetConVar("developer"):GetInt() > 0 then
print("[RADS] ERROR: DespawnWeapon function not available globally at line 1783!")
end
end
end
ply:SetActiveWeapon(nil)
ply.curweapon = nil
ply.FakeShooting = false
end
net.Start("ebal_chellele")
net.WriteEntity(ply)
net.WriteString(ply.curweapon or "")
net.Broadcast()
return true
end
end
end)
hook.Add("Player Think", "ragmovement", function(ply, time)
if not ply:Alive() then return end
local rag = ply:GetNWEntity('player_ragdoll')
if not IsValid(rag) or not ply:Alive() then return end
local walkTime = 1 -- rag:SetFlexWeight(5,0)
local eyeangs = ply:EyeAngles()
local head = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_Head1")))
-- Rolling functionality for ragdolls
-- Roll left (A key) - but not while diving
if ply:KeyDown(IN_MOVELEFT) and not timer.Exists("StunTime" .. ply:EntIndex()) and not timer.Exists("Epilepsy" .. ply:EntIndex()) and not ply.Otrub and not (rag.isDiving or false) and not ply.brokenupperspine then
local torso = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_Spine")))
local pelvis = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_Pelvis")))
if IsValid(torso) and IsValid(pelvis) then
-- Get the ragdoll's up vector to determine which way is "up"
local ragUp = torso:GetAngles():Up()
-- Only roll if the ragdoll is somewhat upright (not already on its side)
local upDot = ragUp:Dot(Vector(0,0,1))
-- Get world-aligned roll direction (left is -Y in Source)
local rollDirection = Vector(0, -1, 0)
-- Reduced force and torque values
local rollForce = 40
local rollTorque = 80
-- Apply downward force on the "high" side to initiate roll
local sideOffset = torso:GetPos() + Vector(0, -20, 0) -- Left side
torso:ApplyForceOffset(Vector(0, 0, -rollForce*2), sideOffset)
-- Apply gentle torque for rotation assistance
torso:ApplyTorqueCenter(Vector(rollTorque, 0, 0))
pelvis:ApplyTorqueCenter(Vector(rollTorque, 0, 0))
-- Apply main rolling force to the body center
torso:ApplyForceCenter(rollDirection * rollForce)
pelvis:ApplyForceCenter(rollDirection * rollForce)
end
end
-- Roll right (D key) - but not while diving
if ply:KeyDown(IN_MOVERIGHT) and not timer.Exists("StunTime" .. ply:EntIndex()) and not timer.Exists("Epilepsy" .. ply:EntIndex()) and not ply.Otrub and not (rag.isDiving or false) and not ply.brokenupperspine then
local torso = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_Spine")))
local pelvis = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_Pelvis")))
if IsValid(torso) and IsValid(pelvis) then
-- Get the ragdoll's up vector to determine which way is "up"
local ragUp = torso:GetAngles():Up()
-- Only roll if the ragdoll is somewhat upright (not already on its side)
local upDot = ragUp:Dot(Vector(0,0,1))
-- Get world-aligned roll direction (right is +Y in Source)
local rollDirection = Vector(0, 1, 0)
-- Reduced force and torque values
local rollForce = 40
local rollTorque = 80
-- Apply downward force on the "high" side to initiate roll
local sideOffset = torso:GetPos() + Vector(0, 20, 0) -- Right side
torso:ApplyForceOffset(Vector(0, 0, -rollForce*2), sideOffset)
-- Apply gentle torque for rotation assistance
torso:ApplyTorqueCenter(Vector(-rollTorque, 0, 0))
pelvis:ApplyTorqueCenter(Vector(-rollTorque, 0, 0))
-- Apply main rolling force to the body center
torso:ApplyForceCenter(rollDirection * rollForce)
pelvis:ApplyForceCenter(rollDirection * rollForce)
end
end
if ply:KeyDown(IN_ATTACK) and not timer.Exists("StunTime" .. ply:EntIndex()) and not timer.Exists("Epilepsy" .. ply:EntIndex()) and not ply.Otrub and not ply.brokenupperspine then
local pos = ply:EyePos()
pos[3] = head:GetPos()[3]
if not ply.FakeShooting then
local phys = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_L_Hand")))
local ang = ply:EyeAngles()
ang:RotateAroundAxis(eyeangs:Forward(), 90)
ang:RotateAroundAxis(eyeangs:Right(), 75)
-- Apply shock responsiveness factor
local responsivenessFactor = rag.responsivenessFactor or 1
local shadowparams = {
secondstoarrive = 0.4 / responsivenessFactor,
pos = head:GetPos() + eyeangs:Forward() * 50 + eyeangs:Right() * -5,
angle = ang,
maxangular = 670 * responsivenessFactor,
maxangulardamp = 600,
maxspeeddamp = 50,
maxspeed = 500 * responsivenessFactor,
teleportdistance = 0,
deltatime = 0.01,
}
phys:Wake()
phys:ComputeShadowControl(shadowparams)
end
end
-- Check if weapon is automatic using the Automatic table
if ply.curweapon and Automatic and Automatic[ply.curweapon] then
-- Debug: Print automatic weapon detection
if ply.curweapon == "wep_mann_hmcd_akm" then
if GetConVar("developer"):GetInt() > 0 then
print("[RADS DEBUG] AKM detected as AUTOMATIC, using KeyDown for continuous fire")
end
end
if ply:KeyDown(IN_ATTACK) then if ply.FakeShooting then
if _G.FireShot then
_G.FireShot(ply.wep)
else
if GetConVar("developer"):GetInt() > 0 then
print("[RADS] ERROR: FireShot function not available globally at line 1898!")
end
end
end end
else
-- Debug: Print semi-automatic weapon detection
if ply.curweapon == "wep_mann_hmcd_akm" then
if GetConVar("developer"):GetInt() > 0 then
print("[RADS DEBUG] AKM detected as SEMI-AUTO, using KeyPressed for single shots")
print("[RADS DEBUG] Automatic table value for AKM:", Automatic and Automatic["wep_mann_hmcd_akm"] or "nil")
end
end
if ply:KeyPressed(IN_ATTACK) then if ply.FakeShooting then
if _G.FireShot then
_G.FireShot(ply.wep)
else
if GetConVar("developer"):GetInt() > 0 then
print("[RADS] ERROR: FireShot function not available globally at line 1900!")
end
end
end end
end
if ply:KeyDown(IN_JUMP) and (table.Count(constraint.FindConstraints(ply:GetNWEntity("player_ragdoll"), 'Rope')) > 0 or ((rag.IsWeld or 0) > 0)) and (ply.lastuntietry or 0) < CurTime() and not timer.Exists("StunTime" .. ply:EntIndex()) and not timer.Exists("Epilepsy" .. ply:EntIndex()) and not ply.Otrub and not ply.brokenupperspine then -- if(ply:KeyDown(IN_MOVERIGHT)) and !timer.Exists("StunTime"..ply:EntIndex()) and !timer.Exists("Epilepsy"..ply:EntIndex()) and not ply.Otrub then -- local pos = ply:EyePos() -- pos[3] = head:GetPos()[3] -- local phys = rag:GetPhysicsObjectNum( rag:TranslateBoneToPhysBone(rag:LookupBone( "ValveBiped.Bip01_R_Calf" )) ) -- local ang=ply:EyeAngles() -- ang:RotateAroundAxis(eyeangs:Forward(),90) -- ang:RotateAroundAxis(eyeangs:Right(),80) -- local shadowparams = { -- secondstoarrive=0.4, -- pos=head:GetPos()+eyeangs:Forward()*55+eyeangs:Right()*2, -- angle=ang, -- maxangular=670, -- maxangulardamp=600, -- maxspeeddamp=50, -- maxspeed=500, -- teleportdistance=0, -- deltatime=0.01, -- } -- phys:Wake() -- phys:ComputeShadowControl(shadowparams) -- end -- if(ply:KeyDown(IN_MOVELEFT)) and !timer.Exists("StunTime"..ply:EntIndex()) and !timer.Exists("Epilepsy"..ply:EntIndex()) and not ply.Otrub then -- local pos = ply:EyePos() -- pos[3] = head:GetPos()[3] -- local phys = rag:GetPhysicsObjectNum( rag:TranslateBoneToPhysBone(rag:LookupBone( "ValveBiped.Bip01_L_Calf" )) ) -- local ang=ply:EyeAngles() -- ang:RotateAroundAxis(eyeangs:Forward(),90) -- ang:RotateAroundAxis(eyeangs:Right(),80) -- local shadowparams = { -- secondstoarrive=0.4, -- pos=head:GetPos()+eyeangs:Forward()*55+eyeangs:Right()*2, -- angle=ang, -- maxangular=670, -- maxangulardamp=600, -- maxspeeddamp=50, -- maxspeed=500, -- teleportdistance=0, -- deltatime=0.01, -- } -- phys:Wake() -- phys:ComputeShadowControl(shadowparams) -- end
ply.lastuntietry = CurTime() + 1
rag.IsWeld = math.max((rag.IsWeld or 0) - 0.1, 0)
local RopeCount = table.Count(constraint.FindConstraints(ply:GetNWEntity("player_ragdoll"), 'Rope'))
Ropes = constraint.FindConstraints(ply:GetNWEntity("player_ragdoll"), 'Rope')
Try = math.random(1, 10 * RopeCount)
local phys = rag:GetPhysicsObjectNum(1)
local speed = 200
-- Apply shock responsiveness factor
local responsivenessFactor = rag.responsivenessFactor or 1
local shadowparams = {
secondstoarrive = 0.05 / responsivenessFactor,
pos = phys:GetPos() + phys:GetAngles():Forward() * 20,
angle = phys:GetAngles(),
maxangulardamp = 30,
maxspeeddamp = 30,
maxangular = 90 * responsivenessFactor,
maxspeed = speed * responsivenessFactor,
teleportdistance = 0,
deltatime = 0.01,
}
phys:Wake()
phys:ComputeShadowControl(shadowparams)
if Try > (7 * RopeCount) or ((rag.IsWeld or 0) > 0) then
if RopeCount > 1 or (rag.IsWeld or 0 > 0) then
if RopeCount > 1 then ply:ChatPrint("Left: " .. RopeCount - 1) end
if (rag.IsWeld or 0) > 0 then ply:ChatPrint("All that's left is to knock off the nails: " .. tostring(math.ceil(rag.IsWeld))) end
else
ply:ChatPrint("You've come untied")
end
Ropes[1].Constraint:Remove()
rag:EmitSound("restains.wav", 90, 50, 0.5, CHAN_AUTO)
end
end
if ply:KeyDown(IN_USE) and not timer.Exists("StunTime" .. ply:EntIndex()) and not timer.Exists("Epilepsy" .. ply:EntIndex()) and not ply.Otrub and not ply.brokenupperspine then
-- Mark that player is intentionally raising upper body to prevent neck breaking
ply.raisingUpperBody = true
local phys = head
local angs = ply:EyeAngles()
angs:RotateAroundAxis(angs:Forward(), 90)
-- Apply shock responsiveness factor
local responsivenessFactor = rag.responsivenessFactor or 1
-- Check if ragdoll is airborne or has high velocity
local ragVelocity = rag:GetVelocity():Length()
local isAirborne = ragVelocity > 100 -- Threshold for high velocity/airborne
local shadowparams
if isAirborne then
-- Use current settings for mid-air/high velocity
shadowparams = {
secondstoarrive = 0.45 / responsivenessFactor, -- Increased from 0.5 for smoother movement
pos = head:GetPos() + Vector(0, 0, 20 / math.Clamp(rag:GetVelocity():Length() / 300, 1, 12)),
angle = angs,
maxangulardamp = 20, -- Increased from 10 for more damping
maxspeeddamp = 20, -- Increased from 10 for more damping
maxangular = 360 * responsivenessFactor, -- Reduced from 370 to prevent neck stress
maxspeed = 39 * responsivenessFactor, -- Reduced from 40 for gentler movement
teleportdistance = 0,
deltatime = deltatime,
}
else
-- Use new settings for grounded/low velocity
shadowparams = {
secondstoarrive = 0.15 / responsivenessFactor, -- Increased from 0.5 for smoother movement
pos = head:GetPos() + Vector(0, 0, 20 / math.Clamp(rag:GetVelocity():Length() / 300, 1, 12)),
angle = angs,
maxangulardamp = 35, -- Increased from 10 for more damping
maxspeeddamp = 25, -- Increased from 10 for more damping
maxangular = 450 * responsivenessFactor, -- Reduced from 370 to prevent neck stress
maxspeed = 45 * responsivenessFactor, -- Reduced from 40 for gentler movement
teleportdistance = 0,
deltatime = deltatime,
}
end
head:Wake()
head:ComputeShadowControl(shadowparams)
else
-- Clear the flag when not holding E
if ply.raisingUpperBody then
ply.raisingUpperBody = false
end
end
if ply:KeyDown(IN_ATTACK2) and not timer.Exists("StunTime" .. ply:EntIndex()) and not timer.Exists("Epilepsy" .. ply:EntIndex()) and not ply.Otrub and not ply.brokenupperspine then
local physa = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_R_Hand")))
local phys = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_L_Hand"))) --rhand
local ang = ply:EyeAngles()
ang:RotateAroundAxis(eyeangs:Forward(), 90)
ang:RotateAroundAxis(eyeangs:Right(), 75)
local pos = ply:EyePos()
pos[3] = head:GetPos()[3]
-- Apply shock responsiveness factor
local responsivenessFactor = rag.responsivenessFactor or 1
local shadowparams = {
secondstoarrive = 0.4 / responsivenessFactor,
pos = head:GetPos() + eyeangs:Forward() * 50 + eyeangs:Right() * 15,
angle = ang,
maxangular = 670 * responsivenessFactor,
maxangulardamp = 600,
maxspeeddamp = 50,
maxspeed = 500 * responsivenessFactor,
teleportdistance = 0,
deltatime = 0.01,
}
physa:Wake()
if not ply.suiciding or TwoHandedOrNo[ply.curweapon] then
if TwoHandedOrNo[ply.curweapon] and IsValid(ply.wep) then
local ang = ply:EyeAngles()
ang:RotateAroundAxis(ang:Forward(), 90)
ang:RotateAroundAxis(ang:Up(), 20)
ang:RotateAroundAxis(ang:Right(), 10)
shadowparams.angle = ang
local wepPhys = ply.wep:GetPhysicsObject()
if IsValid(wepPhys) then
wepPhys:ComputeShadowControl(shadowparams)
end
shadowparams.pos = shadowparams.pos
phys:ComputeShadowControl(shadowparams)
shadowparams.pos = shadowparams.pos + eyeangs:Forward() * -50 + eyeangs:Right() * -15
physa:ComputeShadowControl(shadowparams)
elseif IsValid(ply.wep) and IsValid(ply.wep:GetPhysicsObject()) then
-- One-handed weapon (pistol) aiming with counter-force to prevent excessive movement
ang:RotateAroundAxis(ply:EyeAngles():Forward(), 90)
ang:RotateAroundAxis(ply:EyeAngles():Up(), 110)
ang:RotateAroundAxis(eyeangs:Right(), -30)
shadowparams.angle = ang
shadowparams.pos = shadowparams.pos + eyeangs:Right() * -15
-- Apply stronger constraint for pistols to prevent going beyond hand
local pistolShadowParams = {
secondstoarrive = 0.2 / responsivenessFactor, -- Faster response
pos = physa:GetPos() + eyeangs:Forward() * 8 + eyeangs:Right() * -5, -- Closer to hand
angle = ang,
maxangular = 400 * responsivenessFactor, -- Reduced angular movement
maxangulardamp = 800, -- Higher damping
maxspeeddamp = 100, -- Higher speed damping
maxspeed = 200 * responsivenessFactor, -- Reduced max speed
teleportdistance = 0,
deltatime = 0.01,
}
local wepPhys = ply.wep:GetPhysicsObject()
if IsValid(wepPhys) then
wepPhys:ComputeShadowControl(pistolShadowParams)
end
physa:ComputeShadowControl(shadowparams)
else
physa:ComputeShadowControl(shadowparams)
end
else
if ply.FakeShooting and IsValid(ply.wep) then
shadowparams.maxspeed = 500
shadowparams.maxangular = 500
shadowparams.pos = head:GetPos() - ply.wep:GetAngles():Forward() * 12
local wepPhys = ply.wep:GetPhysicsObject()
if IsValid(wepPhys) then
shadowparams.angle = wepPhys:GetAngles()
wepPhys:ComputeShadowControl(shadowparams)
end
physa:ComputeShadowControl(shadowparams)
end
end
end
if ply:KeyDown(IN_SPEED) and not timer.Exists("StunTime" .. ply:EntIndex()) and not timer.Exists("Epilepsy" .. ply:EntIndex()) and not ply.Otrub and not ply.brokenupperspine then
local bone = rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_L_Hand"))
local phys = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_L_Hand")))
if not IsValid(rag.ZacConsLH) and (not rag.ZacNextGrLH or rag.ZacNextGrLH <= CurTime()) then -- if !TwoHandedOrNo[ply.curweapon] then -- local shadowparams = { -- secondstoarrive=0.5, -- pos=head:GetPos(), -- angle=angs, -- maxangulardamp=10, -- maxspeeddamp=10, -- maxangular=370, -- maxspeed=1120, -- teleportdistance=0, -- deltatime=deltatime, -- } -- phys:Wake() -- phys:ComputeShadowControl(shadowparams)
rag.ZacNextGrLH = CurTime() + 0.1
for i = 1, 3 do
local offset = phys:GetAngles():Up() * -5
if i == 2 then offset = phys:GetAngles():Right() * 5 end
if i == 3 then offset = phys:GetAngles():Right() * -5 end
local traceinfo = {
start = phys:GetPos(),
endpos = phys:GetPos() + offset,
filter = rag,
output = trace,
}
local trace = util.TraceLine(traceinfo)
if trace.Hit and not trace.HitSky then
local cons = constraint.Weld(rag, trace.Entity, bone, trace.PhysicsBone, GetConVar('rads_lefthandlimit'):GetInt(), false, false)
if IsValid(cons) then
rag.ZacConsLH = cons
local pos = rag.ZacConsLH:GetPos() -- rag:EmitSound("physics/rubber/rubber_tire_strain3.wav", 100, 100, 1) -- if IsValid(rag.ZacConsLH) then
net.Start("CapturePositionLH")
net.WriteVector(pos)
net.Send(ply)
-- NEW: Hand grappling finger manipulation
local gripAngle = Angle(-25, 0, 0)
for i = 0, 4 do -- 5 fingers
for j = 1, 3 do -- 3 joints per finger
local fingerBone = "ValveBiped.Bip01_L_Finger" .. i .. j
if rag:LookupBone(fingerBone) then
rag:ManipulateBoneAngles(rag:LookupBone(fingerBone), gripAngle)
end
end
end
-- Send grappling icon message
net.Start("showiconleft")
net.Send(ply)
if trace.Entity:IsPlayer() and GetConVar('rads_fallwhengrabbed'):GetBool() then -- end
rads(trace.Entity)
end
end
break
end
end
end
else
if IsValid(rag.ZacConsLH) then
rag.ZacConsLH:Remove()
rag.ZacConsLH = nil
-- NEW: Reset finger angles when releasing grip
local zeroAng = Angle(0, 0, 0)
for i = 0, 4 do -- 5 fingers
for j = 1, 3 do -- 3 joints per finger
local fingerBone = "ValveBiped.Bip01_L_Finger" .. i .. j
if rag:LookupBone(fingerBone) then
rag:ManipulateBoneAngles(rag:LookupBone(fingerBone), zeroAng)
end
end
end
-- Hide grappling icon
net.Start("hideiconleft")
net.Send(ply)
end
end
if ply:KeyDown(IN_WALK) and not timer.Exists("StunTime" .. ply:EntIndex()) and not timer.Exists("Epilepsy" .. ply:EntIndex()) and not ply.Otrub and not ply.brokenupperspine then -- end
local bone = rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_R_Hand"))
local phys = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_R_Hand")))
if not IsValid(rag.ZacConsRH) and (not rag.ZacNextGrRH or rag.ZacNextGrRH <= CurTime()) then
rag.ZacNextGrRH = CurTime() + 0.1
for i = 1, 3 do
local offset = phys:GetAngles():Up() * 5
if i == 2 then offset = phys:GetAngles():Right() * 5 end
if i == 3 then offset = phys:GetAngles():Right() * -5 end
local traceinfo = {
start = phys:GetPos(),
endpos = phys:GetPos() + offset,
filter = rag,
output = trace,
}
local trace = util.TraceLine(traceinfo)
if trace.Hit and not trace.HitSky then
local cons = constraint.Weld(rag, trace.Entity, bone, trace.PhysicsBone, GetConVar('rads_righthandlimit'):GetInt(), false, false)
if IsValid(cons) then
rag.ZacConsRH = cons
local pos = rag.ZacConsRH:GetPos() -- rag:EmitSound("physics/rubber/rubber_tire_strain3.wav", 100, 100, 1) -- if IsValid(rag.ZacConsRH) then
net.Start("CapturePositionRH")
net.WriteVector(pos)
net.Send(ply)
-- NEW: Hand grappling finger manipulation for right hand
local gripAngle = Angle(25, 0, 0) -- Different angle for right hand to prevent "snapped" appearance
for i = 0, 4 do -- 5 fingers
for j = 1, 3 do -- 3 joints per finger
local fingerBone = "ValveBiped.Bip01_R_Finger" .. i .. j
if rag:LookupBone(fingerBone) then
rag:ManipulateBoneAngles(rag:LookupBone(fingerBone), gripAngle)
end
end
end
-- Send grappling icon message
net.Start("showiconright")
net.Send(ply)
if trace.Entity:IsPlayer() and GetConVar('rads_fallwhengrabbed'):GetBool() then -- end
rads(trace.Entity)
end
end
break
end
end
end
else
if IsValid(rag.ZacConsRH) then
rag.ZacConsRH:Remove()
rag.ZacConsRH = nil
-- NEW: Reset finger angles when releasing grip
local zeroAng = Angle(0, 0, 0)
for i = 0, 4 do -- 5 fingers
for j = 1, 3 do -- 3 joints per finger
local fingerBone = "ValveBiped.Bip01_R_Finger" .. i .. j
if rag:LookupBone(fingerBone) then
rag:ManipulateBoneAngles(rag:LookupBone(fingerBone), zeroAng)
end
end
end
-- Hide grappling icon
net.Start("hideiconright")
net.Send(ply)
end
end
if (ply:KeyDown(IN_FORWARD) and IsValid(rag.ZacConsLH)) and not timer.Exists("StunTime" .. ply:EntIndex()) and not timer.Exists("Epilepsy" .. ply:EntIndex()) and not ply.Otrub and not ply.brokenupperspine then
local phys = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_Spine")))
local lh = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_L_Hand")))
local angs = ply:EyeAngles()
angs:RotateAroundAxis(angs:Forward(), 90)
angs:RotateAroundAxis(angs:Up(), 90)
local speed = GetConVar('rads_pullupspeed'):GetInt()
if rag.ZacConsLH.Ent2:GetVelocity():LengthSqr() < 1000 then
-- Apply shock responsiveness factor
local responsivenessFactor = rag.responsivenessFactor or 1
local shadowparams = {
secondstoarrive = 0.5 / responsivenessFactor,
pos = lh:GetPos(),
angle = phys:GetAngles(),
maxangulardamp = 10,
maxspeeddamp = 10,
maxangular = 50 * responsivenessFactor,
maxspeed = speed * responsivenessFactor,
teleportdistance = 0,
deltatime = deltatime,
}
phys:Wake()
phys:ComputeShadowControl(shadowparams)
end
end
if (ply:KeyDown(IN_FORWARD) and IsValid(rag.ZacConsRH)) and not timer.Exists("StunTime" .. ply:EntIndex()) and not timer.Exists("Epilepsy" .. ply:EntIndex()) and not ply.Otrub and not ply.brokenupperspine then
local phys = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_Spine")))
local rh = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_R_Hand")))
local angs = ply:EyeAngles()
angs:RotateAroundAxis(angs:Forward(), 90)
angs:RotateAroundAxis(angs:Up(), 90)
local speed = GetConVar('rads_pullupspeed'):GetInt()
if rag.ZacConsRH.Ent2:GetVelocity():LengthSqr() < 1000 then
-- Apply shock responsiveness factor
local responsivenessFactor = rag.responsivenessFactor or 1
local shadowparams = {
secondstoarrive = 0.5 / responsivenessFactor,
pos = rh:GetPos(),
angle = phys:GetAngles(),
maxangulardamp = 10,
maxspeeddamp = 10,
maxangular = 50 * responsivenessFactor,
maxspeed = speed * responsivenessFactor,
teleportdistance = 0,
deltatime = deltatime,
}
phys:Wake()
phys:ComputeShadowControl(shadowparams)
end
end
if (ply:KeyDown(IN_BACK) and IsValid(rag.ZacConsLH)) and not timer.Exists("StunTime" .. ply:EntIndex()) and not timer.Exists("Epilepsy" .. ply:EntIndex()) and not ply.Otrub and not ply.brokenupperspine then
local phys = rag:GetPhysicsObjectNum(1)
local chst = rag:GetPhysicsObjectNum(0)
local angs = ply:EyeAngles()
angs:RotateAroundAxis(angs:Forward(), 90)
angs:RotateAroundAxis(angs:Up(), 90)
local speed = 30
if rag.ZacConsLH.Ent2:GetVelocity():LengthSqr() < 1000 then
-- Apply shock responsiveness factor
local responsivenessFactor = rag.responsivenessFactor or 1
local shadowparams = {
secondstoarrive = 0.5 / responsivenessFactor,
pos = chst:GetPos(),
angle = phys:GetAngles(),
maxangulardamp = 10,
maxspeeddamp = 10,
maxangular = 50 * responsivenessFactor,
maxspeed = speed * responsivenessFactor,
teleportdistance = 0,
deltatime = deltatime,
}
phys:Wake()
phys:ComputeShadowControl(shadowparams)
end
end
if (ply:KeyDown(IN_BACK) and IsValid(rag.ZacConsRH)) and not timer.Exists("StunTime" .. ply:EntIndex()) and not timer.Exists("Epilepsy" .. ply:EntIndex()) and not ply.Otrub and not ply.brokenupperspine then
local phys = rag:GetPhysicsObjectNum(1)
local chst = rag:GetPhysicsObjectNum(0)
local angs = ply:EyeAngles()
angs:RotateAroundAxis(angs:Forward(), 90)
angs:RotateAroundAxis(angs:Up(), 90)
local speed = 30
if rag.ZacConsRH.Ent2:GetVelocity():LengthSqr() < 1000 then
-- Apply shock responsiveness factor
local responsivenessFactor = rag.responsivenessFactor or 1
local shadowparams = {
secondstoarrive = 0.5 / responsivenessFactor,
pos = chst:GetPos(),
angle = phys:GetAngles(),
maxangulardamp = 10,
maxspeeddamp = 10,
maxangular = 50 * responsivenessFactor,
maxspeed = speed * responsivenessFactor,
teleportdistance = 0,
deltatime = deltatime,
}
phys:Wake()
phys:ComputeShadowControl(shadowparams)
end
end
-- Getting up logic
local head = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_Head1")))
if head and IsValid(head) then
-- REMOVED: Leg restriction code - players can now get up regardless of leg condition
local ragdollVelocity = rag:GetVelocity():Length()
local MaxUp = 400 -- Hardcoded from rads_maxupspeed
local UpSpeed = 400 -- Hardcoded from rads_upspeed
local wakeTime = 2.5 -- Hardcoded from rads_waketime
-- Prevent get up if traveling at high speed (500-600 units/second)
if ragdollVelocity >= 500 and ply.gettingUp then
ply.gettingUp = false
ply.upValue = 0
ply.lastGetUpAttempt = CurTime()
ply:ChatPrint("You're moving too fast to get up safely!")
return
end
-- Height-based automatic get up for airborne ragdolls that are already getting up
if ply.gettingUp then
-- Check if ragdoll is airborne by tracing downward
local traceData = {
start = rag:GetPos(),
endpos = rag:GetPos() + Vector(0, 0, -200), -- Trace 200 units down
filter = rag
}
local trace = util.TraceLine(traceData)
local heightAboveGround = rag:GetPos().z - trace.HitPos.z
-- If ragdoll is high enough above ground (120+ units) and already getting up, auto-complete get up
if heightAboveGround >= 120 then
rads(ply)
ply.gettingUp = false
ply.upValue = 0
ply.lastGetUpAttempt = CurTime()
return -- Exit early since we completed the get up
end
end
-- Improved interruption logic - less sensitive to damage
if ragdollVelocity > 300 then -- Increased threshold from 200 to 300
ply.gettingUp = false
ply.upValue = 0
ply.lastGetUpAttempt = CurTime()
-- Remove takingDamage interruption entirely
-- REMOVED: Automatic get up triggering based on time and velocity
-- elseif not ply.gettingUp and CurTime() - ply.lastGetUpAttempt >= wakeTime and not ply.brokenspine and not ply.Otrub then
-- -- Only start getting up if ragdoll is relatively still and enough time has passed
-- if ragdollVelocity < 60 then -- Reduced threshold from 80 to 60 for faster response
-- ply.gettingUp = true
-- ply.upValue = 0
-- end
end
-- KEEP: Manual get up physics (triggered by rads_ragdolize command)
if ply.gettingUp then
-- Check for failure conditions during get up process
local shouldFailGetUp = false
local failureReason = ""
-- Check if player becomes unconscious during get up
if ply.Otrub then
shouldFailGetUp = true
failureReason = "You lost consciousness while trying to get up."
end
-- Check if player gets concussion during get up
if ply.concussionActive then
shouldFailGetUp = true
failureReason = "The concussion makes it impossible to get up."
end
-- If failure conditions are met, interrupt the get up process
if shouldFailGetUp then
ply.gettingUp = false
ply.upValue = 0
ply.lastGetUpAttempt = CurTime()
ply:ChatPrint(failureReason)
return -- Exit early to prevent further get up physics
end
if ply.upValue < MaxUp then
ply.upValue = math.Approach(ply.upValue, MaxUp, FrameTime() * UpSpeed)
-- Enhanced getting up physics - stronger and more coordinated
local spine = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_Spine")))
local pelvis = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_Pelvis")))
local chest = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(rag:LookupBone("ValveBiped.Bip01_Spine2")))
-- Apply stronger force to multiple body parts for better body raising
if IsValid(head) then
head:ApplyForceCenter(Vector(0, 0, 1) * ply.upValue * 0.5) -- Increased from 0.4 to 0.5
end
if IsValid(chest) then
chest:ApplyForceCenter(Vector(0, 0, 1) * ply.upValue * 0.6) -- New chest force for better torso lifting
end
if IsValid(spine) then
spine:ApplyForceCenter(Vector(0, 0, 1) * ply.upValue * 0.5) -- Increased from 0.4 to 0.5
end
if IsValid(pelvis) then
pelvis:ApplyForceCenter(Vector(0, 0, 1) * ply.upValue * 0.3) -- Increased from 0.2 to 0.3
end
-- Add slight forward momentum to help with getting up motion
local forward = rag:GetForward()
if IsValid(pelvis) then
pelvis:ApplyForceCenter(forward * ply.upValue * 0.1)
end
else
-- Ragdoll is fully up, restore player
rads(ply)
ply.gettingUp = false
ply.upValue = 0
ply.lastGetUpAttempt = CurTime()
end
end
end
end)
util.AddNetworkString('CapturePositionRH')
util.AddNetworkString('CapturePositionLH')
hook.Add("Player Think", "Pulse", function(ply, curTime)
if not ply.pulse and ply:Alive() then ply.pulse = 70 end
local rag = ply:GetNWEntity('player_ragdoll')
local lastPulseChange = ply.lastPulseChange or 0
if curTime - lastPulseChange >= 2 then
local change = math.random(-7, 7)
if ply.pulse >= 130 then
ply.pulse = math.max(ply.pulse - math.random(3, 5), 0)
ply:SetNWBool("radshighpulse", true)
elseif ply.pulse <= 50 and ply:Alive() then
ply.pulse = math.min(ply.pulse + math.random(10, 20), 100)
ply:SetNWBool("radshighpulse", false)
end
if not ply:Alive() then ply.pulse = 0 end
if ply.pulse >= 55 and ply.pulse <= 129 then ply:SetNWBool("radshighpulse", false) end
ply.pulse = ply.pulse + change
rag.pulse = ply.pulse
-- FIXED: Network the pulse value to clients
ply:SetNWInt("PlayerPulse", ply.pulse)
ply.lastPulseChange = curTime
end
end)
-- Utility function to apply heavy fall effects (sound, organ damage, pain)
local function RADS_HeavyFallEffect(ent, impactPos)
if not IsValid(ent) then return end
-- Play random heavy fall sound
local sounds = {"fallheavy1.wav", "fallheavy2.wav", "fallheavy3.wav", "fallheavy4.wav"}
local snd = sounds[math.random(1, #sounds)]
ent:EmitSound(snd, 80, 100, 1, CHAN_AUTO)
-- Get the player (for ragdoll, get owner)
local ply = ent
if ent:IsRagdoll() and ent:GetNWEntity("owner") and ent:GetNWEntity("owner"):IsPlayer() then
ply = ent:GetNWEntity("owner")
end
if not IsValid(ply) or not ply:IsPlayer() then return end
if not ply.Organs then return end
-- Damage all inner organs except brain and arteries, but including skull
local ignore_organs = {
brain = true,
artery = true,
radial_artery_l = true,
radial_artery_r = true,
femoral_artery_l = true,
femoral_artery_r = true,
popliteal_artery_l = true,
popliteal_artery_r = true
}
for organ, _ in pairs(ply.Organs) do
if not ignore_organs[organ] then
-- Damage: set to 0 or reduce by a large amount (here, set to 0 for dramatic effect)
if organ == "skull" then
ply.Organs[organ] = math.max(ply.Organs[organ] - 15, 0) -- skull: not instant break
else
ply.Organs[organ] = math.max(ply.Organs[organ] - 999, 0)
end
end
end
-- Add moderate pain (reduced from 800)
ply.pain = (ply.pain or 0) + 160
end
-- Utility function for extreme fall: smash all organs, kill, loud sound
local function RADS_DeathFallEffect(ent, impactPos)
if not IsValid(ent) then return end
-- Play random heavy fall sound, louder
local sounds = {"fallheavy1.wav", "fallheavy2.wav", "fallheavy3.wav", "fallheavy4.wav"}
local snd = sounds[math.random(1, #sounds)]
ent:EmitSound(snd, 120, 100, 1, CHAN_AUTO)
-- Get the player (for ragdoll, get owner)
local ply = ent
if ent:IsRagdoll() and ent:GetNWEntity("owner") and ent:GetNWEntity("owner"):IsPlayer() then
ply = ent:GetNWEntity("owner")
end
if not IsValid(ply) or not ply:IsPlayer() then return end
if not ply.Organs then return end
-- Smash all organs
for organ, _ in pairs(ply.Organs) do
ply.Organs[organ] = 0
end
ply.pain = (ply.pain or 0) + 400
-- Kill the player if possible
if ply:Alive() then ply:Kill() end
end
-- Extend OnPlayerHitGround for heavy and death fall logic
hook.Add("OnPlayerHitGround", "RADS_HeavyFallImpact", function(ply, a, b, speed)
-- Increased thresholds for more realistic fall damage
local velocityThreshold = GetConVar("rads_fallonspeedlimit_threshold"):GetInt()
if speed >= velocityThreshold then -- Use ConVar for death fall threshold
local impactPos = ply:GetPos()
RADS_DeathFallEffect(ply, impactPos)
elseif speed >= 800 then -- Increased from 500 for heavy fall
local impactPos = ply:GetPos()
RADS_HeavyFallEffect(ply, impactPos)
end
-- Velocity-based pain for broken/dislocated legs on ground impact
if speed >= 200 then -- Only apply pain for significant impacts
local hasLegInjury = false
local painMultiplier = 1.0
-- Check for broken legs (higher pain multiplier)
if ply:GetNWBool("RADS_LeftLegBroken") or ply:GetNWBool("RADS_RightLegBroken") then
hasLegInjury = true
painMultiplier = painMultiplier + 2.0 -- 3x pain for broken legs
end
-- Check for dislocated legs (moderate pain multiplier)
if ply:GetNWBool("RADS_LeftLegDislocated") or ply:GetNWBool("RADS_RightLegDislocated") then
hasLegInjury = true
painMultiplier = painMultiplier + 1.0 -- 2x pain for dislocated legs
end
-- Apply velocity-based pain if leg injuries are present
if hasLegInjury then
-- Calculate pain based on impact velocity
-- Speed ranges from 200 (minimum) to 1200+ (maximum)
local velocityFactor = math.Clamp((speed - 200) / 1000, 0, 1) -- Normalize to 0-1 range
local basePain = 5 + (velocityFactor * 25) -- 5-30 base pain
local finalPain = basePain * painMultiplier
-- Apply realistic pain system for impact on injured legs
local damageIntensity = finalPain
-- Check for severe impact pain (instant pain threshold)
if finalPain >= 40 or speed >= 800 then
-- Severe impact: instant pain + pain debt
local instantPain = finalPain * 0.6 -- 60% instant for severe impact
local painDebt = finalPain * 0.4 -- 40% debt
ply.pain = (ply.pain or 0) + instantPain
ply.painDebt = (ply.painDebt or 0) + painDebt
ply.lastDamageTime = CurTime()
ply.damageIntensity = (ply.damageIntensity or 0) + damageIntensity
ply:ChatPrint("The impact on your injured legs causes agonizing pain!")
elseif finalPain >= 20 then
-- Moderate impact: balanced pain
local instantPain = finalPain * 0.3 -- 30% instant
local painDebt = finalPain * 0.7 -- 70% debt
ply.pain = (ply.pain or 0) + instantPain
ply.painDebt = (ply.painDebt or 0) + painDebt
ply.lastDamageTime = CurTime()
ply.damageIntensity = (ply.damageIntensity or 0) + damageIntensity
ply:ChatPrint("Landing hard on your injured legs hurts terribly.")
elseif finalPain >= 10 then
-- Minor impact: mostly pain debt
local instantPain = finalPain * 0.15 -- 15% instant
local painDebt = finalPain * 0.85 -- 85% debt
ply.pain = (ply.pain or 0) + instantPain
ply.painDebt = (ply.painDebt or 0) + painDebt
ply.lastDamageTime = CurTime()
ply.damageIntensity = (ply.damageIntensity or 0) + damageIntensity
ply:ChatPrint("The impact aggravates your leg injuries.")
else
-- Light impact: almost all pain debt
local instantPain = finalPain * 0.1 -- 10% instant
local painDebt = finalPain * 0.9 -- 90% debt
ply.pain = (ply.pain or 0) + instantPain
ply.painDebt = (ply.painDebt or 0) + painDebt
ply.lastDamageTime = CurTime()
ply.damageIntensity = (ply.damageIntensity or 0) + damageIntensity
ply:ChatPrint("Your injured legs ache from the impact.")
end
end
end
end)
-- Add PhysicsCollide callback for ragdolls to detect heavy and death ground impacts
hook.Add("OnEntityCreated", "RADS_RagdollHeavyFall", function(ent)
if not ent:IsRagdoll() then return end
-- Only add once
if ent._radsHeavyFallPhysicsCollide then return end
ent._radsHeavyFallPhysicsCollide = true
ent:AddCallback("PhysicsCollide", function(ragdoll, data)
-- Check for diving landing (any ground impact while diving)
if ragdoll.isDiving and data.HitNormal.z > 0.5 then
-- End diving state when ragdoll hits ground
ragdoll.isDiving = false
end
-- Only care about ground impacts (normal.z > 0.7)
local now = CurTime()
-- Increased thresholds for ragdoll impacts
if data.Speed >= 1300 and data.HitNormal.z > 0.7 then -- Increased from 950
if not ragdoll._lastDeathFallTime or now - ragdoll._lastDeathFallTime > 1 then
ragdoll._lastDeathFallTime = now
RADS_DeathFallEffect(ragdoll, data.HitPos)
end
elseif data.Speed >= 750 and data.HitNormal.z > 0.7 then -- Increased from 450
if not ragdoll._lastHeavyFallTime or now - ragdoll._lastHeavyFallTime > 1 then
ragdoll._lastHeavyFallTime = now
RADS_HeavyFallEffect(ragdoll, data.HitPos)
end
end
end)
end)
-- Utility: Apply realistic body relaxation (gradual rigor mortis release)
function ApplyRigorMortis(rag)
if not IsValid(rag) then return end
-- Initial pose: arms and legs extended, head tilted back (mimicking immediate post-mortem state)
local poseBones = {
{"ValveBiped.Bip01_L_UpperArm", Angle(0, 0, -80)},
{"ValveBiped.Bip01_R_UpperArm", Angle(0, 0, 80)},
{"ValveBiped.Bip01_L_Forearm", Angle(0, 0, -40)},
{"ValveBiped.Bip01_R_Forearm", Angle(0, 0, 40)},
{"ValveBiped.Bip01_L_Thigh", Angle(0, 0, -30)},
{"ValveBiped.Bip01_R_Thigh", Angle(0, 0, 30)},
{"ValveBiped.Bip01_L_Calf", Angle(0, 0, 0)},
{"ValveBiped.Bip01_R_Calf", Angle(0, 0, 0)},
{"ValveBiped.Bip01_Head1", Angle(-60, 0, 0)}
}
-- Apply initial pose
for _, v in ipairs(poseBones) do
local bone = rag:LookupBone(v[1])
if bone then
rag:ManipulateBoneAngles(bone, v[2])
end
end
-- Create strong initial welds (rigor mortis)
local pelvis = rag:LookupBone("ValveBiped.Bip01_Pelvis")
rag._rigorWelds = {}
rag._rigorStrength = {} -- Store strength values separately
rag._rigorStartTime = CurTime()
local function createGradualWeld(boneName, initialStrength)
local bone = rag:LookupBone(boneName)
if bone and pelvis then
local phys1 = rag:TranslateBoneToPhysBone(bone)
local phys2 = rag:TranslateBoneToPhysBone(pelvis)
if phys1 and phys2 and phys1 ~= phys2 then
-- Create weld with initial strength
local cons = constraint.Weld(rag, rag, phys1, phys2, initialStrength, true, false)
if cons then
table.insert(rag._rigorWelds, cons)
rag._rigorStrength[cons] = initialStrength -- Store strength separately
end
end
end
end
-- Create initial welds (much reduced strength for less stiffness)
createGradualWeld("ValveBiped.Bip01_L_UpperArm", 2500)
createGradualWeld("ValveBiped.Bip01_R_UpperArm", 2500)
createGradualWeld("ValveBiped.Bip01_L_Thigh", 2500)
createGradualWeld("ValveBiped.Bip01_R_Thigh", 2500)
-- Less stiff rigor mortis: shorter stiff period, faster relaxation
local stiffDuration = math.Rand(1.5, 3) -- Reduced initial stiffness period
local relaxDuration = math.Rand(4, 8) -- Faster relaxation period
local totalDuration = stiffDuration + relaxDuration
-- Keep initial strong welds for stiff period, then relax quickly
timer.Simple(stiffDuration, function()
if not IsValid(rag) then return end
-- Quick relaxation steps over the relaxDuration
local relaxationSteps = 8
local stepInterval = relaxDuration / relaxationSteps
for step = 1, relaxationSteps do
timer.Simple(step * stepInterval, function()
if not IsValid(rag) then return end
-- Rapidly reduce weld strength
if rag._rigorWelds and rag._rigorStrength then
for _, cons in ipairs(rag._rigorWelds) do
if IsValid(cons) and rag._rigorStrength[cons] then
-- Sharp drop in strength for realistic relaxation
local progress = step / relaxationSteps
local initialStrength = rag._rigorStrength[cons]
local remainingStrength = initialStrength * math.pow(1 - progress, 2) -- Exponential decay
-- Update weld strength with faster decay
cons:SetTable({
forcelimit = math.max(50, remainingStrength),
torquelimit = math.max(25, remainingStrength * 0.3)
})
end
end
end
-- Faster bone relaxation with natural variation
local boneProgress = math.pow(step / relaxationSteps, 1.5) -- Accelerated return
for _, v in ipairs(poseBones) do
local bone = rag:LookupBone(v[1])
if bone then
local targetAngle = Angle(
math.Rand(-5, 5), -- Natural variation
math.Rand(-5, 5),
math.Rand(-5, 5)
)
local currentAngle = rag:GetManipulateBoneAngles(bone)
local relaxedAngle = LerpAngle(boneProgress, currentAngle, targetAngle)
rag:ManipulateBoneAngles(bone, relaxedAngle)
end
end
end)
end
end)
-- Final cleanup after relaxation is complete
timer.Simple(totalDuration + 0.5, function()
if not IsValid(rag) then return end
-- Remove all remaining welds
if rag._rigorWelds then
for _, cons in ipairs(rag._rigorWelds) do
if IsValid(cons) then cons:Remove() end
end
rag._rigorWelds = nil
end
if rag._rigorStrength then
rag._rigorStrength = nil
end
-- Ensure final relaxed pose with natural variation
for _, v in ipairs(poseBones) do
local bone = rag:LookupBone(v[1])
if bone then
rag:ManipulateBoneAngles(bone, Angle(
math.Rand(-2, 2),
math.Rand(-2, 2),
math.Rand(-2, 2)
))
end
end
end)
end
end
if CLIENT then
surface.CreateFont("ScFont", {
font = "Coolvetica",
size = 24,
weight = 1100,
outline = false
})
lastView = nil
local organData = {}
-- Enhanced debug display system
surface.CreateFont("RADS_DebugTitle", {
font = "Coolvetica",
size = 18,
weight = 600,
outline = true
})
surface.CreateFont("RADS_DebugText", {
font = "Coolvetica",
size = 14,
weight = 500,
outline = true
})
surface.CreateFont("RADS_DebugValue", {
font = "Coolvetica",
size = 13,
weight = 400,
outline = true
})
local function GetHealthColor(percentage)
if percentage >= 75 then return Color(46, 204, 113) end -- Green
if percentage >= 50 then return Color(241, 196, 15) end -- Yellow
if percentage >= 25 then return Color(230, 126, 34) end -- Orange
return Color(231, 76, 60) -- Red
end
local function DrawEnhancedDebugDisplay()
local ply = LocalPlayer()
if not IsValid(ply) then return end
local scrW, scrH = ScrW(), ScrH()
local panelW, panelH = 280, 380
local x, y = scrW - panelW - 20, 20
-- Semi-transparent background
draw.RoundedBox(8, x, y, panelW, panelH, Color(0, 0, 0, 180))
draw.RoundedBox(8, x + 2, y + 2, panelW - 4, panelH - 4, Color(20, 20, 20, 120))
-- Title
draw.SimpleText("TRAUMA DEBUG", "RADS_DebugTitle", x + panelW/2, y + 15, Color(255, 255, 255), TEXT_ALIGN_CENTER)
local yOffset = 45
local lineHeight = 18
-- Player Health
local health = ply:Health()
local healthColor = GetHealthColor(health)
draw.SimpleText("Health:", "RADS_DebugText", x + 15, y + yOffset, Color(200, 200, 200))
draw.SimpleText(health .. "/100", "RADS_DebugValue", x + panelW - 15, y + yOffset, healthColor, TEXT_ALIGN_RIGHT)
yOffset = yOffset + lineHeight
-- Blood Level (using RADS_ClientData or networked variable as fallback)
local blood = (RADS_ClientData and RADS_ClientData.blood) or ply:GetNWInt("PlayerBlood", 5000)
local bloodPercentage = math.Clamp((blood / 5000) * 100, 0, 100)
local bloodColor = GetHealthColor(bloodPercentage)
draw.SimpleText("Blood:", "RADS_DebugText", x + 15, y + yOffset, Color(200, 200, 200))
draw.SimpleText(blood .. "ml", "RADS_DebugValue", x + panelW - 15, y + yOffset, bloodColor, TEXT_ALIGN_RIGHT)
yOffset = yOffset + lineHeight
-- Pain Level (using RADS_ClientData)
local pain = (RADS_ClientData and RADS_ClientData.pain) or 0
local painLimitConVar = GetConVar("rads_painlimit")
local painLimit = painLimitConVar and painLimitConVar:GetInt() or 190
local painColor = pain > 150 and Color(231, 76, 60) or (pain > 100 and Color(230, 126, 34) or Color(46, 204, 113))
draw.SimpleText("Pain:", "RADS_DebugText", x + 15, y + yOffset, Color(200, 200, 200))
draw.SimpleText(pain .. "/" .. painLimit, "RADS_DebugValue", x + panelW - 15, y + yOffset, painColor, TEXT_ALIGN_RIGHT)
yOffset = yOffset + lineHeight
-- Shock Level (using correct networked variable)
local shock = ply:GetNWFloat("RADS_Shock", 0)
local shockColor = shock > 60 and Color(231, 76, 60) or (shock > 30 and Color(230, 126, 34) or Color(46, 204, 113))
draw.SimpleText("Shock:", "RADS_DebugText", x + 15, y + yOffset, Color(200, 200, 200))
draw.SimpleText(math.floor(shock) .. "/100", "RADS_DebugValue", x + panelW - 15, y + yOffset, shockColor, TEXT_ALIGN_RIGHT)
yOffset = yOffset + lineHeight
-- Pulse Rate (using correct networked variable)
local pulse = ply:GetNWInt("PlayerPulse", 70)
local pulseColor = (pulse > 120 or pulse < 50) and Color(231, 76, 60) or Color(46, 204, 113)
draw.SimpleText("Pulse:", "RADS_DebugText", x + 15, y + yOffset, Color(200, 200, 200))
draw.SimpleText(pulse .. " BPM", "RADS_DebugValue", x + panelW - 15, y + yOffset, pulseColor, TEXT_ALIGN_RIGHT)
yOffset = yOffset + lineHeight
-- Consciousness Status (using correct networked variable)
local isUnconscious = ply:GetNWBool("Otrub", false)
local consciousnessColor = isUnconscious and Color(231, 76, 60) or Color(46, 204, 113)
draw.SimpleText("Consciousness:", "RADS_DebugText", x + 15, y + yOffset, Color(200, 200, 200))
draw.SimpleText(isUnconscious and "Unconscious" or "Conscious", "RADS_DebugValue", x + panelW - 15, y + yOffset, consciousnessColor, TEXT_ALIGN_RIGHT)
yOffset = yOffset + lineHeight
-- Breathing Status (calculated from lung health)
local leftLungHealth = (ply.Organs and ply.Organs['left_lung']) or 5
local rightLungHealth = (ply.Organs and ply.Organs['right_lung']) or 5
local isBreathing = leftLungHealth > 0 or rightLungHealth > 0
local breathColor = isBreathing and Color(46, 204, 113) or Color(231, 76, 60)
draw.SimpleText("Breathing:", "RADS_DebugText", x + 15, y + yOffset, Color(200, 200, 200))
draw.SimpleText(isBreathing and "Normal" or "Not Breathing", "RADS_DebugValue", x + panelW - 15, y + yOffset, breathColor, TEXT_ALIGN_RIGHT)
yOffset = yOffset + lineHeight + 10
-- Organ Health Section
draw.SimpleText("ORGAN HEALTH", "RADS_DebugText", x + 15, y + yOffset, Color(255, 255, 255))
yOffset = yOffset + lineHeight + 5
-- Organ maximum health values
local maxHealth = {
["liver"] = 15,
["stomach"] = 15,
["intestines"] = 30,
["heart"] = 9,
["left_lung"] = 5,
["right_lung"] = 5
}
-- Display organ data if available
local organsToDisplay = (RADS_ClientData and RADS_ClientData.organs) or (organData and organData.organs)
if organsToDisplay and next(organsToDisplay) ~= nil then
for organ, health in pairs(organsToDisplay) do
local maxHP = maxHealth[organ] or 10
local organPercentage = math.Clamp((health / maxHP) * 100, 0, 100)
local organColor = GetHealthColor(organPercentage)
local organName = string.gsub(organ, "_", " ")
organName = string.upper(string.sub(organName, 1, 1)) .. string.sub(organName, 2)
draw.SimpleText(organName .. ":", "RADS_DebugValue", x + 25, y + yOffset, Color(180, 180, 180))
draw.SimpleText(math.floor(organPercentage) .. "%", "RADS_DebugValue", x + panelW - 15, y + yOffset, organColor, TEXT_ALIGN_RIGHT)
yOffset = yOffset + 15
end
else
draw.SimpleText("No organ data available", "RADS_DebugValue", x + 25, y + yOffset, Color(150, 150, 150))
end
end
net.Receive("info_org", function()
organData = net.ReadTable()
end)
-- Timer to request organism_info data periodically
timer.Create("RADS_RequestOrganismInfo", 1, 0, function()
local drawOrgConVar = GetConVar('rads_draworg')
if drawOrgConVar and drawOrgConVar:GetBool() and IsValid(LocalPlayer()) then
net.Start("request_organism_info")
net.SendToServer()
end
end)
hook.Add("HUDPaint", "DrawEnhancedDebugDisplay", function()
local drawOrgConVar = GetConVar('rads_draworg')
if drawOrgConVar and drawOrgConVar:GetBool() then
DrawEnhancedDebugDisplay()
end
end)
CreateClientConVar("rads_draworg", "0", {FCVAR_ARCHIVE, ""})
CreateClientConVar("rads_thirdperson", "0", {FCVAR_ARCHIVE, "Thirdperson"})
-- Server-side admin-only viewmode command
if SERVER then
CreateConVar("rads_viewmode", "1", {FCVAR_ARCHIVE, FCVAR_NOTIFY}, "0 means view from eyes but with movement,1 means from eyes without movement - Admin only")
concommand.Add("rads_viewmode", function(ply, cmd, args)
if not IsValid(ply) or not ply:IsAdmin() then
if IsValid(ply) then
ply:ChatPrint("[RADS] Only admins can change the view mode for all players.")
end
return
end
if not args[1] then
ply:ChatPrint("[RADS] Usage: rads_viewmode <0|1>")
return
end
local newMode = tonumber(args[1])
if newMode ~= 0 and newMode ~= 1 then
ply:ChatPrint("[RADS] Invalid mode. Use 0 or 1.")
return
end
GetConVar("rads_viewmode"):SetInt(newMode)
local modeText = newMode == 0 and "free camera movement" or "locked to ragdoll eyes"
for _, p in pairs(player.GetAll()) do
p:ChatPrint("[RADS] Admin " .. ply:Name() .. " changed view mode to: " .. modeText)
end
end)
else
-- Client-side: Get viewmode from server convar
function GetViewMode()
local serverCvar = GetConVar("rads_viewmode")
return serverCvar and serverCvar:GetInt() or 1
end
end
CreateClientConVar("rads_mouthscale", "6", {FCVAR_ARCHIVE, "Mouth Scale while speaking"})
CreateClientConVar("rads_viewfov", "100", {FCVAR_ARCHIVE, "Fov in ragdoll."})
CreateClientConVar("rads_disablelerp", "0", {FCVAR_ARCHIVE, ""})
CreateClientConVar("rads_drawmotd", "1", {FCVAR_ARCHIVE, "Draw motd"})
-- Key binding for handcuff removal
hook.Add("PlayerButtonDown", "RADS_HandcuffRemoval", function(ply, button)
if button == KEY_H and input.IsKeyDown(KEY_LALT) then
RunConsoleCommand("rads_remove_handcuffs")
end
end)
-- Simple ragdoll color sync from server
net.Receive(
"ragplayercolor",
function()
local ent = net.ReadEntity()
local col = net.ReadVector()
if IsValid(ent) and isvector(col) then
function ent:GetPlayerColor()
return col
end
end
end
)
local helmEnt
net.Receive("nodraw_helmet", function() helmEnt = net.ReadEntity() end)
if IsValid(helmEnt) then
helmEnt:SetNoDraw(true)
helmEnt:SetColor(Color(0, 0, 0, 0))
helmEnt:SetRenderMode(RENDERMODE_TRANSCOLOR)
end
hook.Add("Think", "mouthanim", function()
for i, ply in pairs(player.GetAll()) do
local ent = IsValid(ply:GetNWEntity("player_ragdoll")) and ply:GetNWEntity("player_ragdoll") or ply
local flexes = {ent:GetFlexIDByName("jaw_drop"), ent:GetFlexIDByName("left_part"), ent:GetFlexIDByName("right_part"), ent:GetFlexIDByName("left_mouth_drop"), ent:GetFlexIDByName("right_mouth_drop")}
local volume = ply:VoiceVolume()
local weight = math.Clamp(volume * 75, 0, 1.5) or 0 -- Further reduced from 150 to 75, and max from 3 to 1.5
if ply:IsSpeaking() then
for k, v in pairs(flexes) do
ent:SetFlexWeight(v, weight)
end
end
end
end)
local oldFakeOrigin = Vector(0, 0, 0)
local oldFakeAng = Angle(0, 0, 0)
local oldOrigin = Vector(0, 0, 0)
local oldAng = Angle(0, 0, 0)
local lerping = 1
local MyLerp = 0
function HomigradCam(ply, vec, ang, fov, znear, zfar)
local eye = ply:GetAttachment(ply:LookupAttachment("eyes"))
local org = eye.Pos
local ang1 = LerpAngle(0, ply:EyeAngles(), eye.Ang)
local org1 = eye.Pos + eye.Ang:Up() * 2 + eye.Ang:Forward() * 2.5
if ply:GetNWBool("radsfa") == true and IsValid(ply:GetNWEntity("player_ragdoll")) then
local attach = ply:GetNWEntity("player_ragdoll"):GetAttachment(1)
local headBoneIndex = ply:GetNWEntity("player_ragdoll"):LookupBone("ValveBiped.Bip01_Head1")
-- Only hide head if not already exploded by gore system
if not ply:GetNWEntity("player_ragdoll").goreHeadExploded then
ply:GetNWEntity("player_ragdoll"):ManipulateBoneScale(headBoneIndex, Vector(0, 0, 0))
end
lerping = Lerp(3 * FrameTime(), lerping, 0)
local view = {
origin = LerpVector(lerping, attach.Pos, oldOrigin),
angles = LerpAngle(lerping, LerpAngle(0.35, ang1, attach.Ang), oldAng),
fov = fov,
drawviewer = true
}
oldFakeOrigin = view.origin
oldFakeAng = view.angles
return view
end
if ply:InVehicle() == true then
-- org = eye.Pos + eye.Ang:Forward() * 0.8
ang = eye.Ang
MyLerp = 1
ply:ManipulateBoneScale(ply:LookupBone("ValveBiped.Bip01_Head1"), vector_origin)
anglerp = LerpAngle(MyLerp, ang1, ang)
else
-- Restore head visibility when not in vehicle (unless head exploded)
local headBone = ply:LookupBone("ValveBiped.Bip01_Head1")
if headBone and not ply.headExploded then
ply:ManipulateBoneScale(headBone, Vector(1, 1, 1))
end
anglerp = LerpAngle(MyLerp / 2, ang1, sightAng or Angle(0, 0, 0))
end
lerping = Lerp(3 * FrameTime(), lerping, 1)
local view = {
origin = LerpVector(lerping, oldFakeOrigin, LerpVector(MyLerp, org1, org)),
angles = LerpAngle(lerping, oldFakeAng, LerpAngle(0.01, anglerp, ang1)),
fov = fov,
drawviewer = true,
-- znear = 0.2
}
oldOrigin = view.origin
oldAng = view.angles
return view
end
function RadsMM(ply, origin, angles, fov)
local rag = ply:GetNWEntity("player_ragdoll")
if IsValid(rag) then
local att = rag:GetAttachment(rag:LookupAttachment("eyes"))
if att then
local view = {}
local v = angles
if lastView == nil then
lastView = {
origin = att.Pos,
angles = att.Ang,
fov = fov
}
end
local lerpedAngles = LerpAngle(0.8, lastView.angles, att.Ang)
local lerpedang = LerpAngle(0.8, lastView.angles, v)
if GetViewMode() == 1 then
-- Mode 1: Camera locked to ragdoll eyes (original behavior)
view.origin = att.Pos
view.angles = lerpedAngles
else
-- Mode 0: Camera at ragdoll eyes but allows mouse movement
if GetConVar("rads_disablelerp"):GetInt() == 1 then
view.origin = att.Pos
view.angles = Angle(lerpedang.p, lerpedang.y, 0)
else
view.origin = att.Pos
view.angles = lerpedang
end
end
view.znear = 1
view.fov = fov
view.drawviewer = true
lastView = {
origin = view.origin,
angles = view.angles,
fov = fov
}
return view
end
end
end
net.Receive("REMOVECALC", function(ply) hook.Remove('CalcView', 'govnishe') end)
net.Receive('ADDCALC', function(ply)
-- hook.Add("CalcView", "govnishe", RadsMM)
-- hook.Add("CalcView", "govnishe", HomigradCam)
end)
hook.Add("CalcView", "RADS.ForceFirstPerson", function(ply, origin, angles, fov)
-- Handle ragdoll first-person view
if ply:GetNWBool("radsfa") == true and IsValid(ply:GetNWEntity("player_ragdoll")) then
local rag = ply:GetNWEntity("player_ragdoll")
local attachIndex = rag:LookupAttachment("eyes")
local att = attachIndex and rag:GetAttachment(attachIndex)
local camPos = att and att.Pos or rag:GetPos()
-- Clamp camera distance to ragdoll origin (prevents flying away)
if (camPos - rag:GetPos()):Length() > 50 then
camPos = rag:GetPos()
end
local finalAngles
if GetViewMode() == 0 then
-- Free camera movement - use player's current view angles
finalAngles = angles
else
-- Locked camera - use ragdoll's eye angles (default behavior)
finalAngles = att and att.Ang or rag:GetAngles()
end
-- Apply tinnitus screenshake for ragdolls
if (tinnitusRagdollActive and CurTime() < tinnitusRagdollEndTime) or tinnitusFadeOutActive then
local currentShakeStrength
if tinnitusFadeOutActive then
-- During fade-out, use fade multiplier to gradually reduce shake to 0
local elapsed = CurTime() - tinnitusFadeOutStartTime
local progress = math.Clamp(elapsed / tinnitusFadeOutDuration, 0, 1)
local fadeMultiplier = 1 - progress
currentShakeStrength = tinnitusRagdollShakeStrength * fadeMultiplier
-- Check if fade-out is complete
if progress >= 1 then
tinnitusFadeOutActive = false
tinnitusRagdollActive = false
end
else
-- During normal tinnitus, gradually reduce shake strength over the duration
local elapsed = CurTime() - tinnitusRagdollStartTime
local totalDuration = tinnitusRagdollEndTime - tinnitusRagdollStartTime
local progress = math.Clamp(elapsed / totalDuration, 0, 1)
currentShakeStrength = tinnitusRagdollShakeStrength * (1 - progress * 0.7) -- Reduce to 30% by the end
-- Check if duration ended, start fade-out
if CurTime() >= tinnitusRagdollEndTime then
tinnitusFadeOutActive = true
tinnitusFadeOutStartTime = CurTime()
end
end
-- Apply continuous shake with varying frequency (same as damage.lua)
local t = CurTime()
local tinnitusShake = Angle(
math.sin(t * 8) * currentShakeStrength,
math.cos(t * 8 * 0.8) * currentShakeStrength,
math.sin(t * 8 * 0.6) * currentShakeStrength * 0.5
)
finalAngles = finalAngles + tinnitusShake
end
return {
origin = camPos,
angles = finalAngles,
fov = GetConVar("rads_ragdoll_fov"):GetFloat(),
drawviewer = true
}
end
-- Handle death first-person view
local deathFirstPersonCvar = GetConVar("rads_death_firstperson")
if deathFirstPersonCvar and deathFirstPersonCvar:GetBool() and ply:GetNWBool("rads_dead_firstperson") and not ply:Alive() then
-- Keep the camera at the death position in first-person
return {
origin = origin,
angles = angles,
fov = fov,
drawviewer = false
}
end
return nil
end)
scrw, scrh = ScrW(), ScrH()
hook.Add("RenderScreenspaceEffects", "RADS.FFAFAPFPAP", function()
local ply = LocalPlayer()
local rag = ply:GetNWBool('radsfa')
local pulsehigh = ply:GetNWBool('radshighpulse')
local thirdPersonCvar = GetConVar("rads_thirdperson")
if rag and thirdPersonCvar and not thirdPersonCvar:GetBool() then end
if pulsehigh then
end
end)
local grtodown = Material("vgui/gradient-u")
local grtoup = Material("vgui/gradient-d")
local grtoright = Material("vgui/gradient-l")
local grtoleft = Material("vgui/gradient-r")
pain, painlosing, impulse = 0, 0, 0
net.Receive("info_pain", function()
pain = net.ReadFloat()
painlosing = net.ReadFloat()
end)
-- Tinnitus screenshake variables for ragdolls
local tinnitusRagdollActive = false
local tinnitusRagdollEndTime = 0
local tinnitusRagdollStartTime = 0
local tinnitusRagdollShakeStrength = 0
local tinnitusFadeOutActive = false
local tinnitusFadeOutStartTime = 0
local tinnitusFadeOutDuration = 3 -- Same as damage.lua
-- Network receiver for tinnitus screenshake state
net.Receive("RADS_TinnitusScreenshake", function()
local isActive = net.ReadBool()
local duration = net.ReadFloat()
local shakeStrength = net.ReadFloat()
if isActive then
-- Start tinnitus screenshake for ragdoll
tinnitusRagdollActive = true
tinnitusRagdollStartTime = CurTime()
tinnitusRagdollEndTime = CurTime() + duration
tinnitusRagdollShakeStrength = shakeStrength
tinnitusFadeOutActive = false
else
-- Stop tinnitus screenshake for ragdoll
tinnitusRagdollActive = false
tinnitusRagdollEndTime = 0
tinnitusRagdollStartTime = 0
tinnitusRagdollShakeStrength = 0
tinnitusFadeOutActive = false
end
end)
local ScrW, ScrH = ScrW, ScrH
local math_Clamp = math.Clamp
local k = 0
local k4 = 0
local time = 0
local icons = {}
net.Receive("CapturePositionLH", function()
local posLH = net.ReadVector()
table.insert(icons, {
pos = posLH,
time = CurTime()
})
end)
net.Receive("CapturePositionRH", function()
local posRH = net.ReadVector()
table.insert(icons, {
pos = posRH,
time = CurTime()
})
end)
hook.Add("HUDPaint", "DrawIcons", function()
if posLH ~= nil or posRH ~= nil then
for i, icon in ipairs(icons) do
surface.SetDrawColor(255, 255, 255)
surface.SetMaterial(Material("vgui/gmod_hand"))
surface.DrawTexturedRect(icon.pos.x, icon.pos.y, iconWidth, iconHeight)
if CurTime() - icon.time >= 5 then table.remove(icons, i) end
end
end
end)
local addmat_r = Material("CA/add_r")
local addmat_g = Material("CA/add_g")
local addmat_b = Material("CA/add_b")
local vgbm = Material("vgui/black")
local function DrawCA(rx, gx, bx, ry, gy, by)
render.UpdateScreenEffectTexture()
addmat_r:SetTexture("$basetexture", render.GetScreenEffectTexture())
addmat_g:SetTexture("$basetexture", render.GetScreenEffectTexture())
addmat_b:SetTexture("$basetexture", render.GetScreenEffectTexture())
render.SetMaterial(vgbm)
render.DrawScreenQuad()
render.SetMaterial(addmat_r)
render.DrawScreenQuadEx(-rx / 2, -ry / 2, ScrW() + rx, ScrH() + ry)
render.SetMaterial(addmat_g)
render.DrawScreenQuadEx(-gx / 2, -gy / 2, ScrW() + gx, ScrH() + gy)
render.SetMaterial(addmat_b)
render.DrawScreenQuadEx(-bx / 2, -by / 2, ScrW() + bx, ScrH() + by)
end
net.Receive("info_impulse", function() impulse = net.ReadFloat() * 50 end)
local k3 = 0
hook.Add("RenderScreenspaceEffects", "renderimpulse", function()
local cheapEffectsCvar = GetConVar("rads_cheapeffects")
local cheapEffects = cheapEffectsCvar and cheapEffectsCvar:GetInt() or 0
-- Skip chromatic aberration entirely if cheapeffects >= 2
if cheapEffects >= 2 then return end
k3 = math.Clamp(Lerp(0.01, k3, impulse), 0, 50)
-- Reduce intensity based on cheapeffects level
local intensity = cheapEffects >= 1 and 0.5 or 1.0
DrawCA(4 * k3 * intensity, 2 * k3 * intensity, 0, 2 * k3 * intensity, 1 * k3 * intensity, 0)
end)
net.Receive('RADS.CHATSAY', function()
chat.AddText(Color(255, 0, 221), " ") -- end
end)
-- REMOVED: Consciousness network receiver - migrated to damage.lua
-- Vision enhancement variables
local visionSharpness = 0
local accumulationBlur = 0
local smoothShakeStrength = 0
local smoothShakeSeed = math.random(1000)
local lastShakeTime = 0
local shakeDecay = 2.0
-- Accumulation blur variables (reduced intensity)
local blurAccumulation = 0
local maxBlurAccumulation = 0.3 -- Reduced from 0.8 to 0.3
local blurDecayRate = 0.03 -- Increased decay rate for faster recovery
-- Add vision enhancement CalcView hook with FOV control (separate from ragdoll camera)
hook.Add("CalcView", "RADS_VisionEnhancement", function(ply, pos, angles, fov)
-- Only apply to alive players, not ragdolled ones
if not ply:Alive() or ply:GetNWBool("radsfa") then return end
local shake = Angle(0, 0, 0)
-- Smooth screenshake based on adrenaline/stress
if smoothShakeStrength > 0.1 then
local t = CurTime() + smoothShakeSeed
-- Reduced frequency multipliers for smoother shake
shake = Angle(
math.sin(t * 0.8) * smoothShakeStrength * 0.5, -- Reduced from 2.5 to 0.8
math.cos(t * 0.6) * smoothShakeStrength * 0.3, -- Reduced from 2.2 to 0.6
math.sin(t * 0.4) * smoothShakeStrength * 0.15 -- Reduced from 1.8 to 0.4
)
-- Smoother decay with interpolation
smoothShakeStrength = Lerp(FrameTime() * shakeDecay * 0.5, smoothShakeStrength, 0) -- Added 0.5 multiplier for gentler decay
end
-- Trigger shake on damage or high stress (with reduced sensitivity)
local pulse = ply:GetNWInt("PlayerPulse", 70)
if pulse > 120 and CurTime() - lastShakeTime > 1.0 then -- Increased cooldown from 0.5 to 1.0
smoothShakeStrength = math.min(smoothShakeStrength + (pulse - 120) * 0.01, 1.0) -- Reduced from 0.02 to 0.01 and max from 2.0 to 1.0
lastShakeTime = CurTime()
end
-- Check if player is using a scoped weapon and is aiming
local weapon = ply:GetActiveWeapon()
local useScopedFOV = false
local scopedFOV = nil
if IsValid(weapon) and weapon.Scoped and weapon.ScopeFoV then
-- Check if weapon is currently aiming (using the GetAiming method)
if weapon.GetAiming and weapon:GetAiming() > 99 then
useScopedFOV = true
scopedFOV = weapon.ScopeFoV
end
end
-- Apply custom FOV for standing players, unless using a scoped weapon
local playerFovCvar = GetConVar("rads_player_fov")
local customFOV = useScopedFOV and scopedFOV or (playerFovCvar and playerFovCvar:GetFloat() or 90)
-- Always return a view table to ensure FOV is applied
return {
origin = pos,
angles = angles + shake,
fov = customFOV
}
end)
-- Optimized vision sharpening and accumulation blur effects with performance scaling
local lastVisionEffectUpdate = 0
local visionEffectInterval = 0.016 -- ~60fps base
hook.Add("RenderScreenspaceEffects", "RADS_VisionEffects", function()
local ply = LocalPlayer()
if not IsValid(ply) or not ply:Alive() then return end
-- Don't apply effects if ragdolled (to avoid interfering with ragdoll camera)
if ply:GetNWBool("radsfa") then return end
local cheapEffectsCvar = GetConVar("rads_cheapeffects")
local cheapEffects = cheapEffectsCvar and cheapEffectsCvar:GetInt() or 0
-- Skip all vision effects if cheapeffects >= 2
if cheapEffects >= 2 then return end
-- Throttle updates based on cheapeffects level
if cheapEffects >= 1 then
visionEffectInterval = 0.033 -- ~30fps for moderate performance
if CurTime() - lastVisionEffectUpdate < visionEffectInterval then return end
end
lastVisionEffectUpdate = CurTime()
local pulse = ply:GetNWInt("PlayerPulse", 70)
local adrenalineLevel = ply.adrenaline or 0
-- Vision sharpening based on adrenaline (reduced for cheapeffects)
if adrenalineLevel > 20 then
local targetSharpness = math.Clamp(adrenalineLevel / 100, 0, cheapEffects >= 1 and 0.4 or 0.8)
visionSharpness = Lerp(FrameTime() * 3, visionSharpness, targetSharpness)
else
visionSharpness = Lerp(FrameTime() * 2, visionSharpness, 0)
end
-- Apply vision sharpening (reduced intensity for cheapeffects)
if visionSharpness > 0.1 and cheapEffects < 1 then
local intensity = cheapEffects >= 1 and 0.5 or 1.0
local sharpenTab = {
["$pp_colour_addr"] = 0,
["$pp_colour_addg"] = 0,
["$pp_colour_addb"] = 0,
["$pp_colour_brightness"] = visionSharpness * 0.1 * intensity,
["$pp_colour_contrast"] = 1 + (visionSharpness * 0.3 * intensity),
["$pp_colour_colour"] = 1 + (visionSharpness * 0.2 * intensity),
["$pp_colour_mulr"] = 1,
["$pp_colour_mulg"] = 1,
["$pp_colour_mulb"] = 1
}
DrawColorModify(sharpenTab)
end
-- Skip blur effects entirely if cheapeffects >= 1
if cheapEffects >= 1 then return end
-- Accumulation blur based on stress/fatigue (only for full quality)
local stressFactor = 0
if pulse > 100 then
stressFactor = math.Clamp((pulse - 100) / 50, 0, 1)
end
if ply.pain and ply.pain > 50 then
stressFactor = stressFactor + math.Clamp(ply.pain / 200, 0, 0.5)
end
-- Accumulate blur over time when stressed (reduced accumulation rate)
if stressFactor > 0.2 then
blurAccumulation = math.min(blurAccumulation + (stressFactor * FrameTime() * 0.15), maxBlurAccumulation)
else
blurAccumulation = math.max(blurAccumulation - (FrameTime() * blurDecayRate), 0)
end
-- Apply accumulation blur (reduced intensity)
if blurAccumulation > 0.05 then
DrawMotionBlur(0.05, blurAccumulation * 0.6, 0.005)
end
end)
-- Trigger enhanced shake on damage
hook.Add("EntityTakeDamage", "RADS_VisionShakeOnDamage", function(target, dmginfo)
if target == LocalPlayer() and dmginfo:GetDamage() > 5 then
local shakeAmount = math.Clamp(dmginfo:GetDamage() / 20, 0.5, 3.0)
smoothShakeStrength = math.min(smoothShakeStrength + shakeAmount, 4.0)
lastShakeTime = CurTime()
end
end)
-- Reset pulse-related visual effects on player death
hook.Add("PostPlayerDeath", "RADS_ClearPulseEffects", function()
-- Reset vision enhancement variables
visionSharpness = 0
accumulationBlur = 0
smoothShakeStrength = 0
smoothShakeSeed = math.random(1000)
lastShakeTime = 0
-- Reset accumulation blur variables
blurAccumulation = 0
-- Reset impulse effects
impulse = 0
k3 = 0
-- Reset pain effects
pain = 0
painlosing = 0
-- NOTE: Consciousness effects are now handled in damage.lua
end)
-- Reset pulse-related visual effects on player spawn
hook.Add("PlayerSpawn", "RADS_ResetPulseEffects", function(ply)
if ply == LocalPlayer() then
-- Reset vision enhancement variables
visionSharpness = 0
accumulationBlur = 0
smoothShakeStrength = 0
smoothShakeSeed = math.random(1000)
lastShakeTime = 0
-- Reset accumulation blur variables
blurAccumulation = 0
-- Reset impulse effects
impulse = 0
k3 = 0
-- Reset pain effects
pain = 0
painlosing = 0
-- NOTE: Consciousness effects are now handled in damage.lua
end
end)
-- Bullseye damage redirection hook
hook.Add("EntityTakeDamage", "RADS_BullseyeDamageRedirect", function(target, dmginfo)
if IsValid(target) and target:GetClass() == "npc_bullseye" then
local owner = target:GetNWEntity("owner")
local ragdoll = target:GetNWEntity("ragdoll")
if IsValid(owner) and IsValid(ragdoll) then
-- Check if player is dead - if so, don't redirect damage
if owner:Health() <= 0 then
print("[RADS BULLSEYE] Player " .. owner:Nick() .. " is dead, blocking damage redirection")
-- Remove bullseye since player is dead
if IsValid(target) then
print("[RADS BULLSEYE] Removing bullseye for dead player: " .. owner:Nick())
target:Remove()
if IsValid(ragdoll) then
ragdoll.bullseye = nil
end
end
return true -- Block damage to bullseye
end
print("[RADS BULLSEYE] Redirecting damage from bullseye to player: " .. owner:Nick() .. " (Damage: " .. dmginfo:GetDamage() .. ")")
-- Create new damage info for the player
local newDmgInfo = DamageInfo()
newDmgInfo:SetDamage(dmginfo:GetDamage())
newDmgInfo:SetAttacker(dmginfo:GetAttacker())
newDmgInfo:SetInflictor(dmginfo:GetInflictor())
newDmgInfo:SetDamageType(dmginfo:GetDamageType())
newDmgInfo:SetDamagePosition(dmginfo:GetDamagePosition())
newDmgInfo:SetDamageForce(dmginfo:GetDamageForce())
newDmgInfo:SetReportedPosition(dmginfo:GetReportedPosition())
-- Apply damage to the player
owner:TakeDamageInfo(newDmgInfo)
-- Prevent the bullseye from taking damage
return true
else
print("[RADS BULLSEYE] Warning: Bullseye damaged but owner or ragdoll is invalid")
end
end
end)
-- Death check timer for bullseye cleanup
timer.Create("RADS_BullseyeDeathCheck", 1, 0, function()
for _, ragdoll in pairs(ents.FindByClass("prop_ragdoll")) do
if IsValid(ragdoll) and IsValid(ragdoll.bullseye) then
local owner = ragdoll:GetNWEntity("owner")
if IsValid(owner) and owner:Health() <= 0 then
print("[RADS BULLSEYE] Death check: Removing bullseye for dead player: " .. owner:Nick())
ragdoll.bullseye:Remove()
ragdoll.bullseye = nil
-- Set all NPCs to neutral towards this bullseye (cleanup)
for _, npc in pairs(ents.FindByClass("npc_*")) do
if IsValid(npc) and npc.AddEntityRelationship then
npc:AddEntityRelationship(ragdoll.bullseye, D_NU, 99)
end
end
end
end
end
end)
-- Clientside drowning sound handling
if CLIENT then
local drowningSound = nil
net.Receive("PlayDrowningSound", function()
if drowningSound then
drowningSound:Stop()
end
drowningSound = CreateSound(LocalPlayer(), "drowning.ogg")
if drowningSound then
drowningSound:SetSoundLevel(75)
drowningSound:ChangeVolume(GetConVar("rads_drowning_sound_volume"):GetFloat())
drowningSound:Play()
end
end)
net.Receive("StopDrowningSound", function()
if drowningSound then
drowningSound:Stop()
drowningSound = nil
end
end)
-- Clean up sound on player death/disconnect
hook.Add("PlayerDisconnected", "RADS_CleanupDrowningSound", function(ply)
if ply == LocalPlayer() and drowningSound then
drowningSound:Stop()
drowningSound = nil
end
end)
hook.Add("PostPlayerDeath", "RADS_CleanupDrowningSound", function(ply)
if ply == LocalPlayer() and drowningSound then
drowningSound:Stop()
drowningSound = nil
end
end)
end
end