-- 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