diff --git a/lua/entities/gmod_wire_ledtape.lua b/lua/entities/gmod_wire_ledtape.lua new file mode 100644 index 00000000..43fb23cf --- /dev/null +++ b/lua/entities/gmod_wire_ledtape.lua @@ -0,0 +1,340 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire LED Tape Controller" +ENT.WireDebugName = "LED Tape" + +function ENT:SharedInit() + self.Color = Color(255,255,255) + self.Path = {} +end + +Wire_LEDTape = Wire_LEDTape or {} + +Wire_LEDTape.MaxPoints = 256 +Wire_LEDTape.NumBits = math.ceil( math.log(Wire_LEDTape.MaxPoints, 2) ) + +if CLIENT then + + --[[ + name = tooltip to display in the spawnmenu ........................ (required) + sprite = path to 3x width sprite material ........................... (optional) + scale = scaled texture height divided by original texture height ... (required with sprite) + backlit = draw fullbright base texture even when using a sprite? ..... (optional) + connect = connect the beams in the sprite texture? ................... (optional) + ]]-- + + Wire_LEDTape.materialData = { + ["fasteroid/ledtape01"] = { + name = "5050 Sparse", + sprite = "fasteroid/ledtape01_sprite", + scale = 512 / 904 + }, + ["fasteroid/ledtape02"] = { + name = "5050 Dense", + sprite = "fasteroid/ledtape02_sprite", + scale = 512 / 434 + }, + ["cable/white"] = { + name = "White Cable" + }, + ["arrowire/arrowire2"] = { + name = "Glowing Arrows", + }, + ["fasteroid/elwire"] = { + name = "Electroluminescent Wire", + sprite = "fasteroid/elwire_sprite", + scale = 256 / 2048, + backlit = true + }, + } + + local DEFAULT_SCALE = 0.5 + + local LIGHT_UPDATE_INTERVAL = CreateClientConVar( "wire_ledtape_lightinterval", "0.5", true, false, "How often environmental lighting on LED tape is calculated", 0 ) + + local LIGHT_DIRECTIONS = { + Vector(1,0,0), + Vector(-1,0,0), + Vector(0,1,0), + Vector(0,-1,0), + Vector(0,0,1), + Vector(0,0,-1) + } + + local function getLitNodeColor(node) + if not node.lighting or node.nextlight < CurTime() then + local lightsum = Vector() + local pos = node[1]:LocalToWorld( node[2] ) + for _, dir in ipairs(LIGHT_DIRECTIONS) do + lightsum:Add( render.ComputeLighting(pos, dir) ) + end + lightsum:Mul( 1 / #LIGHT_DIRECTIONS ) + node.lighting = lightsum:ToColor() + node.nextlight = CurTime() + LIGHT_UPDATE_INTERVAL:GetFloat() + end + return node.lighting + end + + + -- This system prevents calling LocalToWorld hundreds of times every frame, as it strains the garbage collector. + -- This is a necessary evil to prevent stuttering. + local LocalToWorld_NoGarbage_Ents = {} + + local function LocalToWorld_NoGarbage(ent, pos) + ent.LEDTapeVecs = ent.LEDTapeVecs or {} + local LEDTapeVecs = ent.LEDTapeVecs + + if ent.LEDTapeLastPos == ent:GetPos() and ent.LEDTapeLastAng == ent:GetAngles() and LEDTapeVecs[pos] then + return LEDTapeVecs[pos] + end + + LEDTapeVecs[pos] = ent:LocalToWorld(pos) + LocalToWorld_NoGarbage_Ents[ent] = true -- update positions at the end + + return LEDTapeVecs[pos] + end + + local function LocalToWorld_NoGarbage_End() + for ent, _ in pairs(LocalToWorld_NoGarbage_Ents) do + if not IsValid(ent) then continue end + ent.LEDTapeLastPos = ent:GetPos() + ent.LEDTapeLastAng = ent:GetAngles() + end + LocalToWorld_NoGarbage_Ents = {} + end + + hook.Add("PostDrawOpaqueRenderables","LEDTapeCleanup",LocalToWorld_NoGarbage_End) + + local function DrawBeams(width, scrollmul, mater, path, getColor, extravertex) + + if not IsValid(path[1][1]) then return end + + local scroll = 0 + local beam = render.AddBeam + + local beam2 = extravertex and beam or function() end -- branchless programming ftw + local vertexnum = extravertex and 3 or 2 + + scrollmul = scrollmul / width -- scale this + + render.SetMaterial(mater) + render.StartBeam(#path * vertexnum) + + local node1 = path[1] + + local pt1 = LocalToWorld_NoGarbage(node1[1], node1[2]) + + beam(pt1, width, scroll, getColor(node1)) + + for i = 2, #path do + local node2 = path[i] + local nodeEnt = node2[1] + if not IsValid(nodeEnt) then continue end + local nodeOffset = node2[2] + + local pt2 = LocalToWorld_NoGarbage(nodeEnt, nodeOffset) + local distance = pt2:Distance(pt1) * scrollmul * 0.5 + + beam( pt1, width, scroll, getColor(node1)) + scroll = scroll + distance + beam( pt2, width, scroll, getColor(node2)) + beam2( pt2, width, scroll, getColor(node2)) -- add another point if extravertex is set, prevents some sprites from looking yucky + + pt1 = pt2 + node1 = node2 + end + + beam(pt1, width, scroll, getColor(node1)) + + render.EndBeam() + return pt1 + end + + function Wire_LEDTape.DrawShaded(width, scrollmul, mater, path) + return DrawBeams(width, scrollmul, mater, path, getLitNodeColor) + end + + function Wire_LEDTape.DrawFullbright(width, scrollmul, color, mater, path, extravert) + return DrawBeams(width, scrollmul, mater, path, function(node) return color end, extravert) + end + + function ENT:Initialize() + + self:SharedInit() + self.ScrollMul = DEFAULT_SCALE + + if CLIENT then + + net.Start("LEDTapeData") + net.WriteEntity(self) + net.WriteBool(true) -- request full update + net.SendToServer() + + local DrawShaded = Wire_LEDTape.DrawShaded + local DrawFullbright = Wire_LEDTape.DrawFullbright + hook.Add("PostDrawOpaqueRenderables", self, function() + + if #self.Path < 2 then return end + + if self.SpriteMaterial then + + if self.Backlit then + DrawFullbright(self.Width, self.ScrollMul / 3, self.Color, self.BaseMaterial, self.Path, false) + else + DrawShaded(self.Width, self.ScrollMul / 3, self.BaseMaterial, self.Path) + end + + DrawFullbright(self.Width * 3, self.ScrollMul, self.Color, self.SpriteMaterial, self.Path, not self.Connect) + + else + DrawFullbright(self.Width, self.ScrollMul, self.Color, self.BaseMaterial, self.Path) + end + + end) + end + + self:SetOverlayText("LED Tape Controller") + + end + + function ENT:Think() + self.Color.r = self:GetNW2Int("LedTape_R") + self.Color.g = self:GetNW2Int("LedTape_G") + self.Color.b = self:GetNW2Int("LedTape_B") + end + + net.Receive("LEDTapeData", function() + + local controller = net.ReadEntity() + if not IsValid(controller) then return end + + local full = net.ReadBool() + + controller.Width = net.ReadFloat() + local mater = net.ReadString() + + controller.BaseMaterial = Material( mater ) + + local metadata = Wire_LEDTape.materialData[mater] + + if metadata then + controller.SpriteMaterial = metadata.sprite and Material( metadata.sprite ) + controller.ScrollMul = metadata.scale or DEFAULT_SCALE + controller.Connect = metadata.connect or false + controller.Backlit = metadata.backlit or false + end + + if not full then return end + + local pathLength = net.ReadUInt(Wire_LEDTape.NumBits) + 1 + for i = 1, pathLength do + table.insert(controller.Path,{net.ReadEntity(), net.ReadVector()}) + end + + controller:SetOverlayText("LED Tape Controller\n(" .. (pathLength-1) .. " Segments)") + + end ) + +end + + +if SERVER then + + util.AddNetworkString("LEDTapeData") + net.Receive("LEDTapeData", function(len, ply) + local controller = net.ReadEntity() + local full = net.ReadBool() + if not IsValid(controller) then return end + table.insert(controller.DownloadQueue, {ply = ply, full = full}) + end ) + + function ENT:Initialize() + BaseClass.Initialize(self) + WireLib.CreateInputs(self, { + "Color [VECTOR]" + }) + self:SharedInit() + self.DownloadQueue = {} + end + + function ENT:SendMaterialUpdate() + net.WriteFloat ( self.Width ) + net.WriteString ( self.BaseMaterial ) + end + + function ENT:SendFullUpdate() + self:SendMaterialUpdate() + net.WriteUInt(#self.Path - 1, Wire_LEDTape.NumBits) + for k, node in ipairs(self.Path) do + net.WriteEntity(node[1]) + net.WriteVector(node[2]) + end + end + + function ENT:Think() + + if self.BaseMaterial and self.Width and self.Path then -- don't send updates with nil stuff! + for _, request in ipairs(self.DownloadQueue) do + net.Start("LEDTapeData") + + net.WriteEntity( self ) + net.WriteBool(request.full) + + if request.full then self:SendFullUpdate() + else self:SendMaterialUpdate() end + + net.Send(request.ply) + end + self.DownloadQueue = {} + end + + BaseClass.Think( self ) + self:NextThink(CurTime() + 0.05) + return true + end + + -- duplicator support + function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + info.BaseMaterial = self.BaseMaterial + info.Width = self.Width + info.Path = {} + for k, node in ipairs(self.Path) do + info.Path[k] = {node[1]:EntIndex(), node[2]} + end + return info + end + + function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + self.BaseMaterial = info.BaseMaterial + self.Width = info.Width + self.Path = {} + for k, node in ipairs(info.Path) do + self.Path[k] = { GetEntByID(node[1], game.GetWorld()), node[2] } + end + end + duplicator.RegisterEntityClass("gmod_wire_ledtape", WireLib.MakeWireEnt, "Data") + + function ENT:TriggerInput(iname, value) + if (iname == "Color") then + self:SetNW2Int("LedTape_R", value[1]) + self:SetNW2Int("LedTape_G", value[2]) + self:SetNW2Int("LedTape_B", value[3]) + end + end + + function MakeWireLEDTapeController( pl, Pos, Ang, model, path, width, material ) + local controller = WireLib.MakeWireEnt(pl, {Class = "gmod_wire_ledtape", Pos = Pos, Angle = Ang, Model = model}) + if not IsValid(controller) then return end + + controller.Path = path + controller.Width = math.Clamp(width,0.1,4) + controller.BaseMaterial = material + + return controller + end + +end + + diff --git a/lua/weapons/gmod_tool/stools/wire_ledtape.lua b/lua/weapons/gmod_tool/stools/wire_ledtape.lua new file mode 100644 index 00000000..4ba247f0 --- /dev/null +++ b/lua/weapons/gmod_tool/stools/wire_ledtape.lua @@ -0,0 +1,188 @@ +WireToolSetup.setCategory( "Visuals" ) +WireToolSetup.open( "ledtape", "LED Tape", "gmod_wire_ledtape", nil, "LED Tape Controllers" ) +WireToolSetup.BaseLang() +WireToolSetup.SetupMax(8) + +--[[ + I would have used the original wirepath system, but it looks like it was never designed for a tool like this. + So we're stuck with whatever horribleness I come up with. Sorry. + - Fast +]]-- + +TOOL.ClientConVar = { + material = "cable/white", + width = "1", + model = "models/beer/wiremod/hydraulic.mdl", + modelsize = "" +} + +TOOL.ToolPath = {} +TOOL.CurWidth = 1 +TOOL.Scale = 1 + +function TOOL:GetConVars() + return self:GetClientInfo("material") or "cable/rope", self:GetClientNumber("width", 3) +end + +local function isLookingAtController(trace) + return trace.Entity and trace.Entity:GetClass() == "gmod_wire_ledtape" +end + +if CLIENT then + + language.Add( "Tool.wire_ledtape.name", "LED Tape Tool (Wire)" ) + language.Add( "Tool.wire_ledtape.desc", "Makes a group of ropes with controllable color" ) + language.Add( "Tool.wire_ledtape.width", "Width:" ) + language.Add( "Tool.wire_ledtape.material", "Material:" ) + TOOL.Information = { + { name = "right_0", stage = 0, text = "Start LED Tape" }, + { name = "left_0", stage = 0, text = "Update material of existing controller" }, + { name = "right_1", stage = 1, text = "Place another point" }, + { name = "left_2", stage = 2, text = "Finish tape and place controller" }, + { name = "right_2", stage = 2, text = "Place more points" }, + { name = "left_3", stage = 3, text = "Finish tape and place controller" }, + } + + for _, data in ipairs(TOOL.Information) do -- needed for wire extras, not sure why + language.Add( "Tool.wire_ledtape." .. data.name, data.text ) + end + + WireToolSetup.setToolMenuIcon( "icon16/chart_line.png" ) + + local YELLOW = Color(255,255,0) -- it wastes memory and strains the garbage collector + + function TOOL:Holster() + self.ToolPath = {} + self:ReleaseGhostEntity() + end + + function TOOL:Preview() + + if #self.ToolPath < 1 then hook.Remove("PostDrawOpaqueRenderables","LEDTape_Preview") return end -- something happened, bail + + render.SetMaterial(self.CurMater) + + local pt2 = Wire_LEDTape.DrawFullbright(self.CurWidth, self.ScrollMul / 3, color_white, self.CurMater, self.ToolPath) + + if pt2 and (#self.ToolPath + 1 < Wire_LEDTape.MaxPoints) then + local eyetrace = LocalPlayer():GetEyeTrace() + local pt3 = eyetrace.HitPos + eyetrace.HitNormal * self.CurWidth * 0.5 + render.DrawLine( pt2, pt3, YELLOW ) + end + + end + +end + +if SERVER then + + function TOOL:MakeEnt(ply, model, Ang, trace) + local material, width = self:GetConVars() + return MakeWireLEDTapeController(ply, trace.HitPos, Ang, model, self.ToolPath, width, material) + end + + function TOOL:Holster() + self:SetStage(0) + self.ToolPath = {} + end + +end + +function TOOL:RightClick( trace ) + + if not trace.Hit or ( trace.Entity:IsValid() and trace.Entity:IsPlayer() ) or trace.Entity:IsWorld() then return end + if ( SERVER and not util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone ) ) then return false end + + local mater, width = self:GetConVars() + + if self:GetStage() == 3 then return false end + + if self:GetStage() == 0 then + self.ToolPath = {} + + self:SetStage(1) + + self.CurMater = Material and Material(mater) or mater -- material on client, string on server + self.CurWidth = width + + if CLIENT then + + hook.Add("PostDrawOpaqueRenderables","LEDTape_Preview", function() self:Preview() end) + + local metadata = Wire_LEDTape.materialData[mater] + self.ScrollMul = metadata and metadata.scale or 1 + + end + end + + local nextPos = trace.Entity:WorldToLocal(trace.HitPos + trace.HitNormal * self.CurWidth * 0.5) + + if #self.ToolPath > 0 then + local prevPoint = self.ToolPath[ #self.ToolPath ] + if prevPoint[1] == trace.Entity and prevPoint[2]:IsEqualTol( nextPos, 0.1 ) then -- disallow placing the same point + return false + end + end + + table.insert(self.ToolPath, {trace.Entity, nextPos}) + + if #self.ToolPath == 1 then + self:SetStage(2) + elseif #self.ToolPath == Wire_LEDTape.MaxPoints then + self:SetStage(3) + end + + return true + +end + +function TOOL:LeftClick( trace ) + + if self:GetStage() == 0 and isLookingAtController(trace) then + if CLIENT then return true end -- only server + local controller = trace.Entity + controller.BaseMaterial = self:GetConVars() + for _, ply in ipairs(player.GetHumans()) do + table.insert(controller.DownloadQueue, {ply = ply, full = false}) + end + return true + end + + if self:GetStage() < 1 or isLookingAtController(trace) then return false end + + local ply = self:GetOwner() + self:SetStage(0) + + if SERVER then + local controller = self:LeftClick_Make(trace, ply) + if isbool(controller) then return controller end + self:LeftClick_PostMake(controller, ply, trace) + end + + self.ToolPath = {} + + return true + +end + +-- I just wanted to match the vanilla look, not write a horrible hacky workaround! +local function emptyRopeMaterialPanel(panel) + table.Empty(panel.Controls) + for k, v in ipairs( panel.List:GetItems() ) do v:Remove() end +end + +function TOOL.BuildCPanel(panel) + + WireToolHelpers.MakeModelSizer(panel, "wire_ledtape_modelsize") + WireDermaExts.ModelSelect(panel, "wire_ledtape_model", list.Get( "Wire_Hydraulic_Models" ), 1, true) + + panel:NumSlider("#Tool.wire_ledtape.width","wire_ledtape_width",0,4,2) + local ropeMaterials = panel:AddControl( "RopeMaterial", { Label = "#Tool.wire_ledtape.material", convar = "wire_ledtape_material" } ) + + emptyRopeMaterialPanel(ropeMaterials) -- remove garry's materials + + for texpath, data in pairs( Wire_LEDTape.materialData ) do -- add mine + ropeMaterials:AddMaterial(data.name, texpath) + end + +end \ No newline at end of file diff --git a/materials/fasteroid/elwire.vmt b/materials/fasteroid/elwire.vmt new file mode 100644 index 00000000..0cabe2d1 --- /dev/null +++ b/materials/fasteroid/elwire.vmt @@ -0,0 +1,8 @@ +"Cable" +{ + "$basetexture" "fasteroid/elwire" + "$vertexcolor" 1 + $alphatest 1 + $alphatestreference .5 + $allowalphatocoverage 1 +} \ No newline at end of file diff --git a/materials/fasteroid/elwire.vtf b/materials/fasteroid/elwire.vtf new file mode 100644 index 00000000..60c4632c Binary files /dev/null and b/materials/fasteroid/elwire.vtf differ diff --git a/materials/fasteroid/elwire_sprite.vmt b/materials/fasteroid/elwire_sprite.vmt new file mode 100644 index 00000000..f6d38eb2 --- /dev/null +++ b/materials/fasteroid/elwire_sprite.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "fasteroid/elwire_sprite" + "$additive" 1 + "$vertexcolor" 1 + "$decal" 1 +} \ No newline at end of file diff --git a/materials/fasteroid/elwire_sprite.vtf b/materials/fasteroid/elwire_sprite.vtf new file mode 100644 index 00000000..437aea81 Binary files /dev/null and b/materials/fasteroid/elwire_sprite.vtf differ diff --git a/materials/fasteroid/ledtape01.vmt b/materials/fasteroid/ledtape01.vmt new file mode 100644 index 00000000..a7442d44 --- /dev/null +++ b/materials/fasteroid/ledtape01.vmt @@ -0,0 +1,8 @@ +"Cable" +{ + "$basetexture" "fasteroid/ledtape01" + "$vertexcolor" 1 + $alphatest 1 + $alphatestreference .5 + $allowalphatocoverage 1 +} diff --git a/materials/fasteroid/ledtape01.vtf b/materials/fasteroid/ledtape01.vtf new file mode 100644 index 00000000..9422b9a5 Binary files /dev/null and b/materials/fasteroid/ledtape01.vtf differ diff --git a/materials/fasteroid/ledtape01_sprite.vmt b/materials/fasteroid/ledtape01_sprite.vmt new file mode 100644 index 00000000..523c55e8 --- /dev/null +++ b/materials/fasteroid/ledtape01_sprite.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fasteroid/ledtape01_sprite" + "$nocull" 1 + "$additive" 1 + "$vertexcolor" 1 + "$decal" 1 +} diff --git a/materials/fasteroid/ledtape01_sprite.vtf b/materials/fasteroid/ledtape01_sprite.vtf new file mode 100644 index 00000000..5f0ef57b Binary files /dev/null and b/materials/fasteroid/ledtape01_sprite.vtf differ diff --git a/materials/fasteroid/ledtape02.vmt b/materials/fasteroid/ledtape02.vmt new file mode 100644 index 00000000..cf7ca3bd --- /dev/null +++ b/materials/fasteroid/ledtape02.vmt @@ -0,0 +1,5 @@ +"Cable" +{ + "$basetexture" "fasteroid/ledtape02" + "$vertexcolor" 1 +} diff --git a/materials/fasteroid/ledtape02.vtf b/materials/fasteroid/ledtape02.vtf new file mode 100644 index 00000000..0f53206b Binary files /dev/null and b/materials/fasteroid/ledtape02.vtf differ diff --git a/materials/fasteroid/ledtape02_sprite.vmt b/materials/fasteroid/ledtape02_sprite.vmt new file mode 100644 index 00000000..12ec82d6 --- /dev/null +++ b/materials/fasteroid/ledtape02_sprite.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fasteroid/ledtape02_sprite" + "$nocull" 1 + "$additive" 1 + "$vertexcolor" 1 + "$decal" 1 +} diff --git a/materials/fasteroid/ledtape02_sprite.vtf b/materials/fasteroid/ledtape02_sprite.vtf new file mode 100644 index 00000000..e33b5bd8 Binary files /dev/null and b/materials/fasteroid/ledtape02_sprite.vtf differ