From cb39c2944ec4d659dbf38d85b8f6760838e30961 Mon Sep 17 00:00:00 2001 From: LiddulBOFH <13317534+LiddulBOFH@users.noreply.github.com> Date: Sat, 28 Jan 2023 01:41:17 -0600 Subject: [PATCH 1/2] Player Damage Overhaul Decent overhaul in how player damage occurs - Actually functional hitbox system unlike the old system - Suit armor matters now Now, when an ACF bullet hits a player, a collection of hitboxes is checked with ray intersection to figure out what part of the body is hit, and then damage is calculated from there. If a player has any suit armor, 2 more hitboxes are checked (vest and helmet) and if they are hit first, then the bullet must "penetrate" them. This gives the player a possible chance to survive small caliber rounds. The body hitboxes include the head, torso, and legs. Arms were left out for less overhead, and would have required further checking for overpen, as right now the system will stop at the first part hit. The vest covers front and back, but leaves the sides of the torso vulnerable, and the helmet covers the top 1/3rd of the head. This system could also potentially pave the way for player hitboxes while in seats, but that requires work towards an "alias" entity that fits the playermodel that would get hit instead of the seat. Due to the nature of actual player bone hitboxes, it was far less reliable to check for them with traces, but maybe work can be done to use those some day. --- lua/acf/core/utilities/util_sv.lua | 129 +++++++++++++++++++++++++++++ lua/acf/damage/damage_sv.lua | 76 ++++------------- 2 files changed, 146 insertions(+), 59 deletions(-) diff --git a/lua/acf/core/utilities/util_sv.lua b/lua/acf/core/utilities/util_sv.lua index 2d43572e6..7e5337059 100644 --- a/lua/acf/core/utilities/util_sv.lua +++ b/lua/acf/core/utilities/util_sv.lua @@ -468,4 +468,133 @@ do -- Extra overlay text return Result end +end + +do -- Special squishy functions + local BoneList = { + head = {boneName = "ValveBiped.Bip01_Head1",group = "head",min = Vector(-6,-6,-4),max = Vector(8,4,4)}, + + spine = {boneName = "ValveBiped.Bip01_Spine",group = "chest",min = Vector(-6,-4,-9),max = Vector(18,10,9)}, + + lthigh = {boneName = "ValveBiped.Bip01_L_Thigh",group = "limb",min = Vector(0,-4,-4),max = Vector(18,4,4)}, + lcalf = {boneName = "ValveBiped.Bip01_L_Calf",group = "limb",min = Vector(0,-4,-4),max = Vector(18,4,4)}, + + rthigh = {boneName = "ValveBiped.Bip01_R_Thigh",group = "limb",min = Vector(0,-3,-3),max = Vector(18,3,3)}, + rcalf = {boneName = "ValveBiped.Bip01_R_Calf",group = "limb",min = Vector(0,-3,-3),max = Vector(18,3,3)}, + } + + local ArmorHitboxes = { -- only applied if the entity has armor greater than 0 + helmet = {boneName = "ValveBiped.Bip01_Head1",group = "helmet",min = Vector(4.5,-6.5,-4.5),max = Vector(8.5,4.5,4.5)}, + vest = {boneName = "ValveBiped.Bip01_Spine",group = "vest",min = Vector(-5,-5,-8),max = Vector(17,11,8)}, + } + + -- The goal of this is to provide a much sturdier way to get the part of a player that got hit with a bullet + -- This will ignore any bone manipulation too + function ACF.GetBestSquishyHitBox(Entity,RayStart,RayDir) + local Bones = {} + local CheckList = {} + + for k,v in pairs(BoneList) do + CheckList[k] = v + end + + if Entity:IsPlayer() and Entity:Armor() > 0 then + for k,v in pairs(ArmorHitboxes) do + CheckList[k] = v + end + end + + --if true then return "none" end + + for k,v in pairs(CheckList) do + local bone = Entity:LookupBone(v.boneName) + if bone then Bones[k] = bone end + end + + if table.IsEmpty(Bones) then return "none" end + + local HitBones = {} + + for k,v in pairs(Bones) do + local BoneData = CheckList[k] + local BonePos,BoneAng = Entity:GetBonePosition(v) + + local HitPos = util.IntersectRayWithOBB(RayStart, RayDir * 64, BonePos, BoneAng, BoneData.min, BoneData.max) + if HitPos ~= nil then + HitBones[k] = HitPos + end + end + + if table.IsEmpty(HitBones) then return "none" end -- No boxes got hit, so return the default + if table.Count(HitBones) == 1 then return CheckList[next(HitBones)].group end -- Single box got hit, just return that + + local BestChoice = next(HitBones) + local BestDist = HitBones[BestChoice]:DistToSqr(RayStart) + + for k,v in pairs(HitBones) do + if BestChoice == k then continue end + local BoxPosDist = HitBones[k]:DistToSqr(RayStart) + if BoxPosDist < BestDist then BestChoice = k BestDist = BoxPosDist end + end + + return CheckList[BestChoice].group + end + + ACF.SquishyFuncs = {} + + function ACF.SquishyFuncs.DamageHelmet(Entity,HitRes,DmgResult) + local Damage = 0 + + DmgResult:SetThickness(12.5) -- helmet armor, sorta just shot in the dark for thickness + HitRes = DmgResult:Compute() + + if HitRes.Overkill > 0 then -- Went through helmet + return ACF.SquishyFuncs.DamageHead(Entity,HitRes,DmgResult) + else return Damage,HitRes end + end + + function ACF.SquishyFuncs.DamageHead(Entity,HitRes,DmgResult) + local Damage = 0 + local Mass = Entity:GetPhysicsObject():GetMass() or 100 + + DmgResult:SetThickness(Mass * 0.075) -- skull is around 7-8mm on average for humans, but this gets thicker with bigger creatures + + HitRes = DmgResult:Compute() + Damage = Damage + HitRes.Damage * 10 + + if HitRes.Overkill > 0 then -- Went through skull + DmgResult:SetThickness(0.01) -- squishy squishy brain matter, no resistance + HitRes = DmgResult:Compute() + Damage = Damage + (HitRes.Damage * 50 * math.max(1,HitRes.Overkill / 4)) -- yuge damage, yo brains just got scrambled by a BOOLET + return Damage,HitRes + else return Damage,HitRes end + end + + function ACF.SquishyFuncs.DamageVest(Entity,HitRes,DmgResult) + local Damage = 0 + + DmgResult:SetThickness(15) -- Vest armor, also a shot in the dark for thickness + HitRes = DmgResult:Compute() + + if HitRes.Overkill > 0 then -- Went through vest + return ACF.SquishyFuncs.DamageChest(Entity,HitRes,DmgResult) + else return Damage,HitRes end + end + + function ACF.SquishyFuncs.DamageChest(Entity,HitRes,DmgResult) + local Damage = 0 + local Size = Entity:BoundingRadius() + + DmgResult:SetThickness(Size * 0.25 * 0.02) -- the SKIN and SKELETON, just some generic trashy "armor" + + HitRes = DmgResult:Compute() + Damage = Damage + HitRes.Damage * 10 + + if HitRes.Overkill > 0 then -- Went through body surface + DmgResult:SetThickness(0.05) -- fleshy organs, ain't much here + HitRes = DmgResult:Compute() + Damage = Damage + (HitRes.Damage * 25 * math.max(1,HitRes.Overkill / 5)) -- some decent damage, vital organs got hurt for sure + return Damage,HitRes + else return Damage,HitRes end + end end \ No newline at end of file diff --git a/lua/acf/damage/damage_sv.lua b/lua/acf/damage/damage_sv.lua index 6efac2d76..ef937702b 100644 --- a/lua/acf/damage/damage_sv.lua +++ b/lua/acf/damage/damage_sv.lua @@ -47,77 +47,35 @@ end -- @param DmgInfo A DamageInfo object. -- @return The output of the DamageResult object. function Damage.doSquishyDamage(Entity, DmgResult, DmgInfo) - local Bone = DmgInfo:GetHitGroup() + local Hitbox = ACF.GetBestSquishyHitBox(Entity,DmgInfo:GetHitPos(),(DmgInfo:GetHitPos() - DmgInfo:GetOrigin()):GetNormalized()) local Size = Entity:BoundingRadius() - local Mass = Entity:GetPhysicsObject():GetMass() local HitRes = DmgResult:GetBlank() local Damage = 0 DmgResult:SetFactor(1) -- We don't care about the penetration factor on squishy targets - if Bone then - --This means we hit the head - if Bone == 1 then - --Set the skull thickness as a percentage of Squishy weight, this gives us 2mm for a player, about 22mm for an Antlion Guard. Seems about right - DmgResult:SetThickness(Mass * 0.02) - - HitRes = DmgResult:Compute() - Damage = HitRes.Damage * 20 - - --If we manage to penetrate the skull, then MASSIVE DAMAGE - if HitRes.Overkill > 0 then - --A quarter the bounding radius seems about right for most critters head size - DmgResult:SetThickness(Size * 0.25 * 0.01) - - HitRes = DmgResult:Compute() - Damage = Damage + HitRes.Damage * 100 - end - - -- Then to check if we can get out of the other side, 2x skull + 1x brains - DmgResult:SetThickness(Mass * 0.065) - - HitRes = DmgResult:Compute() - Damage = Damage + HitRes.Damage * 20 - elseif Bone == 0 or Bone == 2 or Bone == 3 then - -- This means we hit the torso. We are assuming body armour/tough exoskeleton/zombie don't give fuck here, so it's tough - -- Set the armour thickness as a percentage of Squishy weight, this gives us 8mm for a player, about 90mm for an Antlion Guard. Seems about right - DmgResult:SetThickness(Mass * 0.08) - - HitRes = DmgResult:Compute() - Damage = HitRes.Damage * 5 - - if HitRes.Overkill > 0 then - -- Half the bounding radius seems about right for most critters torso size - DmgResult:SetThickness(Size * 0.5 * 0.02) - - HitRes = DmgResult:Compute() - Damage = Damage + HitRes.Damage * 50 -- If we penetrate the armour then we get into the important bits inside, so DAMAGE - end - - --Then to check if we can get out of the other side, 2x armour + 1x guts - DmgResult:SetThickness(Mass * 0.185) - - HitRes = DmgResult:Compute() - elseif Bone == 10 then - -- This means we hit a backpack or something - -- Arbitrary size, most of the gear carried is pretty small - DmgResult:SetThickness(Size * 0.1 * 0.02) + if Hitbox == "none" then -- Default damage + DmgResult:SetThickness(Size * 0.2 * 0.02) - HitRes = DmgResult:Compute() - Damage = HitRes.Damage * 2 -- Damage is going to be fright and shrapnel, nothing much + HitRes = DmgResult:Compute() + Damage = HitRes.Damage * 20 + else + -- Using player armor for fake armor works decently, as even if you don't take actual damage, the armor takes 1 point of damage, so it can potentially wear off + -- These funcs are also done on a hierarchy sort of system, so if the helmet is penetrated, then DamageHead is called, same for Vest -> Chest + if Hitbox == "helmet" then + Damage,HitRes = ACF.SquishyFuncs.DamageHelmet(Entity,HitRes,DmgResult) + elseif Hitbox == "head" then + Damage,HitRes = ACF.SquishyFuncs.DamageHead(Entity,HitRes,DmgResult) + elseif Hitbox == "vest" then + Damage,HitRes = ACF.SquishyFuncs.DamageVest(Entity,HitRes,DmgResult) + elseif Hitbox == "chest" then + Damage,HitRes = ACF.SquishyFuncs.DamageChest(Entity,HitRes,DmgResult) else - --Just in case we hit something not standard DmgResult:SetThickness(Size * 0.2 * 0.02) HitRes = DmgResult:Compute() - Damage = HitRes.Damage * 30 + Damage = HitRes.Damage * 10 end - else - -- Just in case we hit something not standard - DmgResult:SetThickness(Size * 0.2 * 0.02) - - HitRes = DmgResult:Compute() - Damage = HitRes.Damage * 10 end Entity:TakeDamage(Damage, DmgInfo:GetAttacker(), DmgInfo:GetInflictor()) From 40038d992aebcb6d2ddad0b0da4d003b972c002f Mon Sep 17 00:00:00 2001 From: LiddulBOFH <13317534+LiddulBOFH@users.noreply.github.com> Date: Sat, 28 Jan 2023 01:48:49 -0600 Subject: [PATCH 2/2] The One Variable To Stop Them All --- lua/acf/core/utilities/util_sv.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/acf/core/utilities/util_sv.lua b/lua/acf/core/utilities/util_sv.lua index 7e5337059..1978aae54 100644 --- a/lua/acf/core/utilities/util_sv.lua +++ b/lua/acf/core/utilities/util_sv.lua @@ -531,7 +531,7 @@ do -- Special squishy functions local BestChoice = next(HitBones) local BestDist = HitBones[BestChoice]:DistToSqr(RayStart) - for k,v in pairs(HitBones) do + for k,_ in pairs(HitBones) do if BestChoice == k then continue end local BoxPosDist = HitBones[k]:DistToSqr(RayStart) if BoxPosDist < BestDist then BestChoice = k BestDist = BoxPosDist end