Files
wnsrc/lua/stormfox2/framework/sh_thunder.lua
lifestorm 94063e4369 Upload
2024-08-04 22:55:00 +03:00

535 lines
16 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/
--]]
StormFox2.Thunder = {}
---Returns true if it is thundering.
---@return boolean
---@shared
function StormFox2.Thunder.IsThundering()
return StormFox2.Data.Get("nThunder", 0) > 0
end
---Returns the amount of posible strikes pr minute.
---@return number
---@shared
function StormFox2.Thunder.GetActivity()
return StormFox2.Data.Get("nThunder", 0)
end
if SERVER then
local THUNDER_MAKE_SKYBOX = 0
local THUNDER_TRACE_ERROR = 1
local THUNDER_SUCCESS = 2
local function ETHull(pos,pos2,size,mask)
local t = util.TraceHull( {
start = pos,
endpos = pos2,
maxs = Vector(size,size,4),
mins = Vector(-size,-size,0),
mask = mask
} )
t.HitPos = t.HitPos or pos + pos2
return t
end
-- Damages an entity
local function StrikeDamageEnt( ent )
if not ent or not IsValid(ent) then return end
local effectdata = EffectData()
effectdata:SetOrigin( ent:GetPos() )
effectdata:SetEntity(ent)
effectdata:SetMagnitude(2)
effectdata:SetScale(3)
for i = 1,100 do
util.Effect( "TeslaHitboxes", effectdata, true, true )
end
local ctd = DamageInfo()
ctd:IsDamageType(DMG_SHOCK)
ctd:SetDamage(math.Rand(90,200))
local vr = VectorRand()
vr.z = math.abs(vr.z)
ctd:SetDamageForce(vr * 1000)
ctd:SetInflictor(game.GetWorld())
ctd:SetAttacker(game.GetWorld())
ent:TakeDamageInfo(ctd)
end
-- Damanges an area
local function StrikeDamagePos( vPos )
local b_InWater = bit.band( util.PointContents( vPos ), CONTENTS_WATER ) == CONTENTS_WATER
local t = {}
for _,ent in ipairs(ents.FindInSphere(vPos,750)) do
-- It hit in water, and you're nearby
if b_InWater and ent:WaterLevel() > 0 then
StrikeDamageEnt(ent)
table.insert(t, ent)
elseif ent:GetPos():Distance( vPos ) < 150 then
StrikeDamageEnt(ent)
table.insert(t, ent)
end
end
hook.Run("StormFox2.Thunder.OnStrike", vPos, t)
end
-- Make STrike
local Sway = 100
-- Traces a lightningstrike from sky to ground.
local BrushZ = math.max(1, ( StormFox2.Map.MaxSize().z - StormFox2.Map.MinSize().z ) / 20000)
local function MakeStrikeDown( vPos, nTraceSize )
if not nTraceSize then nTraceSize = 512 end
-- Find the sky position at the area
local SkyPos = StormFox2.DownFall.FindSky( vPos, vector_up, 8 )
if not SkyPos then -- Unable to find sky above. Get the higest point
SkyPos = Vector(vPos.x, vPos.y, StormFox2.Map.MaxSize().z)
SkyPos = StormFox2.DownFall.FindSky( SkyPos, vector_up, 1 ) or SkyPos
end
--debugoverlay.Box(SkyPos, Vector(1,1,1) * -20, Vector(1,1,1) * 20, 15, Color(255,0,0))
-- Find the strike distance (Some maps are suuuper tall)
local tr = ETHull(SkyPos - vector_up * 10, Vector( vPos.x, vPos.y, StormFox2.Map.MinSize().z), 256, MASK_SOLID_BRUSHONLY )
if tr.AllSolid or tr.Fraction == 0 then -- This is inside solid and should instead be done in the skybox.
return THUNDER_MAKE_SKYBOX, SkyPos
end
SkyPos.z = math.min( SkyPos.z, tr.HitPos.z + 8000 )
local line = {}
table.insert(line,pos)
-- Create a line down
local olddir = Vector(0,0,-1)
local m_dis = math.max(1, ( SkyPos.z - StormFox2.Map.MinSize().z ) / 20000)
local pos = SkyPos - vector_up * 5
local _n = nTraceSize / 40
for i = 20, 1, -1 do
-- Random sway
local randir = Angle(math.Rand(-Sway,Sway) + 90,math.random(360),math.Rand(-Sway,Sway)):Forward() + olddir
randir:Normalize()
randir.z = -math.abs(randir.z)
olddir = randir
local pos2 = pos + randir * math.Rand(800, 1000) * m_dis
local m_dis = math.max(1, ( StormFox2.Map.MaxSize().z - StormFox2.Map.MinSize().z ) / 20000)
local tr = ETHull(pos, pos2, nTraceSize - i * _n )
--debugoverlay.Line(pos, pos2, 10)
if not tr.Hit then
table.insert(line, pos2)
pos = pos2
else
if tr.HitSky then -- We hit the side of the skybox. Go to other way
olddir = -olddir
else
table.insert(line, tr.HitPos)
return THUNDER_SUCCESS, line, tr
end
end
end
return line, tr
end
-- Creates a lightniingstrike up ( Useful when forcing a lightning strike to hit something )
local function MakeStrikeUp( vPos )
local SkyPos = StormFox2.DownFall.FindSky( vPos, vector_up, 8 )
if not SkyPos then -- Unable to find sky above. Get the higest point
SkyPos = Vector(vPos.x, vPos.y, StormFox2.Map.MaxSize().z)
SkyPos = StormFox2.DownFall.FindSky( SkyPos, vector_up, 1 ) or SkyPos
end
local olddir = Vector(0,0,-1)
local pos = vPos
local m_dis = math.max(1, ( StormFox2.Map.MaxSize().z - StormFox2.Map.MinSize().z ) / 20000)
local list = {pos}
for i = 1, 20 do
local randir = Angle(math.Rand(-Sway,Sway) + 90,math.random(360),math.Rand(-Sway,Sway)):Forward() + olddir
randir:Normalize()
randir.z = -math.abs(randir.z)
olddir = randir
local pos2 = pos + -randir * math.Rand(800, 1000) * m_dis
if pos2.z >= SkyPos.z then
table.insert(list, Vector(pos2.x, pos2.y, SkyPos.z))
break
else
pos = pos2
table.insert(list, pos)
end
end
return table.Reverse(list)
end
local function LightFluff(tList, b_InSkybox )
local n_Length = math.Rand(.4,0.7)
local n_Light = math.random(200, 250)
net.Start(StormFox2.Net.Thunder)
net.WriteBool( true )
net.WriteUInt(#tList, 5)
for i = 1, #tList do
net.WriteVector(tList[i])
end
net.WriteBool( b_InSkybox )
net.WriteFloat(n_Length)
net.WriteUInt(n_Light,8)
net.Broadcast()
end
---Creates a lightningstrike at a given position. Will return a hit entity as a second argument.
---@param pos Vector
---@return boolean success
---@return Entity
---@server
function StormFox2.Thunder.CreateAt( pos )
local t_Var, tList, tr
local b_InSkybox = false
local vMapMin = StormFox2.Map.MinSize()
local vMapMax = StormFox2.Map.MaxSize()
if not pos then
pos = Vector( math.Rand(vMapMin.x, vMapMax.x), math.Rand(vMapMin.y, vMapMax.y), math.Rand(vMapMax.z, vMapMin.z / 2) )
end
local bInside = pos.x >= vMapMin.x and pos.x <= vMapMax.x and vMapMin.y and pos.y <= vMapMax.y
if bInside then
t_Var, tList, tr = MakeStrikeDown( pos )
if t_Var == THUNDER_MAKE_SKYBOX then
bInside = false
end
end
if not bInside then -- Outside the map
tList = MakeStrikeUp( Vector(pos.x, pos.y, StormFox2.Map.MinSize().z) )
b_InSkybox = true
end
if not tList then return false end -- Unable to create lightning strike here.
local hPos = tr and tr.HitPos or tList[#tList]
if not hPos then return end
if tr and IsValid( tr.Entity ) then
table.insert(tList, tr.Entity:GetPos() + tr.Entity:OBBCenter())
hPos = tr.Entity:GetPos() + tr.Entity:OBBCenter()
end
StrikeDamagePos( hPos )
LightFluff(tList, b_InSkybox )
return true, tr and IsValid( tr.Entity ) and tr.Entity
end
---Creates a lightning strike to hit the given position / entity.
---@param zPosOrEnt Vector|Entity
---@param bRangeDamage number
---@return boolean success
---@server
function StormFox2.Thunder.Strike( zPosOrEnt, bRangeDamage )
-- Strike the entity
if not bRangeDamage and zPosOrEnt.Health then
local ent = zPosOrEnt
timer.Simple(0.4, function()
StrikeDamageEnt( ent )
end)
end
if zPosOrEnt.GetPos then
if zPosOrEnt.OBBCenter then
zPosOrEnt = zPosOrEnt:GetPos() + zPosOrEnt:OBBCenter()
else
zPosOrEnt = zPosOrEnt:GetPos()
end
end
if bRangeDamage then
timer.Simple(0.4, function() StrikeDamagePos( zPosOrEnt ) end)
end
local b_InSkybox = not StormFox2.Map.IsInside( zPosOrEnt )
--if b_InSkybox then
-- zPosOrEnt = StormFox2.Map.WorldtoSkybox( zPosOrEnt )
--end
local tList = MakeStrikeUp( zPosOrEnt )
LightFluff(tList, false )
return true
end
---Creates a rumble.
---@param pos Vector
---@param bLight boolean
---@server
function StormFox2.Thunder.Rumble( pos, bLight )
if not pos then
local vMapMin = StormFox2.Map.MinSize()
local vMapMax = StormFox2.Map.MaxSize()
pos = Vector( math.Rand(vMapMin.x, vMapMax.x), math.Rand(vMapMin.y, vMapMax.y), math.Rand(vMapMax.z, vMapMin.z / 2) )
end
local n_Length = bLight and math.Rand(.4,0.7) or 0
local n_Light = bLight and math.random(150, 250) or 0
net.Start( StormFox2.Net.Thunder )
net.WriteBool( false )
net.WriteVector( pos )
net.WriteFloat(n_Length)
net.WriteUInt(n_Light,8)
net.Broadcast()
end
-- Enables thunder and makes them spawn at random, until set off or another weather gets selected
local b = false
local n = math.max(StormFox2.Map.MaxSize().x, StormFox2.Map.MaxSize().y, -StormFox2.Map.MinSize().x,-StormFox2.Map.MinSize().y)
if StormFox2.Map.Has3DSkybox() then
n = n * 1.5
end
do
local n = 0
---Enables / Disables thunder.
---@param bEnable boolean
---@param nActivityPrMinute? number
---@param nTimeAmount? number
---@server
function StormFox2.Thunder.SetEnabled( bEnable, nActivityPrMinute, nTimeAmount )
n = 0
if bEnable then
StormFox2.Network.Set("nThunder", nActivityPrMinute)
if nTimeAmount then
StormFox2.Network.Set("nThunder", 0, nTimeAmount)
end
else
StormFox2.Network.Set("nThunder", 0)
end
end
hook.Add("Think", "StormFox2.thunder.activity", function()
if not StormFox2.Thunder.IsThundering() then return end
if n >= CurTime() then return end
local a = StormFox2.Thunder.GetActivity()
n = CurTime() + math.random(50, 60) / a
-- Strike or rumble
if math.random(1,3) < 3 then
StormFox2.Thunder.CreateAt()
else
StormFox2.Thunder.Rumble( nil, math.random(10) > 1 )
end
end)
end
else
lightningStrikes = lightningStrikes or {}
local _Light, _Stop, _Length = 0,0,0
---Returns light created by thunder.
---@return number
---@client
function StormFox2.Thunder.GetLight()
if _Light <= 0 then return 0 end
if _Stop < CurTime() then
_Light = 0
return 0
end
local t = (_Stop - CurTime()) / _Length
local c = math.abs(math.sin( t * math.pi ))
local l = _Light * c
return math.random(l, l * 0.5) -- Flicker a bit
end
-- 0 - 2000
local CloseStrikes = {"sound/stormfox2/amb/thunder_strike.ogg"}
-- 2000 - 20000
local MediumStrikes = {"sound/stormfox2/amb/thunder_strike.ogg", "sound/stormfox2/amb/thunder_strike2.ogg"}
-- 20000 +
local FarStrikes = {}
if IsMounted("csgo") or IsMounted("left4dead2") then
table.insert(FarStrikes, "ambient/weather/thunderstorm/lightning_strike_1.wav")
table.insert(FarStrikes, "ambient/weather/thunderstorm/lightning_strike_4.wav")
end
if #FarStrikes < 1 then
table.insert(FarStrikes, "sound/stormfox2/amb/thunder_strike2.ogg")
end
local snd_buffer = {}
local function StrikeEffect( pos, n_Length )
local dlight = DynamicLight( 1 )
if ( dlight ) then
dlight.pos = pos
dlight.r = 255
dlight.g = 255
dlight.b = 255
dlight.brightness = 6
dlight.Decay = 3000 / n_Length
dlight.Size = 256 * 8
dlight.DieTime = CurTime() + n_Length
end
local effectdata = EffectData()
local s = math.random(5, 8)
effectdata:SetOrigin( pos + vector_up * 4 )
effectdata:SetMagnitude( s / 2 )
effectdata:SetNormal(vector_up)
effectdata:SetRadius( 8 )
util.Effect( "Sparks", effectdata, true, true )
end
local function PlayStrike( vPos, nViewDis, viewPos )
local snd = ""
if nViewDis <= 2000 then
snd = table.Random(CloseStrikes)
elseif nViewDis <= 15000 then
snd = table.Random(MediumStrikes)
else
snd = table.Random(FarStrikes)
end
if string.sub(snd, 0, 6 ) == "sound/" or string.sub(snd,-4) == ".ogg" then
sound.PlayFile( snd, "3dnoplay", function( station, errCode, errStr )
if ( IsValid( station ) ) then
station:Set3DFadeDistance( 0, 10 )
station:SetVolume( 1)
station:SetPos(vPos)
station:Play()
end
end)
else
surface.PlaySound( snd )
end
end
--[[
Sound moves about 343 meters pr second
52.49 hU = 1 meter ~ 18.004 hU pr second
]]
local b = true
local function SndThink()
if #snd_buffer < 1 then
hook.Remove("Think","StormFox2.Thunder.SndDis")
b = false
return
end
local r = {}
local view = StormFox2.util.ViewEntity():GetPos()
local c = CurTime() - 0.2
for k,v in ipairs( snd_buffer ) do
local travel = (c - v[2]) * 18004
local vDis = view:Distance( v[1] )
if vDis - travel < 0 then
table.insert(r, k)
PlayStrike( v[1], vDis, view )
end
end
for i = #r, 1, -1 do
table.remove(snd_buffer, r[i])
end
end
hook.Add("Think","StormFox2.Thunder.SndDis", SndThink)
local function Strike( tList, b_InSkybox, n_Length, n_Light )
table.insert(lightningStrikes, {CurTime() + n_Length, n_Length, b_InSkybox, tList, true})
local pos = tList[#tList][1]
sound.Play("ambient/energy/weld" .. math.random(1,2) .. ".wav", pos)
if not b then
hook.Add("Think","StormFox2.Thunder.SndDis", SndThink)
end
table.insert(snd_buffer, {pos, CurTime()})
local c = CurTime()
_Light = 255
_Length = .7
_Stop = math.max(c + _Length, _Stop)
end
local function Rumble( vPos, n_Length, n_Light )
-- Thunder is at 120dB
local c = CurTime()
_Light = n_Light
_Length = n_Length
_Stop = math.max(c + n_Length, _Stop)
sound.Play("ambient/atmosphere/thunder" .. math.random(3,4) .. ".wav", StormFox2.util.ViewEntity():GetPos(), 150)
end
local Sway = 120
net.Receive( StormFox2.Net.Thunder, function(len)
local b_Strike = net.ReadBool()
if b_Strike then
local tList = {}
local old
local n = net.ReadUInt(5)
for i = 1, n do
local randir = Angle(math.Rand(-Sway,Sway) + 90,math.random(360),math.Rand(-Sway,Sway))
local new = net.ReadVector()
if old then
randir = randir:Forward() + (new - old):Angle():Forward() * 2
else
randir = randir:Forward()
end
old = new
tList[i] = {new,math.Rand(1.2,1.5),randir,math.random(0,1)}
--debugoverlay.Sphere(new, 15, 15, Color(255,255,255), true)
end
local b_InSkybox = net.ReadBool()
Strike(tList, b_InSkybox, net.ReadFloat(), net.ReadUInt(8))
else
local vPos = net.ReadVector()
Rumble(vPos, net.ReadFloat(), net.ReadUInt(8) )
end
end)
-- Render Strikes
local tex = {(Material("stormfox2/effects/lightning"))}
local texend = {(Material("stormfox2/effects/lightning_end")),(Material("stormfox2/effects/lightning_end2"))}
for k, v in ipairs( texend ) do
v:SetFloat("$nofog",1)
end
local t = 0
hook.Add("PostDrawOpaqueRenderables","StormFox2.Render.Lightning",function(a,sky)
if a or #lightningStrikes < 1 then return end
if sky then return end -- Doesn't work yet
local r = {}
local c = CurTime()
local col = Color( 255, 255, 255, 255)
for k, v in ipairs( lightningStrikes ) do
-- Render world or skybox
--if v[3] ~= sky then continue end
-- Remove if dead
if v[1] < c then
table.insert(r, k)
continue
end
local life = 1 - ( v[1] - c ) / v[2] -- 0 - 1
if life < 0.6 then
col.a = 425 * life
else
col.a = 637.5 * (1 - life)
end
local fuzzy = life < 0.6
local i = 0.6 / #v[4]
if v[5] and not fuzzy then
StrikeEffect( v[4][#v[4]][1] , life )
lightningStrikes[k][5] = false
end
-- Render beams
render.SetMaterial(tex[1])
render.StartBeam(#v[4])
local l = math.Rand(0.4, 0.8)
for k2, v2 in ipairs( v[4] ) do
if life < k2 * i then break end
local tp = 0.1
render.AddBeam( v2[1], 400, (l * (k2 - 1)) % 1, col )
end
render.EndBeam()
-- Render strikes
if fuzzy then
for k2, v2 in ipairs( v[4] ) do
if life < k2 * i or k2 + 3 >= #v[4] then break end
local n2 = life * 2- k2 * 0.04
local vec = v2[1]
local tp = 1 / #v[4] * k2
local n = k2 % #texend + 1
render.SetMaterial(texend[n])
local w,h = texend[n]:Width() * n2,texend[n]:Height() * n2
render.DrawBeam( vec, vec + v2[3] * h * v2[2], w * v2[2], 1 - n2, 1, col )
end
else
local v1 = v[4][1][1]
local v2 = v[4][#v[4]][1]
local vc = (v1 + v2) / 2
vc.z = v2.z
local vc2 = Vector(vc.x,vc.y,v1.z)
local a = math.max(0, 1 - life - 0.2)
col.a = a * 1275
render.SetMaterial(Material("stormfox2/effects/lightning_light"))
render.DrawBeam(vc, vc2, 24400, 0.3 , 0.7, col)
end
end
for i = #r, 1, -1 do
table.remove(lightningStrikes, r[i])
end
end)
end