Files
wnsrc/gamemodes/helix/plugins/3dpanel.lua
lifestorm 9c918c46e5 Upload
2024-08-04 23:12:27 +03:00

434 lines
12 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/
--]]
local PLUGIN = PLUGIN
PLUGIN.name = "3D Panels"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Adds web panels that can be placed on the map."
-- List of available panel dislays.
PLUGIN.list = PLUGIN.list or {}
CAMI.RegisterPrivilege({
Name = "Helix - Manage Panels",
MinAccess = "admin"
})
if (SERVER) then
util.AddNetworkString("ixPanelList")
util.AddNetworkString("ixPanelAdd")
util.AddNetworkString("ixPanelRemove")
-- Called when the player is sending client info.
function PLUGIN:PlayerInitialSpawn(client)
-- Send the list of panel displays.
timer.Simple(1, function()
if (IsValid(client)) then
local json = util.TableToJSON(self.list)
local compressed = util.Compress(json)
local length = compressed:len()
net.Start("ixPanelList")
net.WriteUInt(length, 32)
net.WriteData(compressed, length)
net.Send(client)
end
end)
end
-- Adds a panel to the list, sends it to the players, and saves data.
function PLUGIN:AddPanel(position, angles, url, scale, brightness)
scale = math.Clamp((scale or 1) * 0.1, 0.001, 5)
brightness = math.Clamp(math.Round((brightness or 100) * 2.55), 1, 255)
-- Find an ID for this panel within the list.
local index = #self.list + 1
-- Add the panel to the list so it can be sent and saved.
self.list[index] = {position, angles, nil, nil, scale, url, nil, brightness}
-- Send the panel information to the players.
net.Start("ixPanelAdd")
net.WriteUInt(index, 32)
net.WriteVector(position)
net.WriteAngle(angles)
net.WriteFloat(scale)
net.WriteString(url)
net.WriteUInt(brightness, 8)
net.Broadcast()
-- Save the plugin data.
self:SavePanels()
end
-- Removes a panel that are within the radius of a position.
function PLUGIN:RemovePanel(position, radius)
-- Default the radius to 100.
radius = radius or 100
local panelsDeleted = {}
-- Loop through all of the panels.
for k, v in pairs(self.list) do
if (k == 0) then
continue
end
-- Check if the distance from our specified position to the panel is less than the radius.
if (v[1]:Distance(position) <= radius) then
panelsDeleted[#panelsDeleted + 1] = k
end
end
-- Save the plugin data if we actually changed anything.
if (#panelsDeleted > 0) then
-- Invert index table to delete from highest -> lowest
panelsDeleted = table.Reverse(panelsDeleted)
for _, v in ipairs(panelsDeleted) do
-- Remove the panel from the list of panels.
table.remove(self.list, v)
-- Tell the players to stop showing the panel.
net.Start("ixPanelRemove")
net.WriteUInt(v, 32)
net.Broadcast()
end
self:SavePanels()
end
-- Return the number of deleted panels.
return #panelsDeleted
end
-- Called after entities have been loaded on the map.
function PLUGIN:LoadData()
self.list = self:GetData() or {}
-- Formats table to sequential to support legacy panels.
self.list = table.ClearKeys(self.list)
end
-- Called when the plugin needs to save information.
function PLUGIN:SavePanels()
self:SetData(self.list)
end
else
local function IsUsingPanelAddTool()
if !IsValid(LocalPlayer()) then return false end
if !IsValid(LocalPlayer():GetActiveWeapon()) then return false end
if !LocalPlayer():GetActiveWeapon():GetClass() then return false end
if !LocalPlayer():GetTool() then return false end
if !LocalPlayer():GetTool().Mode then return false end
if (LocalPlayer():GetActiveWeapon():GetClass() == "gmod_tool" and LocalPlayer():GetTool().Mode == "sh_paneladd") then
return true
end
return false
end
-- Pre-define the zero index in client before the net receives
PLUGIN.list[0] = PLUGIN.list[0] or 0
-- Holds the current cached material and filename.
local cachedPreview = {}
local function CacheMaterial(index)
if (index < 1) then
return
end
local info = PLUGIN.list[index]
local url = !IsUsingPanelAddTool() and info[6] or GetConVar("sh_paneladd_url"):GetString() or ""
if url == "" then return end
local exploded = string.Explode("/", url)
local filename = exploded[#exploded]
local path = "helix/"..Schema.folder.."/"..PLUGIN.uniqueID.."/"
if (file.Exists(path..filename, "DATA")) then
local material = Material("../data/"..path..filename, "noclamp smooth")
if (!material:IsError()) then
info[7] = material
-- Set width and height
info[3] = material:GetInt("$realwidth")
info[4] = material:GetInt("$realheight")
end
else
file.CreateDir(path)
http.Fetch(url, function(body)
file.Write(path..filename, body)
local material = Material("../data/"..path..filename, "noclamp smooth")
if (!material:IsError()) then
info[7] = material
-- Set width and height
info[3] = material:GetInt("$realwidth")
info[4] = material:GetInt("$realheight")
end
end)
end
end
local function UpdateCachedPreview(url)
local path = "helix/"..Schema.folder.."/"..PLUGIN.uniqueID.."/"
-- Gets the file name
local exploded = string.Explode("/", url)
local filename = exploded[#exploded]
if (file.Exists(path..filename, "DATA")) then
local preview = Material("../data/"..path..filename, "noclamp smooth")
-- Update the cached preview if success
if (!preview:IsError()) then
cachedPreview = {url, preview}
else
cachedPreview = {}
end
else
file.CreateDir(path)
http.Fetch(url, function(body)
file.Write(path..filename, body)
local preview = Material("../data/"..path..filename, "noclamp smooth")
-- Update the cached preview if success
if (!preview:IsError()) then
cachedPreview = {url, preview}
else
cachedPreview = {}
end
end)
end
end
-- Receives new panel objects that need to be drawn.
net.Receive("ixPanelAdd", function()
local index = net.ReadUInt(32)
local position = net.ReadVector()
local angles = net.ReadAngle()
local scale = net.ReadFloat()
local url = net.ReadString()
local brightness = net.ReadUInt(8)
if (url != "") then
PLUGIN.list[index] = {position, angles, nil, nil, scale, url, nil, brightness}
CacheMaterial(index)
PLUGIN.list[0] = #PLUGIN.list
end
end)
net.Receive("ixPanelRemove", function()
local index = net.ReadUInt(32)
table.remove(PLUGIN.list, index)
PLUGIN.list[0] = #PLUGIN.list
end)
-- Receives a full update on ALL panels.
net.Receive("ixPanelList", function()
local length = net.ReadUInt(32)
local data = net.ReadData(length)
local uncompressed = util.Decompress(data)
if (!uncompressed) then
ErrorNoHalt("[Helix] Unable to decompress panel data!\n")
return
end
-- Set the list of panels to the ones provided by the server.
PLUGIN.list = util.JSONToTable(uncompressed)
-- Will be saved, but refresh just to make sure.
PLUGIN.list[0] = #PLUGIN.list
local CacheQueue = {}
-- Loop through the list of panels.
for k, _ in pairs(PLUGIN.list) do
if (k == 0) then
continue
end
CacheQueue[#CacheQueue + 1] = k
end
if (#CacheQueue == 0) then
return
end
timer.Create("ixCache3DPanels", 1, #CacheQueue, function()
if (#CacheQueue > 0) then
CacheMaterial(CacheQueue[1])
table.remove(CacheQueue, 1)
else
timer.Remove("ixCache3DPanels")
end
end)
end)
-- Called after all translucent objects are drawn.
function PLUGIN:PostDrawTranslucentRenderables(bDrawingDepth, bDrawingSkybox)
if (bDrawingDepth or bDrawingSkybox) then
return
end
local toolUsage = IsUsingPanelAddTool()
if toolUsage then UpdateCachedPreview(GetConVar("sh_paneladd_url"):GetString() or "") end
-- Panel preview
if (ix.chat.currentCommand == "paneladd" or toolUsage) then
self:PreviewPanel()
end
-- Store the position of the player to be more optimized.
local ourPosition = LocalPlayer():GetPos()
local panel = self.list
for i = 1, panel[0] do
local position = panel[i][1]
local image = panel[i][7]
-- Older panels do not have a brightness index
local brightness = panel[i][8] or 255
if (panel[i][7] and ourPosition:DistToSqr(position) <= 2796203) then
local width, height = panel[i][3] or image:Width(), panel[i][4] or image:Height()
cam.Start3D2D(position, panel[i][2], panel[i][5] or 0.1)
render.PushFilterMin(TEXFILTER.ANISOTROPIC)
render.PushFilterMag(TEXFILTER.ANISOTROPIC)
surface.SetDrawColor(brightness, brightness, brightness)
surface.SetMaterial(image)
surface.DrawTexturedRect(-width * 0.5, -height * 0.5, width, height)
render.PopFilterMag()
render.PopFilterMin()
cam.End3D2D()
end
end
end
function PLUGIN:ChatTextChanged(text)
if (ix.chat.currentCommand == "paneladd") then
-- Allow time for ix.chat.currentArguments to update
timer.Simple(0, function()
local arguments = ix.chat.currentArguments
if (!arguments[1]) then
return
end
UpdateCachedPreview(arguments[1])
end)
end
end
function PLUGIN:PreviewPanel()
local arguments = ix.chat.currentArguments
local arguments2 = GetConVar("sh_paneladd_url"):GetString()
-- if there's no URL, then no preview.
if IsUsingPanelAddTool() then
arguments = {}
end
if (!arguments[1] and !arguments2 or IsUsingPanelAddTool() and arguments2 and arguments2 == "") then
return
end
-- If the material is valid, preview the panel
if (cachedPreview[2] and !cachedPreview[2]:IsError()) then
local trace = LocalPlayer():GetEyeTrace()
local angles = trace.HitNormal:Angle()
angles:RotateAroundAxis(angles:Up(), 90)
angles:RotateAroundAxis(angles:Forward(), 90)
-- validate argument types
local cBrightness = IsUsingPanelAddTool() and GetConVar("sh_paneladd_brightness"):GetInt() or 100
local cScale = IsUsingPanelAddTool() and GetConVar("sh_paneladd_scale"):GetInt() or 1
local scale = math.Clamp((tonumber(arguments[2]) or cScale) * 0.1, 0.001, 5)
local brightness = math.Clamp(math.Round((tonumber(arguments[3]) or cBrightness) * 2.55), 1, 255)
-- Attempt to collect the dimensions from the Material
local width, height = cachedPreview[2]:GetInt("$realwidth"), cachedPreview[2]:GetInt("$realheight")
local position = (trace.HitPos + angles:Up() * 1)
local ourPosition = LocalPlayer():GetPos()
if (ourPosition:DistToSqr(position) <= 4194304) then
cam.Start3D2D(position, angles, scale or 0.1)
render.PushFilterMin(TEXFILTER.ANISOTROPIC)
render.PushFilterMag(TEXFILTER.ANISOTROPIC)
surface.SetDrawColor(brightness, brightness, brightness)
surface.SetMaterial(cachedPreview[2])
surface.DrawTexturedRect(-width * 0.5, -height * 0.5, width or cachedPreview[2]:Width(),
height or cachedPreview[2]:Height())
render.PopFilterMag()
render.PopFilterMin()
cam.End3D2D()
end
end
end
end
ix.command.Add("PanelAdd", {
description = "@cmdPanelAdd",
privilege = "Manage Panels",
adminOnly = true,
arguments = {
ix.type.string,
bit.bor(ix.type.number, ix.type.optional),
bit.bor(ix.type.number, ix.type.optional)
},
OnRun = function(self, client, url, scale, brightness)
-- Get the position and angles of the panel.
local trace = client:GetEyeTrace()
local position = trace.HitPos
local angles = trace.HitNormal:Angle()
angles:RotateAroundAxis(angles:Up(), 90)
angles:RotateAroundAxis(angles:Forward(), 90)
-- Add the panel.
PLUGIN:AddPanel(position + angles:Up() * 1, angles, url, scale, brightness)
return "@panelAdded"
end
})
ix.command.Add("PanelRemove", {
description = "@cmdPanelRemove",
privilege = "Manage Panels",
adminOnly = true,
arguments = bit.bor(ix.type.number, ix.type.optional),
OnRun = function(self, client, radius)
-- Get the origin to remove panel.
local trace = client:GetEyeTrace()
local position = trace.HitPos
-- Remove the panel(s) and get the amount removed.
local amount = PLUGIN:RemovePanel(position, radius)
return "@panelRemoved", amount
end
})