--[[
| 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)
