1029 lines
33 KiB
Lua
1029 lines
33 KiB
Lua
WireToolSetup.setCategory( "Chips, Gates" )
|
|
WireToolSetup.open("expression2", "Expression 2", "gmod_wire_expression2", nil, "Expression2s")
|
|
|
|
if CLIENT then
|
|
language.Add("Tool.wire_expression2.name", "Expression 2 Tool (Wire)")
|
|
language.Add("Tool.wire_expression2.desc", "Spawns an Expression 2 chip for use with the wire system.")
|
|
language.Add("sboxlimit_wire_expressions", "You've hit the Expression limit!")
|
|
|
|
TOOL.Information = {
|
|
{ name = "left", text = "Create " .. TOOL.Name },
|
|
{ name = "right", text = "Open " .. TOOL.Name .. " in Editor" },
|
|
}
|
|
|
|
--WireToolSetup.setToolMenuIcon( "beer/wiremod/gate_e2" )
|
|
WireToolSetup.setToolMenuIcon( "vgui/e2logo" )
|
|
end
|
|
|
|
TOOL.ClientConVar = {
|
|
model = "models/beer/wiremod/gate_e2.mdl",
|
|
modelsize = "",
|
|
scriptmodel = "",
|
|
select = "",
|
|
autoindent = 1,
|
|
}
|
|
|
|
TOOL.MaxLimitName = "wire_expressions"
|
|
WireToolSetup.BaseLang()
|
|
|
|
if SERVER then
|
|
CreateConVar('sbox_maxwire_expressions', 20)
|
|
|
|
function TOOL:MakeEnt(ply, model, Ang, trace)
|
|
return MakeWireExpression2(ply, trace.HitPos, Ang, model)
|
|
end
|
|
|
|
function TOOL:PostMake(ent)
|
|
self:Upload(ent)
|
|
end
|
|
|
|
function TOOL:LeftClick_Update( trace )
|
|
self:Upload(trace.Entity)
|
|
end
|
|
|
|
function TOOL:Reload(trace)
|
|
if trace.Entity:IsPlayer() then return false end
|
|
if CLIENT then return true end
|
|
|
|
local player = self:GetOwner()
|
|
|
|
if IsValid(trace.Entity) and trace.Entity:GetClass() == "gmod_wire_expression2" and trace.Entity.context then
|
|
trace.Entity:Reset()
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
util.AddNetworkString("WireExpression2_OpenEditor")
|
|
function TOOL:RightClick(trace)
|
|
if trace.Entity:IsPlayer() then return false end
|
|
|
|
local player = self:GetOwner()
|
|
|
|
if IsValid(trace.Entity) and trace.Entity:GetClass() == "gmod_wire_expression2" then
|
|
self:Download(player, trace.Entity)
|
|
return true
|
|
end
|
|
|
|
net.Start("WireExpression2_OpenEditor") net.Send(player)
|
|
return false
|
|
end
|
|
|
|
function TOOL:Upload(ent)
|
|
WireLib.Expression2Upload( self:GetOwner(), ent )
|
|
end
|
|
|
|
function WireLib.Expression2Upload( ply, target, filepath )
|
|
if not IsValid( target ) then error( "Invalid entity specified" ) end
|
|
net.Start("wire_expression2_tool_upload")
|
|
net.WriteUInt(target:EntIndex(), 16)
|
|
net.WriteString( filepath or "" )
|
|
net.WriteInt( target.buffer and tonumber(util.CRC( target.buffer )) or -1, 32 ) -- send the hash so we know if there's any difference
|
|
net.Send(ply)
|
|
end
|
|
|
|
function TOOL:Download(ply, ent)
|
|
WireLib.Expression2Download(ply, ent, nil, true)
|
|
end
|
|
|
|
-- --------------------------------------------------------------------------------------------------------------------------
|
|
-- --------------------------------------------------------------------------------------------------------------------------
|
|
-- --------------------------------------------------------------------------------------------------------------------------
|
|
-- ---------------------------------------------------- UPLOAD/DOWNLOAD -----------------------------------------------------
|
|
-- --------------------------------------------------------------------------------------------------------------------------
|
|
-- --------------------------------------------------------------------------------------------------------------------------
|
|
-- --------------------------------------------------------------------------------------------------------------------------
|
|
util.AddNetworkString("wire_expression2_tool_upload")
|
|
util.AddNetworkString("wire_expression2_editor_status")
|
|
util.AddNetworkString("wire_expression2_download")
|
|
util.AddNetworkString("wire_expression2_download_wantedfiles")
|
|
util.AddNetworkString("wire_expression2_download_wantedfiles_list")
|
|
util.AddNetworkString("wire_expression2_upload")
|
|
util.AddNetworkString("wire_expression2_progress")
|
|
|
|
-- ------------------------------------------------------------
|
|
-- Serverside Send
|
|
-- ------------------------------------------------------------
|
|
function WireLib.Expression2Download(ply, targetEnt, wantedfiles, uploadandexit)
|
|
if not IsValid(targetEnt) or targetEnt:GetClass() ~= "gmod_wire_expression2" then
|
|
WireLib.AddNotify(ply, "Invalid Expression chip specified.", NOTIFY_ERROR, 7, NOTIFYSOUND_DRIP3)
|
|
return
|
|
end
|
|
|
|
if not IsValid(ply) or not ply:IsPlayer() then -- wtf
|
|
error("Invalid player entity (wtf??). This should never happen. " .. tostring(ply), 0)
|
|
end
|
|
|
|
if not hook.Run( "CanTool", ply, WireLib.dummytrace(targetEnt), "wire_expression2") then
|
|
WireLib.AddNotify(ply, "You're not allowed to download from this Expression (ent index: " .. targetEnt:EntIndex() .. ").", NOTIFY_ERROR, 7, NOTIFYSOUND_DRIP3)
|
|
return
|
|
end
|
|
|
|
local main, includes = targetEnt:GetCode()
|
|
if not includes or not next(includes) then -- There are no includes
|
|
local datastr = WireLib.von.serialize({ { targetEnt.name, main } })
|
|
local numpackets = math.ceil(#datastr / 64000)
|
|
|
|
local n = 0
|
|
for i = 1, #datastr, 64000 do
|
|
timer.Simple( n, function()
|
|
if not IsValid( targetEnt ) then return end
|
|
net.Start("wire_expression2_download")
|
|
net.WriteEntity(targetEnt)
|
|
net.WriteBit(uploadandexit or false)
|
|
net.WriteUInt(numpackets, 16)
|
|
net.WriteString(datastr:sub(i, i + 63999))
|
|
net.Send(ply)
|
|
end)
|
|
n = n + 1
|
|
end
|
|
elseif not wantedfiles then
|
|
local data = {}
|
|
for k, v in pairs(includes) do
|
|
data[#data + 1] = k
|
|
end
|
|
|
|
local datastr = WireLib.von.serialize(data)
|
|
net.Start("wire_expression2_download_wantedfiles_list")
|
|
net.WriteEntity(targetEnt)
|
|
net.WriteBit(uploadandexit or false)
|
|
net.WriteString(datastr)
|
|
net.Send(ply)
|
|
else
|
|
local data = { {}, {} }
|
|
if wantedfiles.main then
|
|
data[1] = { targetEnt.name, main }
|
|
wantedfiles.main = nil
|
|
end
|
|
|
|
for i = 1, #wantedfiles do
|
|
local path = wantedfiles[i]
|
|
if includes[path] then
|
|
data[2][path] = includes[path]
|
|
else
|
|
WireLib.AddNotify(ply, "Nonexistant file requested ('" .. tostring(path) .. "'). File skipped.", NOTIFY_ERROR, 7, NOTIFYSOUND_DRIP3)
|
|
end
|
|
end
|
|
|
|
local datastr = WireLib.von.serialize(data)
|
|
local numpackets = math.ceil(#datastr / 64000)
|
|
local n = 0
|
|
for i = 1, #datastr, 64000 do
|
|
timer.Simple( n, function()
|
|
if not IsValid( targetEnt ) then return end
|
|
net.Start("wire_expression2_download")
|
|
net.WriteEntity(targetEnt)
|
|
net.WriteBit(uploadandexit or false)
|
|
net.WriteUInt(numpackets, 16)
|
|
net.WriteString(datastr:sub(i, i + 63999))
|
|
net.Send(ply)
|
|
end)
|
|
n = n + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
local wantedfiles = {}
|
|
net.Receive("wire_expression2_download_wantedfiles", function(len, ply)
|
|
local toent = net.ReadEntity()
|
|
local uploadandexit = net.ReadBit() ~= 0
|
|
local numpackets = net.ReadUInt(16)
|
|
|
|
if not IsValid(toent) or toent:GetClass() ~= "gmod_wire_expression2" then
|
|
WireLib.AddNotify(ply, "Invalid entity specified to wire_expression2_download_wantedfiles. Download aborted.", NOTIFY_ERROR, 7, NOTIFYSOUND_DRIP3)
|
|
return
|
|
end
|
|
|
|
if not wantedfiles[ply] then wantedfiles[ply] = {} end
|
|
table.insert(wantedfiles[ply], net.ReadString())
|
|
if numpackets <= #wantedfiles[ply] then
|
|
local ok, ret = pcall(WireLib.von.deserialize, E2Lib.decode(table.concat(wantedfiles[ply])))
|
|
wantedfiles[ply] = nil
|
|
if not ok then
|
|
WireLib.AddNotify(ply, "Expression 2 download failed! Error message:\n" .. ret, NOTIFY_ERROR, 7, NOTIFYSOUND_DRIP3)
|
|
print("Expression 2 download failed! Error message:\n" .. ret)
|
|
return
|
|
end
|
|
|
|
WireLib.Expression2Download(ply, toent, ret, uploadandexit)
|
|
end
|
|
end)
|
|
|
|
-- ------------------------------------------------------------
|
|
-- Serverside Receive
|
|
-- ------------------------------------------------------------
|
|
local uploads = {}
|
|
local upload_ents = {}
|
|
net.Receive("wire_expression2_upload", function(len, ply)
|
|
local toent = Entity(net.ReadUInt(16))
|
|
local numpackets = net.ReadUInt(16)
|
|
|
|
if (not IsValid(toent) or toent:GetClass() ~= "gmod_wire_expression2") then
|
|
if uploads[ply] then -- this is to prevent notification spam due to the net library automatically limiting its own transfer rate so that the messages arrive late
|
|
uploads[ply] = nil
|
|
upload_ents[ply] = nil
|
|
WireLib.AddNotify(ply, "Invalid Expression chip specified. Upload aborted.", NOTIFY_ERROR, 7, NOTIFYSOUND_DRIP3)
|
|
end
|
|
return
|
|
end
|
|
|
|
if not hook.Run( "CanTool", ply, WireLib.dummytrace( toent ), "wire_expression2" ) then
|
|
WireLib.AddNotify(ply, "You are not allowed to upload to the target Expression chip. Upload aborted.", NOTIFY_ERROR, 7, NOTIFYSOUND_DRIP3)
|
|
return
|
|
end
|
|
|
|
if upload_ents[ply] ~= toent then -- a new upload was started, abort previous
|
|
uploads[ply] = nil
|
|
end
|
|
|
|
upload_ents[ply] = toent
|
|
|
|
if not uploads[ply] then uploads[ply] = {} end
|
|
uploads[ply][#uploads[ply]+1] = net.ReadString()
|
|
if numpackets <= #uploads[ply] then
|
|
local datastr = E2Lib.decode(table.concat(uploads[ply]))
|
|
uploads[ply] = nil
|
|
local ok, ret = pcall(WireLib.von.deserialize, datastr)
|
|
|
|
if not ok then
|
|
WireLib.AddNotify(ply, "Expression 2 upload failed! Error message:\n" .. ret, NOTIFY_ERROR, 7, NOTIFYSOUND_DRIP3)
|
|
print("Expression 2 upload failed! Error message:\n" .. ret)
|
|
return
|
|
end
|
|
|
|
local code = ret[1]
|
|
|
|
local includes = {}
|
|
for k, v in pairs(ret[2]) do
|
|
includes[k] = v
|
|
end
|
|
|
|
local filepath = ret[3]
|
|
|
|
toent:Setup(code, includes, nil, nil, filepath)
|
|
end
|
|
end)
|
|
|
|
-- ------------------------------------------------------------
|
|
-- Stuff for the remote updater
|
|
-- ------------------------------------------------------------
|
|
|
|
local antispam = {}
|
|
-- Returns true if they are spamming, false if they can go ahead and use it
|
|
local function canhas(ply)
|
|
if not antispam[ply] then antispam[ply] = 0 end
|
|
if antispam[ply] < CurTime() then
|
|
antispam[ply] = CurTime() + 1
|
|
return false
|
|
else
|
|
WireLib.ClientError("This command has a 1 second anti spam protection. Try again in " .. math.Round(antispam[ply] - CurTime(), 2) .. " seconds.", ply)
|
|
return true
|
|
end
|
|
end
|
|
|
|
concommand.Add("wire_expression_forcehalt", function(player, command, args)
|
|
local E2 = tonumber(args[1])
|
|
if not E2 then return end
|
|
E2 = Entity(E2)
|
|
if not IsValid(E2) or E2:GetClass() ~= "gmod_wire_expression2" then return end
|
|
if canhas(player) then return end
|
|
if E2.error then return end
|
|
if hook.Run( "CanTool", player, WireLib.dummytrace( E2 ), "wire_expression2", "halt execution" ) then
|
|
E2:PCallHook("destruct")
|
|
E2:Error("Execution halted (Triggered by: " .. player:Nick() .. ")", "Execution halted")
|
|
if E2.player ~= player then
|
|
WireLib.AddNotify(player, "Expression halted.", NOTIFY_GENERIC, 5, math.random(1, 5))
|
|
player:PrintMessage(HUD_PRINTCONSOLE, "Expression halted.")
|
|
end
|
|
else
|
|
WireLib.ClientError("You do not have permission to halt this E2.", player)
|
|
end
|
|
end)
|
|
|
|
concommand.Add("wire_expression_requestcode", function(player, command, args)
|
|
local E2 = tonumber(args[1])
|
|
if not E2 then return end
|
|
E2 = Entity(E2)
|
|
if canhas(player) then return end
|
|
if not IsValid(E2) or E2:GetClass() ~= "gmod_wire_expression2" then return end
|
|
if hook.Run( "CanTool", player, WireLib.dummytrace( E2 ), "wire_expression2", "request code" ) then
|
|
WireLib.Expression2Download(player, E2)
|
|
WireLib.AddNotify(player, "Downloading code...", NOTIFY_GENERIC, 5, math.random(1, 4))
|
|
player:PrintMessage(HUD_PRINTCONSOLE, "Downloading code...")
|
|
if E2.player ~= player then
|
|
WireLib.AddNotify(E2.player, player:Nick() .. " is reading your E2 '" .. E2.name .. "' using remote updater.", NOTIFY_GENERIC, 5, math.random(1, 4))
|
|
E2.player:PrintMessage(HUD_PRINTCONSOLE, player:Nick() .. " is reading your E2 '" .. E2.name .. "' using remote updater.")
|
|
end
|
|
else
|
|
WireLib.ClientError("You do not have permission to read this E2.", player)
|
|
end
|
|
end)
|
|
|
|
concommand.Add("wire_expression_reset", function(player, command, args)
|
|
local E2 = tonumber(args[1])
|
|
if not E2 then return end
|
|
E2 = Entity(E2)
|
|
if not IsValid(E2) or E2:GetClass() ~= "gmod_wire_expression2" then return end
|
|
if canhas(player) then return end
|
|
if hook.Run( "CanTool", player, WireLib.dummytrace( E2 ), "wire_expression2", "reset" ) then
|
|
if E2.context.data.last or E2.first then return end
|
|
|
|
E2:Reset()
|
|
|
|
WireLib.AddNotify(player, "Expression reset.", NOTIFY_GENERIC, 5, math.random(1, 4))
|
|
player:PrintMessage(HUD_PRINTCONSOLE, "Expression reset.")
|
|
if E2.player ~= player then
|
|
WireLib.AddNotify(E2.player, player:Nick() .. " reset your E2 '" .. E2.name .. "' using remote updater.", NOTIFY_GENERIC, 5, math.random(1, 4))
|
|
E2.player:PrintMessage(HUD_PRINTCONSOLE, player:Nick() .. " reset your E2 '" .. E2.name .. "' using remote updater.")
|
|
end
|
|
else
|
|
WireLib.ClientError("You do not have permission to reset this E2.", player)
|
|
end
|
|
end)
|
|
|
|
------------------------------------------------------
|
|
-- Syncing ops for remote uploader (admin only)
|
|
-- Server part
|
|
------------------------------------------------------
|
|
|
|
local players_synced = {}
|
|
util.AddNetworkString( "wire_expression_sync_ops" )
|
|
concommand.Add("wire_expression_ops_sync", function(player,command,args)
|
|
if not player:IsAdmin() then return end
|
|
|
|
local bool = args[1] ~= "0"
|
|
|
|
if bool then
|
|
players_synced[player] = true
|
|
else
|
|
players_synced[player] = nil
|
|
end
|
|
|
|
if next( players_synced ) and not timer.Exists( "wire_expression_ops_sync" ) then
|
|
|
|
timer.Create( "wire_expression_ops_sync",0.2,0,function()
|
|
local plys = {}
|
|
for ply,_ in pairs( players_synced ) do
|
|
if not IsValid( ply ) then
|
|
players_synced[ply] = nil
|
|
else
|
|
plys[#plys+1] = ply
|
|
end
|
|
end
|
|
if not next( players_synced ) then
|
|
timer.Remove( "wire_expression_ops_sync" )
|
|
end
|
|
|
|
local E2s = ents.FindByClass("gmod_wire_expression2")
|
|
|
|
net.Start( "wire_expression_sync_ops" )
|
|
net.WriteInt( #E2s, 16 )
|
|
for i=1,#E2s do
|
|
net.WriteEntity( E2s[i] )
|
|
local data = E2s[i]:GetOverlayData()
|
|
net.WriteDouble( data.prfbench )
|
|
net.WriteDouble( data.prfcount )
|
|
net.WriteDouble( data.timebench )
|
|
end
|
|
net.Send( plys )
|
|
end)
|
|
elseif not next( players_synced ) and timer.Exists( "wire_expression_ops_sync" ) then
|
|
timer.Remove( "wire_expression_ops_sync" )
|
|
end
|
|
|
|
end)
|
|
elseif CLIENT then
|
|
------------------------------------------------------
|
|
-- Syncing ops for remote uploader (admin only)
|
|
-- Client part
|
|
------------------------------------------------------
|
|
net.Receive( "wire_expression_sync_ops", function( len )
|
|
local num = net.ReadInt( 16 )
|
|
for i=1,num do
|
|
local E2 = net.ReadEntity()
|
|
if E2.GetOverlayData and E2.SetOverlayData then
|
|
local prfbench = net.ReadDouble()
|
|
local prfcount = net.ReadDouble()
|
|
local timebench = net.ReadDouble()
|
|
|
|
local data = E2:GetOverlayData() or {}
|
|
|
|
E2:SetOverlayData( {
|
|
txt = data.txt or "(generic)",
|
|
error = data.error or false,
|
|
prfbench = prfbench,
|
|
prfcount = prfcount,
|
|
timebench = timebench
|
|
} )
|
|
end
|
|
end
|
|
end )
|
|
|
|
--------------------------------------------------------------
|
|
-- Clientside Send
|
|
--------------------------------------------------------------
|
|
|
|
local queue_max = 0
|
|
local queue = {}
|
|
local sending = false
|
|
|
|
local upload_queue
|
|
|
|
-- send next E2
|
|
local function next_queue()
|
|
local queue_progress = (queue_max > 1 and (1-((#queue-1) / queue_max)) * 100 or nil)
|
|
Expression2SetProgress( nil, queue_progress )
|
|
table.remove( queue, 1 )
|
|
|
|
-- Clear away all removed E2s from the queue
|
|
while true do
|
|
if #queue == 0 then break end
|
|
if queue[1].timeStarted + 2 < CurTime() -- only clear it if more than 2 seconds has passed since the upload was requested (if the user has high ping)
|
|
and not IsValid( Entity(queue[1].targetEnt) ) then
|
|
table.remove( queue, 1 )
|
|
else
|
|
break
|
|
end
|
|
end
|
|
|
|
timer.Simple( 1, function() -- wait a while before doing anything so stuff doesn't lag
|
|
if #queue == 0 then
|
|
Expression2SetProgress()
|
|
sending = false -- done sending
|
|
queue_max = 0
|
|
else
|
|
upload_queue() -- send next
|
|
end
|
|
end)
|
|
end
|
|
|
|
upload_queue = function(first)
|
|
local q = queue[1]
|
|
|
|
local targetEnt = q.targetEnt
|
|
local datastr = q.datastr
|
|
local timeStarted = q.timeStarted
|
|
|
|
local queue_progress = (queue_max > 1 and (1-((#queue-1) / queue_max)) * 100 or nil)
|
|
Expression2SetProgress(1, queue_progress)
|
|
|
|
local numpackets = math.ceil(#datastr / 64000)
|
|
local delay = first and 0.01 or 1
|
|
local packet = 0
|
|
local exited = false
|
|
for i = 1, #datastr, 64000 do
|
|
timer.Simple( delay, function()
|
|
packet = packet + 1
|
|
if timeStarted + 2 < CurTime() and -- only remove the E2 from the queue if more than 2 seconds has passed since the upload was requested (if the user has high ping)
|
|
not IsValid(Entity( targetEnt )) then
|
|
if exited then
|
|
return
|
|
else
|
|
exited = true
|
|
next_queue()
|
|
return
|
|
end
|
|
end
|
|
|
|
if packet == numpackets then
|
|
next_queue()
|
|
end
|
|
|
|
local queue_progress = (queue_max > 1 and (1-((#queue-1) / queue_max)) * 100 or nil)
|
|
Expression2SetProgress( packet / numpackets * 100, queue_progress )
|
|
|
|
net.Start("wire_expression2_upload")
|
|
net.WriteUInt(targetEnt, 16)
|
|
net.WriteUInt(numpackets, 16)
|
|
net.WriteString(datastr:sub(i, i + 63999))
|
|
net.SendToServer()
|
|
end)
|
|
delay = delay + 1
|
|
end
|
|
end
|
|
|
|
function WireLib.Expression2Upload(targetEnt, code, filepath)
|
|
if not targetEnt then targetEnt = LocalPlayer():GetEyeTrace().Entity or NULL end
|
|
if isentity(targetEnt) then
|
|
if not IsValid(targetEnt) then return end -- We don't know what entity its going to
|
|
targetEnt = targetEnt:EntIndex()
|
|
end
|
|
|
|
for i=1,#queue do
|
|
if queue[i].targetEnt == targetEnt then
|
|
WireLib.AddNotify("You're already uploading that E2!", NOTIFY_ERROR, 7, NOTIFYSOUND_ERROR1)
|
|
return
|
|
end
|
|
end
|
|
|
|
if not code and not wire_expression2_editor then return end -- If the player leftclicks without opening the editor or cpanel (first spawn)
|
|
code = code or wire_expression2_editor:GetCode()
|
|
filepath = filepath or wire_expression2_editor:GetChosenFile()
|
|
local err, includes
|
|
|
|
if e2_function_data_received then
|
|
err, includes = wire_expression2_validate(code)
|
|
if err then
|
|
WireLib.AddNotify(err, NOTIFY_ERROR, 7, NOTIFYSOUND_ERROR1)
|
|
return
|
|
end
|
|
else
|
|
WireLib.AddNotify("The Expression 2 function data has not been transferred to the client yet;\n uploading the E2 to the server for validation.\nNote that any includes will not be sent. You must wait for the function data to finish\n transmitting before you are able to use includes.", NOTIFY_ERROR, 14, NOTIFYSOUND_DRIP3)
|
|
|
|
-- This message is so long, the user might not be able to read it fast enough. Printing it to the console so they can read it there, too.
|
|
Msg("The Expression 2 function data has not been transferred to the client yet; uploading the E2 to the server for validation.\nNote that any includes will not be sent. You must wait for the function data to finish transmitting before you are able to use includes.\n")
|
|
end
|
|
|
|
local datastr
|
|
if includes then
|
|
local newincludes = {}
|
|
for k, v in pairs(includes) do
|
|
newincludes[k] = v
|
|
end
|
|
|
|
datastr = E2Lib.encode(WireLib.von.serialize({ code, newincludes, filepath }))
|
|
else
|
|
datastr = E2Lib.encode(WireLib.von.serialize({ code, {}, filepath }))
|
|
end
|
|
|
|
queue[#queue+1] = {
|
|
targetEnt = targetEnt,
|
|
datastr = datastr,
|
|
timeStarted = CurTime()
|
|
}
|
|
|
|
queue_max = queue_max + 1
|
|
|
|
if sending then return end
|
|
sending = true
|
|
upload_queue(true) // true means its the first packet, suppressing the delay
|
|
end
|
|
|
|
net.Receive("wire_expression2_tool_upload", function(len, ply)
|
|
local ent = net.ReadUInt(16)
|
|
local filepath = net.ReadString()
|
|
local hash = net.ReadInt(32)
|
|
if filepath ~= "" then
|
|
if filepath and file.Exists(filepath, "DATA") then
|
|
local str = file.Read(filepath)
|
|
local strhash = tonumber(util.CRC(str))
|
|
if hash ~= strhash then -- Only upload if we need to
|
|
WireLib.Expression2Upload(ent,str,filepath)
|
|
end
|
|
end
|
|
else
|
|
WireLib.Expression2Upload(ent)
|
|
end
|
|
end)
|
|
|
|
--------------------------------------------------------------
|
|
-- Clientside Receive
|
|
--------------------------------------------------------------
|
|
local buffer, count = "", 0
|
|
local current_ent
|
|
net.Receive("wire_expression2_download", function(len)
|
|
local ent = net.ReadEntity()
|
|
|
|
if IsValid( current_ent ) and IsValid( ent ) and ent ~= current_ent then
|
|
-- different E2, reset buffer
|
|
buffer = ""
|
|
count = 0
|
|
end
|
|
|
|
local uploadandexit = net.ReadBit() ~= 0
|
|
local numpackets = net.ReadUInt(16)
|
|
|
|
buffer = buffer .. net.ReadString()
|
|
count = count + 1
|
|
|
|
Expression2SetProgress(count / numpackets * 100, nil, "Downloading")
|
|
if numpackets <= count then
|
|
local ok, ret = pcall(WireLib.von.deserialize, buffer)
|
|
buffer, count = "", 0
|
|
if not ok then
|
|
WireLib.AddNotify(ply, "Expression 2 download failed! Error message:\n" .. ret, NOTIFY_ERROR, 7, NOTIFYSOUND_DRIP3)
|
|
return
|
|
end
|
|
local files = ret
|
|
|
|
local name, main
|
|
if files[1] then
|
|
name = files[1][1]
|
|
main = files[1][2]
|
|
end
|
|
|
|
if not wire_expression2_editor then initE2Editor() end
|
|
|
|
if uploadandexit then
|
|
wire_expression2_editor.chip = ent
|
|
end
|
|
|
|
if files[2] and next(files[2]) then
|
|
for k, v in pairs(files[2]) do
|
|
wire_expression2_editor:Open(k, v)
|
|
end
|
|
end
|
|
|
|
wire_expression2_editor:Open(name, main)
|
|
timer.Create("wire_expression2_reset_progress", 0.75, 1, Expression2SetProgress)
|
|
end
|
|
end)
|
|
|
|
net.Receive("wire_expression2_download_wantedfiles_list", function(len)
|
|
local ent = net.ReadEntity()
|
|
local uploadandexit = net.ReadBit() ~= 0
|
|
local buffer = net.ReadString()
|
|
|
|
local ok, ret = pcall(WireLib.von.deserialize, buffer)
|
|
if not ok then
|
|
WireLib.AddNotify(ply, "Expression 2 file list download failed! Error message:\n" .. ret, NOTIFY_ERROR, 7, NOTIFYSOUND_DRIP3)
|
|
print("Expression 2 file list download failed! Error message:\n" .. ret)
|
|
return
|
|
end
|
|
|
|
local files = ret
|
|
local height = 23
|
|
|
|
local pnl = vgui.Create("DFrame")
|
|
pnl:SetSize(200, 100)
|
|
pnl:Center()
|
|
pnl:SetTitle("Select files to download")
|
|
|
|
local lst = vgui.Create("DPanelList", pnl)
|
|
lst.Paint = function() end
|
|
lst:SetSpacing(2)
|
|
|
|
local selectedfiles = { main = true }
|
|
|
|
local checkboxes = {}
|
|
|
|
local check = vgui.Create("DCheckBoxLabel")
|
|
check:SetText("Main")
|
|
check:Toggle()
|
|
lst:AddItem(check)
|
|
function check:OnChange(val)
|
|
if val then
|
|
selectedfiles.main = true
|
|
else
|
|
selectedfiles.main = nil
|
|
end
|
|
end
|
|
|
|
checkboxes[#checkboxes + 1] = check
|
|
height = height + check:GetTall() + 2
|
|
|
|
for i = 1, #files do
|
|
local path = files[i]
|
|
local check = vgui.Create("DCheckBoxLabel")
|
|
check:SetText(path)
|
|
lst:AddItem(check)
|
|
function check:OnChange(val)
|
|
if val then
|
|
selectedfiles[i] = path
|
|
else
|
|
table.remove(selectedfiles, i)
|
|
end
|
|
end
|
|
|
|
checkboxes[#checkboxes + 1] = check
|
|
height = height + check:GetTall() + 2
|
|
end
|
|
|
|
local selectall = vgui.Create("DButton")
|
|
selectall:SetText("Select all")
|
|
lst:AddItem(selectall)
|
|
function selectall:DoClick()
|
|
selectedfiles = {}
|
|
for k, v in pairs(files) do
|
|
selectedfiles[#selectedfiles + 1] = v
|
|
end
|
|
selectedfiles.main = true
|
|
|
|
for i = 1, #checkboxes do
|
|
if not checkboxes[i]:GetChecked() then checkboxes[i]:Toggle() end -- checkboxes[i]:SetChecked( true )
|
|
end
|
|
end
|
|
|
|
height = height + selectall:GetTall() + 2
|
|
|
|
local selectnone = vgui.Create("DButton")
|
|
selectnone:SetText("Select none")
|
|
lst:AddItem(selectnone)
|
|
function selectnone:DoClick()
|
|
selectedfiles = {}
|
|
|
|
for i = 1, #checkboxes do
|
|
if checkboxes[i]:GetChecked() then checkboxes[i]:Toggle() end -- checkboxes[i]:SetChecked( false )
|
|
end
|
|
end
|
|
|
|
height = height + selectnone:GetTall() + 2
|
|
|
|
local ok = vgui.Create("DButton")
|
|
ok:SetText("Ok")
|
|
ok:SetToolTip("Shortcut for this button: Right click anywhere")
|
|
lst:AddItem(ok)
|
|
function ok:DoClick()
|
|
local haschoice = false
|
|
for k, v in pairs(selectedfiles) do haschoice = true break end
|
|
if not haschoice then pnl:Close() return end
|
|
|
|
local datastr = E2Lib.encode(WireLib.von.serialize(selectedfiles))
|
|
local numpackets = math.ceil(#datastr / 64000)
|
|
for i = 1, #datastr, 64000 do
|
|
net.Start("wire_expression2_download_wantedfiles")
|
|
net.WriteEntity(ent)
|
|
net.WriteBit(uploadandexit)
|
|
net.WriteUInt(numpackets, 16)
|
|
net.WriteString(datastr:sub(i, i + 63999))
|
|
net.SendToServer()
|
|
end
|
|
|
|
pnl:Close()
|
|
end
|
|
|
|
height = height + ok:GetTall()
|
|
|
|
local down = input.IsMouseDown(MOUSE_RIGHT)
|
|
function pnl:Think()
|
|
if not down and input.IsMouseDown(MOUSE_RIGHT) then
|
|
ok:DoClick()
|
|
end
|
|
down = input.IsMouseDown(MOUSE_RIGHT)
|
|
end
|
|
|
|
pnl:SetTall(math.min(height + 2, ScrH() / 2))
|
|
lst:EnableVerticalScrollbar(true)
|
|
lst:StretchToParent(2, 23, 2, 2)
|
|
pnl:MakePopup()
|
|
pnl:SetVisible(true)
|
|
end)
|
|
|
|
--------------------------------------------------------------
|
|
function TOOL.BuildCPanel(panel)
|
|
local w, h = panel:GetSize()
|
|
|
|
WireToolHelpers.MakeModelSizer(panel, "wire_expression2_modelsize")
|
|
--[[
|
|
local ParentPanel = vgui.Create( "DPanel", panel )
|
|
ParentPanel:SetSize(w,h-40)
|
|
ParentPanel:Dock(TOP)
|
|
]]
|
|
--[[
|
|
local MaterialGallery = vgui.Create( "DCollapsibleCategory", ParentPanel )
|
|
MaterialGallery:SetSize(w,100)
|
|
]]
|
|
-- lazy.. lazy.. lazy.. deprecated..
|
|
panel:AddControl("MaterialGallery", {
|
|
Height = "100",
|
|
Width = "100",
|
|
Rows = 2,
|
|
Stretch = false,
|
|
ConVar = "wire_expression2_select",
|
|
Options = {
|
|
["Modern"] = { wire_expression2_select = "Modern", Value = "Modern", Material = "beer/wiremod/gate_e2", wire_expression2_model = "models/beer/wiremod/gate_e2.mdl" },
|
|
["Expression"] = { wire_expression2_select = "Expression", Value = "Expression", Material = "models/expression 2/exprssn", wire_expression2_model = "models/expression 2/cpu_expression.mdl" },
|
|
["Microchip"] = { wire_expression2_select = "Microchip", Value = "Microchip", Material = "models/expression 2/mcrochp", wire_expression2_model = "models/expression 2/cpu_microchip.mdl" },
|
|
["Interface"] = { wire_expression2_select = "Interface", Value = "Interface", Material = "models/expression 2/intrfce", wire_expression2_model = "models/expression 2/cpu_interface.mdl" },
|
|
["Controller"] = { wire_expression2_select = "Controller", Value = "Controller", Material = "models/expression 2/cntrllr", wire_expression2_model = "models/expression 2/cpu_controller.mdl" },
|
|
["Processor"] = { wire_expression2_select = "Processor", Value = "Processor", Material = "models/expression 2/prcssor", wire_expression2_model = "models/expression 2/cpu_processor.mdl" },
|
|
}
|
|
})
|
|
|
|
if (wire_expression2_editor == nil) then initE2Editor() end
|
|
|
|
local FileBrowser = vgui.Create("wire_expression2_browser", panel)
|
|
FileBrowser.OpenOnSingleClick = wire_expression2_editor
|
|
panel:AddPanel(FileBrowser)
|
|
FileBrowser:Setup("expression2")
|
|
FileBrowser:SetSize(w, 300)
|
|
FileBrowser:DockMargin(5, 5, 5, 5)
|
|
FileBrowser:DockPadding(5, 5, 5, 5)
|
|
FileBrowser:Dock(TOP)
|
|
function FileBrowser:OnFileOpen(filepath, newtab)
|
|
wire_expression2_editor:Open(filepath, nil, newtab)
|
|
end
|
|
|
|
local OpenEditor = panel:Button("Open Editor")
|
|
OpenEditor.DoClick = function(button)
|
|
wire_expression2_editor:Open()
|
|
end
|
|
|
|
local NewExpression = panel:Button("New Expression")
|
|
NewExpression.DoClick = function(button)
|
|
wire_expression2_editor:Open()
|
|
wire_expression2_editor:NewScript()
|
|
end
|
|
end
|
|
|
|
function initE2Editor()
|
|
wire_expression2_editor = vgui.Create("Expression2EditorFrame")
|
|
wire_expression2_editor:Setup("Expression 2 Editor", "expression2", "E2")
|
|
end
|
|
|
|
function openE2Editor()
|
|
if (wire_expression2_editor == nil) then initE2Editor() end
|
|
wire_expression2_editor:Open()
|
|
end
|
|
|
|
net.Receive("WireExpression2_OpenEditor", openE2Editor)
|
|
|
|
--[[
|
|
Expression 2 Tool Screen for Garry's Mod
|
|
Andreas "Syranide" Svensson, me@syranide.com
|
|
]] --
|
|
|
|
local fontTable = {
|
|
font = "Arial",
|
|
size = 40,
|
|
weight = 1000,
|
|
antialias = true,
|
|
additive = false,
|
|
}
|
|
surface.CreateFont("Expression2ToolScreenFont", fontTable)
|
|
fontTable.size = 30
|
|
surface.CreateFont("Expression2ToolScreenSubFont", fontTable)
|
|
|
|
local percent = nil
|
|
local percent2 = nil
|
|
local name = "Unnamed"
|
|
local what = "Uploading"
|
|
|
|
function Expression2SetName(n)
|
|
name = n
|
|
if not name then
|
|
name = "Unnamed"
|
|
return
|
|
end
|
|
|
|
surface.SetFont("Expression2ToolScreenSubFont")
|
|
local ww = surface.GetTextSize("...")
|
|
|
|
local w, h = surface.GetTextSize(name)
|
|
if w < 240 then return end
|
|
|
|
while true do
|
|
local w, h = surface.GetTextSize(name)
|
|
if w < 240 - ww then break end
|
|
name = string.sub(name, 1, -2)
|
|
end
|
|
|
|
name = string.Trim(name) .. "..."
|
|
end
|
|
|
|
function Expression2SetProgress(p, p2, w)
|
|
what = w or "Uploading"
|
|
percent = p and math.Clamp(p,0,100) or nil
|
|
percent2 = p2 and math.Clamp(p2,0,100) or nil
|
|
end
|
|
|
|
function DrawTextOutline(text, font, x, y, color, xalign, yalign, bordercolor, border)
|
|
for i = 0, 8 do
|
|
draw.SimpleText(text, font, x + border * math.sin(i * math.pi / 4), y + border * math.cos(i * math.pi / 4), bordercolor, xalign, yalign)
|
|
end
|
|
|
|
draw.SimpleText(text, font, x, y, color, xalign, yalign)
|
|
end
|
|
|
|
local CogColor = Color(150, 34, 34, 255)
|
|
local CogTexture = surface.GetTextureID("expression 2/cog")
|
|
if CogTexture == surface.GetTextureID("texturemissing") then CogTexture = nil end
|
|
|
|
function TOOL:DrawToolScreen(width, height)
|
|
surface.SetDrawColor(32, 32, 32, 255)
|
|
surface.DrawRect(0, 0, 256, 256)
|
|
|
|
if CogTexture then
|
|
local ToColor = Color(150, 34, 34, 255)
|
|
if percent then
|
|
ToColor = Color(34, 150, 34, 255)
|
|
end
|
|
|
|
local CogDelta = 750 * FrameTime()
|
|
|
|
CogColor.r = CogColor.r + math.max(-CogDelta, math.min(CogDelta, ToColor.r - CogColor.r))
|
|
CogColor.g = CogColor.g + math.max(-CogDelta, math.min(CogDelta, ToColor.g - CogColor.g))
|
|
CogColor.b = CogColor.b + math.max(-CogDelta, math.min(CogDelta, ToColor.b - CogColor.b))
|
|
|
|
surface.SetTexture(CogTexture)
|
|
surface.SetDrawColor(CogColor.r, CogColor.g, CogColor.b, 255)
|
|
surface.DrawTexturedRectRotated(256, 256, 455, 455, RealTime() * 10)
|
|
surface.DrawTexturedRectRotated(30, 30, 227.5, 227.5, RealTime() * -20 + 12.5)
|
|
end
|
|
|
|
surface.SetFont("Expression2ToolScreenFont")
|
|
local w, h = surface.GetTextSize(" ")
|
|
surface.SetFont("Expression2ToolScreenSubFont")
|
|
local w2, h2 = surface.GetTextSize(" ")
|
|
|
|
if percent or percent2 then
|
|
surface.SetFont("Expression2ToolScreenFont")
|
|
local w, h = surface.GetTextSize(what)
|
|
DrawTextOutline(what, "Expression2ToolScreenFont", 128, 128, Color(224, 224, 224, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, Color(0, 0, 0, 255), 4)
|
|
|
|
if percent then
|
|
draw.RoundedBox(4, 128 - w / 2 - 2, 128 + h / 2 - 0, ((w+4) * percent) / 100, h2 - 4, Color(0, 0, 0, 255))
|
|
draw.RoundedBox(2, 128 - w / 2 + 2, 128 + h / 2 + 4, ((w-4) * percent) / 100, h2 - 12, Color(224, 224, 224, 255))
|
|
end
|
|
|
|
if percent2 then
|
|
draw.RoundedBox(4, 128 - w / 2 - 2, 128 + h / 2 + 24, ((w+4) * percent2) / 100, h2 - 4, Color(0, 0, 0, 255))
|
|
draw.RoundedBox(2, 128 - w / 2 + 2, 128 + h / 2 + 28, ((w-4) * percent2) / 100, h2 - 12, Color(224, 224, 224, 255))
|
|
end
|
|
elseif name then
|
|
DrawTextOutline("Expression 2", "Expression2ToolScreenFont", 128, 128, Color(224, 224, 224, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, Color(0, 0, 0, 255), 4)
|
|
DrawTextOutline(name, "Expression2ToolScreenSubFont", 128, 128 + (h + h2) / 2 - 4, Color(224, 224, 224, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, Color(0, 0, 0, 255), 4)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- -------- 'in editor' animation ------------------------
|
|
|
|
if SERVER then
|
|
|
|
-- -------------- client-side event handling ------------------
|
|
-- this might fit better elsewhere
|
|
|
|
local wire_expression2_event = {}
|
|
|
|
concommand.Add("wire_expression2_event", function(ply, command, args)
|
|
local handler = wire_expression2_event[args[1]]
|
|
if not handler then return end
|
|
return handler(ply, args)
|
|
end)
|
|
|
|
-- actual editor open/close handlers
|
|
|
|
function wire_expression2_event.editor_open(ply, args)
|
|
net.Start("wire_expression2_editor_status")
|
|
net.WriteEntity(ply)
|
|
net.WriteBit(true)
|
|
net.Broadcast()
|
|
end
|
|
|
|
function wire_expression2_event.editor_close(ply, args)
|
|
net.Start("wire_expression2_editor_status")
|
|
net.WriteEntity(ply)
|
|
net.WriteBit(false)
|
|
net.Broadcast()
|
|
end
|
|
|
|
elseif CLIENT then
|
|
|
|
local busy_players = {}
|
|
hook.Add("EntityRemoved", "wire_expression2_busy_animation", function(ply)
|
|
busy_players[ply] = nil
|
|
end)
|
|
|
|
local emitter = ParticleEmitter(vector_origin)
|
|
|
|
net.Receive("wire_expression2_editor_status", function(len)
|
|
local ply = net.ReadEntity()
|
|
local status = net.ReadBit() ~= 0 -- net.ReadBit returns 0 or 1, despite net.WriteBit taking a boolean
|
|
if not IsValid(ply) or ply == LocalPlayer() then return end
|
|
|
|
busy_players[ply] = status or nil
|
|
end)
|
|
|
|
local rolldelta = math.rad(80)
|
|
timer.Create("wire_expression2_editor_status", 1, 0, function()
|
|
rolldelta = -rolldelta
|
|
for ply, _ in pairs(busy_players) do
|
|
local BoneIndx = ply:LookupBone("ValveBiped.Bip01_Head1") or ply:LookupBone("ValveBiped.HC_Head_Bone") or 0
|
|
local BonePos, BoneAng = ply:GetBonePosition(BoneIndx)
|
|
local particle = emitter:Add("expression 2/cog_world", BonePos + Vector(0, 0, 16))
|
|
if particle then
|
|
particle:SetColor(150, 34, 34)
|
|
particle:SetVelocity(Vector(0, 0, 17))
|
|
|
|
particle:SetDieTime(3)
|
|
particle:SetLifeTime(0)
|
|
|
|
particle:SetStartSize(10)
|
|
particle:SetEndSize(10)
|
|
|
|
particle:SetStartAlpha(255)
|
|
particle:SetEndAlpha(0)
|
|
|
|
particle:SetRollDelta(rolldelta)
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
|
|
local prevmodel, prevvalid
|
|
function validModelCached(model)
|
|
if model ~= prevmodel then
|
|
prevmodel = model
|
|
prevvalid = util.IsValidModel(model)
|
|
end
|
|
return prevvalid
|
|
end
|
|
|
|
TOOL.Model = "models/beer/wiremod/gate_e2.mdl"
|
|
function TOOL:GetModel()
|
|
local scriptmodel = self:GetClientInfo("scriptmodel")
|
|
if scriptmodel and scriptmodel ~= "" and validModelCached(scriptmodel) then return Model(scriptmodel) end
|
|
return WireToolObj.GetModel(self)
|
|
end
|