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

299 lines
11 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/
--]]
--[[-------------------------------------------------------------------------
Footsteps and logic.
- Overrides default footstepsounds with terrain-sounds
---------------------------------------------------------------------------]]
local NetL = {"npc_zombie", "npc_poisonzombie", "npc_vortigaunt", "npc_antlion", "npc_fastzombie"} -- These entites only play sounds serverside and needs to be networked.
local BL = {"npc_hunter"} -- Tehse entities should not get their sound replaced
local find = string.find
local bAlwaysFootstep = false -- This is set to true on cold maps
local defaultSnowName = "snow.step"
local defaultSnowSnd = {
"stormfox/footstep/footstep_snow0.ogg",
"stormfox/footstep/footstep_snow1.ogg",
"stormfox/footstep/footstep_snow2.ogg",
"stormfox/footstep/footstep_snow3.ogg",
"stormfox/footstep/footstep_snow4.ogg",
"stormfox/footstep/footstep_snow5.ogg",
"stormfox/footstep/footstep_snow6.ogg",
"stormfox/footstep/footstep_snow7.ogg",
"stormfox/footstep/footstep_snow8.ogg",
"stormfox/footstep/footstep_snow9.ogg"
}
if SERVER then
util.AddNetworkString("StormFox2.feetfix")
end
-- We use this to cache the last foot for the players.
local lastFoot = {}
hook.Add("PlayerFootstep", "StormFox2.lastfootprint", function(ply, pos, foot, sound, volume, filter, ...)
lastFoot[ply] = foot
end)
-- Local functions
--local noSpam = {}
local cache = {}
-- Returns the foot from sounddata
local function GetFootstep(tab)
local ent = tab.Entity
if not ent or not IsValid(ent) then return end
if not ent:IsPlayer() and not ent:IsNPC() and not ent:IsNextBot() then return end
--if (noSpam[ent] or 0) > CurTime() then return end
-- Check to see if it is a footstep
local OriginalSnd = tab.OriginalSoundName:lower()
local foot = -1
if cache[OriginalSnd] then
foot = cache[OriginalSnd]
elseif string.match(OriginalSnd, "npc_antlionguard.farstep") or string.match(OriginalSnd, "npc_antlionguard.nearstep") then
foot = lastFoot[ent] or -1
elseif find(OriginalSnd, "stepleft",1,true) or find(OriginalSnd, "gallopleft",1,true) then
foot = 0
cache[OriginalSnd] = 0
elseif find(OriginalSnd, "stepright",1,true) or find(OriginalSnd, "gallopright",1,true) then
foot = 1
cache[OriginalSnd] = 1
elseif find(OriginalSnd, ".footstep",1,true) or find(tab.SoundName:lower(),"^player/footsteps",1) then
foot = lastFoot[ent] or -1
else -- Invalid
return
end
-- No footstep spam
--noSpam[ent] = CurTime() + 0.01
return foot
end
-- TraceHull for the given entity
local function EntTraceTexture(ent,pos) -- Returns the texture the entity is "on"
local mt = ent:GetMoveType()
if mt < 2 or mt > 3 then return end -- Not walking.
local filter = ent
if ent.GetViewEntity then
filter = ent:GetViewEntity()
end
local t = util.TraceHull( {
start = pos + Vector(0,0,30),
endpos = pos + Vector(0,0,-60),
maxs = ent:OBBMaxs(),
mins = ent:OBBMins(),
collisiongroup = ent:GetCollisionGroup(),
filter = filter
} )
if not t.Hit then return end -- flying
if t.Entity and IsValid(t.Entity) and t.HitNonWorld and t.HitTexture == "**studio**" then
return
end
return t.HitTexture
end
-- Returns true if the entity is on replaced texture.
local function IsOnReplacedTex(ent,snd,pos)
if ent._sf2ns and ent._sf2ns > CurTime() then return false, ent._sf2nt or "nil" end
ent._sf2ns = CurTime() + 0.1
local sTexture = EntTraceTexture(ent,pos)
ent._sf2nt = sTexture
if not sTexture then return false,"nil" end
local mat = Material(sTexture)
if not mat then return false, sTexture end
if mat:IsError() and (ent:IsNPC() or string.find(snd,"grass") or string.find(snd,"dirt")) then -- Used by maps
return true, sTexture
end
if StormFox2.Terrain.HasMaterialChanged(mat) then return true, sTexture end
return false,sTexture
end
-- Footstep overwrite and logic
hook.Add("EntityEmitSound", "StormFox2.footstep.detecter", function(data)
if not StormFox2.Terrain then return end
local cT = StormFox2.Terrain.GetCurrent()
if not cT then return end
-- Only enable if we edit or need footsteps.
if not (bAlwaysFootstep or (cT and cT.footstepLisen)) then return end
-- Check if the server has disabled the footprint logic on their side.
if SERVER and not game.SinglePlayer() and not StormFox2.Setting.GetCache("footprint_enablelogic",true) then return end
-- Check if it is a footstep sound of some sort.
local foot = GetFootstep(data) -- Returns [-1 = invalid, 0 = left, 1 = right]
if not foot then return end
-- Checks to see if the texturem the entity stands on, have been replaced.
local bReplace, sTex = IsOnReplacedTex(data.Entity,data.SoundName:lower(),data.Pos or data.Entity:GetPos())
-- Overwrite the sound if needed.
local changed
if bReplace and cT.footprintSnds then
if cT.footprintSnds[2] then
data.OriginalSoundName = cT.footprintSnds[2] .. (foot == 0 and "left" or "right")
end
if not cT.footprintSnds[1] then
data.SoundName = "ambient/_period.wav"
else
data.SoundName = table.Random(cT.footprintSnds[1])
data.OriginalSoundName = data.SoundName
end
changed = true
end
-- Call footstep hook
hook.Run("StormFox2.terrain.footstep", data.Entity, foot, data.SoundName, sTex, bReplace )
-- Singleplayer and server-sounds fix
if SERVER and (game.SinglePlayer() or table.HasValue(NetL, data.Entity:GetClass())) then
net.Start("StormFox2.feetfix",true)
net.WriteEntity(data.Entity)
net.WriteInt(foot or 1,2)
net.WriteString(data.SoundName)
net.WriteString(sTex)
net.WriteBool(bReplace)
net.Broadcast()
end
-- Call terrain function
if cT.footstepFunc then
cT.footstepFunc(data.Entity, foot, data.SoundName, sTex, bReplace)
end
return changed
end)
-- Singleplayer and entity fix
if CLIENT then
net.Receive("StormFox2.feetfix",function()
local cT = StormFox2.Terrain.GetCurrent()
if not cT then return end
local ent = net.ReadEntity()
if not IsValid(ent) then return end
local foot = net.ReadInt(2)
local sndName = net.ReadString()
local sTex = net.ReadString()
local bReplace = net.ReadBool()
if cT.footstepFunc then
cT.footstepFunc(ent, foot, sndName, sTex, bReplace)
end
hook.Run("StormFox2.terrain.footstep", ent, foot, sndName, sTex, bReplace)
end)
end
--[[-------------------------------------------------------------------------
Footprint render
---------------------------------------------------------------------------]]
if CLIENT then
local sin,cos,rad,clamp,ceil,min = math.sin,math.cos,math.rad,math.Clamp,math.ceil,math.min
local prints = {}
local footstep_maxlife = 30
local function ET(pos,pos2,mask,filter)
local t = util.TraceLine( {
start = pos,
endpos = pos + pos2,
mask = mask,
filter = filter
} )
if not t then -- tracer failed, this should not happen. Create a fake result.
local t = {}
t.HitPos = pos + pos2
return t
end
t.HitPos = t.HitPos or (pos + pos2)
return t
end
local function AddPrint(ent,foot)
-- Foot calc
local velspeed = ent:GetVelocity():Length()
local y = rad(ent:GetAngles().y)
local fy = y + rad((foot * 2 - 1) * -90)
local l = 5 * ent:GetModelScale()
local ex = Vector(cos(fy) * l + cos(y) * l,sin(fy) * l + sin(y) * l,0)
local pos = ent:GetPos() + ex
-- Find impact
local tr = ET(pos + Vector(0,0,20),Vector(0,0,-40),MASK_SOLID_BRUSHONLY,ent)
if not tr.Hit then return end -- In space?
-- If no bone_angle then angle math
local normal = -tr.HitNormal
-- CalcAng
local yawoff
if ent:IsPlayer() then
yawoff = normal:Angle().y - ent:EyeAngles().y + 180
else
yawoff = normal:Angle().y - ent:GetAngles().y + 180
end
table.insert(prints,{tr.HitPos, normal,foot,ent:GetModelScale() or 1,CurTime() + footstep_maxlife,clamp(velspeed / 300,1,2),yawoff})
-- pos, normal,foot,scale, life, lengh, yawoff
end
-- Footprint logic
local BL = {"npc_hunter","monster_bigmomma","npc_vortigaunt","npc_dog","npc_fastzombie","npc_stalker"} -- Blacklist footprints
local function CanPrint(ent)
local c = ent:GetClass()
for i,v in ipairs(BL) do
if find(c, v,1,true) then return false end
end
if find(ent:GetModel(),"_torso",1,true) then return false end
return true
end
hook.Add("StormFox2.terrain.footstep", "StormFox2.terrain.makefootprint", function(ent, foot, sSnd, sTexture, bReplace )
if foot < 0 then return end -- Invalid foot
if not bReplace and bAlwaysFootstep then -- This is a cold map, check for snow
if not find(sTexture:lower(),"snow",1,true) then return end
elseif bReplace then -- This is terrain
local cT = StormFox2.Terrain.GetCurrent()
if not cT then return end
if not cT.footprints then return end
else -- Invalid
return
end
if not CanPrint(ent) then return end
if not StormFox2.Setting.GetCache("footprint_enabled",true) then return end
if StormFox2.Setting.GetCache("footprint_playeronly",false) and not ent:IsPlayer() then return end
local n_max = StormFox2.Setting.GetCache("footprint_max",200)
if #prints > n_max then
table.remove(prints, 1)
end
AddPrint(ent,foot)
end)
-- Footprint render
local mat = {Material("stormfox2/effects/foot_hq.png"),Material("stormfox2/effects/foot_hql.png"),Material("stormfox2/effects/foot_m.png"),Material("stormfox2/effects/foot_s.png")}
local function getMat(q,foot)
if q == 1 then
if foot == 0 then
return mat[2]
else
return mat[1]
end
end
return mat[q + 1]
end
local DrawQuadEasy = render.DrawQuadEasy
local bC = Color(0,0,0,255)
hook.Add("PreDrawOpaqueRenderables","StormFox2.Terrain.Footprints",function()
if not StormFox2.Setting.GetCache("footprint_enabled",true) then return end
if #prints < 1 then return end
local lp = StormFox2.util.RenderPos()
local del = {}
local footstep_dis = StormFox2.Setting.GetCache("footprint_distance",2000,"The renderdistance for footprints") ^ 2
for k,v in pairs(prints) do
local pos,normal,foot,scale,life,lengh,yawoff = v[1],v[2],v[3],v[4],v[5],v[6],v[7]
local blend = life - CurTime()
if blend <= 0 then
table.insert(del,k)
else
local q = min(ceil(lp:DistToSqr(pos) / footstep_dis),4)
if q >= 4 then continue end
render.SetMaterial(getMat(q,foot))
if foot == 0 and q > 1 then
DrawQuadEasy( pos + Vector(0,0,q / 3 + 1), normal, 6 * scale, 10 * scale * lengh, bC, yawoff )
else
DrawQuadEasy( pos + Vector(0,0,q / 3), normal, -6 * scale, 10 * scale * lengh, bC, yawoff )
end
end
end
for i = #del,1,-1 do
table.remove(prints,del[i])
end
end)
end
-- If the map is cold or has snow, always check for footsteps.
bAlwaysFootstep = StormFox2.Map.IsCold() or StormFox2.Map.HasSnow() -- This is a cold map.
if CLIENT then
StormFox2.Setting.AddCL("footprint_enabled",true) -- Add footprint setting
StormFox2.Setting.AddCL("footprint_max",200) -- Add footprint setting
StormFox2.Setting.AddCL("footprint_distance",2000) -- Add footprint setting
StormFox2.Setting.AddCL("footprint_playeronly",false) -- Add footprint setting
end
StormFox2.Setting.AddSV("footprint_enablelogic",true)