mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 21:33:46 +03:00
467 lines
17 KiB
Lua
467 lines
17 KiB
Lua
--[[
|
|
| 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 |