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

1181 lines
31 KiB
Lua

-- Compatibility Global
if not WireLib then return end
WireAddon = 1
local ents = ents
local timer = timer
local string = string
local math_clamp = math.Clamp
local table = table
local hook = hook
local concommand = concommand
local Msg = Msg
local MsgN = MsgN
local pairs = pairs
local ipairs = ipairs
local IsValid = IsValid
local tostring = tostring
local Vector = Vector
local Color = Color
local Material = Material
local HasPorts = WireLib.HasPorts -- Very important for checks!
function WireLib.PortComparator(a,b)
return a.Num < b.Num
end
-- Allow to specify the description and type, like "Name (Description) [TYPE]"
local function ParsePortName(namedesctype, fbtype, fbdesc)
local namedesc, tp = namedesctype:match("^(.+) %[(.+)%]$")
if not namedesc then
namedesc = namedesctype
tp = fbtype
end
local name, desc = namedesc:match("^(.+) %((.*)%)$")
if not name then
name = namedesc
desc = fbdesc
end
return name, desc, tp
end
local Inputs = {}
local Outputs = {}
local CurLink = {}
local CurTime = CurTime
-- helper function that pcalls an input
function WireLib.TriggerInput(ent, name, value, ...)
if (not IsValid(ent) or not HasPorts(ent) or not ent.Inputs or not ent.Inputs[name]) then return end
ent.Inputs[name].Value = value
if (not ent.TriggerInput) then return end
local ok, ret = xpcall(ent.TriggerInput, debug.traceback, ent, name, value, ...)
if not ok then
local ply = WireLib.GetOwner(ent)
local owner_msg = IsValid(ply) and (" by %s"):format(tostring(ply)) or ""
local message = ("Wire error (%s%s):\n%s\n"):format(tostring(ent),owner_msg, ret)
WireLib.ErrorNoHalt(message)
if IsValid(ply) then WireLib.ClientError(message, ply) end
end
end
-- an array of data types
WireLib.DT = {
NORMAL = {
Zero = 0
}, -- Numbers
VECTOR = {
Zero = Vector(0, 0, 0)
},
ANGLE = {
Zero = Angle(0, 0, 0)
},
COLOR = {
Zero = Color(0, 0, 0)
},
ENTITY = {
Zero = NULL
},
STRING = {
Zero = ""
},
TABLE = {
Zero = {n={},ntypes={},s={},stypes={},size=0},
},
BIDIRTABLE = {
Zero = {n={},ntypes={},s={},stypes={},size=0},
BiDir = true
},
ANY = {
Zero = 0
},
ARRAY = {
Zero = {}
},
BIDIRARRAY = {
Zero = {},
BiDir = true
},
}
function WireLib.CreateSpecialInputs(ent, names, types, descs)
types = types or {}
descs = descs or {}
local ent_ports = {}
ent.Inputs = ent_ports
for n,v in pairs(names) do
local name, desc, tp = ParsePortName(v, types[n] or "NORMAL", descs and descs[n])
local port = {
Entity = ent,
Name = name,
Desc = desc,
Type = tp,
Value = WireLib.DT[ tp ].Zero,
Material = "tripmine_laser",
Color = Color(255, 255, 255, 255),
Width = 1,
Num = n,
}
local idx = 1
while (Inputs[idx]) do
idx = idx+1
end
port.Idx = idx
ent_ports[name] = port
Inputs[idx] = port
end
WireLib._SetInputs(ent)
return ent_ports
end
function WireLib.CreateSpecialOutputs(ent, names, types, descs)
types = types or {}
descs = descs or {}
local ent_ports = {}
ent.Outputs = ent_ports
for n,v in pairs(names) do
local name, desc, tp = ParsePortName(v, types[n] or "NORMAL", descs and descs[n])
local port = {
Entity = ent,
Name = name,
Desc = desc,
Type = tp,
Value = WireLib.DT[ tp ].Zero,
Connected = {},
TriggerLimit = 8,
Num = n,
}
local idx = 1
while (Outputs[idx]) do
idx = idx+1
end
port.Idx = idx
ent_ports[name] = port
Outputs[idx] = port
end
WireLib._SetOutputs(ent)
return ent_ports
end
function WireLib.AdjustSpecialInputs(ent, names, types, descs)
types = types or {}
descs = descs or {}
local ent_ports = ent.Inputs or {}
for n,v in ipairs(names) do
local name, desc, tp = ParsePortName(v, types[n] or "NORMAL", descs and descs[n])
if (ent_ports[name]) then
if tp ~= ent_ports[name].Type then
timer.Simple(0, function() WireLib.Link_Clear(ent, name) end)
ent_ports[name].Value = WireLib.DT[tp].Zero
ent_ports[name].Type = tp
end
ent_ports[name].Keep = true
ent_ports[name].Num = n
ent_ports[name].Desc = descs[n]
else
local port = {
Entity = ent,
Name = name,
Desc = desc,
Type = tp,
Value = WireLib.DT[ tp ].Zero,
Material = "tripmine_laser",
Color = Color(255, 255, 255, 255),
Width = 1,
Keep = true,
Num = n,
}
local idx = 1
while (Inputs[idx]) do
idx = idx+1
end
port.Idx = idx
ent_ports[name] = port
Inputs[idx] = port
end
end
for portname,port in pairs(ent_ports) do
if (port.Keep) then
port.Keep = nil
else
WireLib.Link_Clear(ent, portname)
ent_ports[portname] = nil
end
end
WireLib._SetInputs(ent)
return ent_ports
end
function WireLib.AdjustSpecialOutputs(ent, names, types, descs)
types = types or {}
descs = descs or {}
local ent_ports = ent.Outputs or {}
for n,v in ipairs(names) do
local name, desc, tp = ParsePortName(v, types[n] or "NORMAL", descs and descs[n])
if (ent_ports[name]) then
if tp ~= ent_ports[name].Type then
WireLib.DisconnectOutput(ent, name)
ent_ports[name].Type = tp
end
ent_ports[name].Keep = true
ent_ports[name].Num = n
ent_ports[name].Desc = descs[n]
else
local port = {
Keep = true,
Name = name,
Desc = descs[n],
Type = types[n] or "NORMAL",
Value = WireLib.DT[ (types[n] or "NORMAL") ].Zero,
Connected = {},
TriggerLimit = 8,
Num = n,
}
local idx = 1
while (Outputs[idx]) do
idx = idx+1
end
port.Idx = idx
ent_ports[name] = port
Outputs[idx] = port
end
end
for portname,port in pairs(ent_ports) do
if (port.Keep) then
port.Keep = nil
else
WireLib.DisconnectOutput(ent, portname)
ent_ports[portname] = nil
end
end
WireLib._SetOutputs(ent)
return ent_ports
end
--- Disconnects all wires from the given output.
function WireLib.DisconnectOutput(entity, output_name)
local output = entity.Outputs[output_name]
if output == nil then return end
for _, input in pairs_consume(output.Connected) do
if IsValid(input.Entity) then
WireLib.Link_Clear(input.Entity, input.Name)
end
end
end
function WireLib.RetypeInputs(ent, iname, itype, descs)
if not HasPorts(ent) then return end
local ent_ports = ent.Inputs
if (not ent_ports[iname]) or (not itype) then return end
if itype ~= ent_ports[iname].Type then
WireLib.Link_Clear(ent, iname)
ent_ports[iname].Type = itype
end
ent_ports[iname].Desc = descs
ent_ports[iname].Value = WireLib.DT[itype].Zero
WireLib._SetInputs(ent)
end
function WireLib.RetypeOutputs(ent, oname, otype, descs)
if not HasPorts(ent) then return end
local ent_ports = ent.Outputs
if (not ent_ports[oname]) or (not otype) then return end
if otype ~= ent_ports[oname].Type then
WireLib.DisconnectOutput(ent, oname)
ent_ports[oname].Type = otype
end
ent_ports[oname].Desc = descs
ent_ports[oname].Value = WireLib.DT[otype].Zero
WireLib._SetOutputs(ent)
end
-- force_outputs is only needed for existing components to allow them to be updated
function WireLib.Restored(ent, force_outputs)
if not HasPorts(ent) then return end
local ent_ports = ent.Inputs
if (ent_ports) then
for name,port in pairs(ent_ports) do
if (not port.Material) then -- Must be an old save
port.Name = name
if (port.Ropes) then
for _,rope in pairs(port.Ropes) do
rope:Remove()
end
port.Ropes = nil
end
end
port.Entity = ent
port.Type = port.Type or "NORMAL"
port.Material = port.Material or "cable/blue_elec"
port.Color = port.Color or Color(255, 255, 255, 255)
port.Width = port.Width or 2
port.StartPos = port.StartPos or Vector(0, 0, 0)
if (port.Src) and (not port.Path) then
port.Path = { { Entity = port.Src, Pos = Vector(0, 0, 0) } }
end
local idx = 1
while (Inputs[idx]) do
idx = idx+1
end
port.Idx = idx
Inputs[idx] = port
end
end
local ent_ports = ent.Outputs
if (ent_ports) then
for _,port in pairs(ent_ports) do
port.Entity = ent
port.Type = port.Type or "NORMAL"
local idx = 1
while (Outputs[idx]) do
idx = idx+1
end
port.Idx = idx
Outputs[idx] = port
end
elseif (force_outputs) then
ent.Outputs = WireLib.CreateOutputs(ent, force_outputs)
end
end
local function ClearPorts(ports, ConnectEnt, DontSendToCL)
local Valid, EmergencyBreak = true, 0
-- There is a strange bug, not all the links get removed at once.
-- It works when you run it multiple times.
while (Valid and (EmergencyBreak < 32)) do
local newValid = nil
for k,v in ipairs(ports) do
local Ent, Name = v.Entity, v.Name
if (IsValid(Ent) and (not ConnectEnt or (ConnectEnt == Ent))) then
local ports = Ent.Inputs
if (ports) then
local port = ports[Name]
if (port) then
WireLib.Link_Clear(Ent, Name, DontSendToCL)
newValid = true
end
end
end
end
Valid = newValid
EmergencyBreak = EmergencyBreak + 1 -- Prevents infinite loops if something goes wrong.
end
end
-- Set DontUnList to true, if you want to call WireLib._RemoveWire(eid) manually.
function WireLib.Remove(ent, DontUnList)
--Clear the inputs
local ent_ports = ent.Inputs
if (ent_ports) then
for _,inport in pairs(ent_ports) do
local Source = inport.Src
if (IsValid(Source)) then
local Outports = Source.Outputs
if (Outports) then
local outport = Outports[inport.SrcId]
if (outport) then
ClearPorts(outport.Connected, ent, true)
end
end
end
Inputs[inport.Idx] = nil
end
end
--Clear the outputs
local ent_ports = ent.Outputs
if (ent_ports) then
for _,outport in pairs(ent_ports) do
ClearPorts(outport.Connected)
Outputs[outport.Idx] = nil
end
end
ent.Inputs = nil -- Remove the inputs
ent.Outputs = nil -- Remove the outputs
ent.IsWire = nil -- Remove the wire mark
if (DontUnList) then return end -- Set DontUnList to true if you want to remove ent from the list manually.
WireLib._RemoveWire(ent:EntIndex()) -- Remove entity from the list, so it doesn't count as a wire able entity anymore. Very important for IsWire checks!
end
local function Wire_Link(dst, dstid, src, srcid, path)
if (not IsValid(dst) or not HasPorts(dst) or not dst.Inputs or not dst.Inputs[dstid]) then
Msg("Wire_link: Invalid destination!\n")
return
end
if (not IsValid(src) or not HasPorts(src) or not src.Outputs or not src.Outputs[srcid]) then
Msg("Wire_link: Invalid source!\n")
return
end
local input = dst.Inputs[dstid]
local output = src.Outputs[srcid]
if (IsValid(input.Src)) then
if (input.Src.Outputs) then
local oldOutput = input.Src.Outputs[input.SrcId]
if (oldOutput) then
for k,v in ipairs(oldOutput.Connected) do
if (v.Entity == dst) and (v.Name == dstid) then
table.remove(oldOutput.Connected, k)
end
end
end
end
end
input.Src = src
input.SrcId = srcid
input.Path = path
WireLib.Paths.Add(input)
WireLib._SetLink(input)
table.insert(output.Connected, { Entity = dst, Name = dstid })
if dst.OnInputWireLink then
-- ENT:OnInputWireLink(iName, iType, oEnt, oName, oType)
dst:OnInputWireLink(dstid, input.Type, src, srcid, output.Type)
end
if src.OnOutputWireLink then
-- ENT:OnOutputWireLink(oName, oType, iEnt, iName, iType)
src:OnOutputWireLink(srcid, output.Type, dst, dstid, input.Type)
end
WireLib.TriggerInput(dst, dstid, output.Value)
end
function WireLib.TriggerOutput(ent, oname, value, iter)
if not IsValid(ent) then return end
if not HasPorts(ent) then return end
if (not ent.Outputs) then return end
local output = ent.Outputs[oname]
if (output) and (value ~= output.Value or output.Type == "ARRAY" or output.Type == "TABLE") then
local timeOfFrame = CurTime()
if timeOfFrame ~= output.TriggerTime then
-- Reset the TriggerLimit every frame
output.TriggerLimit = 4
output.TriggerTime = timeOfFrame
elseif output.TriggerLimit <= 0 then
return
end
output.TriggerLimit = output.TriggerLimit - 1
output.Value = value
if (iter) then
for _,dst in ipairs(output.Connected) do
if (IsValid(dst.Entity)) then
iter:Add(dst.Entity, dst.Name, value)
end
end
return
end
iter = WireLib.CreateOutputIterator()
for _,dst in ipairs(output.Connected) do
if (IsValid(dst.Entity)) then
WireLib.TriggerInput(dst.Entity, dst.Name, value, iter)
end
end
iter:Process()
end
end
local function Wire_Unlink(ent, iname, DontSendToCL)
if not HasPorts(ent) then return end
local input = ent.Inputs[iname]
if (input) then
if (IsValid(input.Src)) then
local outputs = input.Src.Outputs or {}
local output = outputs[input.SrcId]
if (output) then
for k,v in ipairs(output.Connected) do
if (v.Entity == ent) and (v.Name == iname) then
table.remove(output.Connected, k)
end
end
-- untested
if input.Src.OnOutputWireLink then
-- ENT:OnOutputWireLink(oName, oType, iEnt, iName, iType)
input.Src:OnOutputWireLink(input.SrcId, outputs[input.SrcId].Type, ent, iname, input.Type)
end
end
-- untested
if ent.OnInputWireUnlink then
-- ENT:OnInputWireUnlink(iName, iType, oEnt, oName, oType)
ent:OnInputWireUnlink(iname, input.Type, input.Src, input.SrcId, outputs[input.SrcId].Type)
end
end
input.Src = nil
input.SrcId = nil
input.Path = nil
WireLib.TriggerInput(ent, iname, WireLib.DT[input.Type].Zero, nil)
if (DontSendToCL) then return end
WireLib._SetLink(input)
end
end
function WireLib.Link_Start(idx, ent, pos, iname, material, color, width)
if not IsValid(ent) then return end
if not HasPorts(ent) then return end
if (not ent.Inputs or not ent.Inputs[iname]) then return end
local input = ent.Inputs[iname]
if not input.Path then input.Path = {} end
CurLink[idx] = {
Dst = ent,
DstId = iname,
Path = input.Path,
OldPath = {}
}
for i=1, #input.Path do
CurLink[idx].OldPath[i] = input.Path[i]
input.Path[i] = nil
end
input.StartPos = pos
input.Material = material
input.Color = color
input.Width = math_clamp(width, 0, 5)
return true
end
function WireLib.Link_Node(idx, ent, pos)
if not CurLink[idx] then return end
if not IsValid(CurLink[idx].Dst) then return end
if not IsValid(ent) then return end -- its the world, give up
table.insert(CurLink[idx].Path, { Entity = ent, Pos = pos })
WireLib.Paths.Add(CurLink[idx].Dst.Inputs[CurLink[idx].DstId])
end
function WireLib.Link_End(idx, ent, pos, oname, pl)
if not CurLink[idx] then return end
if not IsValid(CurLink[idx].Dst) then return end
if not HasPorts(CurLink[idx].Dst) then return end
if not IsValid(ent) then return end
if not HasPorts(ent) then return end
if not ent.Outputs then return end
if (CurLink[idx].Dst:GetClass() == "gmod_wire_sensor") and (ent:GetClass() ~= "gmod_wire_target_finder") then
MsgN("Wire_link: Beacon Sensor can only be wired to a Target Finder!")
if pl then
WireLib.AddNotify(pl, "Beacon Sensor can only be wired to a Target Finder!", NOTIFY_GENERIC, 7)
end
WireLib.Link_Cancel(idx)
return
end
local input = CurLink[idx].Dst.Inputs[CurLink[idx].DstId]
local output = ent.Outputs[oname]
if not output then
--output = { Type = "NORMAL" }
local text = "Selected output not found or no output present."
MsgN(text)
if pl then WireLib.AddNotify(pl, text, NOTIFY_GENERIC, 7) end
WireLib.Link_Cancel(idx)
return
end
--Msg("input type= " .. input.Type .. " output type= " .. (output.Type or "NIL") .. "\n") -- I bet that was getting anoying (TAD2020)
if (input.Type ~= output.Type) and (input.Type ~= "ANY") and (output.Type ~= "ANY") then
local text = "Data Type Mismatch! Input takes "..input.Type.." and Output gives "..output.Type
MsgN(text)
if pl then WireLib.AddNotify(pl, text, NOTIFY_GENERIC, 7) end
WireLib.Link_Cancel(idx)
return
end
table.insert(CurLink[idx].Path, { Entity = ent, Pos = pos })
Wire_Link(CurLink[idx].Dst, CurLink[idx].DstId, ent, oname, CurLink[idx].Path)
if (WireLib.DT[input.Type].BiDir) then
Wire_Link(ent, oname, CurLink[idx].Dst, CurLink[idx].DstId, {})
end
CurLink[idx] = nil
end
function WireLib.Link_Cancel(idx)
if not CurLink[idx] then return end
if not IsValid(CurLink[idx].Dst) then return end
if CurLink[idx].input then
CurLink[idx].Path = CurLink[idx].input.Path
else
WireLib.Paths.Add({Entity = CurLink[idx].Dst, Name = CurLink[idx].DstId, Width = 0})
end
CurLink[idx] = nil
end
function WireLib.Link_Clear(ent, iname, DontSendToCL)
WireLib.Paths.Add({Entity = ent, Name = iname, Width = 0})
Wire_Unlink(ent, iname, DontSendToCL)
end
function WireLib.WireAll(ply, ient, oent, ipos, opos, material, color, width)
if not IsValid(ient) or not IsValid(oent) or not ient.Inputs or not oent.Outputs then return false end
for iname, _ in pairs(ient.Inputs) do
if oent.Outputs[iname] then
WireLib.Link_Start(ply:UniqueID(), ient, ipos, iname, material or "arrowire/arrowire2", color or Color(255,255,255), width or 0)
WireLib.Link_End(ply:UniqueID(), oent, opos, iname, ply)
end
end
end
do -- class OutputIterator
local OutputIterator = {}
OutputIterator.__index = OutputIterator
function OutputIterator:Add(ent, iname, value)
table.insert(self, { Entity = ent, IName = iname, Value = value })
end
function OutputIterator:Process()
if self.Processing then return end -- should not occur
self.Processing = true
while #self > 0 do
local nextelement = self[1]
table.remove(self, 1)
WireLib.TriggerInput(nextelement.Entity, nextelement.IName, nextelement.Value, self)
end
self.Processing = nil
end
function WireLib.CreateOutputIterator()
return setmetatable({}, OutputIterator)
end
end -- class OutputIterator
duplicator.RegisterEntityModifier("WireDupeInfo", function(ply, Ent, DupeInfo)
-- this does nothing for now, we need the blank function to get the duplicator to copy the WireDupeInfo into the pasted ent
end)
-- used for welding wired stuff, if trace is world, the ent is not welded and is frozen instead
function WireLib.Weld(ent, traceEntity, tracePhysicsBone, DOR, collision, AllowWorldWeld)
if (not ent or not traceEntity or traceEntity:IsNPC() or traceEntity:IsPlayer()) then return end
local phys = ent:GetPhysicsObject()
if ( traceEntity:IsValid() ) or ( traceEntity:IsWorld() and AllowWorldWeld ) then
local const = constraint.Weld( ent, traceEntity, 0, tracePhysicsBone, 0, (not collision), DOR )
-- Don't disable collision if it's not attached to anything
if (not collision) then
if phys:IsValid() then phys:EnableCollisions( false ) end
ent.nocollide = true
end
return const
else
if phys:IsValid() then ent:GetPhysicsObject():EnableMotion( false ) end
return nil
end
end
function WireLib.BuildDupeInfo( Ent )
if not Ent.Inputs then return {} end
local info = { Wires = {} }
for portname,input in pairs(Ent.Inputs) do
if (IsValid(input.Src)) then
info.Wires[portname] = {
StartPos = input.StartPos,
Material = input.Material,
Color = input.Color,
Width = input.Width,
Src = input.Src:EntIndex(),
SrcId = input.SrcId,
SrcPos = Vector(0, 0, 0),
}
if (input.Path) then
info.Wires[portname].Path = {}
for _,v in ipairs(input.Path) do
if (IsValid(v.Entity)) then
table.insert(info.Wires[portname].Path, { Entity = v.Entity:EntIndex(), Pos = v.Pos })
end
end
local n = #info.Wires[portname].Path
if (n > 0) and (info.Wires[portname].Path[n].Entity == info.Wires[portname].Src) then
info.Wires[portname].SrcPos = info.Wires[portname].Path[n].Pos
table.remove(info.Wires[portname].Path, n)
end
end
end
end
return info
end
function WireLib.ApplyDupeInfo( ply, ent, info, GetEntByID )
if info.extended and not ent.extended then
WireLib.CreateWirelinkOutput( ply, ent, {true} ) -- old dupe compatibility; use the new function
end
local idx = 0
if IsValid(ply) then idx = ply:UniqueID() end -- Map Save loading does not have a ply
if (info.Wires) then
for k,input in pairs(info.Wires) do
local ent2 = GetEntByID(input.Src)
-- Input alias
if ent.Inputs and not ent.Inputs[k] then -- if the entity has any inputs and the input 'k' is not one of them...
if ent.InputAliases and ent.InputAliases[k] then
k = ent.InputAliases[k]
else
Msg("ApplyDupeInfo: Error, Could not find input '" .. k .. "' on entity type: '" .. ent:GetClass() .. "'\n")
continue
end
end
if IsValid( ent2 ) then
-- Wirelink and entity outputs
-- These are required if whichever duplicator you're using does not do entity modifiers before it runs PostEntityPaste
-- because if so, the wirelink and entity outputs may not have been created yet
if input.SrcId == "link" or input.SrcId == "wirelink" then -- If the target entity has no wirelink output, create one (& more old dupe compatibility)
input.SrcId = "wirelink"
if not ent2.extended then
WireLib.CreateWirelinkOutput( ply, ent2, {true} )
end
elseif input.SrcId == "entity" and ((ent2.Outputs and not ent2.Outputs.entity) or not ent2.Outputs) then -- if the input name is 'entity', and the target entity doesn't have that output...
WireLib.CreateEntityOutput( ply, ent2, {true} )
end
-- Output alias
if ent2.Outputs and not ent2.Outputs[input.SrcId] then -- if the target entity has any outputs and the output 'input.SrcId' is not one of them...
if ent2.OutputAliases and ent2.OutputAliases[input.SrcId] then
input.SrcId = ent2.OutputAliases[input.SrcId]
else
Msg("ApplyDupeInfo: Error, Could not find output '" .. input.SrcId .. "' on entity type: '" .. ent2:GetClass() .. "'\n")
continue
end
end
end
WireLib.Link_Start(idx, ent, input.StartPos, k, input.Material, input.Color, input.Width)
if input.Path then
for _,v in ipairs(input.Path) do
local ent2 = GetEntByID(v.Entity)
if IsValid(ent2) then
WireLib.Link_Node(idx, ent2, v.Pos)
else
Msg("ApplyDupeInfo: Error, Could not find the entity for wire path\n")
end
end
end
if IsValid(ent2) then
WireLib.Link_End(idx, ent2, input.SrcPos, input.SrcId)
else
Msg("ApplyDupeInfo: Error, Could not find the output entity\n")
end
end
end
end
function WireLib.RefreshSpecialOutputs(ent)
local names = {}
local types = {}
local descs = {}
if ent.Outputs then
for _,output in pairs(ent.Outputs) do
local index = output.Num
names[index] = output.Name
types[index] = output.Type
descs[index] = output.Desc
end
ent.Outputs = WireLib.AdjustSpecialOutputs(ent, names, types, descs)
else
ent.Outputs = WireLib.CreateSpecialOutputs(ent, names, types, descs)
end
WireLib.TriggerOutput(ent, "link", ent)
end
function WireLib.CreateInputs(ent, names, descs)
return WireLib.CreateSpecialInputs(ent, names, {}, descs)
end
function WireLib.CreateOutputs(ent, names, descs)
return WireLib.CreateSpecialOutputs(ent, names, {}, descs)
end
function WireLib.AdjustInputs(ent, names, descs)
return WireLib.AdjustSpecialInputs(ent, names, {}, descs)
end
function WireLib.AdjustOutputs(ent, names, descs)
return WireLib.AdjustSpecialOutputs(ent, names, {}, descs)
end
-- Backwards compatibility
Wire_CreateInputs = WireLib.CreateInputs
Wire_CreateOutputs = WireLib.CreateOutputs
Wire_AdjustInputs = WireLib.AdjustInputs
Wire_AdjustOutputs = WireLib.AdjustOutputs
Wire_Restored = WireLib.Restored
Wire_Remove = WireLib.Remove
Wire_TriggerOutput = WireLib.TriggerOutput
Wire_Link_Start = WireLib.Link_Start
Wire_Link_Node = WireLib.Link_Node
Wire_Link_End = WireLib.Link_End
Wire_Link_Cancel = WireLib.Link_Cancel
Wire_Link_Clear = WireLib.Link_Clear
Wire_CreateOutputIterator = WireLib.CreateOutputIterator
Wire_BuildDupeInfo = WireLib.BuildDupeInfo
Wire_ApplyDupeInfo = WireLib.ApplyDupeInfo
-- prevent applyForce+Anti-noclip-based killing contraptions
hook.Add("InitPostEntity", "antiantinoclip", function()
local ENT = scripted_ents.GetList().rt_antinoclip_handler
if not ENT then return end
ENT = ENT.t
local rt_antinoclip_handler_StartTouch = ENT.StartTouch
function ENT:StartTouch(...)
if self.speed >= 20 then return end
local phys = self.Ent:GetPhysicsObject()
if phys:IsValid() and phys:GetAngleVelocity():Length() > 20 then return end
rt_antinoclip_handler_StartTouch(self, ...)
end
--local rt_antinoclip_handler_Think = ENT.Think
function ENT:Think()
local t = CurTime()
local dt = t-self.lastt
self.lastt = t
local phys = self.Ent:GetPhysicsObject()
local pos
if phys:IsValid() then
pos = phys:LocalToWorld(phys:GetMassCenter())
else
pos = self.Ent:GetPos()
end
self.speed = pos:Distance(self.oldpos)/dt
self.oldpos = pos
--rt_antinoclip_handler_Think(self, ...)
end
ENT.speed = 20
ENT.lastt = 0
ENT.oldpos = Vector(0,0,0)
end)
function WireLib.GetOwner(ent)
return E2Lib.getOwner({}, ent)
end
function WireLib.NumModelSkins(model)
if NumModelSkins then
return NumModelSkins(model)
end
local info = util.GetModelInfo(model)
return info and info.SkinCount
end
--- @return whether the given player can spawn an object with the given model and skin
function WireLib.CanModel(player, model, skin)
if not util.IsValidModel(model) then return false end
if skin ~= nil then
local count = WireLib.NumModelSkins(model)
if skin < 0 or (count and skin >= count) then return false end
end
if IsValid(player) and player:IsPlayer() and not hook.Run("PlayerSpawnObject", player, model, skin) then return false end
return true
end
function WireLib.MakeWireEnt( pl, Data, ... )
Data.Class = scripted_ents.Get(Data.Class).ClassName
if IsValid(pl) and not pl:CheckLimit(Data.Class:sub(6).."s") then return false end
if Data.Model and not WireLib.CanModel(pl, Data.Model, Data.Skin) then return false end
local ent = ents.Create( Data.Class )
if not IsValid(ent) then return false end
duplicator.DoGeneric( ent, Data )
ent:Spawn()
ent:Activate()
duplicator.DoGenericPhysics( ent, pl, Data ) -- Is deprecated, but is the only way to access duplicator.EntityPhysics.Load (its local)
ent:SetPlayer(pl)
if ent.Setup then ent:Setup(...) end
if IsValid(pl) then pl:AddCount( Data.Class:sub(6).."s", ent ) end
local phys = ent:GetPhysicsObject()
if IsValid(phys) then
if Data.frozen then phys:EnableMotion(false) end
if Data.nocollide then phys:EnableCollisions(false) end
end
return ent
end
-- Adds an input alias so that we can rename inputs on entities without breaking old dupes
-- Usage: WireLib.AddInputAlias( old, new ) works if used in the entity's file
-- or WireLib.AddInputAlias( class, old, new ) if used elsewhere
-- or WireLib.AddInputAlias( entity, old, new ) for a specific entity
function WireLib.AddInputAlias( class, old, new )
if not new then
new = old
old = class
class = nil
end
local ENT_table
if not class and ENT then
ENT_table = ENT
elseif isstring( class ) then
ENT_table = scripted_ents.GetStored( class )
elseif isentity( class ) and IsValid( class ) then
ENT_table = class
else
error( "Invalid class or entity specified" )
return
end
if not ENT_table.InputAliases then ENT_table.InputAliases = {} end
ENT_table.InputAliases[old] = new
end
-- Adds an output alias so that we can rename outputs on entities without breaking old dupes
-- Usage: WireLib.AddOutputAlias( old, new ) works if used in the entity's file
-- or WireLib.AddOutputAlias( class, old, new ) if used elsewhere
-- or WireLib.AddOutputAlias( entity, old, new ) for a specific entity
function WireLib.AddOutputAlias( class, old, new )
if not new then
new = old
old = class
class = nil
end
local ENT_table
if not class and ENT then
ENT_table = ENT
elseif isstring( class ) then
ENT_table = scripted_ents.GetStored( class )
elseif isentity( class ) and IsValid( class ) then
ENT_table = class
else
error( "Invalid class or entity specified" )
return
end
if not ENT_table.OutputAliases then ENT_table.OutputAliases = {} end
ENT_table.OutputAliases[old] = new
end
local function effectiveMass(ent)
if not isentity(ent) then return 1 end
if ent:IsWorld() then return 99999 end
if not IsValid(ent) or not IsValid(ent:GetPhysicsObject()) then return 1 end
return ent:GetPhysicsObject():GetMass()
end
function WireLib.CalcElasticConsts(Ent1, Ent2)
local minMass = math.min(effectiveMass(Ent1), effectiveMass(Ent2))
local const = minMass * 100
local damp = minMass * 20
return const, damp
end
-- Returns a string like "Git f3a4ac3" or "SVN 2703" or "Workshop" or "Extracted"
-- The partial git hash can be plugged into https://github.com/wiremod/wire/commit/f3a4ac3 to show the actual commit
local cachedversion
function WireLib.GetVersion()
-- If we've already found our version just return that again
if cachedversion then return cachedversion end
-- Find what our legacy folder is called
local wirefolder = "addons/wire"
if not file.Exists(wirefolder, "GAME") then
for k, folder in pairs(({file.Find("addons/*", "GAME")})[2]) do
if folder:find("wire") and not folder:find("extra") then
wirefolder = "addons/"..folder
break
end
end
end
if file.Exists(wirefolder, "GAME") then
if file.Exists(wirefolder.."/.git", "GAME") then
cachedversion = "Git "..(file.Read(wirefolder.."/.git/refs/heads/master", "GAME") or "Unknown"):sub(1,7)
elseif file.Exists(wirefolder.."/.svn", "GAME") then
-- Note: This method will likely only detect TortoiseSVN installs
local wcdb = file.Read(wirefolder.."/.svn/wc.db", "GAME") or ""
local start = wcdb:find("/wiremod/wire/!svn/ver/%d+/branches%)")
if start then
cachedversion = "SVN "..wcdb:sub(start+23, start+26)
else
cachedversion = "SVN Unknown"
end
else
cachedversion = "Extracted"
end
end
-- Check if we're Workshop version first
for k, addon in pairs(engine.GetAddons()) do
if addon.wsid == "160250458" then
cachedversion = "Workshop"
return cachedversion
end
end
if not cachedversion then cachedversion = "Unknown" end
return cachedversion
end
concommand.Add("wireversion", function(ply,cmd,args)
local text = "Wiremod's version: '"..WireLib.GetVersion().."'"
if IsValid(ply) then
ply:ChatPrint(text)
else
print(text)
end
end, nil, "Prints the server's Wiremod version")
local material_blacklist = {
["engine/writez"] = true,
["pp/copy"] = true,
["effects/ar2_altfire1"] = true
}
function WireLib.IsValidMaterial(material)
material = string.sub(material, 1, 260)
local path = string.StripExtension(string.GetNormalizedFilepath(string.lower(material)))
if material_blacklist[path] then return "" end
return material
end
function WireLib.SetColor(ent, color)
color.r = math_clamp(color.r, 0, 255)
color.g = math_clamp(color.g, 0, 255)
color.b = math_clamp(color.b, 0, 255)
color.a = ent:IsPlayer() and ent:GetColor().a or math_clamp(color.a, 0, 255)
local rendermode = ent:GetRenderMode()
if rendermode == RENDERMODE_NORMAL or rendermode == RENDERMODE_TRANSALPHA then
rendermode = color.a == 255 and RENDERMODE_NORMAL or RENDERMODE_TRANSALPHA
ent:SetRenderMode(rendermode)
else
rendermode = nil -- Don't modify the current stored modifier
end
ent:SetColor(color)
duplicator.StoreEntityModifier(ent, "colour", { Color = color, RenderMode = rendermode })
end
if not WireLib.PatchedDuplicator then
WireLib.PatchedDuplicator = true
local localPos
local oldSetLocalPos = duplicator.SetLocalPos
function duplicator.SetLocalPos(pos, ...)
localPos = pos
return oldSetLocalPos(pos, ...)
end
local oldPaste = duplicator.Paste
function duplicator.Paste(player, entityList, constraintList, ...)
local result = { oldPaste(player, entityList, constraintList, ...) }
local createdEntities, createdConstraints = result[1], result[2]
local data = {
EntityList = entityList, ConstraintList = constraintList,
CreatedEntities = createdEntities, CreatedConstraints = createdConstraints,
Player = player, HitPos = localPos,
}
hook.Run("AdvDupe_FinishPasting", {data}, 1)
return unpack(result)
end
end