--[[ | 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/ --]] AddCSLuaFile() ENT.Type = "anim" ENT.Base = "base_anim" ENT.PrintName = "Invisible streetlight" ENT.Author = "Nak" ENT.Purpose = "Light area up" ENT.Instructions = "Place it somewhere" ENT.Category = "StormFox2" ENT.Editable = true ENT.Spawnable = false ENT.AdminOnly = true ENT.RenderGroup = RENDERGROUP_TRANSLUCENT ENT.DisableDuplicator = true local RENDER_DISTANCE = 3500 ^ 2 hook.Add("EntityKeyValue", "StormFox2.SLightinvis", function(ent, key, val) if not IsValid( ent ) then return end if ent:GetClass() ~= "stormfox_streetlight_invisible" then return end key = key:lower() if ent._launch then if key == "lighttype" then ent:SetLightType( tonumber( val ) or 0 ) elseif key == "lightcolour" then ent:SetLightColor( Vector( val ) or Vector(1,1,1) ) elseif key == "lightcolor" then ent:SetLightColor( Vector( val ) or Vector(1,1,1) ) elseif key == "lightbrightness" then ent:SetLightBrightness( tonumber( val ) or 0 ) end else ent._launchoption = ent._launchoption or {} ent._launchoption[key] = val end end) function ENT:Initialize() if SERVER then self._launch = true self:SetModel( "models/hunter/blocks/cube025x025x025.mdl" ) self:PhysicsInit( SOLID_VPHYSICS ) self:SetMoveType( MOVETYPE_NONE ) self:SetSolid( SOLID_VPHYSICS ) self:AddFlags( FL_WORLDBRUSH ) self:AddEFlags(EFL_NO_PHYSCANNON_INTERACTION) self:SetKeyValue("gmod_allowphysgun", 0) self:AddEFlags( EFL_NO_DAMAGE_FORCES ) self:SetCollisionGroup(COLLISION_GROUP_WORLD) self:DrawShadow(false) self:SetLightColor(Vector(1,1,1)) self:SetLightBrightness(1) if self._launchoption then if self._launchoption["lighttype"] then self:SetLightType( tonumber( self._launchoption["lighttype"] ) ) end local col = self._launchoption["lightcolor"] or self._launchoption["lightcolour"] if col then self:SetLightColor( Vector( col ) or Vector(1,1,1) ) end if self._launchoption["lightbrightness"] then self:SetLightBrightness( tonumber( self._launchoption["lightbrightness"] ) ) end end end end hook.Add( "PhysgunPickup", "StormFox2.StreetLight.DisallowPickup", function( ply, ent ) if ent and ent:GetClass() == "stormfox_streetlight_invisible" then return false end end ) hook.Add("CanPlayerUnfreeze", "StormFox2.StreetLight.DisallowUnfreeze", function( ply, ent ) if ent and ent:GetClass() == "stormfox_streetlight_invisible" then return false end end) local INVALID = 0 local POINTLIGHT= 1 local SPOTLIGHT = 2 local FAKESPOT = 3 local options = { ["2D Sprite"] = POINTLIGHT, ["2D Beam"] = FAKESPOT, ["ProjectedTexture"] = SPOTLIGHT } function ENT:SetupDataTables() self:NetworkVar( "Int", 0, "LightType", { KeyName = "LightType", Edit = { type = "Combo", values = options } }) self:NetworkVar( "Vector", 0, "LightColor",{ KeyName = "LightColor", Edit = { type = "VectorColor" } } ) self:NetworkVar( "Float", 0, "LightBrightness",{ KeyName = "Brightness", Edit = { type = "Float", min = 0, max = 10 } } ) end function ENT:CanProperty(_, str) if str == "skin" then return false elseif str == "drive" then return false elseif str == "collision" then return false elseif str == "persist" then return true end return true end if CLIENT then local base_mat = Material("stormfox2/entities/streetlight_invis") local base_mat2 = Material("stormfox2/entities/streetlight_invis_point") local base_mat3 = Material("stormfox2/entities/streetlight_invis_beam") local equip_tool = false local d_tab = {} hook.Add("PostRender", "StormFox2.StreetLights.Update", function() d_tab = {} equip_tool = false local wep = LocalPlayer():GetActiveWeapon() if not wep or not IsValid(wep) then return end if wep:GetClass() ~= "sf2_tool" then return end equip_tool = true d_tab = ents.FindByClass("stormfox_streetlight_invisible") end) hook.Add("PreDrawHalos", "StormFox2.StreetLights.Halo", function() if not equip_tool then return end if not d_tab or #d_tab < 1 then return end halo.Add( d_tab, color_white, 2, 2, 1, true,true ) end) function ENT:DrawSelfCheck() if not equip_tool then return end render.SetMaterial(base_mat) render.DrawBox(self:GetPos(), self:GetAngles(), self:OBBMins(), self:OBBMaxs(), color_white) if self:GetLightType() == POINTLIGHT then render.SetMaterial(base_mat2) else render.SetMaterial(base_mat3) end local s = self:OBBMaxs().x local f = self:GetAngles():Up() render.DrawQuadEasy(self:GetPos() + -f * s * 1.005, -f, s * 2, s * 2, color_white, 0) end -- We use a variable to tell if we should render any lights nearby. Since it can be is costly. local m_render = false local sorter = function(a,b) return a[2] < b[2] end local Max_PointLight = 10 local Max_SpotLight = 4 local Max_ShadowLight = 2 local Max_Fake = 40 _STORMFOX2_PTLIGHT = _STORMFOX2_PTLIGHT or { ents = {}, used = 0 } -- Handles and returns a project texture. -- Returns the projected texture and ID local function RequestLight() _STORMFOX2_PTLIGHT.used = _STORMFOX2_PTLIGHT.used + 1 if _STORMFOX2_PTLIGHT.used > Max_SpotLight then return end -- Max 4 local pfent = _STORMFOX2_PTLIGHT.ents[_STORMFOX2_PTLIGHT.used] if IsValid(pfent) then pfent:SetEnableShadows( _STORMFOX2_PTLIGHT.used < Max_ShadowLight ) return pfent, _STORMFOX2_PTLIGHT.used end _STORMFOX2_PTLIGHT.ents[_STORMFOX2_PTLIGHT.used] = ProjectedTexture() pfent = _STORMFOX2_PTLIGHT.ents[_STORMFOX2_PTLIGHT.used] if not IsValid(fent) then return end pfent:SetTexture( "effects/flashlight001" ) pfent:SetBrightness(2) pfent:SetEnableShadows( _STORMFOX2_PTLIGHT.used < Max_ShadowLight ) return pfent, _STORMFOX2_PTLIGHT.used end local cost = {} local row = 0 hook.Add("PostDrawTranslucentRenderables", "StormFox2.Streetlights", function(a, b, c) if a or b or c or not (m_render or _STORMFOX2_PTLIGHT.ents[1]) then return end -- Check the light-range local b_on = StormFox2.Map.GetLightRaw() < 20 if row < 6 and b_on then -- On row = math.min(row + FrameTime() * 0.75, 6) elseif row > 0 and not b_on then row = math.max(row - FrameTime() * 0.95, 0) end if row <= 0 and not _STORMFOX2_PTLIGHT.ents[1] then return end -- No lights are on. No need to calculate things local view = StormFox2.util.GetCalcView() local rp = view.pos + view.ang:Forward() * 250 -- Sort the lights. So we get the closest first local t = {} local nID = math.Round(row, 0) for k, v in ipairs(ents.FindByClass("stormfox_streetlight_invisible")) do if v:EntIndex() % 6 >= row then continue end table.insert(t, {v,math.max(0, v:GetPos():DistToSqr(rp) - (v._tDis2 or 0))}) end table.sort(t, sorter) -- Setup cost calculation cost[1] = Max_PointLight cost[2] = Max_SpotLight cost[3] = Max_Fake local vAng = view.ang local vNorm = vAng:Forward() _STORMFOX2_PTLIGHT.used = 0 -- Draw lights local fD = math.Clamp((1 - (StormFox2.Fog.GetDistance() / 6000)),0, 1) for _, tab in ipairs(t) do local n = 1 - (tab[2] / RENDER_DISTANCE) if n <= 0 then continue end tab[1]:DrawLight(n * (0.85 + 0.45 * fD),view.pos,vAng,vNorm) end for i = 4, 1, -1 do if not IsValid(_STORMFOX2_PTLIGHT.ents[i]) then continue end if _STORMFOX2_PTLIGHT.used < i then _STORMFOX2_PTLIGHT.ents[i]:Remove() _STORMFOX2_PTLIGHT.ents[i] = nil else break end end m_render = false end) function ENT:DrawTranslucent() if not m_render then -- Wait until some lights is in render local rp = StormFox2.util.RenderPos() local d = rp:DistToSqr(self:GetPos()) if d > RENDER_DISTANCE then return end end self:DrawSelfCheck() -- Draw if tool is out m_render = true end local m_lamp = Material("stormfox2/effects/light_beam") local col = Color(255,255,255,155) function ENT:TraceDown() if self._tPos then return self._tPos, self._tDis, self._norm end local norm = -self:GetAngles():Up() local tr = util.TraceLine({ start = self:GetPos() + norm * 100, endpos = self:GetPos() + norm * 1000, filter = self, mask = MASK_SOLID_BRUSHONLY }) self._tPos = tr.HitPos or self:GetPos() self._tDis = self._tPos:Distance(self:GetPos()) self._tDis2 = self._tDis^2 self._norm = norm return self._tPos, self._tDis, norm end local m_spot = Material('stormfox2/effects/spotlight') local m_flash = Material('stormfox2/effects/flashlight_a') function ENT:DrawPoint(dist, add, size, ignoreB) size = (size or 80) * (not ignoreB and self:GetLightBrightness() or 1) local v = self:GetLightColor() col.a = 255 * dist col.r = v.x * 255 col.g = v.y * 255 col.b = v.z * 255 render.SetMaterial(m_spot) if add then render.DrawSprite(self:GetPos() + add, size, size, col) else render.DrawSprite(self:GetPos(), size, size, col) end end local c_flash = Color(255,255,255,255) function ENT:DrawSpot(dist, fake) dist = (dist - .5) * 2 if dist <= 0 then return end if not fake then local pEnt, id = RequestLight() if not IsValid(pEnt) then return end local v = self:GetLightColor() local bright = math.Round(dist * 2, 1) * self:GetLightBrightness() if self._lastB == bright and (self._lastID or -1) == id and self._r == v.x and self._g == v.y and self._b == v.z then return end self._lastB = bright self._r = v.x self._g = v.y self._b = v.z pEnt:SetBrightness(bright) local pos, dis, norm = self:TraceDown() local up = self:GetAngles():Up() local aDown = -up:Angle() pEnt:SetPos(self:GetPos() + norm * 20) pEnt:SetAngles(norm:Angle()) pEnt:SetFarZ( dis * 1.2 ) pEnt:SetTexture( "effects/flashlight001" ) pEnt:SetColor(Color(v.x * 255, v.y * 255, v.z * 255)) pEnt:Update() self._lastID = id else local pos, dis = self:TraceDown() local up = self:GetAngles():Up() c_flash.a = math.min(255, 15 * dist * self:GetLightBrightness()) local v = self:GetLightColor() if c_flash.a <= 0 then return end local s = (dis - 20) * 1.6 render.SetMaterial(m_flash) render.DrawQuadEasy(pos + up, up, s, s, c_flash, 0) end end -- Dist goes from 1 to 0. Where 0 is RENDER_DISTANCE units away. function ENT:DrawBeam(dist, vPos) local vNorm2 = (vPos - self:GetPos()):Angle():Forward() local pos, dis = self:TraceDown() local up = self:GetAngles():Up() local dot = up:Dot(vNorm2) local abs_dot = math.abs(dot) if abs_dot < 0.9 then local a = dist * 255 * math.Clamp(0.9 - abs_dot,0,1) col.a = math.Clamp(a * self:GetLightBrightness(), 0, 255) local v = self:GetLightColor() col.r = v.x * 255 col.g = v.y * 255 col.b = v.z * 255 render.SetMaterial(m_lamp) --local m = (pos - self:GetPos()):GetNormalized() --render.StartBeam( 3 ) -- render.AddBeam( self:GetPos(), dis * 0.8, 0, col ) -- render.AddBeam( self:GetPos() + m * dis / 8,dis * 0.8, 0.1, col ) -- render.AddBeam( pos , dis * 0.8, 0.99, col ) --render.EndBeam() render.DrawBeam(self:GetPos(),pos , dis * 0.8, 0, 0.99, col) end if dot < 0 then self:DrawPoint(dist * -dot, vNorm2 * 15 - up * 2, dis / 3, true) end end function ENT:DrawLight(dist,vPos,vAng,vNorm) dist = math.min(dist, 1) local lType = self:GetLightType() if lType == 0 then return end -- Invalid local fake = false if cost[lType] <= 0 then-- Ignore if lType == SPOTLIGHT then fake = true end end cost[lType] = cost[lType] - 1 if lType == POINTLIGHT then self:DrawPoint(dist) elseif lType == FAKESPOT then self:DrawBeam(dist,vPos,vNorm) elseif lType == SPOTLIGHT then self:DrawBeam(dist,vPos,vNorm) self:DrawSpot(dist, fake) end end else -- Save local file_location = "stormfox2/streetlights/" .. game.GetMap() .. ".json" hook.Add( "ShutDown", "StormFox2.Streetlights.Save", function() local tab = {} for k, ent in ipairs(ents.FindByClass("stormfox_streetlight_invisible")) do if ent:CreatedByMap() then continue end table.insert(tab, { ent:GetLightType(), ent:GetPos(), ent:GetAngles(), ent:GetLightColor(), ent:GetLightBrightness() }) end local out = util.TableToJSON( tab ) StormFox2.FileWrite(file_location, out) end) hook.Add( "InitPostEntity", "StormFox2.Streetlights.Load", function() local fil = file.Read(file_location, "DATA" ) if not fil then return end local tab = util.JSONToTable( fil ) if ( !tab ) then return end for k,v in ipairs(tab) do local ent = ents.Create("stormfox_streetlight_invisible") ent:SetLightType(v[1]) ent:SetPos(v[2]) ent:SetAngles(v[3]) ent:Spawn() if not v[4] then v[4] = Vector(1,1,1) end ent:SetLightColor(Vector(v[4].x, v[4].y, v[4].z)) ent:SetLightBrightness(v[5] or 1) end end ) end