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