Ballsocket created between bones 0 and 1 at local position 1.744202 -0.145233 1.399414
Ballsocket created between bones 1 and 2 at local position 3.987091 -0.436371 4.739258
Ballsocket created between bones 2 and 3 at local position 4.972076 -0.710571 8.365234
Ballsocket created between bones 3 and 4 at local position 6.177246 -1.216614 14.448242
Ballsocket created between bones 4 and 5 at local position 6.842438 -1.724304 20.492188
Ballsocket created between bones 5 and 6 at local position 6.035278 -1.957214 23.768555
Ballsocket created between bones 6 and 7 at local position 3.597656 -2.348572 25.971680
Ballsocket created between bones 4 and 8 at local position 6.398254 -0.689941 19.919922
Ballsocket created between bones 8 and 9 at local position 5.864868 3.168091 20.395508
Ballsocket created between bones 9 and 10 at local position 8.056122 9.730713 15.770508
Ballsocket created between bones 10 and 11 at local position 10.977295 16.462219 6.979492
Ballsocket created between bones 11 and 12 at local position 11.897644 20.681946 1.085938
Ballsocket created between bones 4 and 13 at local position 6.328430 -2.620117 19.765625
Ballsocket created between bones 13 and 14 at local position 5.516754 -6.456360 19.625977
Error: Invalid physics objects for bones 14 and/or 15
Error: Invalid physics objects for bones 15 and/or 16
Error: Invalid physics objects for bones 16 and/or 17
Error: Invalid physics objects for bones 0 and/or 18
Error: Invalid physics objects for bones 18 and/or 19
Error: Invalid physics objects for bones 19 and/or 20
Error: Invalid physics objects for bones 20 and/or 21
Error: Invalid physics objects for bones 0 and/or 22
Error: Invalid physics objects for bones 22 and/or 23
Error: Invalid physics objects for bones 23 and/or 24
Error: Invalid physics objects for bones 24 and/or 25
Error: Invalid physics objects for bones 16 and/or 26
Error: Invalid or identical bone positions for bones 26 and 27
Error: Invalid or identical bone positions for bones 27 and 28
Error: Invalid physics objects for bones 16 and/or 29
Error: Invalid or identical bone positions for bones 29 and 30
Error: Invalid or identical bone positions for bones 30 and 31
Error: Invalid physics objects for bones 16 and/or 32
Error: Invalid or identical bone positions for bones 32 and 33
Error: Invalid or identical bone positions for bones 33 and 34
Error: Invalid physics objects for bones 16 and/or 35
Error: Invalid or identical bone positions for bones 35 and 36
Error: Invalid or identical bone positions for bones 36 and 37
Error: Invalid physics objects for bones 16 and/or 38
Error: Invalid or identical bone positions for bones 38 and 39
Error: Invalid or identical bone positions for bones 39 and 40
Error: Invalid physics objects for bones 11 and/or 41
Error: Invalid or identical bone positions for bones 41 and 42
Error: Invalid or identical bone positions for bones 42 and 43
Error: Invalid physics objects for bones 11 and/or 44
Error: Invalid or identical bone positions for bones 44 and 45
Error: Invalid or identical bone positions for bones 45 and 46
Error: Invalid physics objects for bones 11 and/or 47
Error: Invalid or identical bone positions for bones 47 and 48
Error: Invalid or identical bone positions for bones 48 and 49
Error: Invalid physics objects for bones 11 and/or 50
Error: Invalid or identical bone positions for bones 50 and 51
Error: Invalid or identical bone positions for bones 51 and 52
Error: Invalid physics objects for bones 11 and/or 53
Error: Invalid or identical bone positions for bones 53 and 54
Error: Invalid or identical bone positions for bones 54 and 55
Error: Invalid physics objects for bones 14 and/or 56
Error: Invalid physics objects for bones 15 and/or 57
Error: Invalid physics objects for bones 10 and/or 58
Error: Invalid physics objects for bones 9 and/or 59
Error: Invalid physics objects for bones 14 and/or 60
Error: Invalid physics objects for bones 8 and/or 61
Error: Invalid physics objects for bones 10 and/or 62
Error: Invalid physics objects for bones 9 and/or 63
Error: Invalid physics objects for bones 14 and/or 64
Error: Invalid physics objects for bones 13 and/or 65
Error: Invalid physics objects for bones 15 and/or 66
Error: Invalid physics objects for bones 9 and/or 67
```lua
--[[
    Enhanced Ragdoll Physics Script for Garry's Mod
    Features:
    - Hitgroup-Based Force Application
    - Custom Ragdoll Models
    - Physics Constraints with Enhanced Validations
    - Environmental Interactions
]]
-- Table to store the last damage information for entities
local EntityLastDamage = {}
-- Define damage type constants
local DAMAGE_TYPE = {
    BULLET = DMG_BULLET,
    BLAST = DMG_BLAST,
    SLASH = DMG_SLASH,
    FIRE = DMG_BURN,
    CHEMICAL = DMG_ACID,
    EXPLOSION = DMG_BLAST_SURFACE,
    FALL = DMG_FALL,
    GENERIC = DMG_GENERIC,
}

-- Define hitgroup constants
local HITGROUP_HEAD = 0
local HITGROUP_CHEST = 1
local HITGROUP_STOMACH = 2
local HITGROUP_LEFTARM = 3
local HITGROUP_RIGHTARM = 4
local HITGROUP_LEFTLEG = 5
local HITGROUP_RIGHTLEG = 6
local HITGROUP_GENERIC = 7
-- Table to track created ragdolls to prevent duplicates
local CreatedRagdolls = {}
-- Configuration for Hitgroup-Based Force Application
local HITGROUP_FORCE_MULTIPLIERS = {
    [HITGROUP_HEAD] = {
        force = 1000,
        torque = 500
    },
    [HITGROUP_CHEST] = {
        force = 800,
        torque = 400
    },
    [HITGROUP_STOMACH] = {
        force = 600,
        torque = 300
    },
    [HITGROUP_LEFTARM] = {
        force = 500,
        torque = 250
    },
    [HITGROUP_RIGHTARM] = {
        force = 500,
        torque = 250
    },
    [HITGROUP_LEFTLEG] = {
        force = 400,
        torque = 200
    },
    [HITGROUP_RIGHTLEG] = {
        force = 400,
        torque = 200
    },
    -- Default for unspecified hitgroups
    [HITGROUP_GENERIC] = {
        force = 700,
        torque = 350
    },
}

-- Configuration for Custom Ragdoll Models
local CUSTOM_RAGDOLL_MODELS = {
    ["models/player/kleiner.mdl"] = "models/player/kleiner_ragdoll.mdl", -- Ensure this model exists
    ["models/player/male_01.mdl"] = "models/player/male_ragdoll.mdl", -- Ensure this model exists
}

-- Add more model mappings as needed
-- Hook to store the last damage information when an entity takes damage
hook.Add("EntityTakeDamage", "StoreEntityLastDamageType", function(target, dmginfo)
    if not IsValid(target) then return end
    if target:IsPlayer() or target:IsNPC() then
        local hitgroup = HITGROUP_GENERIC -- Default hitgroup
        -- Only attempt to get hitgroup for players
        if target:IsPlayer() and dmginfo.GetHitGroup and isfunction(dmginfo.GetHitGroup) then hitgroup = dmginfo:GetHitGroup() or HITGROUP_GENERIC end
        -- Store damage information
        EntityLastDamage[target:EntIndex()] = {
            dmgType = dmginfo:GetDamageType(),
            attacker = dmginfo:GetAttacker(),
            damagePos = dmginfo:GetDamagePosition() or target:GetPos(),
            hitgroup = hitgroup
        }
    end
end)

-- Utility function to retrieve a custom ragdoll model if mapped
local function GetCustomRagdollModel(originalModel)
    return CUSTOM_RAGDOLL_MODELS[originalModel] or originalModel
end

-- Utility function to determine force and torque based on hitgroup
local function GetForceByHitgroup(hitgroup)
    local config = HITGROUP_FORCE_MULTIPLIERS[hitgroup] or HITGROUP_FORCE_MULTIPLIERS[HITGROUP_GENERIC]
    return config.force, config.torque
end

-- Function to get the parent bone index for a given bone
local function GetParentBone(ent, boneIndex)
    if not IsValid(ent) then return -1 end
    return ent:GetBoneParent(boneIndex)
end

-- Function to create a realistic ragdoll with enhanced physics
local function CreateRealisticRagdoll(victim, position, angles, model, dmgInfo)
    if not IsValid(victim) then return end
    -- Use a custom ragdoll model if available
    local ragdollModel = GetCustomRagdollModel(model)
    local rag = ents.Create("prop_ragdoll")
    if not IsValid(rag) then
        print("Error: Failed to create ragdoll entity.")
        return
    end

    rag:SetModel(ragdollModel)
    rag:SetPos(position)
    rag:SetAngles(angles)
    rag:SetNWBool("IsCustomRagdoll", true)
    rag:Spawn()
    -- Optional: Set a unique name for the ragdoll
    rag:SetName("CustomRagdoll_" .. rag:EntIndex())
    -- Calculate the direction of force based on damage position
    local forceDir = Vector(0, 0, 1) -- Default upward force
    if victim:GetPos() ~= dmgInfo.damagePos then
        forceDir = (rag:GetPos() - dmgInfo.damagePos):GetNormalized()
    elseif IsValid(dmgInfo.attacker) then
        forceDir = (rag:GetPos() - dmgInfo.attacker:GetPos()):GetNormalized()
    end

    -- Determine force and torque magnitudes based on hitgroup
    local forceMagnitude, torqueMagnitude = GetForceByHitgroup(dmgInfo.hitgroup)
    -- Adjust force vector based on damage type
    local forceVector = Vector(0, 0, 0)
    if bit.band(dmgInfo.dmgType, DAMAGE_TYPE.EXPLOSION) ~= 0 then
        forceVector = forceDir * (forceMagnitude * 1.2) + Vector(0, 0, 500)
    elseif bit.band(dmgInfo.dmgType, DAMAGE_TYPE.BLAST) ~= 0 then
        forceVector = forceDir * forceMagnitude + Vector(0, 0, 400)
    elseif bit.band(dmgInfo.dmgType, DAMAGE_TYPE.BULLET) ~= 0 then
        forceVector = forceDir * forceMagnitude + Vector(0, 0, 450)
    elseif bit.band(dmgInfo.dmgType, DAMAGE_TYPE.FIRE) ~= 0 or bit.band(dmgInfo.dmgType, DAMAGE_TYPE.CHEMICAL) ~= 0 then
        forceVector = Vector(0, 0, forceMagnitude)
    elseif bit.band(dmgInfo.dmgType, DAMAGE_TYPE.SLASH) ~= 0 then
        forceVector = forceDir * forceMagnitude + Vector(0, 0, 350)
    else
        forceVector = Vector(0, 0, forceMagnitude)
    end

    -- Generate random torque based on torque magnitude
    local torqueVector = Vector(math.Rand(-torqueMagnitude, torqueMagnitude), math.Rand(-torqueMagnitude, torqueMagnitude), math.Rand(-torqueMagnitude, torqueMagnitude))
    -- Apply force and torque with a slight random delay for realism
    timer.Simple(0.05, function()
        if not IsValid(rag) then return end
        for i = 0, rag:GetPhysicsObjectCount() - 1 do
            local phys = rag:GetPhysicsObjectNum(i)
            if IsValid(phys) then
                -- Apply central force with slight variation
                local variation = Vector(math.Rand(-30, 30), math.Rand(-30, 30), math.Rand(-30, 30))
                phys:ApplyForceCenter(forceVector + variation)
                -- Apply torque for rotational movement
                phys:AddAngleVelocity(torqueVector)
                -- Adjust individual physics properties for more realism
                phys:SetMass(math.Clamp(phys:GetMass(), 5, 100)) -- Clamp mass to a realistic range
                phys:EnableGravity(true)
                phys:EnableDrag(true)
            end
        end
    end)

    -- Setup Physics Constraints for structural integrity
    timer.Simple(0.1, function()
        if not IsValid(rag) then return end
        local boneCount = rag:GetBoneCount()
        for i = 0, boneCount - 1 do
            local parentBone = GetParentBone(rag, i)
            -- Only create constraints if parentBone is valid (i.e., not -1) and within boneCount
            if parentBone >= 0 and parentBone < boneCount and i < boneCount then
                -- Ensure both parentBone and i are valid numbers within boneCount
                if type(parentBone) == "number" and type(i) == "number" then
                    -- Получаем позиции двух костей
                    local pos1 = rag:GetBonePosition(parentBone)
                    local pos2 = rag:GetBonePosition(i)
                    -- Проверка валидности позиций костей
                    if not pos1 or not pos2 or pos1 == pos2 then
                        print(string.format("Error: Invalid or identical bone positions for bones %d and %d", parentBone, i))
                        -- Переход к следующей итерации цикла
                        -- В Lua 5.1 нет оператора 'continue', поэтому используем вложенные условия
                    else
                        -- Получаем физические объекты костей
                        local phys1 = rag:GetPhysicsObjectNum(parentBone)
                        local phys2 = rag:GetPhysicsObjectNum(i)
                        if not (IsValid(phys1) and IsValid(phys2)) then
                            print(string.format("Error: Invalid physics objects for bones %d and/or %d", parentBone, i))
                            -- Переход к следующей итерации цикла
                        else
                            -- Вычисляем среднюю позицию между костями
                            local constraintPos = (pos1 + pos2) / 2
                            -- Преобразуем мировую позицию в локальную относительно ent1
                            local localPos = rag:WorldToLocal(constraintPos)
                            -- Создаем шарнир
                            local constraintEntity = constraint.Ballsocket(rag, -- Entity1
                                rag, -- Entity2 (та же сущность для констрейнта между костями)
                                parentBone, -- Bone1 (индекс родительской кости)
                                i, -- Bone2 (индекс текущей кости)
                                localPos, -- LocalPos (локальная позиция относительно ent1)
                                15, -- Forcelimit
                                0, -- Torquelimit
                                0)

                            -- nocollide
                            -- Проверка успешности создания констрейнта
                            if not IsValid(constraintEntity) then
                                print(string.format("Error: Failed to create Ballsocket between bones %d and %d", parentBone, i))
                            else
                                print(string.format("Ballsocket created between bones %d and %d at local position %s", parentBone, i, tostring(localPos)))
                            end
                        end
                    end
                else
                    print(string.format("Error: Invalid bone indices - parentBone: %s, bone: %s", tostring(parentBone), tostring(i)))
                end
            else
                if parentBone >= boneCount or i >= boneCount then print(string.format("Error: Bone index out of range - parentBone: %d, bone: %d | boneCount: %d", parentBone, i, boneCount)) end
            end
        end
    end)

    -- Handle Environmental Interactions
    -- Ensures ragdoll collides appropriately with the world and other entities
    rag:SetCollisionGroup(COLLISION_GROUP_PASSABLE_DOOR)
    rag:PhysicsInit(SOLID_VPHYSICS)
    rag:SetMoveType(MOVETYPE_VPHYSICS)
    rag:SetSolid(SOLID_VPHYSICS)
    -- Automatically remove the ragdoll after an extended period for performance
    timer.Simple(600, function() if IsValid(rag) then rag:Remove() end end)
end

-- Function to retrieve damage information with default values
local function GetDamageInfo(ent)
    return EntityLastDamage[ent:EntIndex()] or {
        dmgType = DAMAGE_TYPE.GENERIC,
        attacker = nil,
        damagePos = ent:GetPos(),
        hitgroup = HITGROUP_GENERIC
    }
end

-- Function to remove default ragdolls and replace them with custom ones
local function RemoveDefaultRagdoll(pos)
    local closestRagdoll = nil
    local minDistance = 100
    for _, ent in ipairs(ents.FindByClass("prop_ragdoll")) do
        if not ent:GetNWBool("IsCustomRagdoll", false) then
            local distance = ent:GetPos():Distance(pos)
            if distance < minDistance then
                closestRagdoll = ent
                minDistance = distance
            end
        end
    end

    if IsValid(closestRagdoll) then closestRagdoll:Remove() end
end

-- Hook to handle player deaths
hook.Add("PlayerDeath", "CreateRealisticCorpse_Player", function(victim, inflictor, attacker)
    if not IsValid(victim) then return end
    if CreatedRagdolls[victim:EntIndex()] then return end
    CreatedRagdolls[victim:EntIndex()] = true
    local model = victim:GetModel()
    local pos = victim:GetPos()
    local ang = victim:EyeAngles()
    local dmgInfo = GetDamageInfo(victim)
    CreateRealisticRagdoll(victim, pos, ang, model, dmgInfo)
    -- Remove the default ragdoll shortly after spawning the custom one
    timer.Simple(0.1, function()
        if not IsValid(victim) then return end
        RemoveDefaultRagdoll(pos)
    end)
end)

-- Hook to handle NPC deaths
hook.Add("OnNPCKilled", "CreateRealisticCorpse_NPC", function(npc, attacker, inflictor)
    if not IsValid(npc) then return end
    if CreatedRagdolls[npc:EntIndex()] then return end
    CreatedRagdolls[npc:EntIndex()] = true
    local model = npc:GetModel()
    local pos = npc:GetPos()
    local ang = npc:GetAngles()
    local dmgInfo = GetDamageInfo(npc)
    CreateRealisticRagdoll(npc, pos, ang, model, dmgInfo)
    -- Remove the default ragdoll shortly after spawning the custom one
    timer.Simple(0.1, function()
        if not IsValid(npc) then return end
        RemoveDefaultRagdoll(pos)
    end)
end)

-- Hook to clean up damage data when entities are removed
hook.Add("OnEntityRemoved", "CleanUpEntityDamageData", function(ent)
    if not IsValid(ent) then return end
    EntityLastDamage[ent:EntIndex()] = nil
    CreatedRagdolls[ent:EntIndex()] = nil
end)

-- Hook to clean up data when players disconnect
hook.Add("PlayerDisconnected", "CleanUpPlayerDamageData", function(ply)
    if not IsValid(ply) then return end
    EntityLastDamage[ply:EntIndex()] = nil
    CreatedRagdolls[ply:EntIndex()] = nil
end)
```