dobrograd-13-06-2022/garrysmod/addons/feature-wire/lua/wire/stools/value.lua
Jonny_Bro (Nikita) e4d5311906 first commit
2023-11-16 15:01:19 +05:00

431 lines
12 KiB
Lua

WireToolSetup.setCategory( "Input, Output" )
WireToolSetup.open( "value", "Constant Value", "gmod_wire_value", nil, "Constant Values" )
if CLIENT then
language.Add("Tool.wire_value.name", "Value Tool (Wire)")
language.Add("Tool.wire_value.desc", "Spawns a constant value for use with the wire system.")
TOOL.Information = {
{ name = "left", text = "Create/Update " .. TOOL.Name },
{ name = "right", text = "Copy settings" },
}
WireToolSetup.setToolMenuIcon( "icon16/database_go.png" )
end
WireToolSetup.BaseLang()
WireToolSetup.SetupMax( 20 )
-- shared helper functions
local function netWriteValues( selectedValues )
local amount = math.Clamp(#selectedValues,0,20)
net.WriteUInt(amount,5)
for i=1,amount do
local DataType, Value = selectedValues[i].DataType, selectedValues[i].Value
net.WriteString( selectedValues[i].DataType )
net.WriteString( string.sub(selectedValues[i].Value,1,3000) )
end
end
local function netReadValues()
local t = {}
local amount = net.ReadUInt(5)
for i=1,amount do
t[i] = {
DataType=net.ReadString(),
Value=net.ReadString()
}
end
return t
end
if SERVER then
local playerValues = {}
util.AddNetworkString( "wire_value_values" )
net.Receive( "wire_value_values", function( length, ply )
playerValues[ply] = netReadValues()
end)
function TOOL:GetConVars()
return playerValues[self:GetOwner()] or {}
end
function TOOL:RightClick(trace)
if not IsValid(trace.Entity) or trace.Entity:GetClass() != "gmod_wire_value" then return false end
playerValues[self:GetOwner()] = trace.Entity.value
net.Start("wire_value_values")
netWriteValues(trace.Entity.value)
net.Send(self:GetOwner())
end
end
TOOL.ClientConVar = {
model = "models/kobilica/value.mdl",
modelsize = "",
guesstype = "1",
}
if CLIENT then
function TOOL:RightClick(trace)
return IsValid(trace.Entity) and trace.Entity:GetClass() == "gmod_wire_value"
end
-- Supported data types
local types_lookup = {
Number = "NORMAL",
String = "STRING",
Angle = "ANGLE",
Vector = "VECTOR",
["2D Vector"] = "VECTOR2",
["4D Vector"] = "VECTOR4",
}
local types_lookup2 = {
NORMAL = "Number",
STRING = "String",
ANGLE = "Angle",
VECTOR = "Vector",
VECTOR2 = "2D Vector",
VECTOR4 = "4D Vector",
}
local types_ordered = { "Number", "String", "Angle", "Vector", "2D Vector", "4D Vector" }
local ValuePanels = {}
local selectedValues = {}
local panels = {}
local slider
local typeGuessCheckbox
local itemPanel
local resetButton
local SendUpdate
-- Saves the values in cookies so that they can be loaded next session
local function saveValues( values )
values = values or selectedValues
local old_amount = cookie.GetNumber( "wire_constant_value_amount", 0 )
cookie.Set( "wire_constant_value_amount", #values )
if old_amount > #values then
for i=#values+1,old_amount do
cookie.Delete( "wire_constant_value_value" .. i )
cookie.Delete( "wire_constant_value_type" .. i )
end
end
for k, v in pairs( values ) do
cookie.Set( "wire_constant_value_value" .. k, string.sub(v.Value,1,3000) )
cookie.Set( "wire_constant_value_type" .. k, v.DataType )
end
end
-- Loads the values from cookies
-- Don't worry about performance, because while garry's cookies are saved in a database,
-- they're also saved in Lua tables, which means they're accessed instantly.
local function loadValues( dontupdate )
local oldSendUpdate = SendUpdate
SendUpdate = function() end -- Don't update now
local amount = cookie.GetNumber( "wire_constant_value_amount", 0 )
slider:SetValue(amount)
for i=1,amount do
local tp = cookie.GetString( "wire_constant_value_type" .. i, "NORMAL" )
local val = cookie.GetString( "wire_constant_value_value" .. i, "0" )
tp = types_lookup2[tp] or "Number"
selectedValues[i].DataType = tp
selectedValues[i].Value = val
panels[i].valueEntry:SetValue( val )
panels[i].typeSelection:SetText( tp )
panels[i].typeSelection:OnSelect( _, tp )
end
SendUpdate = oldSendUpdate -- okay now it's fine to update again
end
-- Sends the values to the server
function SendUpdate()
net.Start("wire_value_values")
netWriteValues(selectedValues)
net.SendToServer()
saveValues()
end
local validityChecks = {
Number = function( val ) return tonumber(val) ~= nil end,
["2D Vector"] = function( val ) local x,y = string.match( val, "^ *([^%s,]+) *, *([^%s,]+) *$" ) return tonumber(x) ~= nil and tonumber(y) ~= nil end,
Vector = function( val ) local x,y,z = string.match( val, "^ *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *$" ) return tonumber(x) ~= nil and tonumber(y) ~= nil and tonumber(z) ~= nil end,
["4D Vector"] = function( val ) local x,y,z,w = string.match( val, "^ *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *, *([%d.]+) *$" ) return tonumber(x) ~= nil and tonumber(y) ~= nil and tonumber(z) ~= nil and tonumber(w) ~= nil end,
String = function( val ) return true end,
}
validityChecks.Angle = validityChecks.Vector -- it's the same as vectors
local examples = {
Number = "12.34",
["2D Vector"] = "12.34, 12.34",
Vector = "12.34, 12.34, 12.34",
["4D Vector"] = "12.34, 12.34, 12.34, 12.34",
String = "Hello World",
Angle = "90, 180, 360",
}
-- Check if what the user wrote is a valid value of the specified type
local function validateValue( str, tp )
if validityChecks[tp] then
return validityChecks[tp]( str )
else
return false
end
end
local typeGuessing = { -- we're not checking angle because it's indistinguishable from vectors
-- dropdown position, function
{1, validityChecks.Number},
{5, validityChecks["2D Vector"]},
{4, validityChecks.Vector},
{6, validityChecks["4D Vector"]},
{2, validityChecks.String},
}
-- Guess the type of the value the user wrote
local function guessType( str, typeSelection )
for i=1,#typeGuessing do
local dropdownPos = typeGuessing[i][1]
local func = typeGuessing[i][2]
if func( str ) then
typeSelection:ChooseOptionID( dropdownPos )
return
end
end
end
-- Add another value panel
local function AddValue( panel, id )
local w = panel:GetWide()
selectedValues[id] = {
DataType = "NORMAL",
Value = 0,
}
local pnl = vgui.Create( "DCollapsibleCategory", panel )
pnl:SetWide( w )
pnl:SetLabel( "Value " .. id )
pnl:Dock( TOP )
pnl.id = id
local top_panel = vgui.Create( "DPanel", pnl )
top_panel.Paint = function() end
top_panel:Dock( TOP )
local _ = vgui.Create( "DPanel", top_panel ) -- this was the only solution I could think of to properly align this shit
_:Dock( RIGHT )
_.Paint = function() end
_:SetSize( 16, 14 )
local rem = vgui.Create( "DImageButton", _ )
rem:SetImage( "icon16/delete.png" )
rem:SizeToContents()
rem:SetPos( 0, 4 )
rem:SetToolTip( "Remove this value" )
rem.DoClick = function()
if #selectedValues == 1 then -- can't remove the last value
resetButton:DoClick() -- instead, do a reset
return
end
local id = pnl.id
panels[id]:Remove()
table.remove( panels, id )
table.remove( selectedValues, id )
slider:SetValue( math.max( slider:GetValue() - 1, 1 ) )
for i=id, math.Clamp(math.Round(slider:GetValue()),1,20) do
panels[i].id = i
panels[i]:SetLabel( "Value " .. i )
end
end
local typeSelection = vgui.Create( "DComboBox", top_panel )
typeSelection:Dock( FILL )
typeSelection:DockMargin( 2, 2, 2, 2 )
pnl.typeSelection = typeSelection
typeSelection.OnSelect = function( panel, index, value )
selectedValues[pnl.id].DataType = types_lookup[value] or "NORMAL"
SendUpdate()
local val, tp = selectedValues[pnl.id].Value, selectedValues[pnl.id].DataType
tp = types_lookup2[tp] or "Number"
if validateValue( val, tp ) then
pnl.valueEntry:SetToolTip()
pnl.parseIcon:SetImage( "icon16/accept.png" )
else
pnl.valueEntry:SetToolTip( "This is not a valid " .. string.lower( tp ) .. ".\nExample: '" .. (examples[tp] or "No example available for this type") .. "'." )
pnl.parseIcon:SetImage( "icon16/cancel.png" )
end
end
for k,v in pairs( types_ordered ) do
typeSelection:AddChoice(v)
end
local valueEntry = vgui.Create( "DTextEntry",pnl )
pnl.valueEntry = valueEntry
valueEntry:Dock( TOP )
valueEntry:DockMargin( 2, 2, 2, 2 )
valueEntry.OnChange = function( panel )
selectedValues[pnl.id].Value = panel:GetValue()
local val, tp = selectedValues[pnl.id].Value, selectedValues[pnl.id].DataType
tp = types_lookup2[tp] or "Number"
if typeGuessCheckbox:GetChecked() then
guessType( val, typeSelection )
else
if validateValue( val, tp ) then
pnl.valueEntry:SetToolTip()
pnl.parseIcon:SetImage( "icon16/accept.png" )
else
pnl.valueEntry:SetToolTip( "This is not a valid " .. string.lower( tp ) .. ".\nExample: '" .. (examples[tp] or "No example available for this type") .. "'." )
pnl.parseIcon:SetImage( "icon16/cancel.png" )
end
end
end
local oldLoseFocus = valueEntry.OnLoseFocus
valueEntry.OnLoseFocus = function( panel )
selectedValues[pnl.id].Value = panel:GetValue()
SendUpdate()
oldLoseFocus(panel) -- Otherwise we can't close the spawnmenu!
end
local parseIcon = vgui.Create( "DImage", valueEntry )
pnl.parseIcon = parseIcon
parseIcon:Dock( RIGHT )
parseIcon:DockMargin( 2,2,2,2 )
parseIcon:SetImage( "icon16/accept.png" )
parseIcon:SizeToContents()
typeSelection:ChooseOptionID( 1 )
return pnl
end
-- Receive values from the server (when they right click to copy)
net.Receive( "wire_value_values", function( length )
saveValues( netReadValues() )
if not IsValid(slider) then -- They right clicked without opening the cpanel first, just save the values
return
end
loadValues()
end)
-- Build context menu panel
function TOOL.BuildCPanel( panel )
WireToolHelpers.MakeModelSizer(panel, "wire_value_modelsize")
ModelPlug_AddToCPanel(panel, "Value", "wire_value", true)
local reset = panel:Button("Reset Values")
resetButton = reset
typeGuessCheckbox = panel:CheckBox( "Automatically guess types", "wire_value_guesstype" )
typeGuessCheckbox:SetToolTip(
[[When enabled, the type dropdown will automatically be updated as you type with
guessed types. It's unable to guess angles because they look the same as vectors.
The green check you see inside the text boxes is the validator. If the value you write
is a value that can't be parsed as the selected type, the green check will turn into
a red X to indicate there's an error (You can then hover your cursor over the text box
to see what's wrong).
There will never be an error if auto type guessing is enabled (unless you manually
set the type), because it will automatically set the type to a string when all other
types fail.]] )
local w,_ = panel:GetSize()
local valueSlider = vgui.Create( "DNumSlider" )
slider = valueSlider
panel:AddItem( valueSlider )
valueSlider:SetText( "Amount:" )
valueSlider:SetMin(1)
valueSlider:SetMax(20)
valueSlider:SetDark( true )
valueSlider:SetDecimals( 0 )
local LastValueAmount = 0
reset.DoClick = function( panel )
valueSlider:SetValue(1)
for k,v in pairs(panels) do
v:Remove()
panels[k] = nil
end
for k,v in pairs( selectedValues ) do
selectedValues[k] = nil
end
LastValueAmount = 0
valueSlider:OnValueChanged( 1 )
SendUpdate()
end
valueSlider.OnValueChanged = function( valueSlider, value )
local value = math.Clamp(math.Round(tonumber(value)),1,20)
if value ~= LastValueAmount then
if value > LastValueAmount then
for i = LastValueAmount + 1, value, 1 do
panels[i] = AddValue( itemPanel, i )
end
elseif value < LastValueAmount then
for i = value + 1, LastValueAmount, 1 do
selectedValues[i] = nil
if IsValid(panels[i]) then panels[i]:Remove() end
panels[i] = nil
end
end
itemPanel:SetTall( value * 73 )
LastValueAmount = value
SendUpdate()
end
end
itemPanel = vgui.Create( "DPanel" )
itemPanel.Paint = function() end
panel:AddItem( itemPanel )
itemPanel:SetTall( 73 )
loadValues()
SendUpdate()
local pnl = vgui.Create( "DPanel" )
panel:AddItem( pnl )
pnl.Paint = function() end
pnl:SetTall( 16 )
local add = vgui.Create( "DImageButton", pnl )
add:SetImage( "icon16/add.png" )
add:SizeToContents()
add:SetToolTip( "Add a new value" )
function pnl.PerformLayout()
add:Center()
end
function add.DoClick()
slider:SetValue( math.min( slider:GetValue() + 1, 20 ) )
end
end
end