Files
wnsrc/lua/arccw/shared/sh_penetration.lua

467 lines
17 KiB
Lua
Raw Normal View History

2024-08-04 22:55:00 +03:00
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local mth = math
local m_rand = mth.Rand
local m_lerp = Lerp
local function draw_debug()
return (CLIENT or game.SinglePlayer()) and ArcCW.ConVars["dev_shootinfo"]:GetInt() >= 2
end
function ArcCW:GetRicochetChance(penleft, tr)
if !ArcCW.ConVars["enable_ricochet"]:GetBool() then return 0 end
local degree = tr.HitNormal:Dot((tr.StartPos - tr.HitPos):GetNormalized())
local ricmult = ArcCW.PenTable[tr.MatType] or 1
-- 0 at 1
-- 100 at 0
local c = Lerp(degree, math.min(penleft * ricmult * 2, 45), 0)
-- c = c * ArcCW.ConVars["ricochet_mult"]:GetFloat()
-- c = 100
return math.Clamp(c, 0, 100)
end
function ArcCW:IsPenetrating(ptr, ptrent)
if ptrent:IsWorld() then
return ptr.Contents != CONTENTS_EMPTY
elseif IsValid(ptrent) then
local withinbounding = false
local hboxset = ptrent:GetHitboxSet()
local hitbone = ptrent:GetHitBoxBone(ptr.HitBox, hboxset)
if hitbone then
-- If we hit a hitbox, compare against that hitbox only
local mins, maxs = ptrent:GetHitBoxBounds(ptr.HitBox, hboxset)
local bonepos, boneang = ptrent:GetBonePosition(hitbone)
mins = mins * 1.1
maxs = maxs * 1.1
local lpos = WorldToLocal(ptr.HitPos, ptr.HitNormal:Angle(), bonepos, boneang)
withinbounding = lpos:WithinAABox(mins, maxs)
if draw_debug() then
debugoverlay.BoxAngles(bonepos, mins, maxs, boneang, 5, Color(255, 255, 255, 10))
end
elseif util.PointContents(ptr.HitPos) != CONTENTS_EMPTY then
-- Otherwise default to rotated OBB
local mins, maxs = ptrent:OBBMins(), ptrent:OBBMaxs()
withinbounding = ptrent:WorldToLocal(ptr.HitPos):WithinAABox(mins, maxs)
if draw_debug() then
debugoverlay.BoxAngles(ptrent:GetPos(), mins, maxs, ptrent:GetAngles(), 5, Color(255, 255, 255, 10))
end
end
if draw_debug() then
debugoverlay.Cross(ptr.HitPos, withinbounding and 4 or 6, 5, withinbounding and Color(255, 255, 0) or Color(128, 255, 0), true)
end
return withinbounding
end
return false
end
function ArcCW:DoPenetration(tr, damage, bullet, penleft, physical, alreadypenned)
local hitpos, startpos = tr.HitPos, tr.StartPos
local dir = (hitpos - startpos):GetNormalized()
-- Added in e5adb54: "temporarily disable visual pen bullet until a solution is found"
-- i don't remember the issue though
-- if CLIENT then
-- return
-- end
if tr.HitSky then return end
if penleft <= 0 then return end
alreadypenned = alreadypenned or {}
local skip = false
local trent = tr.Entity
local penmult = ArcCW.PenTable[tr.MatType] or 1
local pentracelen = 4
local curr_ent = trent
local startpen = penleft
if !tr.HitWorld then penmult = penmult * 1.5 end
if trent.mmRHAe then penmult = trent.mmRHAe end
penmult = penmult * m_rand(0.9, 1.1) * m_rand(0.9, 1.1)
local endpos = hitpos
local td = {}
td.start = endpos
td.endpos = endpos + (dir * pentracelen)
td.mask = MASK_SHOT
local ptr = util.TraceLine(td)
local ptrent = ptr.Entity
if ArcCW:GetRicochetChance(penleft, tr) > math.random(0, 100) then
local degree = tr.HitNormal:Dot((tr.StartPos - tr.HitPos):GetNormalized())
if degree == 0 or degree == 1 then return end
sound.Play(ArcCW.RicochetSounds[math.random(#ArcCW.RicochetSounds)], tr.HitPos)
if (tr.Normal:Length() == 0) then return end
-- ACT3_ShootPBullet(tr.HitPos, ((2 * degree * tr.HitNormal) + tr.Normal) * (vel * math.Rand(0.25, 0.75)), owner, inflictor, bulletid, false, 1, penleft, dist)
-- return
dir = (2 * degree * tr.HitNormal) + tr.Normal
ang = dir:Angle()
ang = ang + (AngleRand() * (1 - degree) * 15 / 360)
dir = ang:Forward()
local d = math.Rand(0.25, 0.95)
penleft = penleft * d
skip = true
end
if !ArcCW.ConVars["enable_penetration"]:GetBool() then return end
local factor = 1
while !skip and penleft > 0 and ArcCW:IsPenetrating(ptr, ptrent) and ptr.Fraction < 1 and ptrent == curr_ent do
penleft = penleft - (pentracelen * penmult) * factor
-- Prevent extremely long penetrations (such as with glass)
factor = factor * 1.05
td.start = endpos
td.endpos = endpos + (dir * pentracelen)
td.mask = MASK_SHOT
ptr = util.TraceLine(td)
-- This is never called because curr_ent is never updated, genius
-- Damage is handled in abullet.Callback anyways
--[[]
if ptrent != curr_ent then
ptrent = ptr.Entity
curr_ent = ptrent
local ptrhp = ptr.HitPos
-- local dist = (ptrhp - tr.StartPos):Length() * ArcCW.HUToM
local pdelta = penleft / bullet.Penetration
local dmg = DamageInfo()
dmg:SetDamageType(bullet.DamageType)
dmg:SetDamage(damage * pdelta)
dmg:SetDamagePosition(ptrhp)
if IsValid(ptrent) and !alreadypenned[ptrent:EntIndex()] then ptrent:TakeDamageInfo(dmg) end
penmult = ArcCW.PenTable[ptr.MatType] or 1
if !ptr.HitWorld then penmult = penmult * 1.5 end
if ptrent.mmRHAe then penmult = ptrent.mmRHAe end
penmult = penmult * m_rand(0.9, 1.1) * m_rand(0.9, 1.1)
debugoverlay.Line(endpos, endpos + (dir * pentracelen), 10, Color(0, 0, 255), true)
end
]]
if draw_debug() then
local pdeltap = penleft / bullet.Penetration
local colorlr = m_lerp(pdeltap, 0, 255)
debugoverlay.Line(endpos, endpos + (dir * pentracelen), 10, Color(255, colorlr, colorlr), true)
end
endpos = endpos + (dir * pentracelen)
dir = dir + (VectorRand() * 0.025 * penmult)
end
if penleft > 0 then
if (dir:Length() == 0) then return end
-- Recover penetration lost from extra distance in the trace
--penleft = penleft + ptr.Fraction * pentracelen / penmult
if draw_debug() then
debugoverlay.Text(endpos + Vector(0, 0, 2), "(" .. math.Round(penleft, 2) .. "mm)", 5)
end
local pdelta = penleft / bullet.Penetration
local attacker = bullet.Attacker
if !IsValid(attacker) then
attacker = game.GetWorld()
end
if physical then
if !ptr.HitWorld then
alreadypenned[ptrent:EntIndex()] = true
end
local newbullet = {}
newbullet.DamageMin = bullet.DamageMin or 1
newbullet.DamageMax = bullet.DamageMax or 10
newbullet.Range = bullet.Range or 100
newbullet.DamageType = bullet.DamageType or DMG_BULLET
newbullet.Penleft = penleft
newbullet.Penetration = bullet.Penetration
newbullet.Num = bullet.Num or 1
newbullet.Pos = endpos
local spd = bullet.Vel:Length()
newbullet.Attacker = bullet.Attacker
newbullet.Vel = dir * spd * (penleft / startpen)
newbullet.Drag = bullet.Drag or 1
newbullet.Travelled = bullet.Travelled + (endpos - hitpos):Length()
newbullet.Damaged = alreadypenned
newbullet.Profile = bullet.Profile or 1
newbullet.Gravity = bullet.Gravity or 1
newbullet.StartTime = bullet.StartTime or CurTime()
newbullet.PhysBulletImpact = bullet.PhysBulletImpact or true
newbullet.Weapon = bullet.Weapon
if bit.band( util.PointContents( endpos ), CONTENTS_WATER ) == CONTENTS_WATER then
newbullet.Underwater = true
end
table.insert(ArcCW.PhysBullets, newbullet)
ArcCW:SendBullet(newbullet)
else
local abullet = {}
abullet.Attacker = owner
abullet.Dir = dir
abullet.Src = endpos
abullet.Spread = Vector(0, 0, 0)
abullet.Damage = 0
abullet.Num = 1
abullet.Force = 0
abullet.Distance = 33000
abullet.Tracer = 0
--abullet.IgnoreEntity = ptr.Entity
abullet.Callback = function(att, btr, dmg)
local dist = bullet.Travelled * ArcCW.HUToM
bullet.Travelled = bullet.Travelled + (btr.HitPos - endpos):Length()
if alreadypenned[btr.Entity:EntIndex()] then
dmg:SetDamage(0)
else
dmg:SetDamageType(bullet.DamageType)
dmg:SetDamage(bullet.Weapon:GetDamage(dist, true) * pdelta, true)
end
if draw_debug() then
local e = endpos + dir * (btr.HitPos - endpos):Length()
debugoverlay.Line(endpos, e, 10, Color(150, 150, 150), true)
debugoverlay.Cross(e, 3, 10, alreadypenned[btr.Entity:EntIndex()] and Color(0, 128, 255) or Color(255, 128, 0), true)
debugoverlay.Text(e, math.Round(penleft, 1) .. "mm", 10)
end
if (CLIENT or game.SinglePlayer()) and ArcCW.ConVars["dev_shootinfo"]:GetInt() >= 1 and IsValid(btr.Entity) and !alreadypenned[btr.Entity:EntIndex()] then
local str = string.format("%ddmg/%dm(%d%%)", math.floor(bullet.Weapon:GetDamage(dist)), dist, math.Round((1 - bullet.Weapon:GetRangeFraction(dist)) * 100))
debugoverlay.Text(btr.Entity:WorldSpaceCenter(), str, 5)
end
alreadypenned[btr.Entity:EntIndex()] = true
ArcCW:DoPenetration(btr, damage, bullet, penleft, false, alreadypenned)
-- if !game.SinglePlayer() and CLIENT then
local fx = EffectData()
fx:SetStart(tr.HitPos)
fx:SetOrigin(btr.HitPos)
util.Effect("arccw_ricochet", fx)
-- end
end
attacker:FireBullets(abullet)
end
--[[
local atk = bullet.Attacker
local supbullet = {}
supbullet.Src = hitpos
supbullet.Dir = -dir
supbullet.Damage = 0
supbullet.Distance = 8
supbullet.Tracer = 0
supbullet.Force = 0
attacker:FireBullets(supbullet, true)
]]
end
end
function ArcCW:BulletCallback(att, tr, dmg, bullet, phys)
local wep = phys and bullet.Weapon or bullet
local hitpos, hitnormal = tr.HitPos, tr.HitNormal
local trent = tr.Entity
local dist = (phys and bullet.Travelled or (hitpos - tr.StartPos):Length() ) * ArcCW.HUToM
local pen = IsValid(wep) and wep:GetBuff("Penetration") or bullet.Penleft
if ArcCW.ConVars["dev_shootinfo"]:GetInt() >= 1 then
debugoverlay.Cross(hitpos, 1, 5, SERVER and Color(255, 0, 0) or Color(0, 0, 255), true)
end
local randfactor = IsValid(wep) and wep:GetBuff("DamageRand") or 0
local mul = 1
if randfactor > 0 then
mul = mul * math.Rand(1 - randfactor, 1 + randfactor)
end
local delta = !IsValid(wep) and math.Clamp(bullet.Travelled / (bullet.Range / ArcCW.HUToM), 0, 1) or wep:GetRangeFraction(dist)
local calc_damage = (!IsValid(wep) and Lerp(delta, bullet.DamageMax, bullet.DamageMin) or wep:GetDamage(dist, true)) * mul
local dmgtyp = !IsValid(wep) and bullet.DamageType or wep:GetBuff_Override("Override_DamageType", wep.DamageType) or DMG_BULLET
local hit = {}
hit.att = att
hit.tr = tr
hit.dmg = dmg
hit.range = dist
hit.damage = calc_damage
hit.dmgtype = dmgtyp
hit.penleft = pen
if IsValid(wep) then
hit = wep:GetBuff_Hook("Hook_BulletHit", hit)
if !hit then return end
end
if bullet.Damaged and bullet.Damaged[tr.Entity:EntIndex()] then
dmg:SetDamage(0)
else
dmg:SetDamageType(hit.dmgtype)
dmg:SetDamage(hit.damage)
end
local dmgtable
if phys and IsValid(bullet.Weapon) then
dmgtable = bullet.Weapon:GetBuff_Override("Override_BodyDamageMults", bullet.Weapon.BodyDamageMults)
elseif IsValid(wep) then
dmgtable = wep:GetBuff_Override("Override_BodyDamageMults", wep.BodyDamageMults)
else
dmgtable = bullet.BodyDamageMults
end
if dmgtable then
local hg = tr.HitGroup
local gam = ArcCW.LimbCompensation[engine.ActiveGamemode()] or ArcCW.LimbCompensation[1]
if dmgtable[hg] then
dmg:ScaleDamage(dmgtable[hg])
-- cancelling gmod's stupid default values (but only if we have a multiplier)
if ArcCW.ConVars["bodydamagemult_cancel"]:GetBool() and gam[hg] then dmg:ScaleDamage(gam[hg]) end
end
end
if IsValid(att) and att:IsNPC() then
dmg:ScaleDamage(wep:GetBuff_Mult("Mult_DamageNPC") or 1)
end
local effect = phys and bullet.ImpactEffect or (IsValid(wep) and wep:GetBuff_Override("Override_ImpactEffect", wep.ImpactEffect))
local decal = phys and bullet.ImpactDecal or (IsValid(wep) and wep:GetBuff_Override("Override_ImpactDecal", wep.ImpactDecal))
-- Do our handling of damage types, if not ignored by the gun or some attachment
if IsValid(wep) and !wep:GetBuff_Override("Override_DamageTypeHandled", wep.DamageTypeHandled) then
local _, maxrng = wep:GetMinMaxRange()
-- ignite target
if dmg:IsDamageType(DMG_BURN) and hit.range <= maxrng then
dmg:SetDamageType(dmg:GetDamageType() - DMG_BURN)
effect = "arccw_incendiaryround"
decal = "FadingScorch"
if SERVER then
if vFireInstalled then
CreateVFire(trent, hitpos, hitnormal, hit.damage * 0.02)
else
trent:Ignite(1, 0)
end
end
end
-- explode target
if dmg:IsDamageType(DMG_BLAST) then
if dmg:GetDamage() >= 200 then
effect = "Explosion"
decal = "Scorch"
else
effect = "arccw_incendiaryround"
decal = "FadingScorch"
end
dmg:ScaleDamage(0.5) -- half applied as explosion and half done to hit target
util.BlastDamageInfo(dmg, tr.HitPos, math.Clamp(dmg:GetDamage(), 48, 256))
dmg:SetDamageType(dmg:GetDamageType() - DMG_BLAST)
end
-- damage helicopters
if dmg:IsDamageType(DMG_BULLET) and !dmg:IsDamageType(DMG_AIRBOAT)
and IsValid(hit.tr.Entity) and hit.tr.Entity:GetClass() == "npc_helicopter" then
dmg:SetDamageType(dmg:GetDamageType() + DMG_AIRBOAT)
dmg:ScaleDamage(0.1) -- coostimizable?
elseif dmg:GetDamageType() != DMG_BLAST and IsValid(hit.tr.Entity) and hit.tr.Entity:GetClass() == "npc_combinegunship" then
dmg:SetDamageType(DMG_BLAST)
dmg:ScaleDamage(0.05)
-- there is a damage threshold of 50 for damaging gunships
if dmg:GetDamage() < 50 and dmg:GetDamage() / 200 >= math.random() then
dmg:SetDamage(50)
end
end
-- pure DMG_BUCKSHOT do not create blood decals, somehow
if dmg:GetDamageType() == DMG_BUCKSHOT then
dmg:SetDamageType(dmg:GetDamageType() + DMG_BULLET)
end
end
if SERVER and IsValid(wep) then wep:TryBustDoor(trent, dmg) end
-- INCONSISTENCY: For physbullet, the entire bullet is copied; hitscan bullets reset some attributes in SWEP:DoPenetration (most notably damage)
-- For now, we just reset some changes as a temporary workaround
if !IsValid(wep) then
bullet.Damage = calc_damage
bullet.DamageType = dmgtyp
ArcCW:DoPenetration(tr, hit.damage, bullet, bullet.Penleft, true, bullet.Damaged)
else
wep:DoPenetration(tr, hit.penleft, { [trent:EntIndex()] = true })
end
if effect then
local ed = EffectData()
ed:SetOrigin(hitpos)
ed:SetNormal(hitnormal)
util.Effect(effect, ed)
end
if decal then
util.Decal(decal, tr.StartPos, hitpos - (hitnormal * 16), wep:GetOwner())
end
if (CLIENT or game.SinglePlayer()) and (!phys or SERVER) and ArcCW.ConVars["dev_shootinfo"]:GetInt() >= 1 then
local str = string.format("%ddmg/%dm(%d%%)", math.floor(dmg:GetDamage()), dist, math.Round((1 - delta) * 100))
debugoverlay.Text(hitpos, str, 10)
print(str)
end
if IsValid(wep) then
wep:GetBuff_Hook("Hook_PostBulletHit", hit)
end
end