Files
wnsrc/gamemodes/helix/plugins/3dtext.lua
lifestorm 73479cff9e Upload
2024-08-04 22:55:00 +03:00

425 lines
10 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 Text"
PLUGIN.author = "Chessnut & Votton"
PLUGIN.description = "Adds text that can be placed on the map."
-- List of available text panels
PLUGIN.list = PLUGIN.list or {}
if (SERVER) then
util.AddNetworkString("ixTextList")
util.AddNetworkString("ixTextAdd")
util.AddNetworkString("ixTextRemove")
ix.log.AddType("undo3dText", function(client)
return string.format("%s a supprimé son dernier texte 3D.", client:GetName())
end)
-- Called when the player is sending client info.
function PLUGIN:PlayerInitialSpawn(client)
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("ixTextList")
net.WriteUInt(length, 32)
net.WriteData(compressed, length)
net.Send(client)
end
end)
end
-- Adds a text to the list, sends it to the players, and saves data.
function PLUGIN:AddText(position, angles, text, scale)
local index = #self.list + 1
scale = math.Clamp((scale or 1) * 0.1, 0.001, 5)
self.list[index] = {position, angles, text, scale}
net.Start("ixTextAdd")
net.WriteUInt(index, 32)
net.WriteVector(position)
net.WriteAngle(angles)
net.WriteString(text)
net.WriteFloat(scale)
net.Broadcast()
self:SaveText()
return index
end
-- Removes a text that are within the radius of a position.
function PLUGIN:RemoveText(position, radius)
radius = radius or 100
local textDeleted = {}
for k, v in pairs(self.list) do
if (k == 0) then
continue
end
if (v[1]:Distance(position) <= radius) then
textDeleted[#textDeleted + 1] = k
end
end
if (#textDeleted > 0) then
-- Invert index table to delete from highest -> lowest
textDeleted = table.Reverse(textDeleted)
for _, v in ipairs(textDeleted) do
table.remove(self.list, v)
net.Start("ixTextRemove")
net.WriteUInt(v, 32)
net.Broadcast()
end
self:SaveText()
end
return #textDeleted
end
function PLUGIN:RemoveTextByID(id)
local info = self.list[id]
if (!info) then
return false
end
net.Start("ixTextRemove")
net.WriteUInt(id, 32)
net.Broadcast()
table.remove(self.list, id)
return true
end
function PLUGIN:EditText(position, text, scale, radius)
radius = radius or 100
local textEdited = false
for k, v in pairs(self.list) do
if (k == 0) then
continue
end
if (v[1]:Distance(position) <= radius) then
v[3] = text
v[4] = scale
textEdited = true
net.Start("ixTextAdd")
net.WriteUInt(k, 32)
net.WriteVector(v[1])
net.WriteAngle(v[2])
net.WriteString(text)
net.WriteFloat(scale)
net.Broadcast()
self:SaveText()
end
end
return textEdited
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:SaveText()
self:SetData(self.list)
end
else
local function IsUsingTextAddTool()
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_textadd") 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
language.Add("Undone_ix3dText", "Removed 3D Text")
function PLUGIN:GenerateMarkup(text)
local object = ix.markup.Parse("<font=ix3D2DFont>"..text:gsub("\\n", "\n"))
object.onDrawText = function(surfaceText, font, x, y, color, alignX, alignY, alpha)
local _, textH = surface.GetTextSize( surfaceText )
-- shadow
surface.SetTextPos(x + 1, y + 1 - textH * 0.5)
surface.SetTextColor(0, 0, 0, alpha)
surface.SetFont(font)
surface.DrawText(surfaceText)
surface.SetTextPos(x, y - (textH * 0.5))
surface.SetTextColor(color.r or 255, color.g or 255, color.b or 255, alpha)
surface.SetFont(font)
surface.DrawText(surfaceText)
end
return object
end
-- Receives new text objects that need to be drawn.
net.Receive("ixTextAdd", function()
local index = net.ReadUInt(32)
local position = net.ReadVector()
local angles = net.ReadAngle()
local text = net.ReadString()
local scale = net.ReadFloat()
if (text != "") then
PLUGIN.list[index] = {
position,
angles,
PLUGIN:GenerateMarkup(text),
scale
}
PLUGIN.list[0] = #PLUGIN.list
end
end)
net.Receive("ixTextRemove", function()
local index = net.ReadUInt(32)
table.remove(PLUGIN.list, index)
PLUGIN.list[0] = #PLUGIN.list
end)
-- Receives a full update on ALL texts.
net.Receive("ixTextList", function()
local length = net.ReadUInt(32)
local data = net.ReadData(length)
local uncompressed = util.Decompress(data)
if (!uncompressed) then
ErrorNoHalt("[Helix] Impossible de décompresser les données textuelles\n")
return
end
PLUGIN.list = util.JSONToTable(uncompressed)
-- Will be saved, but refresh just to make sure.
PLUGIN.list[0] = #PLUGIN.list
for k, v in pairs(PLUGIN.list) do
if (k == 0) then
continue
end
local object = ix.markup.Parse("<font=ix3D2DFont>"..v[3]:gsub("\\n", "\n"))
object.onDrawText = function(text, font, x, y, color, alignX, alignY, alpha)
draw.TextShadow({
pos = {x, y},
color = ColorAlpha(color, alpha),
text = text,
xalign = 0,
yalign = alignY,
font = font
}, 1, alpha)
end
v[3] = object
end
end)
function PLUGIN:StartChat()
self.preview = nil
end
function PLUGIN:FinishChat()
self.preview = nil
end
function PLUGIN:HUDPaint()
if (ix.chat.currentCommand != "textremove") then
return
end
local radius = tonumber(ix.chat.currentArguments[1]) or 100
surface.SetDrawColor(200, 30, 30)
surface.SetTextColor(200, 30, 30)
surface.SetFont("ixMenuButtonFont")
local i = 0
for k, v in pairs(self.list) do
if (k == 0) then
continue
end
if (v[1]:Distance(LocalPlayer():GetEyeTraceNoCursor().HitPos) <= radius) then
local screen = v[1]:ToScreen()
surface.DrawLine(
ScrW() * 0.5,
ScrH() * 0.5,
math.Clamp(screen.x, 0, ScrW()),
math.Clamp(screen.y, 0, ScrH())
)
i = i + 1
end
end
if (i > 0) then
local textWidth, textHeight = surface.GetTextSize(i)
surface.SetTextPos(ScrW() * 0.5 - textWidth * 0.5, ScrH() * 0.5 + textHeight + 8)
surface.DrawText(i)
end
end
function PLUGIN:PostDrawTranslucentRenderables(bDrawingDepth, bDrawingSkybox)
if (bDrawingDepth or bDrawingSkybox) then
return
end
local toolUsage = IsUsingTextAddTool()
-- preview for textadd command
if (ix.chat.currentCommand == "textadd" or toolUsage) then
local arguments = ix.chat.currentArguments
local text = toolUsage and GetConVar("sh_textadd_text"):GetString() or tostring(arguments[1] or "")
local scale = math.Clamp((tonumber(arguments[2]) or 1) * 0.1, 0.001, 5)
if toolUsage then
scale = math.Clamp((tonumber(GetConVar("sh_textadd_scale"):GetInt()) or 1) * 0.1, 0.001, 5)
end
local trace = LocalPlayer():GetEyeTraceNoCursor()
local position = trace.HitPos
local angles = trace.HitNormal:Angle()
local markup
angles:RotateAroundAxis(angles:Up(), 90)
angles:RotateAroundAxis(angles:Forward(), 90)
-- markup will error with invalid fonts
pcall(function()
markup = PLUGIN:GenerateMarkup(text)
end)
if (markup) then
cam.Start3D2D(position, angles, scale)
markup:draw(0, 0, 1, 1, 255)
cam.End3D2D()
end
end
local position = LocalPlayer():GetPos()
local texts = self.list
for i = 1, texts[0] do
local distance = texts[i][1]:DistToSqr(position)
if (distance > 1048576) then
continue
end
cam.Start3D2D(texts[i][1], texts[i][2], texts[i][4] or 0.1)
local alpha = (1 - ((distance - 65536) / 768432)) * 255
texts[i][3]:draw(0, 0, 1, 1, alpha)
cam.End3D2D()
end
end
end
ix.command.Add("TextAdd", {
description = "@cmdTextAdd",
adminOnly = true,
arguments = {
ix.type.string,
bit.bor(ix.type.number, ix.type.optional)
},
OnRun = function(self, client, text, scale)
local trace = client:GetEyeTrace()
local position = trace.HitPos
local angles = trace.HitNormal:Angle()
angles:RotateAroundAxis(angles:Up(), 90)
angles:RotateAroundAxis(angles:Forward(), 90)
local index = PLUGIN:AddText(position + angles:Up() * 0.1, angles, text, scale)
undo.Create("ix3dText")
undo.SetPlayer(client)
undo.AddFunction(function()
if (PLUGIN:RemoveTextByID(index)) then
ix.log.Add(client, "undo3dText")
end
end)
undo.Finish()
return "@textAdded"
end
})
ix.command.Add("TextRemove", {
description = "@cmdTextRemove",
adminOnly = true,
arguments = bit.bor(ix.type.number, ix.type.optional),
OnRun = function(self, client, radius)
local trace = client:GetEyeTrace()
local position = trace.HitPos + trace.HitNormal * 2
local amount = PLUGIN:RemoveText(position, radius)
return "@textRemoved", amount
end
})
ix.command.Add("TextEdit", {
description = "@cmdTextEdit",
adminOnly = true,
arguments = {
bit.bor(ix.type.string, ix.type.optional),
bit.bor(ix.type.number, ix.type.optional),
bit.bor(ix.type.number, ix.type.optional)
},
OnRun = function(self, client, text, scale, radius)
local trace = client:GetEyeTrace()
local position = trace.HitPos + trace.HitNormal * 2
local edited = PLUGIN:EditText(position, text, scale, radius)
if (edited) then
return "@textEdited"
else
return "@textNotFound"
end
end
})